diff --git a/e2e/esbuild/src/esbuild.test.ts b/e2e/esbuild/src/esbuild.test.ts index d608d65bcb..acae44e230 100644 --- a/e2e/esbuild/src/esbuild.test.ts +++ b/e2e/esbuild/src/esbuild.test.ts @@ -169,7 +169,6 @@ describe('EsBuild Plugin', () => { expect( readJson(`dist/libs/${parentLib}/package.json`).dependencies ).toEqual({ - 'jsonc-eslint-parser': expect.any(String), // Don't care about the versions, just that they exist rambda: expect.any(String), lodash: expect.any(String), diff --git a/e2e/js/src/js-ts-solution.test.ts b/e2e/js/src/js-ts-solution.test.ts index f73dea3a8b..22b39f22ec 100644 --- a/e2e/js/src/js-ts-solution.test.ts +++ b/e2e/js/src/js-ts-solution.test.ts @@ -3,6 +3,7 @@ import { getPackageManagerCommand, getSelectedPackageManager, newProject, + readJson, runCLI, runCommand, uniq, @@ -177,4 +178,28 @@ ${content}` `Successfully ran target test for project @proj/${viteParentLib}` ); }, 300_000); + + it('should respect and support generating libraries with a name different than the import path', () => { + const lib1 = uniq('lib1'); + + runCLI( + `generate @nx/js:lib packages/${lib1} --name=${lib1} --bundler=vite --linter=eslint --unitTestRunner=jest` + ); + + const packageJson = readJson(`packages/${lib1}/package.json`); + expect(packageJson.nx.name).toBe(lib1); + + expect(runCLI(`build ${lib1}`)).toContain( + `Successfully ran target build for project ${lib1}` + ); + expect(runCLI(`typecheck ${lib1}`)).toContain( + `Successfully ran target typecheck for project ${lib1}` + ); + expect(runCLI(`lint ${lib1}`)).toContain( + `Successfully ran target lint for project ${lib1}` + ); + expect(runCLI(`test ${lib1}`)).toContain( + `Successfully ran target test for project ${lib1}` + ); + }, 300_000); }); diff --git a/e2e/node/src/node-ts-solution.test.ts b/e2e/node/src/node-ts-solution.test.ts index 215741739e..0cc843ff20 100644 --- a/e2e/node/src/node-ts-solution.test.ts +++ b/e2e/node/src/node-ts-solution.test.ts @@ -6,6 +6,7 @@ import { killPorts, newProject, promisifiedTreeKill, + readJson, runCLI, runCommand, runCommandUntil, @@ -169,6 +170,30 @@ describe('Node Applications', () => { expect(err).toBeFalsy(); } }, 300_000); + + it('should respect and support generating libraries with a name different than the import path', () => { + const nodeLib = uniq('node-lib'); + const nestLib = uniq('nest-lib'); + + runCLI( + `generate @nx/node:lib packages/${nodeLib} --name=${nodeLib} --buildable` + ); + runCLI( + `generate @nx/nest:lib packages/${nestLib} --name=${nestLib} --buildable` + ); + + const packageJson = readJson(`packages/${nodeLib}/package.json`); + expect(packageJson.nx.name).toBe(nodeLib); + const nestPackageJson = readJson(`packages/${nestLib}/package.json`); + expect(nestPackageJson.nx.name).toBe(nestLib); + + expect(runCLI(`build ${nodeLib}`)).toContain( + `Successfully ran target build for project ${nodeLib}` + ); + expect(runCLI(`build ${nestLib}`)).toContain( + `Successfully ran target build for project ${nestLib}` + ); + }, 300_000); }); function getRandomPort() { diff --git a/e2e/plugin/src/nx-plugin-ts-solution.test.ts b/e2e/plugin/src/nx-plugin-ts-solution.test.ts index 05d68b9ec4..3d859bd48e 100644 --- a/e2e/plugin/src/nx-plugin-ts-solution.test.ts +++ b/e2e/plugin/src/nx-plugin-ts-solution.test.ts @@ -3,8 +3,10 @@ import { cleanupProject, createFile, newProject, + readJson, renameFile, runCLI, + runCommand, uniq, updateFile, updateJson, @@ -103,12 +105,10 @@ describe('Nx Plugin (TS solution)', () => { // Register plugin in nx.json (required for inference) updateJson(`nx.json`, (nxJson) => { - nxJson.plugins = [ - { - plugin: `@${workspaceName}/${plugin}`, - options: { inferredTags: ['my-tag'] }, - }, - ]; + nxJson.plugins.push({ + plugin: `@${workspaceName}/${plugin}`, + options: { inferredTags: ['my-tag'] }, + }); return nxJson; }); @@ -262,4 +262,22 @@ describe('Nx Plugin (TS solution)', () => { expect(() => checkFilesExist(`libs/${generatedProject}`)).not.toThrow(); expect(() => runCLI(`execute ${generatedProject}`)).not.toThrow(); }); + + it('should respect and support generating plugins with a name different than the import path', async () => { + const plugin = uniq('plugin'); + + runCLI( + `generate @nx/plugin:plugin packages/${plugin} --name=${plugin} --linter=eslint --publishable` + ); + + const packageJson = readJson(`packages/${plugin}/package.json`); + expect(packageJson.nx.name).toBe(plugin); + + expect(runCLI(`build ${plugin}`)).toContain( + `Successfully ran target build for project ${plugin}` + ); + expect(runCLI(`lint ${plugin}`)).toContain( + `Successfully ran target lint for project ${plugin}` + ); + }, 90000); }); diff --git a/e2e/react/src/react-ts-solution.test.ts b/e2e/react/src/react-ts-solution.test.ts new file mode 100644 index 0000000000..d75642e019 --- /dev/null +++ b/e2e/react/src/react-ts-solution.test.ts @@ -0,0 +1,41 @@ +import { + cleanupProject, + newProject, + readJson, + runCLI, + uniq, +} from '@nx/e2e/utils'; + +describe('React (TS solution)', () => { + let workspaceName: string; + + beforeAll(() => { + workspaceName = newProject({ preset: 'ts', packages: ['@nx/react'] }); + }); + + afterAll(() => cleanupProject()); + + it('should respect and support generating libraries with a name different than the import path', async () => { + const lib = uniq('lib'); + + runCLI( + `generate @nx/react:library packages/${lib} --name=${lib} --bundler=vite --linter=eslint --unitTestRunner=vitest` + ); + + const packageJson = readJson(`packages/${lib}/package.json`); + expect(packageJson.nx.name).toBe(lib); + + expect(runCLI(`build ${lib}`)).toContain( + `Successfully ran target build for project ${lib}` + ); + expect(runCLI(`typecheck ${lib}`)).toContain( + `Successfully ran target typecheck for project ${lib}` + ); + expect(runCLI(`lint ${lib}`)).toContain( + `Successfully ran target lint for project ${lib}` + ); + expect(runCLI(`test ${lib}`)).toContain( + `Successfully ran target test for project ${lib}` + ); + }, 90000); +}); diff --git a/e2e/remix/src/remix-ts-solution.test.ts b/e2e/remix/src/remix-ts-solution.test.ts index cd06df3cbb..ae0b19f14c 100644 --- a/e2e/remix/src/remix-ts-solution.test.ts +++ b/e2e/remix/src/remix-ts-solution.test.ts @@ -1,4 +1,10 @@ -import { cleanupProject, newProject, runCLI, uniq } from '@nx/e2e/utils'; +import { + cleanupProject, + newProject, + readJson, + runCLI, + uniq, +} from '@nx/e2e/utils'; describe('Remix - TS solution setup', () => { beforeAll(() => { @@ -113,4 +119,28 @@ describe('Remix - TS solution setup', () => { `Successfully ran target test for project @proj/${buildableLibJest}` ); }, 120_000); + + it('should respect and support generating libraries with a name different than the import path', async () => { + const lib = uniq('lib'); + + runCLI( + `generate @nx/remix:library packages/${lib} --name=${lib} --linter=eslint --unitTestRunner=vitest --buildable` + ); + + const packageJson = readJson(`packages/${lib}/package.json`); + expect(packageJson.nx.name).toBe(lib); + + expect(runCLI(`build ${lib}`)).toContain( + `Successfully ran target build for project ${lib}` + ); + expect(runCLI(`typecheck ${lib}`)).toContain( + `Successfully ran target typecheck for project ${lib}` + ); + expect(runCLI(`lint ${lib}`)).toContain( + `Successfully ran target lint for project ${lib}` + ); + expect(runCLI(`test ${lib}`)).toContain( + `Successfully ran target test for project ${lib}` + ); + }, 120_000); }); diff --git a/e2e/vue/src/vue-ts-solution.test.ts b/e2e/vue/src/vue-ts-solution.test.ts index 15e133d6a1..34f8af44b6 100644 --- a/e2e/vue/src/vue-ts-solution.test.ts +++ b/e2e/vue/src/vue-ts-solution.test.ts @@ -1,18 +1,15 @@ import { cleanupProject, - getSelectedPackageManager, newProject, + readJson, runCLI, uniq, updateFile, - updateJson, } from '@nx/e2e/utils'; -describe('Vue Plugin', () => { +describe('Vue (TS solution)', () => { let proj: string; - const pm = getSelectedPackageManager(); - beforeAll(() => { proj = newProject({ packages: ['@nx/vue'], @@ -57,4 +54,32 @@ describe('Vue Plugin', () => { expect(() => runCLI(`test @proj/${lib}`)).not.toThrow(); expect(() => runCLI(`build @proj/${lib}`)).not.toThrow(); }, 300_000); + + it('should respect and support generating libraries with a name different than the import path', async () => { + const lib = uniq('lib'); + + runCLI( + `generate @nx/vue:library packages/${lib} --name=${lib} --bundler=vite --unitTestRunner=vitest` + ); + // lib generator doesn't generate specs, add one + updateFile( + `packages/${lib}/src/foo.spec.ts`, + `test('it should run', () => { + expect(true).toBeTruthy(); + });` + ); + + const packageJson = readJson(`packages/${lib}/package.json`); + expect(packageJson.nx.name).toBe(lib); + + expect(runCLI(`build ${lib}`)).toContain( + `Successfully ran target build for project ${lib}` + ); + expect(runCLI(`typecheck ${lib}`)).toContain( + `Successfully ran target typecheck for project ${lib}` + ); + expect(runCLI(`test ${lib}`)).toContain( + `Successfully ran target test for project ${lib}` + ); + }, 300_000); }); diff --git a/packages/angular/src/generators/application/lib/normalize-options.ts b/packages/angular/src/generators/application/lib/normalize-options.ts index 1d94dbcc18..60372435f2 100644 --- a/packages/angular/src/generators/application/lib/normalize-options.ts +++ b/packages/angular/src/generators/application/lib/normalize-options.ts @@ -1,7 +1,7 @@ import { joinPathFragments, type Tree } from '@nx/devkit'; import { determineProjectNameAndRootOptions, - ensureProjectName, + ensureRootProjectName, } from '@nx/devkit/src/generators/project-name-and-root-utils'; import { Linter } from '@nx/eslint'; import { E2eTestRunner, UnitTestRunner } from '../../../utils/test-runners'; @@ -12,7 +12,7 @@ export async function normalizeOptions( host: Tree, options: Partial ): Promise { - await ensureProjectName(host, options as Schema, 'application'); + await ensureRootProjectName(options as Schema, 'application'); const { projectName: appProjectName, projectRoot: appProjectRoot } = await determineProjectNameAndRootOptions(host, { name: options.name, diff --git a/packages/angular/src/generators/host/host.ts b/packages/angular/src/generators/host/host.ts index 35b41d2afe..966939523f 100644 --- a/packages/angular/src/generators/host/host.ts +++ b/packages/angular/src/generators/host/host.ts @@ -7,7 +7,7 @@ import { } from '@nx/devkit'; import { determineProjectNameAndRootOptions, - ensureProjectName, + ensureRootProjectName, } from '@nx/devkit/src/generators/project-name-and-root-utils'; import { isValidVariable } from '@nx/js'; import { assertNotUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup'; @@ -53,7 +53,7 @@ export async function host(tree: Tree, schema: Schema) { }); } - await ensureProjectName(tree, options, 'application'); + await ensureRootProjectName(options, 'application'); const { projectName: hostProjectName, projectRoot: appRoot } = await determineProjectNameAndRootOptions(tree, { name: options.name, diff --git a/packages/angular/src/generators/library/lib/normalize-options.ts b/packages/angular/src/generators/library/lib/normalize-options.ts index 710d11e87a..82ce5124dc 100644 --- a/packages/angular/src/generators/library/lib/normalize-options.ts +++ b/packages/angular/src/generators/library/lib/normalize-options.ts @@ -1,7 +1,7 @@ import { names, Tree } from '@nx/devkit'; import { determineProjectNameAndRootOptions, - ensureProjectName, + ensureRootProjectName, } from '@nx/devkit/src/generators/project-name-and-root-utils'; import { Linter } from '@nx/eslint'; import { UnitTestRunner } from '../../../utils/test-runners'; @@ -29,7 +29,7 @@ export async function normalizeOptions( ...schema, }; - await ensureProjectName(host, options, 'library'); + await ensureRootProjectName(options, 'library'); const { projectName, names: projectNames, diff --git a/packages/angular/src/generators/remote/remote.ts b/packages/angular/src/generators/remote/remote.ts index 723f4b4143..6809807046 100644 --- a/packages/angular/src/generators/remote/remote.ts +++ b/packages/angular/src/generators/remote/remote.ts @@ -8,7 +8,7 @@ import { } from '@nx/devkit'; import { determineProjectNameAndRootOptions, - ensureProjectName, + ensureRootProjectName, } from '@nx/devkit/src/generators/project-name-and-root-utils'; import { assertNotUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup'; import { swcHelpersVersion } from '@nx/js/src/utils/versions'; @@ -32,7 +32,7 @@ export async function remote(tree: Tree, schema: Schema) { ); } - await ensureProjectName(tree, options, 'application'); + await ensureRootProjectName(options, 'application'); const { projectName: remoteProjectName } = await determineProjectNameAndRootOptions(tree, { name: options.name, diff --git a/packages/cypress/src/generators/configuration/configuration.ts b/packages/cypress/src/generators/configuration/configuration.ts index b42c025bae..df614c8052 100644 --- a/packages/cypress/src/generators/configuration/configuration.ts +++ b/packages/cypress/src/generators/configuration/configuration.ts @@ -474,10 +474,10 @@ function createPackageJson(tree: Tree, options: NormalizedSchema) { name: importPath, version: '0.0.1', private: true, - nx: { - name: options.project, - }, }; + if (options.project !== importPath) { + packageJson.nx = { name: options.project }; + } writeJson(tree, packageJsonPath, packageJson); } diff --git a/packages/detox/src/generators/application/application.spec.ts b/packages/detox/src/generators/application/application.spec.ts index 3d8dc6b31b..acb80d8f8d 100644 --- a/packages/detox/src/generators/application/application.spec.ts +++ b/packages/detox/src/generators/application/application.spec.ts @@ -548,12 +548,10 @@ describe('detox application generator', () => { expect(tree.read('apps/my-app-e2e/package.json', 'utf-8')) .toMatchInlineSnapshot(` "{ - "name": "my-app-e2e", + "name": "@proj/my-app-e2e", "version": "0.0.1", "private": true, "nx": { - "sourceRoot": "apps/my-app-e2e/src", - "projectType": "application", "implicitDependencies": [ "my-app" ] @@ -660,5 +658,34 @@ describe('detox application generator', () => { " `); }); + + it('should respect the provided e2e name', async () => { + writeJson(tree, 'apps/my-app/package.json', { + name: 'my-app', + }); + + await detoxApplicationGenerator(tree, { + e2eDirectory: 'apps/my-app-e2e', + appProject: 'my-app', + e2eName: 'my-app-e2e', + linter: Linter.None, + framework: 'react-native', + addPlugin: true, + skipFormat: true, + }); + + const packageJson = readJson(tree, 'apps/my-app-e2e/package.json'); + expect(packageJson.name).toBe('@proj/my-app-e2e'); + expect(packageJson.nx.name).toBe('my-app-e2e'); + // Make sure keys are in idiomatic order + expect(Object.keys(packageJson)).toMatchInlineSnapshot(` + [ + "name", + "version", + "private", + "nx", + ] + `); + }); }); }); diff --git a/packages/detox/src/generators/application/lib/add-linting.spec.ts b/packages/detox/src/generators/application/lib/add-linting.spec.ts index 5c2ece3576..3f60572a84 100644 --- a/packages/detox/src/generators/application/lib/add-linting.spec.ts +++ b/packages/detox/src/generators/application/lib/add-linting.spec.ts @@ -13,6 +13,7 @@ describe('Add Linting', () => { e2eDirectory: 'my-app-e2e', e2eProjectName: 'my-app-e2e', e2eProjectRoot: 'apps/my-app-e2e', + importPath: '@proj/my-app-e2e', appProject: 'my-app', appFileName: 'my-app', appClassName: 'MyApp', @@ -30,6 +31,7 @@ describe('Add Linting', () => { e2eDirectory: 'my-app-e2e', e2eProjectName: 'my-app-e2e', e2eProjectRoot: 'apps/my-app-e2e', + importPath: '@proj/my-app-e2e', appProject: 'my-app', appFileName: 'my-app', appClassName: 'MyApp', @@ -49,6 +51,7 @@ describe('Add Linting', () => { e2eDirectory: 'my-app-e2e', e2eProjectName: 'my-app-e2e', e2eProjectRoot: 'apps/my-app-e2e', + importPath: '@proj/my-app-e2e', appProject: 'my-app', appFileName: 'my-app', appClassName: 'MyApp', diff --git a/packages/detox/src/generators/application/lib/add-project.spec.ts b/packages/detox/src/generators/application/lib/add-project.spec.ts index 4a351574b6..a88a27b215 100644 --- a/packages/detox/src/generators/application/lib/add-project.spec.ts +++ b/packages/detox/src/generators/application/lib/add-project.spec.ts @@ -32,6 +32,7 @@ describe('Add Project', () => { e2eDirectory: 'my-app-e2e', e2eProjectName: 'my-app-e2e', e2eProjectRoot: 'apps/my-app-e2e', + importPath: '@proj/my-app-e2e', appProject: 'my-app', appFileName: 'my-app', appClassName: 'MyApp', @@ -81,6 +82,7 @@ describe('Add Project', () => { e2eDirectory: 'my-dir-my-app-e2e', e2eProjectName: 'my-dir-my-app-e2e', e2eProjectRoot: 'apps/my-dir/my-app-e2e', + importPath: '@proj/my-dir-my-app-e2e', appProject: 'my-dir-my-app', appFileName: 'my-app', appClassName: 'MyApp', diff --git a/packages/detox/src/generators/application/lib/add-project.ts b/packages/detox/src/generators/application/lib/add-project.ts index ebbdf53a98..3afccb4507 100644 --- a/packages/detox/src/generators/application/lib/add-project.ts +++ b/packages/detox/src/generators/application/lib/add-project.ts @@ -25,12 +25,14 @@ export function addProject(host: Tree, options: NormalizedSchema) { if (isUsingTsSolutionSetup(host)) { writeJson(host, joinPathFragments(options.e2eProjectRoot, 'package.json'), { - name: options.e2eProjectName, + name: options.importPath, version: '0.0.1', private: true, nx: { - sourceRoot: `${options.e2eProjectRoot}/src`, - projectType: 'application', + name: + options.e2eProjectName !== options.importPath + ? options.e2eProjectName + : undefined, targets: hasPlugin ? undefined : getTargets(options), implicitDependencies: [options.appProject], }, diff --git a/packages/detox/src/generators/application/lib/create-files.spec.ts b/packages/detox/src/generators/application/lib/create-files.spec.ts index 85110b09e4..8ba30d5129 100644 --- a/packages/detox/src/generators/application/lib/create-files.spec.ts +++ b/packages/detox/src/generators/application/lib/create-files.spec.ts @@ -15,6 +15,7 @@ describe('Create Files', () => { e2eDirectory: 'my-app-e2e', e2eProjectName: 'my-app-e2e', e2eProjectRoot: 'apps/my-app-e2e', + importPath: '@proj/my-app-e2e', appProject: 'my-app', appFileName: 'my-app', appClassName: 'MyApp', diff --git a/packages/detox/src/generators/application/lib/normalize-options.spec.ts b/packages/detox/src/generators/application/lib/normalize-options.spec.ts index ca5fc83010..2bcc6fe504 100644 --- a/packages/detox/src/generators/application/lib/normalize-options.spec.ts +++ b/packages/detox/src/generators/application/lib/normalize-options.spec.ts @@ -27,10 +27,10 @@ describe('Normalize Options', () => { expect(options).toEqual({ addPlugin: true, framework: 'react-native', - e2eName: 'my-app-e2e', e2eDirectory: 'apps/my-app-e2e', e2eProjectName: 'my-app-e2e', e2eProjectRoot: 'apps/my-app-e2e', + importPath: '@proj/my-app-e2e', appProject: 'my-app', appFileName: 'my-app', appClassName: 'MyApp', @@ -62,11 +62,11 @@ describe('Normalize Options', () => { appClassName: 'MyApp', appFileName: 'my-app', appRoot: 'apps/my-app', - e2eName: 'my-app-e2e', e2eDirectory: 'apps/my-app-e2e', appProject: 'my-app', e2eProjectName: 'my-app-e2e', e2eProjectRoot: 'apps/my-app-e2e', + importPath: '@proj/my-app-e2e', framework: 'react-native', isUsingTsSolutionConfig: false, js: false, @@ -94,6 +94,7 @@ describe('Normalize Options', () => { appFileName: 'my-app', appRoot: 'apps/my-app', e2eProjectRoot: 'directory', + importPath: '@proj/directory-my-app-e2e', e2eName: 'directory-my-app-e2e', e2eDirectory: 'directory', e2eProjectName: 'directory-my-app-e2e', diff --git a/packages/detox/src/generators/application/lib/normalize-options.ts b/packages/detox/src/generators/application/lib/normalize-options.ts index ed8868d94f..f80ce4195a 100644 --- a/packages/detox/src/generators/application/lib/normalize-options.ts +++ b/packages/detox/src/generators/application/lib/normalize-options.ts @@ -1,18 +1,16 @@ import { names, readNxJson, readProjectConfiguration, Tree } from '@nx/devkit'; -import { - determineProjectNameAndRootOptions, - ensureProjectName, -} from '@nx/devkit/src/generators/project-name-and-root-utils'; +import { determineProjectNameAndRootOptions } from '@nx/devkit/src/generators/project-name-and-root-utils'; import { Schema } from '../schema'; import { isUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup'; -export interface NormalizedSchema extends Schema { +export interface NormalizedSchema extends Omit { appFileName: string; // the file name of app to be tested in kebab case appClassName: string; // the class name of app to be tested in pascal case appExpoName: string; // the expo name of app to be tested in class case appRoot: string; // the root path of e2e project. e.g. apps/app-directory/app e2eProjectName: string; // the name of e2e project e2eProjectRoot: string; // the root path of e2e project. e.g. apps/e2e-directory/e2e-app + importPath: string; isUsingTsSolutionConfig?: boolean; } @@ -20,12 +18,15 @@ export async function normalizeOptions( host: Tree, options: Schema ): Promise { - const { projectName: e2eProjectName, projectRoot: e2eProjectRoot } = - await determineProjectNameAndRootOptions(host, { - name: options.e2eName, - projectType: 'application', - directory: options.e2eDirectory, - }); + const { + projectName, + projectRoot: e2eProjectRoot, + importPath, + } = await determineProjectNameAndRootOptions(host, { + name: options.e2eName, + projectType: 'application', + directory: options.e2eDirectory, + }); const nxJson = readNxJson(host); const addPlugin = process.env.NX_ADD_PLUGINS !== 'false' && @@ -37,6 +38,10 @@ export async function normalizeOptions( ); const { root: appRoot } = readProjectConfiguration(host, options.appProject); + const isUsingTsSolutionConfig = isUsingTsSolutionSetup(host); + const e2eProjectName = + !isUsingTsSolutionConfig || options.e2eName ? projectName : importPath; + return { ...options, appFileName, @@ -44,10 +49,10 @@ export async function normalizeOptions( appDisplayName: options.appDisplayName || appClassName, appExpoName: options.appDisplayName?.replace(/\s/g, '') || appClassName, appRoot, - e2eName: e2eProjectName, e2eProjectName, e2eProjectRoot, - isUsingTsSolutionConfig: isUsingTsSolutionSetup(host), + importPath, + isUsingTsSolutionConfig, js: options.js ?? false, }; } diff --git a/packages/devkit/src/generators/project-name-and-root-utils.spec.ts b/packages/devkit/src/generators/project-name-and-root-utils.spec.ts index 28a0dee4d7..c6f2fcc55c 100644 --- a/packages/devkit/src/generators/project-name-and-root-utils.spec.ts +++ b/packages/devkit/src/generators/project-name-and-root-utils.spec.ts @@ -9,261 +9,259 @@ import { determineProjectNameAndRootOptions } from './project-name-and-root-util describe('determineProjectNameAndRootOptions', () => { let tree: Tree; - describe('no layout', () => { - beforeEach(() => { - tree = createTreeWithEmptyWorkspace(); + beforeEach(() => { + tree = createTreeWithEmptyWorkspace(); - setCwd(''); + setCwd(''); - jest.clearAllMocks(); + jest.clearAllMocks(); + }); + + it('should return the last part of the directory as name', async () => { + const result = await determineProjectNameAndRootOptions(tree, { + directory: 'shared/lib-name', + projectType: 'library', }); - it('should return the last part of the directory as name', async () => { - const result = await determineProjectNameAndRootOptions(tree, { - directory: 'shared/lib-name', + expect(result).toStrictEqual({ + projectName: 'lib-name', + names: { + projectSimpleName: 'lib-name', + projectFileName: 'lib-name', + }, + importPath: '@proj/lib-name', + projectRoot: 'shared/lib-name', + }); + }); + + it('should use "@" scoped directory as the project name and import path', async () => { + const result = await determineProjectNameAndRootOptions(tree, { + directory: 'packages/@scope/lib-name', + projectType: 'library', + }); + + expect(result).toEqual({ + projectName: '@scope/lib-name', + names: { + projectSimpleName: 'lib-name', + projectFileName: 'lib-name', + }, + importPath: '@scope/lib-name', + projectRoot: 'packages/@scope/lib-name', + }); + }); + + it('should use "@" scoped directory as the project name and import path in deeply nested directory', async () => { + const result = await determineProjectNameAndRootOptions(tree, { + directory: 'packages/shared/@scope/lib-name', + projectType: 'library', + }); + + expect(result).toEqual({ + projectName: '@scope/lib-name', + names: { + projectSimpleName: 'lib-name', + projectFileName: 'lib-name', + }, + importPath: '@scope/lib-name', + projectRoot: 'packages/shared/@scope/lib-name', + }); + }); + + it('should handle Windows path correctly', async () => { + const result = await determineProjectNameAndRootOptions(tree, { + directory: 'shared\\lib-name', + projectType: 'library', + }); + + expect(result).toStrictEqual({ + projectName: 'lib-name', + names: { + projectSimpleName: 'lib-name', + projectFileName: 'lib-name', + }, + importPath: '@proj/lib-name', + projectRoot: 'shared/lib-name', + }); + }); + + it('should use provided import path over scoped name', async () => { + const result = await determineProjectNameAndRootOptions(tree, { + name: '@scope/lib-name', + directory: 'shared', + projectType: 'library', + importPath: '@custom-scope/lib-name', + }); + + expect(result).toEqual({ + projectName: '@scope/lib-name', + names: { + projectSimpleName: 'lib-name', + projectFileName: 'lib-name', + }, + importPath: '@custom-scope/lib-name', + projectRoot: 'shared', + }); + }); + + it('should append the directory to the cwd when the provided directory does not start with the cwd and format is "as-provided"', async () => { + // simulate running in a subdirectory + const originalInitCwd = process.env.INIT_CWD; + process.env.INIT_CWD = join(workspaceRoot, 'some/path'); + + const result = await determineProjectNameAndRootOptions(tree, { + directory: 'nested/lib-name', + projectType: 'library', + }); + + expect(result).toEqual({ + projectName: 'lib-name', + names: { + projectSimpleName: 'lib-name', + projectFileName: 'lib-name', + }, + importPath: '@proj/lib-name', + projectRoot: 'some/path/nested/lib-name', + }); + + // restore original cwd + if (originalInitCwd === undefined) { + delete process.env.INIT_CWD; + } else { + process.env.INIT_CWD = originalInitCwd; + } + }); + + it('should not duplicate the cwd when the provided directory starts with the cwd and format is "as-provided"', async () => { + // simulate running in a subdirectory + const originalInitCwd = process.env.INIT_CWD; + process.env.INIT_CWD = join(workspaceRoot, 'some/path'); + + const result = await determineProjectNameAndRootOptions(tree, { + directory: 'some/path/nested/lib-name', + projectType: 'library', + }); + + expect(result).toEqual({ + projectName: 'lib-name', + names: { + projectSimpleName: 'lib-name', + projectFileName: 'lib-name', + }, + importPath: '@proj/lib-name', + projectRoot: 'some/path/nested/lib-name', + }); + + // restore original cwd + if (originalInitCwd === undefined) { + delete process.env.INIT_CWD; + } else { + process.env.INIT_CWD = originalInitCwd; + } + }); + + it('should return the directory considering the cwd', async () => { + // simulate running in a subdirectory + const originalInitCwd = process.env.INIT_CWD; + process.env.INIT_CWD = join(workspaceRoot, 'some/path'); + + const result = await determineProjectNameAndRootOptions(tree, { + directory: 'lib-name', + projectType: 'library', + }); + + expect(result).toEqual({ + projectName: 'lib-name', + names: { + projectSimpleName: 'lib-name', + projectFileName: 'lib-name', + }, + importPath: '@proj/lib-name', + projectRoot: 'some/path/lib-name', + }); + + // restore original cwd + if (originalInitCwd === undefined) { + delete process.env.INIT_CWD; + } else { + process.env.INIT_CWD = originalInitCwd; + } + }); + + it('should return the project name and directory as provided for root projects', async () => { + updateJson(tree, 'package.json', (json) => { + json.name = 'lib-name'; + return json; + }); + + const result = await determineProjectNameAndRootOptions(tree, { + name: 'lib-name', + directory: '.', + projectType: 'library', + rootProject: true, + }); + + expect(result).toEqual({ + projectName: 'lib-name', + names: { + projectSimpleName: 'lib-name', + projectFileName: 'lib-name', + }, + importPath: 'lib-name', + projectRoot: '.', + }); + }); + + it('should throw when an invalid directory is provided', async () => { + await expect( + determineProjectNameAndRootOptions(tree, { + directory: '!scope/lib-name', projectType: 'library', - }); + }) + ).rejects.toThrow(/directory should match/); + }); - expect(result).toStrictEqual({ - projectName: 'lib-name', - names: { - projectSimpleName: 'lib-name', - projectFileName: 'lib-name', - }, - importPath: '@proj/lib-name', - projectRoot: 'shared/lib-name', - }); - }); - - it('should use "@" scoped directory as the project name and import path', async () => { - const result = await determineProjectNameAndRootOptions(tree, { - directory: 'packages/@scope/lib-name', - projectType: 'library', - }); - - expect(result).toEqual({ - projectName: '@scope/lib-name', - names: { - projectSimpleName: 'lib-name', - projectFileName: 'lib-name', - }, - importPath: '@scope/lib-name', - projectRoot: 'packages/@scope/lib-name', - }); - }); - - it('should use "@" scoped directory as the project name and import path in deeply nested directory', async () => { - const result = await determineProjectNameAndRootOptions(tree, { - directory: 'packages/shared/@scope/lib-name', - projectType: 'library', - }); - - expect(result).toEqual({ - projectName: '@scope/lib-name', - names: { - projectSimpleName: 'lib-name', - projectFileName: 'lib-name', - }, - importPath: '@scope/lib-name', - projectRoot: 'packages/shared/@scope/lib-name', - }); - }); - - it('should handle Windows path correctly', async () => { - const result = await determineProjectNameAndRootOptions(tree, { - directory: 'shared\\lib-name', - projectType: 'library', - }); - - expect(result).toStrictEqual({ - projectName: 'lib-name', - names: { - projectSimpleName: 'lib-name', - projectFileName: 'lib-name', - }, - importPath: '@proj/lib-name', - projectRoot: 'shared/lib-name', - }); - }); - - it('should use provided import path over scoped name', async () => { - const result = await determineProjectNameAndRootOptions(tree, { - name: '@scope/lib-name', + it('should throw when an invalid name is provided', async () => { + await expect( + determineProjectNameAndRootOptions(tree, { + name: '!scope/lib-name', directory: 'shared', projectType: 'library', - importPath: '@custom-scope/lib-name', - }); + }) + ).rejects.toThrow(/name should match/); + }); - expect(result).toEqual({ - projectName: '@scope/lib-name', - names: { - projectSimpleName: 'lib-name', - projectFileName: 'lib-name', - }, - importPath: '@custom-scope/lib-name', - projectRoot: 'shared', - }); + it('should handle providing a path including "@" with multiple segments as the project name', async () => { + const result = await determineProjectNameAndRootOptions(tree, { + directory: 'shared/@scope/lib-name/testing', + projectType: 'library', }); - it('should append the directory to the cwd when the provided directory does not start with the cwd and format is "as-provided"', async () => { - // simulate running in a subdirectory - const originalInitCwd = process.env.INIT_CWD; - process.env.INIT_CWD = join(workspaceRoot, 'some/path'); + expect(result).toEqual({ + projectName: '@scope/lib-name/testing', + names: { + projectSimpleName: 'testing', + projectFileName: 'lib-name-testing', + }, + importPath: '@scope/lib-name/testing', + projectRoot: 'shared/@scope/lib-name/testing', + }); + }); - const result = await determineProjectNameAndRootOptions(tree, { - directory: 'nested/lib-name', - projectType: 'library', - }); - - expect(result).toEqual({ - projectName: 'lib-name', - names: { - projectSimpleName: 'lib-name', - projectFileName: 'lib-name', - }, - importPath: '@proj/lib-name', - projectRoot: 'some/path/nested/lib-name', - }); - - // restore original cwd - if (originalInitCwd === undefined) { - delete process.env.INIT_CWD; - } else { - process.env.INIT_CWD = originalInitCwd; - } + it('should handle providing a path including multiple "@" as the project name', async () => { + const result = await determineProjectNameAndRootOptions(tree, { + directory: 'shared/@foo/@scope/libName', + projectType: 'library', }); - it('should not duplicate the cwd when the provided directory starts with the cwd and format is "as-provided"', async () => { - // simulate running in a subdirectory - const originalInitCwd = process.env.INIT_CWD; - process.env.INIT_CWD = join(workspaceRoot, 'some/path'); - - const result = await determineProjectNameAndRootOptions(tree, { - directory: 'some/path/nested/lib-name', - projectType: 'library', - }); - - expect(result).toEqual({ - projectName: 'lib-name', - names: { - projectSimpleName: 'lib-name', - projectFileName: 'lib-name', - }, - importPath: '@proj/lib-name', - projectRoot: 'some/path/nested/lib-name', - }); - - // restore original cwd - if (originalInitCwd === undefined) { - delete process.env.INIT_CWD; - } else { - process.env.INIT_CWD = originalInitCwd; - } - }); - - it('should return the directory considering the cwd', async () => { - // simulate running in a subdirectory - const originalInitCwd = process.env.INIT_CWD; - process.env.INIT_CWD = join(workspaceRoot, 'some/path'); - - const result = await determineProjectNameAndRootOptions(tree, { - directory: 'lib-name', - projectType: 'library', - }); - - expect(result).toEqual({ - projectName: 'lib-name', - names: { - projectSimpleName: 'lib-name', - projectFileName: 'lib-name', - }, - importPath: '@proj/lib-name', - projectRoot: 'some/path/lib-name', - }); - - // restore original cwd - if (originalInitCwd === undefined) { - delete process.env.INIT_CWD; - } else { - process.env.INIT_CWD = originalInitCwd; - } - }); - - it('should return the project name and directory as provided for root projects', async () => { - updateJson(tree, 'package.json', (json) => { - json.name = 'lib-name'; - return json; - }); - - const result = await determineProjectNameAndRootOptions(tree, { - name: 'lib-name', - directory: '.', - projectType: 'library', - rootProject: true, - }); - - expect(result).toEqual({ - projectName: 'lib-name', - names: { - projectSimpleName: 'lib-name', - projectFileName: 'lib-name', - }, - importPath: 'lib-name', - projectRoot: '.', - }); - }); - - it('should throw when an invalid directory is provided', async () => { - await expect( - determineProjectNameAndRootOptions(tree, { - directory: '!scope/lib-name', - projectType: 'library', - }) - ).rejects.toThrow(/directory should match/); - }); - - it('should throw when an invalid name is provided', async () => { - await expect( - determineProjectNameAndRootOptions(tree, { - name: '!scope/lib-name', - directory: 'shared', - projectType: 'library', - }) - ).rejects.toThrow(/name should match/); - }); - - it('should handle providing a path including "@" with multiple segments as the project name', async () => { - const result = await determineProjectNameAndRootOptions(tree, { - directory: 'shared/@scope/lib-name/testing', - projectType: 'library', - }); - - expect(result).toEqual({ - projectName: '@scope/lib-name/testing', - names: { - projectSimpleName: 'testing', - projectFileName: 'lib-name-testing', - }, - importPath: '@scope/lib-name/testing', - projectRoot: 'shared/@scope/lib-name/testing', - }); - }); - - it('should handle providing a path including multiple "@" as the project name', async () => { - const result = await determineProjectNameAndRootOptions(tree, { - directory: 'shared/@foo/@scope/libName', - projectType: 'library', - }); - - expect(result).toEqual({ - projectName: '@scope/libName', - names: { - projectSimpleName: 'libName', - projectFileName: 'libName', - }, - importPath: '@scope/libName', - projectRoot: 'shared/@foo/@scope/libName', - }); + expect(result).toEqual({ + projectName: '@scope/libName', + names: { + projectSimpleName: 'libName', + projectFileName: 'libName', + }, + importPath: '@scope/libName', + projectRoot: 'shared/@foo/@scope/libName', }); }); }); diff --git a/packages/devkit/src/generators/project-name-and-root-utils.ts b/packages/devkit/src/generators/project-name-and-root-utils.ts index 5a4a74f357..ee1e449e0b 100644 --- a/packages/devkit/src/generators/project-name-and-root-utils.ts +++ b/packages/devkit/src/generators/project-name-and-root-utils.ts @@ -42,7 +42,7 @@ export type ProjectNameAndRootOptions = { /** * Normalized import path for the project. */ - importPath?: string; + importPath: string; }; export async function determineProjectNameAndRootOptions( @@ -88,11 +88,8 @@ export async function determineProjectNameAndRootOptions( } } - let importPath: string | undefined = undefined; - if (options.projectType === 'library') { - importPath = - options.importPath ?? resolveImportPath(tree, name, projectRoot); - } + const importPath = + options.importPath ?? resolveImportPath(tree, name, projectRoot); return { projectName: name, @@ -125,24 +122,17 @@ export function resolveImportPath( return importPath; } -export async function ensureProjectName( - tree: Tree, - options: Omit, +export async function ensureRootProjectName( + options: { directory: string; name?: string }, projectType: 'application' | 'library' ): Promise { - if (!options.name) { - if (options.directory === '.' && getRelativeCwd() === '') { - const result = await prompt<{ name: string }>({ - type: 'input', - name: 'name', - message: `What do you want to name the ${projectType}?`, - }).then(({ name }) => (options.name = name)); - } - const { projectName } = await determineProjectNameAndRootOptions(tree, { - ...options, - projectType, + if (!options.name && options.directory === '.' && getRelativeCwd() === '') { + const result = await prompt<{ name: string }>({ + type: 'input', + name: 'name', + message: `What do you want to name the ${projectType}?`, }); - options.name = projectName; + options.name = result.name; } } diff --git a/packages/esbuild/src/generators/configuration/configuration.ts b/packages/esbuild/src/generators/configuration/configuration.ts index 4225d3f6ff..7f0be67d8d 100644 --- a/packages/esbuild/src/generators/configuration/configuration.ts +++ b/packages/esbuild/src/generators/configuration/configuration.ts @@ -11,7 +11,10 @@ import { import { addBuildTargetDefaults } from '@nx/devkit/src/generators/target-defaults-utils'; import { getOutputDir, getUpdatedPackageJsonContent } from '@nx/js'; import { getImportPath } from '@nx/js/src/utils/get-import-path'; -import { isUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup'; +import { + getProjectSourceRoot, + isUsingTsSolutionSetup, +} from '@nx/js/src/utils/typescript/ts-solution-setup'; import { basename, dirname, join } from 'node:path/posix'; import { mergeTargetConfigurations } from 'nx/src/devkit-internals'; import { PackageJson } from 'nx/src/utils/package-json'; @@ -78,10 +81,11 @@ function addBuildTarget( }; if (isTsSolutionSetup) { - buildOptions.declarationRootDir = - project.sourceRoot ?? tree.exists(`${project.root}/src`) - ? `${project.root}/src` - : project.root; + buildOptions.declarationRootDir = getProjectSourceRoot( + tree, + project.sourceRoot, + project.root + ); } else { buildOptions.assets = []; diff --git a/packages/eslint/src/generators/lint-project/lint-project.spec.ts b/packages/eslint/src/generators/lint-project/lint-project.spec.ts index c7c1ef1bf5..e815da6627 100644 --- a/packages/eslint/src/generators/lint-project/lint-project.spec.ts +++ b/packages/eslint/src/generators/lint-project/lint-project.spec.ts @@ -505,6 +505,9 @@ describe('@nx/eslint:lint-project', () => { } " `); + expect( + readJson(tree, 'package.json').devDependencies['jsonc-eslint-parser'] + ).toBeDefined(); }); it('should generate a project config for buildable lib with lintFilePatterns if provided', async () => { diff --git a/packages/eslint/src/generators/lint-project/lint-project.ts b/packages/eslint/src/generators/lint-project/lint-project.ts index b19cd8c736..2bb334f92d 100644 --- a/packages/eslint/src/generators/lint-project/lint-project.ts +++ b/packages/eslint/src/generators/lint-project/lint-project.ts @@ -1,4 +1,5 @@ import { + addDependenciesToPackageJson, createProjectGraphAsync, formatFiles, GeneratorCallback, @@ -38,6 +39,7 @@ import { BASE_ESLINT_CONFIG_FILENAMES, } from '../../utils/config-file'; import { hasEslintPlugin } from '../utils/plugin'; +import { jsoncEslintParserVersion } from '../../utils/versions'; import { setupRootEsLint } from './setup-root-eslint'; import { getProjectType } from '@nx/js/src/utils/typescript/ts-solution-setup'; @@ -165,13 +167,29 @@ export async function lintProjectGeneratorInternal( // additionally, the companion e2e app would have `rootProject: true` // so we need to check for the root path as well if (!options.rootProject || projectConfig.root !== '.') { + const addDependencyChecks = + options.addPackageJsonDependencyChecks || + isBuildableLibraryProject(tree, projectConfig); createEsLintConfiguration( tree, options, projectConfig, options.setParserOptionsProject, - options.rootProject + options.rootProject, + addDependencyChecks ); + + if (addDependencyChecks) { + tasks.push( + addDependenciesToPackageJson( + tree, + {}, + { 'jsonc-eslint-parser': jsoncEslintParserVersion }, + undefined, + true + ) + ); + } } // Buildable libs need source analysis enabled for linting `package.json`. @@ -201,7 +219,8 @@ function createEsLintConfiguration( options: LintProjectOptions, projectConfig: ProjectConfiguration, setParserOptionsProject: boolean, - rootProject: boolean + rootProject: boolean, + addDependencyChecks: boolean ) { // we are only extending root for non-standalone projects or their complementary e2e apps const extendedRootConfig = rootProject ? undefined : findEslintFile(tree); @@ -224,10 +243,6 @@ function createEsLintConfiguration( } } - const addDependencyChecks = - options.addPackageJsonDependencyChecks || - isBuildableLibraryProject(tree, projectConfig); - const overrides: Linter.ConfigOverride[] = useFlatConfig( tree ) diff --git a/packages/eslint/src/utils/versions.ts b/packages/eslint/src/utils/versions.ts index 4da70c1aa6..d27c7b5467 100644 --- a/packages/eslint/src/utils/versions.ts +++ b/packages/eslint/src/utils/versions.ts @@ -4,6 +4,7 @@ export const eslintVersion = '~8.57.0'; export const eslintrcVersion = '^2.1.1'; export const eslintConfigPrettierVersion = '^9.0.0'; export const typescriptESLintVersion = '^7.16.0'; +export const jsoncEslintParserVersion = '^2.1.0'; // Updated linting stack for ESLint v9, typescript-eslint v8 export const eslint9__typescriptESLintVersion = '^8.19.0'; diff --git a/packages/expo/src/generators/application/application.spec.ts b/packages/expo/src/generators/application/application.spec.ts index 05acbf5ee5..99a715e37b 100644 --- a/packages/expo/src/generators/application/application.spec.ts +++ b/packages/expo/src/generators/application/application.spec.ts @@ -331,8 +331,10 @@ describe('app', () => { }); describe('TS solution setup', () => { - it('should add project references when using TS solution', async () => { - const tree = createTreeWithEmptyWorkspace(); + let tree: Tree; + + beforeEach(() => { + tree = createTreeWithEmptyWorkspace(); tree.write('.gitignore', ''); updateJson(tree, 'package.json', (json) => { json.workspaces = ['packages/*', 'apps/*']; @@ -349,7 +351,9 @@ describe('app', () => { files: [], references: [], }); + }); + it('should add project references when using TS solution', async () => { await expoApplicationGenerator(tree, { directory: 'my-app', displayName: 'myApp', @@ -368,14 +372,15 @@ describe('app', () => { }, ] `); + const packageJson = readJson(tree, 'my-app/package.json'); + expect(packageJson.name).toBe('@proj/my-app'); + expect(packageJson.nx).toBeUndefined(); // Make sure keys are in idiomatic order - expect(Object.keys(readJson(tree, 'my-app/package.json'))) - .toMatchInlineSnapshot(` + expect(Object.keys(packageJson)).toMatchInlineSnapshot(` [ "name", "version", "private", - "nx", ] `); expect(readJson(tree, 'my-app/tsconfig.json')).toMatchInlineSnapshot(` @@ -476,5 +481,32 @@ describe('app', () => { } `); }); + + it('should respect provided name', async () => { + await expoApplicationGenerator(tree, { + directory: 'my-app', + name: 'my-app', + displayName: 'myApp', + linter: Linter.EsLint, + e2eTestRunner: 'none', + skipFormat: false, + js: false, + unitTestRunner: 'jest', + addPlugin: true, + }); + + const packageJson = readJson(tree, 'my-app/package.json'); + expect(packageJson.name).toBe('@proj/my-app'); + expect(packageJson.nx.name).toBe('my-app'); + // Make sure keys are in idiomatic order + expect(Object.keys(packageJson)).toMatchInlineSnapshot(` + [ + "name", + "version", + "private", + "nx", + ] + `); + }); }); }); diff --git a/packages/expo/src/generators/application/application.ts b/packages/expo/src/generators/application/application.ts index 77b9682ea9..231df7b6d7 100644 --- a/packages/expo/src/generators/application/application.ts +++ b/packages/expo/src/generators/application/application.ts @@ -62,6 +62,12 @@ export async function expoApplicationGeneratorInternal( await createApplicationFiles(host, options); addProject(host, options); + // If we are using the new TS solution + // We need to update the workspace file (package.json or pnpm-workspaces.yaml) to include the new project + if (options.isTsSolutionSetup) { + addProjectToTsSolutionWorkspace(host, options.appProjectRoot); + } + const lintTask = await addLinting(host, { ...options, projectRoot: options.appProjectRoot, @@ -100,12 +106,6 @@ export async function expoApplicationGeneratorInternal( : undefined ); - // If we are using the new TS solution - // We need to update the workspace file (package.json or pnpm-workspaces.yaml) to include the new project - if (options.useTsSolution) { - addProjectToTsSolutionWorkspace(host, options.appProjectRoot); - } - sortPackageJsonFields(host, options.appProjectRoot); if (!options.skipFormat) { diff --git a/packages/expo/src/generators/application/lib/add-e2e.ts b/packages/expo/src/generators/application/lib/add-e2e.ts index 4734949259..63e0279e7a 100644 --- a/packages/expo/src/generators/application/lib/add-e2e.ts +++ b/packages/expo/src/generators/application/lib/add-e2e.ts @@ -51,8 +51,6 @@ export async function addE2e( version: '0.0.1', private: true, nx: { - projectType: 'application', - sourceRoot: joinPathFragments(options.e2eProjectRoot, 'src'), implicitDependencies: [options.projectName], }, } @@ -134,8 +132,6 @@ export async function addE2e( version: '0.0.1', private: true, nx: { - projectType: 'application', - sourceRoot: joinPathFragments(options.e2eProjectRoot, 'src'), implicitDependencies: [options.projectName], }, } @@ -205,7 +201,7 @@ export async function addE2e( e2eDirectory: options.e2eProjectRoot, appProject: options.projectName, appDisplayName: options.displayName, - appName: options.name, + appName: options.simpleName, framework: 'expo', setParserOptionsProject: options.setParserOptionsProject, skipFormat: true, diff --git a/packages/expo/src/generators/application/lib/add-project.ts b/packages/expo/src/generators/application/lib/add-project.ts index 6067f11810..fd43014fae 100644 --- a/packages/expo/src/generators/application/lib/add-project.ts +++ b/packages/expo/src/generators/application/lib/add-project.ts @@ -2,7 +2,6 @@ import { addProjectConfiguration, joinPathFragments, ProjectConfiguration, - readNxJson, TargetConfiguration, Tree, writeJson, @@ -12,10 +11,9 @@ import { hasExpoPlugin } from '../../../utils/has-expo-plugin'; import { NormalizedSchema } from './normalize-options'; import { addBuildTargetDefaults } from '@nx/devkit/src/generators/target-defaults-utils'; import { isUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup'; -import { getImportPath } from '@nx/js/src/utils/get-import-path'; +import type { PackageJson } from 'nx/src/utils/package-json'; export function addProject(host: Tree, options: NormalizedSchema) { - const nxJson = readNxJson(host); const hasPlugin = hasExpoPlugin(host); if (!hasPlugin) { @@ -31,19 +29,29 @@ export function addProject(host: Tree, options: NormalizedSchema) { }; if (isUsingTsSolutionSetup(host)) { - const packageName = getImportPath(host, options.name); - writeJson(host, joinPathFragments(options.appProjectRoot, 'package.json'), { - name: packageName, + const packageJson: PackageJson = { + name: options.importPath, version: '0.0.1', private: true, - nx: { - name: packageName === options.name ? undefined : options.name, - projectType: 'application', - sourceRoot: `${options.appProjectRoot}/src`, - targets: hasPlugin ? undefined : getTargets(options), - tags: options.parsedTags?.length ? options.parsedTags : undefined, - }, - }); + }; + + if (options.importPath !== options.projectName) { + packageJson.nx = { name: options.projectName }; + } + if (!hasPlugin) { + packageJson.nx ??= {}; + packageJson.nx.targets = getTargets(options); + } + if (options.parsedTags?.length) { + packageJson.nx ??= {}; + packageJson.nx.tags = options.parsedTags; + } + + writeJson( + host, + joinPathFragments(options.appProjectRoot, 'package.json'), + packageJson + ); } else { addProjectConfiguration( host, diff --git a/packages/expo/src/generators/application/lib/normalize-options.spec.ts b/packages/expo/src/generators/application/lib/normalize-options.spec.ts index 6ae07b52ef..2a1c81d8b5 100644 --- a/packages/expo/src/generators/application/lib/normalize-options.spec.ts +++ b/packages/expo/src/generators/application/lib/normalize-options.spec.ts @@ -28,11 +28,12 @@ describe('Normalize Options', () => { directory: 'my-app', displayName: 'MyApp', lowerCaseName: 'myapp', - name: 'my-app', + simpleName: 'my-app', parsedTags: [], projectName: 'my-app', linter: Linter.EsLint, e2eTestRunner: 'none', + importPath: '@proj/my-app', unitTestRunner: 'jest', skipFormat: false, js: true, @@ -60,11 +61,12 @@ describe('Normalize Options', () => { directory: 'myApp', displayName: 'MyApp', lowerCaseName: 'myapp', - name: 'myApp', + simpleName: 'myApp', parsedTags: [], projectName: 'myApp', linter: Linter.EsLint, e2eTestRunner: 'none', + importPath: '@proj/myApp', skipFormat: false, js: true, unitTestRunner: 'jest', @@ -93,10 +95,12 @@ describe('Normalize Options', () => { displayName: 'MyApp', lowerCaseName: 'myapp', name: 'my-app', + simpleName: 'my-app', directory: 'directory', parsedTags: [], projectName: 'my-app', e2eTestRunner: 'none', + importPath: '@proj/my-app', unitTestRunner: 'jest', linter: Linter.EsLint, skipFormat: false, @@ -125,10 +129,11 @@ describe('Normalize Options', () => { directory: 'directory/my-app', displayName: 'MyApp', lowerCaseName: 'myapp', - name: 'my-app', + simpleName: 'my-app', parsedTags: [], projectName: 'my-app', e2eTestRunner: 'none', + importPath: '@proj/my-app', unitTestRunner: 'jest', linter: Linter.EsLint, skipFormat: false, @@ -158,10 +163,11 @@ describe('Normalize Options', () => { className: 'MyApp', displayName: 'My App', lowerCaseName: 'myapp', - name: 'my-app', + simpleName: 'my-app', parsedTags: [], projectName: 'my-app', e2eTestRunner: 'none', + importPath: '@proj/my-app', unitTestRunner: 'jest', linter: Linter.EsLint, skipFormat: false, diff --git a/packages/expo/src/generators/application/lib/normalize-options.ts b/packages/expo/src/generators/application/lib/normalize-options.ts index fe33090610..45f5d5724f 100644 --- a/packages/expo/src/generators/application/lib/normalize-options.ts +++ b/packages/expo/src/generators/application/lib/normalize-options.ts @@ -1,15 +1,18 @@ import { names, readNxJson, Tree } from '@nx/devkit'; import { determineProjectNameAndRootOptions, - ensureProjectName, + ensureRootProjectName, } from '@nx/devkit/src/generators/project-name-and-root-utils'; import { isUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup'; import { Schema } from '../schema'; -export interface NormalizedSchema extends Schema { +export interface NormalizedSchema + extends Omit { className: string; + simpleName: string; projectName: string; appProjectRoot: string; + importPath: string; lowerCaseName: string; parsedTags: string[]; rootProject: boolean; @@ -22,11 +25,12 @@ export async function normalizeOptions( host: Tree, options: Schema ): Promise { - await ensureProjectName(host, options, 'application'); + await ensureRootProjectName(options, 'application'); const { - projectName: appProjectName, + projectName, names: projectNames, projectRoot: appProjectRoot, + importPath, } = await determineProjectNameAndRootOptions(host, { name: options.name, projectType: 'application', @@ -38,12 +42,16 @@ export async function normalizeOptions( nxJson.useInferencePlugins !== false; options.addPlugin ??= addPluginDefault; - const { className } = names(options.name); + const { className } = names(projectName); const parsedTags = options.tags ? options.tags.split(',').map((s) => s.trim()) : []; const rootProject = appProjectRoot === '.'; + const isTsSolutionSetup = isUsingTsSolutionSetup(host); + const appProjectName = + !isTsSolutionSetup || options.name ? projectName : importPath; + const e2eProjectName = rootProject ? 'e2e' : `${appProjectName}-e2e`; const e2eProjectRoot = rootProject ? 'e2e' : `${appProjectRoot}-e2e`; @@ -51,16 +59,17 @@ export async function normalizeOptions( ...options, unitTestRunner: options.unitTestRunner || 'jest', e2eTestRunner: options.e2eTestRunner || 'none', - name: projectNames.projectSimpleName, + simpleName: projectNames.projectSimpleName, className, lowerCaseName: className.toLowerCase(), displayName: options.displayName || className, projectName: appProjectName, appProjectRoot, + importPath, parsedTags, rootProject, e2eProjectName, e2eProjectRoot, - isTsSolutionSetup: isUsingTsSolutionSetup(host), + isTsSolutionSetup, }; } diff --git a/packages/expo/src/generators/library/lib/normalize-options.ts b/packages/expo/src/generators/library/lib/normalize-options.ts index d60d1e50e5..6b164b425a 100644 --- a/packages/expo/src/generators/library/lib/normalize-options.ts +++ b/packages/expo/src/generators/library/lib/normalize-options.ts @@ -1,17 +1,17 @@ import { readNxJson, Tree } from '@nx/devkit'; import { determineProjectNameAndRootOptions, - ensureProjectName, + ensureRootProjectName, } from '@nx/devkit/src/generators/project-name-and-root-utils'; import { Schema } from '../schema'; import { isUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup'; -import { getImportPath } from '@nx/js/src/utils/get-import-path'; export interface NormalizedSchema extends Schema { name: string; fileName: string; projectName: string; projectRoot: string; + importPath: string; routePath: string; parsedTags: string[]; isUsingTsSolutionConfig: boolean; @@ -21,7 +21,7 @@ export async function normalizeOptions( host: Tree, options: Schema ): Promise { - await ensureProjectName(host, options, 'library'); + await ensureRootProjectName(options, 'library'); const { projectName, names: projectNames, @@ -49,9 +49,8 @@ export async function normalizeOptions( fileName: projectName, routePath: `/${projectNames.projectSimpleName}`, name: projectName, - projectName: isUsingTsSolutionConfig - ? importPath ?? getImportPath(host, projectName) - : projectName, + projectName: + isUsingTsSolutionConfig && !options.name ? importPath : projectName, projectRoot, parsedTags, importPath, diff --git a/packages/expo/src/generators/library/library.spec.ts b/packages/expo/src/generators/library/library.spec.ts index 582a86102b..72bb265331 100644 --- a/packages/expo/src/generators/library/library.spec.ts +++ b/packages/expo/src/generators/library/library.spec.ts @@ -503,7 +503,6 @@ describe('lib', () => { }, "main": "./src/index.ts", "name": "@proj/my-lib", - "nx": {}, "peerDependencies": { "react": "~18.3.1", "react-native": "0.76.3", @@ -520,7 +519,6 @@ describe('lib', () => { "main", "types", "exports", - "nx", "peerDependencies", ] `); @@ -639,7 +637,6 @@ describe('lib', () => { "main": "./src/index.ts", "module": "./dist/index.esm.js", "name": "@proj/my-lib", - "nx": {}, "peerDependencies": { "react": "~18.3.1", "react-native": "0.76.3", @@ -649,5 +646,38 @@ describe('lib', () => { } `); }); + + it('should set "nx.name" in package.json when the user provides a name that is different than the package name', async () => { + await expoLibraryGenerator(appTree, { + ...defaultSchema, + directory: 'my-lib', + name: 'my-lib', // import path contains the npm scope, so it would be different + skipFormat: true, + }); + + expect(readJson(appTree, 'my-lib/package.json').nx).toStrictEqual({ + name: 'my-lib', + }); + }); + + it('should not set "nx.name" in package.json when the provided name matches the package name', async () => { + await expoLibraryGenerator(appTree, { + ...defaultSchema, + directory: 'my-lib', + name: '@proj/my-lib', + skipFormat: true, + }); + + expect(readJson(appTree, 'my-lib/package.json').nx).toBeUndefined(); + }); + + it('should not set "nx.name" in package.json when the user does not provide a name', async () => { + await expoLibraryGenerator(appTree, { + ...defaultSchema, // defaultSchema has no name + skipFormat: true, + }); + + expect(readJson(appTree, 'my-lib/package.json').nx).toBeUndefined(); + }); }); }); diff --git a/packages/expo/src/generators/library/library.ts b/packages/expo/src/generators/library/library.ts index 0516ef70d0..a754e84cf9 100644 --- a/packages/expo/src/generators/library/library.ts +++ b/packages/expo/src/generators/library/library.ts @@ -176,19 +176,30 @@ async function addProject( }; if (options.isUsingTsSolutionConfig) { - writeJson(host, joinPathFragments(options.projectRoot, 'package.json'), { + const packageJson: PackageJson = { name: options.projectName, version: '0.0.1', ...determineEntryFields(options), - nx: { - tags: options.parsedTags?.length ? options.parsedTags : undefined, - }, files: options.publishable ? ['dist', '!**/*.tsbuildinfo'] : undefined, peerDependencies: { react: reactVersion, 'react-native': reactNativeVersion, }, - }); + }; + + if (options.projectName !== options.importPath) { + packageJson.nx = { name: options.projectName }; + } + if (options.parsedTags?.length) { + packageJson.nx ??= {}; + packageJson.nx.tags = options.parsedTags; + } + + writeJson( + host, + joinPathFragments(options.projectRoot, 'package.json'), + packageJson + ); } else { addProjectConfiguration(host, options.name, project); } diff --git a/packages/express/src/generators/application/application.spec.ts b/packages/express/src/generators/application/application.spec.ts index f8efdeb939..bbb5656273 100644 --- a/packages/express/src/generators/application/application.spec.ts +++ b/packages/express/src/generators/application/application.spec.ts @@ -185,9 +185,6 @@ describe('app', () => { { "name": "@proj/myapp", "nx": { - "name": "myapp", - "projectType": "application", - "sourceRoot": "myapp/src", "targets": { "build": { "configurations": { @@ -217,10 +214,10 @@ describe('app', () => { "serve": { "configurations": { "development": { - "buildTarget": "myapp:build:development", + "buildTarget": "@proj/myapp:build:development", }, "production": { - "buildTarget": "myapp:build:production", + "buildTarget": "@proj/myapp:build:production", }, }, "defaultConfiguration": "development", @@ -229,7 +226,7 @@ describe('app', () => { ], "executor": "@nx/js:node", "options": { - "buildTarget": "myapp:build", + "buildTarget": "@proj/myapp:build", "runBuildTargetDependencies": false, }, }, diff --git a/packages/express/src/generators/application/application.ts b/packages/express/src/generators/application/application.ts index 181dc8d5f3..f094cbeba1 100644 --- a/packages/express/src/generators/application/application.ts +++ b/packages/express/src/generators/application/application.ts @@ -9,7 +9,7 @@ import { } from '@nx/devkit'; import { determineProjectNameAndRootOptions, - ensureProjectName, + ensureRootProjectName, } from '@nx/devkit/src/generators/project-name-and-root-utils'; import { applicationGenerator as nodeApplicationGenerator } from '@nx/node'; import { tslibVersion } from '@nx/node/src/utils/versions'; @@ -106,7 +106,7 @@ async function normalizeOptions( host: Tree, options: Schema ): Promise { - await ensureProjectName(host, options, 'application'); + await ensureRootProjectName(options, 'application'); const { projectName: appProjectName, projectRoot: appProjectRoot } = await determineProjectNameAndRootOptions(host, { name: options.name, diff --git a/packages/js/src/generators/library/library.spec.ts b/packages/js/src/generators/library/library.spec.ts index 92660897c4..659c65a257 100644 --- a/packages/js/src/generators/library/library.spec.ts +++ b/packages/js/src/generators/library/library.spec.ts @@ -2126,6 +2126,7 @@ describe('lib', () => { directory: 'my-lib', unitTestRunner: 'jest', bundler, + useProjectJson: false, }); expect(tree.exists('my-lib/tsconfig.spec.json')).toBeTruthy(); @@ -2338,5 +2339,72 @@ describe('lib', () => { } `); }); + + it('should set "nx.name" in package.json when the user provides a name that is different than the package name and "useProjectJson" is "false"', async () => { + await libraryGenerator(tree, { + ...defaultOptions, + directory: 'my-lib', + name: 'my-lib', + useProjectJson: false, + bundler: 'none', + addPlugin: true, + }); + + expect(readJson(tree, 'my-lib/package.json').nx).toStrictEqual({ + name: 'my-lib', + }); + }); + + it('should not set "nx.name" in package.json when the provided name matches the package name', async () => { + await libraryGenerator(tree, { + ...defaultOptions, + directory: 'my-lib', + name: '@proj/my-lib', + useProjectJson: false, + bundler: 'none', + addPlugin: true, + }); + + expect(readJson(tree, 'my-lib/package.json').nx).toBeUndefined(); + }); + + it('should not set "nx.name" in package.json when the user does not provide a name', async () => { + await libraryGenerator(tree, { + ...defaultOptions, + directory: 'my-lib', + useProjectJson: false, + bundler: 'none', + addPlugin: true, + }); + + expect(readJson(tree, 'my-lib/package.json').nx).toBeUndefined(); + }); + + it('should set "name" in project.json to the import path when "useProjectJson" is "true"', async () => { + await libraryGenerator(tree, { + ...defaultOptions, + directory: 'my-lib', + useProjectJson: true, + bundler: 'none', + addPlugin: true, + }); + + expect(readJson(tree, 'my-lib/project.json').name).toBe('@proj/my-lib'); + expect(readJson(tree, 'my-lib/package.json').nx).toBeUndefined(); + }); + + it('should set "name" in project.json to the user-provided name when "useProjectJson" is "true"', async () => { + await libraryGenerator(tree, { + ...defaultOptions, + directory: 'my-lib', + name: 'my-lib', + useProjectJson: true, + bundler: 'none', + addPlugin: true, + }); + + expect(readJson(tree, 'my-lib/project.json').name).toBe('my-lib'); + expect(readJson(tree, 'my-lib/package.json').nx).toBeUndefined(); + }); }); }); diff --git a/packages/js/src/generators/library/library.ts b/packages/js/src/generators/library/library.ts index ee718c6139..cb686d6781 100644 --- a/packages/js/src/generators/library/library.ts +++ b/packages/js/src/generators/library/library.ts @@ -22,7 +22,7 @@ import { } from '@nx/devkit'; import { determineProjectNameAndRootOptions, - ensureProjectName, + ensureRootProjectName, } from '@nx/devkit/src/generators/project-name-and-root-utils'; import { promptWhenInteractive } from '@nx/devkit/src/generators/prompt'; import { addBuildTargetDefaults } from '@nx/devkit/src/generators/target-defaults-utils'; @@ -63,7 +63,6 @@ import type { NormalizedLibraryGeneratorOptions, } from './schema'; import { sortPackageJsonFields } from '../../utils/package-json/sort-fields'; -import { getImportPath } from '../../utils/get-import-path'; import { addReleaseConfigForNonTsSolution, addReleaseConfigForTsSolution, @@ -368,13 +367,7 @@ async function configureProject( } // empty targets are cleaned up automatically by `updateProjectConfiguration` - updateProjectConfiguration( - tree, - options.isUsingTsSolutionConfig - ? options.importPath ?? options.name - : options.name, - projectConfiguration - ); + updateProjectConfiguration(tree, options.name, projectConfiguration); } else if (options.config === 'workspace' || options.config === 'project') { addProjectConfiguration(tree, options.name, projectConfiguration); } else { @@ -682,6 +675,12 @@ function createFiles(tree: Tree, options: NormalizedLibraryGeneratorOptions) { }); } + if (!options.useProjectJson && options.name !== options.importPath) { + packageJson.nx = { + name: options.name, + }; + } + writeJson(tree, packageJsonPath, packageJson); } @@ -760,7 +759,7 @@ async function normalizeOptions( tree: Tree, options: LibraryGeneratorSchema ): Promise { - await ensureProjectName(tree, options, 'library'); + await ensureRootProjectName(options, 'library'); const nxJson = readNxJson(tree); options.addPlugin ??= process.env.NX_ADD_PLUGINS !== 'false' && @@ -901,9 +900,7 @@ async function normalizeOptions( return { ...options, fileName, - name: isUsingTsSolutionConfig - ? getImportPath(tree, projectName) - : projectName, + name: isUsingTsSolutionConfig && !options.name ? importPath : projectName, projectNames, projectRoot, parsedTags, diff --git a/packages/js/src/utils/typescript/ts-solution-setup.ts b/packages/js/src/utils/typescript/ts-solution-setup.ts index 754d86b7e1..0aaee745aa 100644 --- a/packages/js/src/utils/typescript/ts-solution-setup.ts +++ b/packages/js/src/utils/typescript/ts-solution-setup.ts @@ -270,3 +270,16 @@ export function getProjectType( if (!packageJson?.exports) return 'application'; return 'library'; } + +export function getProjectSourceRoot( + tree: Tree, + projectSourceRoot: string | undefined, + projectRoot: string +): string | undefined { + return ( + projectSourceRoot ?? + (tree.exists(joinPathFragments(projectRoot, 'src')) + ? joinPathFragments(projectRoot, 'src') + : projectRoot) + ); +} diff --git a/packages/nest/src/generators/application/application.spec.ts b/packages/nest/src/generators/application/application.spec.ts index b2d68262eb..5e83630b2d 100644 --- a/packages/nest/src/generators/application/application.spec.ts +++ b/packages/nest/src/generators/application/application.spec.ts @@ -209,9 +209,6 @@ describe('application generator', () => { { "name": "@proj/myapp", "nx": { - "name": "myapp", - "projectType": "application", - "sourceRoot": "myapp/src", "targets": { "build": { "configurations": { @@ -232,10 +229,10 @@ describe('application generator', () => { "serve": { "configurations": { "development": { - "buildTarget": "myapp:build:development", + "buildTarget": "@proj/myapp:build:development", }, "production": { - "buildTarget": "myapp:build:production", + "buildTarget": "@proj/myapp:build:production", }, }, "defaultConfiguration": "development", @@ -244,7 +241,7 @@ describe('application generator', () => { ], "executor": "@nx/js:node", "options": { - "buildTarget": "myapp:build", + "buildTarget": "@proj/myapp:build", "runBuildTargetDependencies": false, }, }, diff --git a/packages/nest/src/generators/application/lib/normalize-options.ts b/packages/nest/src/generators/application/lib/normalize-options.ts index e1b30132fd..af3f8e72c4 100644 --- a/packages/nest/src/generators/application/lib/normalize-options.ts +++ b/packages/nest/src/generators/application/lib/normalize-options.ts @@ -1,7 +1,7 @@ import { Tree, readNxJson } from '@nx/devkit'; import { determineProjectNameAndRootOptions, - ensureProjectName, + ensureRootProjectName, } from '@nx/devkit/src/generators/project-name-and-root-utils'; import { Linter } from '@nx/eslint'; import type { Schema as NodeApplicationGeneratorOptions } from '@nx/node/src/generators/application/schema'; @@ -11,7 +11,7 @@ export async function normalizeOptions( tree: Tree, options: ApplicationGeneratorOptions ): Promise { - await ensureProjectName(tree, options, 'application'); + await ensureRootProjectName(options, 'application'); const { projectName: appProjectName, projectRoot: appProjectRoot } = await determineProjectNameAndRootOptions(tree, { name: options.name, diff --git a/packages/nest/src/generators/library/lib/create-files.ts b/packages/nest/src/generators/library/lib/create-files.ts index e7d99e89f3..8c32bf5597 100644 --- a/packages/nest/src/generators/library/lib/create-files.ts +++ b/packages/nest/src/generators/library/lib/create-files.ts @@ -9,8 +9,8 @@ import type { NormalizedOptions } from '../schema'; export function createFiles(tree: Tree, options: NormalizedOptions): void { const substitutions = { - ...options, ...names(options.projectName), + ...options, tmpl: '', offsetFromRoot: offsetFromRoot(options.projectRoot), fileName: options.fileName, diff --git a/packages/nest/src/generators/library/lib/normalize-options.ts b/packages/nest/src/generators/library/lib/normalize-options.ts index cc89b7bee5..5cab842a09 100644 --- a/packages/nest/src/generators/library/lib/normalize-options.ts +++ b/packages/nest/src/generators/library/lib/normalize-options.ts @@ -1,20 +1,19 @@ import { Tree, readNxJson } from '@nx/devkit'; import { determineProjectNameAndRootOptions, - ensureProjectName, + ensureRootProjectName, } from '@nx/devkit/src/generators/project-name-and-root-utils'; import { getNpmScope } from '@nx/js/src/utils/package-json/get-npm-scope'; import type { LibraryGeneratorSchema as JsLibraryGeneratorSchema } from '@nx/js/src/generators/library/schema'; import { Linter } from '@nx/eslint'; import type { LibraryGeneratorOptions, NormalizedOptions } from '../schema'; import { isUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup'; -import { getImportPath } from '@nx/js/src/utils/get-import-path'; export async function normalizeOptions( tree: Tree, options: LibraryGeneratorOptions ): Promise { - await ensureProjectName(tree, options, 'library'); + await ensureRootProjectName(options, 'library'); const { projectName, names: projectNames, @@ -50,9 +49,8 @@ export async function normalizeOptions( linter: options.linter ?? Linter.EsLint, parsedTags, prefix: getNpmScope(tree), // we could also allow customizing this - projectName: isUsingTsSolutionsConfig - ? getImportPath(tree, projectName) - : projectName, + projectName: + isUsingTsSolutionsConfig && !options.name ? importPath : projectName, projectRoot, importPath, service: options.service ?? false, diff --git a/packages/nest/src/generators/library/library.spec.ts b/packages/nest/src/generators/library/library.spec.ts index 795fa67cfa..6088d20371 100644 --- a/packages/nest/src/generators/library/library.spec.ts +++ b/packages/nest/src/generators/library/library.spec.ts @@ -409,7 +409,6 @@ describe('lib', () => { }, }, "private": true, - "type": "module", "types": "./src/index.ts", "version": "0.0.1", } @@ -497,5 +496,42 @@ describe('lib', () => { } `); }); + + it('should set "nx.name" in package.json when the user provides a name that is different than the package name', async () => { + await libraryGenerator(tree, { + directory: 'mylib', + name: 'my-lib', // import path contains the npm scope, so it would be different + linter: 'none', + unitTestRunner: 'none', + skipFormat: true, + }); + + expect(readJson(tree, 'mylib/package.json').nx).toStrictEqual({ + name: 'my-lib', + }); + }); + + it('should not set "nx.name" in package.json when the provided name matches the package name', async () => { + await libraryGenerator(tree, { + directory: 'mylib', + name: '@proj/my-lib', + linter: 'none', + unitTestRunner: 'none', + skipFormat: true, + }); + + expect(readJson(tree, 'mylib/package.json').nx).toBeUndefined(); + }); + + it('should not set "nx.name" in package.json when the user does not provide a name', async () => { + await libraryGenerator(tree, { + directory: 'mylib', + linter: 'none', + unitTestRunner: 'none', + skipFormat: true, + }); + + expect(readJson(tree, 'mylib/package.json').nx).toBeUndefined(); + }); }); }); diff --git a/packages/nest/src/generators/library/library.ts b/packages/nest/src/generators/library/library.ts index 62f257edc1..4cd0ea483b 100644 --- a/packages/nest/src/generators/library/library.ts +++ b/packages/nest/src/generators/library/library.ts @@ -1,6 +1,15 @@ import type { GeneratorCallback, Tree } from '@nx/devkit'; -import { formatFiles, runTasksInSerial } from '@nx/devkit'; +import { + formatFiles, + joinPathFragments, + readJson, + runTasksInSerial, + writeJson, +} from '@nx/devkit'; +import { logShowProjectCommand } from '@nx/devkit/src/utils/log-show-project-command'; import { libraryGenerator as jsLibraryGenerator } from '@nx/js'; +import { ensureDependencies } from '../../utils/ensure-dependencies'; +import initGenerator from '../init/init'; import { addExportsToBarrelFile, addProject, @@ -10,10 +19,7 @@ import { toJsLibraryGeneratorOptions, updateTsConfig, } from './lib'; -import type { LibraryGeneratorOptions } from './schema'; -import initGenerator from '../init/init'; -import { logShowProjectCommand } from '@nx/devkit/src/utils/log-show-project-command'; -import { ensureDependencies } from '../../utils/ensure-dependencies'; +import type { LibraryGeneratorOptions, NormalizedOptions } from './schema'; export async function libraryGenerator( tree: Tree, @@ -34,6 +40,7 @@ export async function libraryGeneratorInternal( tree, toJsLibraryGeneratorOptions(options) ); + updatePackageJson(tree, options); const initTask = await initGenerator(tree, rawOptions); const depsTask = ensureDependencies(tree); deleteFiles(tree, options); @@ -59,3 +66,23 @@ export async function libraryGeneratorInternal( } export default libraryGenerator; + +function updatePackageJson(tree: Tree, options: NormalizedOptions) { + const packageJsonPath = joinPathFragments( + options.projectRoot, + 'package.json' + ); + if (!tree.exists(packageJsonPath)) { + return; + } + + const packageJson = readJson(tree, packageJsonPath); + + if (packageJson.type === 'module') { + // The @nx/js:lib generator can set the type to 'module' which would + // potentially break consumers of the library. + delete packageJson.type; + } + + writeJson(tree, packageJsonPath, packageJson); +} diff --git a/packages/next/src/generators/application/application.spec.ts b/packages/next/src/generators/application/application.spec.ts index a5e44e3d33..171059f518 100644 --- a/packages/next/src/generators/application/application.spec.ts +++ b/packages/next/src/generators/application/application.spec.ts @@ -938,7 +938,6 @@ describe('app (legacy)', () => { ...schema, addPlugin: true, directory: 'myapp', - name: 'myapp', }); expect(readJson(tree, 'tsconfig.json').references).toMatchInlineSnapshot(` @@ -951,14 +950,15 @@ describe('app (legacy)', () => { }, ] `); + const packageJson = readJson(tree, 'myapp/package.json'); + expect(packageJson.name).toBe('@proj/myapp'); + expect(packageJson.nx).toBeUndefined(); // Make sure keys are in idiomatic order - expect(Object.keys(readJson(tree, 'myapp/package.json'))) - .toMatchInlineSnapshot(` + expect(Object.keys(packageJson)).toMatchInlineSnapshot(` [ "name", "version", "private", - "nx", "dependencies", ] `); @@ -1086,6 +1086,29 @@ describe('app (legacy)', () => { } `); }); + + it('should respect the provided name', async () => { + await applicationGenerator(tree, { + ...schema, + addPlugin: true, + directory: 'myapp', + name: 'myapp', + }); + + const packageJson = readJson(tree, 'myapp/package.json'); + expect(packageJson.name).toBe('@proj/myapp'); + expect(packageJson.nx.name).toBe('myapp'); + // Make sure keys are in idiomatic order + expect(Object.keys(packageJson)).toMatchInlineSnapshot(` + [ + "name", + "version", + "private", + "nx", + "dependencies", + ] + `); + }); }); }); diff --git a/packages/next/src/generators/application/application.ts b/packages/next/src/generators/application/application.ts index a469625314..008dc5e4d7 100644 --- a/packages/next/src/generators/application/application.ts +++ b/packages/next/src/generators/application/application.ts @@ -54,8 +54,8 @@ export async function applicationGeneratorInternal(host: Tree, schema: Schema) { js: options.js, skipPackageJson: options.skipPackageJson, skipFormat: true, - addTsPlugin: schema.useTsSolution, - formatter: schema.formatter, + addTsPlugin: options.isTsSolutionSetup, + formatter: options.formatter, platform: 'web', }); tasks.push(jsInitTask); @@ -70,6 +70,12 @@ export async function applicationGeneratorInternal(host: Tree, schema: Schema) { addProject(host, options); + // If we are using the new TS solution + // We need to update the workspace file (package.json or pnpm-workspaces.yaml) to include the new project + if (options.isTsSolutionSetup) { + addProjectToTsSolutionWorkspace(host, options.appProjectRoot); + } + const e2eTask = await addE2e(host, options); tasks.push(e2eTask); @@ -145,12 +151,6 @@ export async function applicationGeneratorInternal(host: Tree, schema: Schema) { options.src ? 'src' : '.' ); - // If we are using the new TS solution - // We need to update the workspace file (package.json or pnpm-workspaces.yaml) to include the new project - if (options.useTsSolution) { - addProjectToTsSolutionWorkspace(host, options.appProjectRoot); - } - sortPackageJsonFields(host, options.appProjectRoot); if (!options.skipFormat) { diff --git a/packages/next/src/generators/application/lib/add-e2e.ts b/packages/next/src/generators/application/lib/add-e2e.ts index 97af47fe5b..5a2c2a3ead 100644 --- a/packages/next/src/generators/application/lib/add-e2e.ts +++ b/packages/next/src/generators/application/lib/add-e2e.ts @@ -54,8 +54,6 @@ export async function addE2e(host: Tree, options: NormalizedSchema) { version: '0.0.1', private: true, nx: { - projectType: 'application', - sourceRoot: joinPathFragments(options.e2eProjectRoot, 'src'), implicitDependencies: [options.projectName], }, } @@ -135,8 +133,6 @@ export async function addE2e(host: Tree, options: NormalizedSchema) { version: '0.0.1', private: true, nx: { - projectType: 'application', - sourceRoot: joinPathFragments(options.e2eProjectRoot, 'src'), implicitDependencies: [options.projectName], }, } diff --git a/packages/next/src/generators/application/lib/add-linting.spec.ts b/packages/next/src/generators/application/lib/add-linting.spec.ts index b7f1ace6c4..c6b272eefc 100644 --- a/packages/next/src/generators/application/lib/add-linting.spec.ts +++ b/packages/next/src/generators/application/lib/add-linting.spec.ts @@ -16,18 +16,20 @@ describe('updateEslint', () => { beforeEach(async () => { schema = { projectName: 'my-app', + projectSimpleName: 'my-app', appProjectRoot: 'my-app', directory: 'my-app', + importPath: '@proj/my-app', linter: Linter.EsLint, unitTestRunner: 'jest', e2eProjectName: 'my-app-e2e', e2eProjectRoot: 'my-app-e2e', outputPath: 'dist/my-app', - name: 'my-app', parsedTags: [], fileName: 'index', e2eTestRunner: 'cypress', styledModule: null, + isTsSolutionSetup: false, }; tree = createTreeWithEmptyWorkspace(); const project: ProjectConfiguration = { diff --git a/packages/next/src/generators/application/lib/add-project.ts b/packages/next/src/generators/application/lib/add-project.ts index 49d837a965..47a6b13e89 100644 --- a/packages/next/src/generators/application/lib/add-project.ts +++ b/packages/next/src/generators/application/lib/add-project.ts @@ -9,9 +9,9 @@ import { } from '@nx/devkit'; import { addBuildTargetDefaults } from '@nx/devkit/src/generators/target-defaults-utils'; import { isUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup'; -import { getImportPath } from '@nx/js/src/utils/get-import-path'; import { nextVersion } from '../../../utils/versions'; import { reactDomVersion, reactVersion } from '@nx/react'; +import type { PackageJson } from 'nx/src/utils/package-json'; export function addProject(host: Tree, options: NormalizedSchema) { const targets: Record = {}; @@ -73,8 +73,8 @@ export function addProject(host: Tree, options: NormalizedSchema) { }; if (isUsingTsSolutionSetup(host)) { - writeJson(host, joinPathFragments(options.appProjectRoot, 'package.json'), { - name: getImportPath(host, options.name), + const packageJson: PackageJson = { + name: options.importPath, version: '0.0.1', private: true, dependencies: { @@ -82,13 +82,21 @@ export function addProject(host: Tree, options: NormalizedSchema) { react: reactVersion, 'react-dom': reactDomVersion, }, - nx: { - name: options.name, - projectType: 'application', - sourceRoot: options.appProjectRoot, - tags: options.parsedTags?.length ? options.parsedTags : undefined, - }, - }); + }; + + if (options.projectName !== options.importPath) { + packageJson.nx = { name: options.projectName }; + } + if (options.parsedTags?.length) { + packageJson.nx ??= {}; + packageJson.nx.tags = options.parsedTags; + } + + writeJson( + host, + joinPathFragments(options.appProjectRoot, 'package.json'), + packageJson + ); } else { addProjectConfiguration(host, options.projectName, { ...project, diff --git a/packages/next/src/generators/application/lib/create-application-files.ts b/packages/next/src/generators/application/lib/create-application-files.ts index faf8f7868b..44cffc1f76 100644 --- a/packages/next/src/generators/application/lib/create-application-files.ts +++ b/packages/next/src/generators/application/lib/create-application-files.ts @@ -41,7 +41,7 @@ export function createApplicationFiles(host: Tree, options: NormalizedSchema) { : ''; const templateVariables = { - ...names(options.name), + ...names(options.projectSimpleName), ...options, dot: '.', tmpl: '', diff --git a/packages/next/src/generators/application/lib/normalize-options.ts b/packages/next/src/generators/application/lib/normalize-options.ts index 5b8782ed72..c89512cb89 100644 --- a/packages/next/src/generators/application/lib/normalize-options.ts +++ b/packages/next/src/generators/application/lib/normalize-options.ts @@ -1,21 +1,26 @@ import { joinPathFragments, names, readNxJson, Tree } from '@nx/devkit'; import { determineProjectNameAndRootOptions, - ensureProjectName, + ensureRootProjectName, } from '@nx/devkit/src/generators/project-name-and-root-utils'; import { Linter } from '@nx/eslint'; import { assertValidStyle } from '@nx/react/src/utils/assertion'; import { Schema } from '../schema'; +import { isUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup'; -export interface NormalizedSchema extends Schema { +export interface NormalizedSchema + extends Omit { projectName: string; + projectSimpleName: string; appProjectRoot: string; + importPath: string; outputPath: string; e2eProjectName: string; e2eProjectRoot: string; parsedTags: string[]; fileName: string; styledModule: null | string; + isTsSolutionSetup: boolean; js?: boolean; } @@ -23,14 +28,18 @@ export async function normalizeOptions( host: Tree, options: Schema ): Promise { - await ensureProjectName(host, options, 'application'); - const { projectName: appProjectName, projectRoot: appProjectRoot } = - await determineProjectNameAndRootOptions(host, { - name: options.name, - projectType: 'application', - directory: options.directory, - rootProject: options.rootProject, - }); + await ensureRootProjectName(options, 'application'); + const { + projectName, + names: projectNames, + projectRoot: appProjectRoot, + importPath, + } = await determineProjectNameAndRootOptions(host, { + name: options.name, + projectType: 'application', + directory: options.directory, + rootProject: options.rootProject, + }); options.rootProject = appProjectRoot === '.'; const nxJson = readNxJson(host); @@ -40,15 +49,18 @@ export async function normalizeOptions( options.addPlugin ??= addPlugin; + const isTsSolutionSetup = + options.useTsSolution || isUsingTsSolutionSetup(host); + const appProjectName = + !isTsSolutionSetup || options.name ? projectName : importPath; + const e2eProjectName = options.rootProject ? 'e2e' : `${appProjectName}-e2e`; const e2eProjectRoot = options.rootProject ? 'e2e' : `${appProjectRoot}-e2e`; - const name = names(options.name).fileName; - const outputPath = joinPathFragments( 'dist', appProjectRoot, - ...(options.rootProject ? [name] : []) + ...(options.rootProject ? [projectNames.projectFileName] : []) ); const parsedTags = options.tags @@ -76,12 +88,14 @@ export async function normalizeOptions( e2eTestRunner: options.e2eTestRunner || 'playwright', fileName, linter: options.linter || Linter.EsLint, - name, outputPath, parsedTags, projectName: appProjectName, + projectSimpleName: projectNames.projectSimpleName, style: options.style || 'css', styledModule, unitTestRunner: options.unitTestRunner || 'jest', + importPath, + isTsSolutionSetup, }; } diff --git a/packages/next/src/generators/library/lib/normalize-options.ts b/packages/next/src/generators/library/lib/normalize-options.ts index cc2d85876e..adc2fc1a57 100644 --- a/packages/next/src/generators/library/lib/normalize-options.ts +++ b/packages/next/src/generators/library/lib/normalize-options.ts @@ -1,7 +1,7 @@ import { readNxJson, Tree } from '@nx/devkit'; import { determineProjectNameAndRootOptions, - ensureProjectName, + ensureRootProjectName, } from '@nx/devkit/src/generators/project-name-and-root-utils'; import { Schema } from '../schema'; import { isUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup'; @@ -16,7 +16,7 @@ export async function normalizeOptions( host: Tree, options: Schema ): Promise { - await ensureProjectName(host, options, 'library'); + await ensureRootProjectName(options, 'library'); const { projectRoot, importPath } = await determineProjectNameAndRootOptions( host, { diff --git a/packages/next/src/generators/library/library.spec.ts b/packages/next/src/generators/library/library.spec.ts index 4180366bc3..7bda5498e3 100644 --- a/packages/next/src/generators/library/library.spec.ts +++ b/packages/next/src/generators/library/library.spec.ts @@ -256,5 +256,45 @@ describe('next library', () => { } `); }); + + it('should set "nx.name" in package.json when the user provides a name that is different than the package name', async () => { + await libraryGenerator(tree, { + directory: 'mylib', + name: 'my-lib', // import path contains the npm scope, so it would be different + linter: 'none', + unitTestRunner: 'none', + style: 'css', + skipFormat: true, + }); + + expect(readJson(tree, 'mylib/package.json').nx).toStrictEqual({ + name: 'my-lib', + }); + }); + + it('should not set "nx.name" in package.json when the provided name matches the package name', async () => { + await libraryGenerator(tree, { + directory: 'mylib', + name: '@proj/my-lib', + linter: 'none', + unitTestRunner: 'none', + style: 'css', + skipFormat: true, + }); + + expect(readJson(tree, 'mylib/package.json').nx).toBeUndefined(); + }); + + it('should not set "nx.name" in package.json when the user does not provide a name', async () => { + await libraryGenerator(tree, { + directory: 'mylib', + linter: 'none', + unitTestRunner: 'none', + style: 'css', + skipFormat: true, + }); + + expect(readJson(tree, 'mylib/package.json').nx).toBeUndefined(); + }); }); }); diff --git a/packages/node/src/generators/application/application.spec.ts b/packages/node/src/generators/application/application.spec.ts index a8f29965b8..b173a296b6 100644 --- a/packages/node/src/generators/application/application.spec.ts +++ b/packages/node/src/generators/application/application.spec.ts @@ -599,9 +599,11 @@ describe('app', () => { }, ] `); + const packageJson = readJson(tree, 'myapp/package.json'); + expect(packageJson.name).toBe('@proj/myapp'); + expect(packageJson.nx.name).toBeUndefined(); // Make sure keys are in idiomatic order - expect(Object.keys(readJson(tree, 'myapp/package.json'))) - .toMatchInlineSnapshot(` + expect(Object.keys(packageJson)).toMatchInlineSnapshot(` [ "name", "version", @@ -613,17 +615,14 @@ describe('app', () => { { "name": "@proj/myapp", "nx": { - "name": "myapp", - "projectType": "application", - "sourceRoot": "myapp/src", "targets": { "serve": { "configurations": { "development": { - "buildTarget": "myapp:build:development", + "buildTarget": "@proj/myapp:build:development", }, "production": { - "buildTarget": "myapp:build:production", + "buildTarget": "@proj/myapp:build:production", }, }, "defaultConfiguration": "development", @@ -632,7 +631,7 @@ describe('app', () => { ], "executor": "@nx/js:node", "options": { - "buildTarget": "myapp:build", + "buildTarget": "@proj/myapp:build", "runBuildTargetDependencies": false, }, }, @@ -717,6 +716,29 @@ describe('app', () => { `); }); + it('should respect the provided name', async () => { + await applicationGenerator(tree, { + directory: 'myapp', + name: 'myapp', + bundler: 'webpack', + unitTestRunner: 'jest', + addPlugin: true, + }); + + const packageJson = readJson(tree, 'myapp/package.json'); + expect(packageJson.name).toBe('@proj/myapp'); + expect(packageJson.nx.name).toBe('myapp'); + // Make sure keys are in idiomatic order + expect(Object.keys(packageJson)).toMatchInlineSnapshot(` + [ + "name", + "version", + "private", + "nx", + ] + `); + }); + it('should use @swc/jest for jest', async () => { await applicationGenerator(tree, { directory: 'apps/my-app', @@ -737,7 +759,7 @@ describe('app', () => { swcJestConfig.swcrc = false; export default { - displayName: 'my-app', + displayName: '@proj/my-app', preset: '../../jest.preset.js', testEnvironment: 'node', transform: { @@ -819,7 +841,7 @@ describe('app', () => { }); expect( - readProjectConfiguration(tree, 'my-app').targets.build.options + readProjectConfiguration(tree, '@proj/my-app').targets.build.options .outputPath ).toBe('apps/my-app/dist'); }); @@ -833,7 +855,7 @@ describe('app', () => { }); expect( - readProjectConfiguration(tree, 'my-app').targets.build.options + readProjectConfiguration(tree, '@proj/my-app').targets.build.options .outputPath ).toBe('apps/my-app/dist'); }); diff --git a/packages/node/src/generators/application/application.ts b/packages/node/src/generators/application/application.ts index 79f7fa6b0b..9858a78a78 100644 --- a/packages/node/src/generators/application/application.ts +++ b/packages/node/src/generators/application/application.ts @@ -1,4 +1,3 @@ -import { getImportPath } from '@nx/js/src/utils/get-import-path'; import { addDependenciesToPackageJson, addProjectConfiguration, @@ -24,7 +23,7 @@ import { } from '@nx/devkit'; import { determineProjectNameAndRootOptions, - ensureProjectName, + ensureRootProjectName, } from '@nx/devkit/src/generators/project-name-and-root-utils'; import { configurationGenerator } from '@nx/jest'; import { @@ -62,10 +61,11 @@ import { } from '@nx/js/src/utils/typescript/ts-solution-setup'; import { sortPackageJsonFields } from '@nx/js/src/utils/package-json/sort-fields'; -export interface NormalizedSchema extends Schema { +export interface NormalizedSchema extends Omit { appProjectRoot: string; parsedTags: string[]; outputPath: string; + importPath: string; isUsingTsSolutionConfig: boolean; } @@ -208,13 +208,11 @@ function addProject(tree: Tree, options: NormalizedSchema) { if (options.isUsingTsSolutionConfig) { writeJson(tree, joinPathFragments(options.appProjectRoot, 'package.json'), { - name: getImportPath(tree, options.name), + name: options.importPath, version: '0.0.1', private: true, nx: { - name: options.name, - projectType: 'application', - sourceRoot: project.sourceRoot, + name: options.name !== options.importPath ? options.name : undefined, targets: project.targets, tags: project.tags?.length ? project.tags : undefined, }, @@ -515,6 +513,12 @@ export async function applicationGeneratorInternal(tree: Tree, schema: Schema) { addAppFiles(tree, options); addProject(tree, options); + // If we are using the new TS solution + // We need to update the workspace file (package.json or pnpm-workspaces.yaml) to include the new project + if (options.isUsingTsSolutionConfig) { + addProjectToTsSolutionWorkspace(tree, options.appProjectRoot); + } + updateTsConfigOptions(tree, options); if (options.linter === Linter.EsLint) { @@ -595,12 +599,6 @@ export async function applicationGeneratorInternal(tree: Tree, schema: Schema) { ); } - // If we are using the new TS solution - // We need to update the workspace file (package.json or pnpm-workspaces.yaml) to include the new project - if (options.isUsingTsSolutionConfig) { - addProjectToTsSolutionWorkspace(tree, options.appProjectRoot); - } - sortPackageJsonFields(tree, options.appProjectRoot); if (!options.skipFormat) { @@ -618,14 +616,17 @@ async function normalizeOptions( host: Tree, options: Schema ): Promise { - await ensureProjectName(host, options, 'application'); - const { projectName: appProjectName, projectRoot: appProjectRoot } = - await determineProjectNameAndRootOptions(host, { - name: options.name, - projectType: 'application', - directory: options.directory, - rootProject: options.rootProject, - }); + await ensureRootProjectName(options, 'application'); + const { + projectName, + projectRoot: appProjectRoot, + importPath, + } = await determineProjectNameAndRootOptions(host, { + name: options.name, + projectType: 'application', + directory: options.directory, + rootProject: options.rootProject, + }); options.rootProject = appProjectRoot === '.'; options.bundler = options.bundler ?? 'esbuild'; @@ -643,6 +644,9 @@ async function normalizeOptions( const isUsingTsSolutionConfig = isUsingTsSolutionSetup(host); const swcJest = options.swcJest ?? isUsingTsSolutionConfig; + const appProjectName = + !isUsingTsSolutionConfig || options.name ? projectName : importPath; + return { addPlugin, ...options, @@ -651,6 +655,7 @@ async function normalizeOptions( ? names(options.frontendProject).fileName : undefined, appProjectRoot, + importPath, parsedTags, linter: options.linter ?? Linter.EsLint, unitTestRunner: options.unitTestRunner ?? 'jest', @@ -660,7 +665,7 @@ async function normalizeOptions( ? joinPathFragments(appProjectRoot, 'dist') : joinPathFragments( 'dist', - options.rootProject ? options.name : appProjectRoot + options.rootProject ? appProjectName : appProjectRoot ), isUsingTsSolutionConfig, swcJest, diff --git a/packages/node/src/generators/e2e-project/e2e-project.spec.ts b/packages/node/src/generators/e2e-project/e2e-project.spec.ts index 97df848f1d..ec2d1eaa12 100644 --- a/packages/node/src/generators/e2e-project/e2e-project.spec.ts +++ b/packages/node/src/generators/e2e-project/e2e-project.spec.ts @@ -176,7 +176,7 @@ describe('e2eProjectGenerator', () => { }); await e2eProjectGenerator(tree, { projectType: 'server', - project: 'api', + project: '@proj/api', addPlugin: true, }); @@ -217,7 +217,7 @@ describe('e2eProjectGenerator', () => { }); await e2eProjectGenerator(tree, { projectType: 'server', - project: 'api', + project: '@proj/api', addPlugin: true, }); @@ -285,7 +285,7 @@ describe('e2eProjectGenerator', () => { }); await e2eProjectGenerator(tree, { projectType: 'cli', - project: 'cli', + project: '@proj/cli', addPlugin: true, }); diff --git a/packages/node/src/generators/e2e-project/e2e-project.ts b/packages/node/src/generators/e2e-project/e2e-project.ts index ce63c90165..38e37e977e 100644 --- a/packages/node/src/generators/e2e-project/e2e-project.ts +++ b/packages/node/src/generators/e2e-project/e2e-project.ts @@ -34,7 +34,6 @@ import { addProjectToTsSolutionWorkspace, isUsingTsSolutionSetup, } from '@nx/js/src/utils/typescript/ts-solution-setup'; -import { getImportPath } from '@nx/js/src/utils/get-import-path'; import { relative } from 'node:path/posix'; import { addSwcTestConfig } from '@nx/js/src/utils/swc/add-swc-config'; @@ -57,17 +56,19 @@ export async function e2eProjectGeneratorInternal( // TODO(@ndcunningham): This is broken.. the outputs are wrong.. and this isn't using the jest generator if (isUsingTsSolutionConfig) { writeJson(host, joinPathFragments(options.e2eProjectRoot, 'package.json'), { - name: getImportPath(host, options.e2eProjectName), + name: options.importPath, version: '0.0.1', private: true, nx: { - name: options.e2eProjectName, - projectType: 'application', + name: + options.e2eProjectName !== options.importPath + ? options.e2eProjectName + : undefined, implicitDependencies: [options.project], targets: { e2e: { executor: '@nx/jest:jest', - outputs: ['{workspaceRoot}/coverage/{e2eProjectRoot}'], + outputs: ['{projectRoot}/test-output/jest/coverage'], options: { jestConfig: `${options.e2eProjectRoot}/jest.config.ts`, passWithNoTests: true, @@ -131,6 +132,7 @@ export async function e2eProjectGeneratorInternal( const coverageDirectory = isUsingTsSolutionConfig ? 'test-output/jest/coverage' : joinPathFragments(rootOffset, 'coverage', options.e2eProjectName); + const projectSimpleName = options.project.split('/').pop(); if (options.projectType === 'server') { generateFiles( host, @@ -138,7 +140,7 @@ export async function e2eProjectGeneratorInternal( options.e2eProjectRoot, { ...options, - ...names(options.rootProject ? 'server' : options.project), + ...names(options.rootProject ? 'server' : projectSimpleName), tsConfigFile, offsetFromRoot: rootOffset, jestPreset, @@ -155,7 +157,7 @@ export async function e2eProjectGeneratorInternal( options.e2eProjectRoot, { ...options, - ...names(options.rootProject ? 'server' : options.project), + ...names(options.rootProject ? 'server' : projectSimpleName), tsConfigFile, offsetFromRoot: rootOffset, tmpl: '', @@ -170,7 +172,7 @@ export async function e2eProjectGeneratorInternal( options.e2eProjectRoot, { ...options, - ...names(options.rootProject ? 'cli' : options.project), + ...names(options.rootProject ? 'cli' : projectSimpleName), mainFile, tsConfigFile, offsetFromRoot: rootOffset, @@ -275,15 +277,26 @@ async function normalizeOptions( tree: Tree, options: Schema ): Promise< - Omit & { e2eProjectRoot: string; e2eProjectName: string } + Omit & { + e2eProjectRoot: string; + e2eProjectName: string; + importPath: string; + } > { - options.directory = options.directory ?? `${options.project}-e2e`; - const { projectName: e2eProjectName, projectRoot: e2eProjectRoot } = - await determineProjectNameAndRootOptions(tree, { - name: options.name, - projectType: 'library', - directory: options.rootProject ? 'e2e' : options.directory, - }); + let directory = options.rootProject ? 'e2e' : options.directory; + if (!directory) { + const projectConfig = readProjectConfiguration(tree, options.project); + directory = `${projectConfig.root}-e2e`; + } + const { + projectName: e2eProjectName, + projectRoot: e2eProjectRoot, + importPath, + } = await determineProjectNameAndRootOptions(tree, { + name: options.name, + projectType: 'application', + directory, + }); const nxJson = readNxJson(tree); const addPlugin = @@ -295,6 +308,7 @@ async function normalizeOptions( ...options, e2eProjectRoot, e2eProjectName, + importPath, port: options.port ?? 3000, rootProject: !!options.rootProject, }; diff --git a/packages/node/src/generators/library/library.spec.ts b/packages/node/src/generators/library/library.spec.ts index 3d488bc497..8e9f4997f7 100644 --- a/packages/node/src/generators/library/library.spec.ts +++ b/packages/node/src/generators/library/library.spec.ts @@ -519,6 +519,7 @@ describe('lib', () => { expect(tree.exists('my-dir/my-lib/src/lib/my-lib.spec.js')).toBeTruthy(); }); }); + describe('TS solution setup', () => { beforeEach(() => { tree = createTreeWithEmptyWorkspace(); @@ -700,5 +701,45 @@ describe('lib', () => { } `); }); + + it('should set "nx.name" in package.json when the user provides a name that is different than the package name', async () => { + await libraryGenerator(tree, { + directory: 'mylib', + name: 'my-lib', + linter: 'none', + unitTestRunner: 'none', + addPlugin: true, + skipFormat: true, + } as Schema); + + expect(readJson(tree, 'mylib/package.json').nx).toStrictEqual({ + name: 'my-lib', + }); + }); + + it('should not set "nx.name" in package.json when the provided name matches the package name', async () => { + await libraryGenerator(tree, { + directory: 'mylib', + name: '@proj/my-lib', + linter: 'none', + unitTestRunner: 'none', + addPlugin: true, + skipFormat: true, + } as Schema); + + expect(readJson(tree, 'mylib/package.json').nx).toBeUndefined(); + }); + + it('should not set "nx.name" in package.json when the user does not provide a name', async () => { + await libraryGenerator(tree, { + directory: 'mylib', + linter: 'none', + unitTestRunner: 'none', + addPlugin: true, + skipFormat: true, + } as Schema); + + expect(readJson(tree, 'mylib/package.json').nx).toBeUndefined(); + }); }); }); diff --git a/packages/node/src/generators/library/library.ts b/packages/node/src/generators/library/library.ts index e67276235c..6ba415c49b 100644 --- a/packages/node/src/generators/library/library.ts +++ b/packages/node/src/generators/library/library.ts @@ -19,7 +19,7 @@ import { } from '@nx/devkit'; import { determineProjectNameAndRootOptions, - ensureProjectName, + ensureRootProjectName, } from '@nx/devkit/src/generators/project-name-and-root-utils'; import { libraryGenerator as jsLibraryGenerator } from '@nx/js'; import { addSwcConfig } from '@nx/js/src/utils/swc/add-swc-config'; @@ -33,7 +33,6 @@ import { addProjectToTsSolutionWorkspace, isUsingTsSolutionSetup, } from '@nx/js/src/utils/typescript/ts-solution-setup'; -import { getImportPath } from '@nx/js/src/utils/get-import-path'; import { sortPackageJsonFields } from '@nx/js/src/utils/package-json/sort-fields'; export interface NormalizedSchema extends Schema { @@ -76,7 +75,7 @@ export async function libraryGeneratorInternal(tree: Tree, schema: Schema) { options.buildable ) { writeJson(tree, joinPathFragments(options.projectRoot, 'package.json'), { - name: getImportPath(tree, options.name), + name: options.importPath, version: '0.0.1', private: true, files: options.publishable ? ['dist', '!**/*.tsbuildinfo'] : undefined, @@ -134,7 +133,7 @@ async function normalizeOptions( tree: Tree, options: Schema ): Promise { - await ensureProjectName(tree, options, 'library'); + await ensureRootProjectName(options, 'library'); const { projectName, names: projectNames, @@ -168,9 +167,8 @@ async function normalizeOptions( return { ...options, fileName, - projectName: isUsingTsSolutionConfig - ? getImportPath(tree, projectName) - : projectName, + projectName: + isUsingTsSolutionConfig && !options.name ? importPath : projectName, projectRoot, parsedTags, importPath, diff --git a/packages/nuxt/src/generators/application/application.spec.ts b/packages/nuxt/src/generators/application/application.spec.ts index 1caf62e7e1..c3dd0d556f 100644 --- a/packages/nuxt/src/generators/application/application.spec.ts +++ b/packages/nuxt/src/generators/application/application.spec.ts @@ -255,14 +255,15 @@ describe('app', () => { }, ] `); + const packageJson = readJson(tree, 'myapp/package.json'); + expect(packageJson.name).toBe('@proj/myapp'); + expect(packageJson.nx).toBeUndefined(); // Make sure keys are in idiomatic order - expect(Object.keys(readJson(tree, 'myapp/package.json'))) - .toMatchInlineSnapshot(` + expect(Object.keys(packageJson)).toMatchInlineSnapshot(` [ "name", "version", "private", - "nx", ] `); expect(readJson(tree, 'myapp/tsconfig.json')).toMatchInlineSnapshot(` @@ -386,5 +387,28 @@ describe('app', () => { } `); }); + + it('should respect the provided name', async () => { + await applicationGenerator(tree, { + directory: 'myapp', + name: 'myapp', + e2eTestRunner: 'playwright', + unitTestRunner: 'vitest', + linter: 'eslint', + }); + + const packageJson = readJson(tree, 'myapp/package.json'); + expect(packageJson.name).toBe('@proj/myapp'); + expect(packageJson.nx.name).toBe('myapp'); + // Make sure keys are in idiomatic order + expect(Object.keys(packageJson)).toMatchInlineSnapshot(` + [ + "name", + "version", + "private", + "nx", + ] + `); + }); }); }); diff --git a/packages/nuxt/src/generators/application/application.ts b/packages/nuxt/src/generators/application/application.ts index 97263130bc..ff07a64af4 100644 --- a/packages/nuxt/src/generators/application/application.ts +++ b/packages/nuxt/src/generators/application/application.ts @@ -32,12 +32,12 @@ import { getNxCloudAppOnBoardingUrl, createNxCloudOnboardingURLForWelcomeApp, } from 'nx/src/nx-cloud/utilities/onboarding'; -import { getImportPath } from '@nx/js/src/utils/get-import-path'; import { addProjectToTsSolutionWorkspace, updateTsconfigFiles, } from '@nx/js/src/utils/typescript/ts-solution-setup'; import { sortPackageJsonFields } from '@nx/js/src/utils/package-json/sort-fields'; +import type { PackageJson } from 'nx/src/utils/package-json'; export async function applicationGenerator(tree: Tree, schema: Schema) { const tasks: GeneratorCallback[] = []; @@ -67,17 +67,25 @@ export async function applicationGenerator(tree: Tree, schema: Schema) { tasks.push(ensureDependencies(tree, options)); if (options.isUsingTsSolutionConfig) { - writeJson(tree, joinPathFragments(options.appProjectRoot, 'package.json'), { - name: getImportPath(tree, options.name), + const packageJson: PackageJson = { + name: options.importPath, version: '0.0.1', private: true, - nx: { - name: options.name, - projectType: 'application', - sourceRoot: `${options.appProjectRoot}/src`, - tags: options.parsedTags?.length ? options.parsedTags : undefined, - }, - }); + }; + + if (options.projectName !== options.importPath) { + packageJson.nx = { name: options.projectName }; + } + if (options.parsedTags?.length) { + packageJson.nx ??= {}; + packageJson.nx.tags = options.parsedTags; + } + + writeJson( + tree, + joinPathFragments(options.appProjectRoot, 'package.json'), + packageJson + ); } else { addProjectConfiguration(tree, options.projectName, { root: options.appProjectRoot, @@ -140,6 +148,12 @@ export async function applicationGenerator(tree: Tree, schema: Schema) { updateGitIgnore(tree); + // If we are using the new TS solution + // We need to update the workspace file (package.json or pnpm-workspaces.yaml) to include the new project + if (options.isUsingTsSolutionConfig) { + addProjectToTsSolutionWorkspace(tree, options.appProjectRoot); + } + tasks.push( await addLinting(tree, { projectName: options.projectName, @@ -193,12 +207,6 @@ export async function applicationGenerator(tree: Tree, schema: Schema) { ); } - // If we are using the new TS solution - // We need to update the workspace file (package.json or pnpm-workspaces.yaml) to include the new project - if (options.isUsingTsSolutionConfig) { - addProjectToTsSolutionWorkspace(tree, options.appProjectRoot); - } - sortPackageJsonFields(tree, options.appProjectRoot); if (!options.skipFormat) await formatFiles(tree); diff --git a/packages/nuxt/src/generators/application/lib/add-e2e.ts b/packages/nuxt/src/generators/application/lib/add-e2e.ts index f53d0be545..cd89618fc5 100644 --- a/packages/nuxt/src/generators/application/lib/add-e2e.ts +++ b/packages/nuxt/src/generators/application/lib/add-e2e.ts @@ -34,8 +34,6 @@ export async function addE2e(host: Tree, options: NormalizedSchema) { version: '0.0.1', private: true, nx: { - projectType: 'application', - sourceRoot: joinPathFragments(options.e2eProjectRoot, 'src'), implicitDependencies: [options.projectName], }, } @@ -106,8 +104,6 @@ export async function addE2e(host: Tree, options: NormalizedSchema) { version: '0.0.1', private: true, nx: { - projectType: 'application', - sourceRoot: joinPathFragments(options.e2eProjectRoot, 'src'), implicitDependencies: [options.projectName], }, } diff --git a/packages/nuxt/src/generators/application/lib/normalize-options.ts b/packages/nuxt/src/generators/application/lib/normalize-options.ts index 4fdee758de..194b1c4b9e 100644 --- a/packages/nuxt/src/generators/application/lib/normalize-options.ts +++ b/packages/nuxt/src/generators/application/lib/normalize-options.ts @@ -1,7 +1,7 @@ import { names, Tree } from '@nx/devkit'; import { determineProjectNameAndRootOptions, - ensureProjectName, + ensureRootProjectName, } from '@nx/devkit/src/generators/project-name-and-root-utils'; import { NormalizedSchema, Schema } from '../schema'; import { isUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup'; @@ -10,16 +10,24 @@ export async function normalizeOptions( host: Tree, options: Schema ): Promise { - await ensureProjectName(host, options, 'application'); - const { projectName: appProjectName, projectRoot: appProjectRoot } = - await determineProjectNameAndRootOptions(host, { - name: options.name, - projectType: 'application', - directory: options.directory, - rootProject: options.rootProject, - }); + await ensureRootProjectName(options, 'application'); + const { + projectName, + names: projectNames, + projectRoot: appProjectRoot, + importPath, + } = await determineProjectNameAndRootOptions(host, { + name: options.name, + projectType: 'application', + directory: options.directory, + rootProject: options.rootProject, + }); options.rootProject = appProjectRoot === '.'; + const isUsingTsSolutionConfig = isUsingTsSolutionSetup(host); + const appProjectName = + !isUsingTsSolutionConfig || options.name ? projectName : importPath; + const e2eProjectName = options.rootProject ? 'e2e' : `${appProjectName}-e2e`; const e2eProjectRoot = options.rootProject ? 'e2e' : `${appProjectRoot}-e2e`; @@ -29,14 +37,15 @@ export async function normalizeOptions( const normalized = { ...options, - name: names(options.name).fileName, + name: projectNames.projectFileName, projectName: appProjectName, appProjectRoot, + importPath, e2eProjectName, e2eProjectRoot, parsedTags, style: options.style ?? 'none', - isUsingTsSolutionConfig: isUsingTsSolutionSetup(host), + isUsingTsSolutionConfig, } as NormalizedSchema; normalized.unitTestRunner ??= 'vitest'; diff --git a/packages/nuxt/src/generators/application/schema.d.ts b/packages/nuxt/src/generators/application/schema.d.ts index bad65c175a..e392b4951d 100644 --- a/packages/nuxt/src/generators/application/schema.d.ts +++ b/packages/nuxt/src/generators/application/schema.d.ts @@ -18,9 +18,10 @@ export interface Schema { useTsSolution?: boolean; } -export interface NormalizedSchema extends Schema { +export interface NormalizedSchema extends Omit { projectName: string; appProjectRoot: string; + importPath: string; e2eProjectName: string; e2eProjectRoot: string; parsedTags: string[]; diff --git a/packages/nx/src/config/to-project-name.spec.ts b/packages/nx/src/config/to-project-name.spec.ts index ee5078c63f..9fbb07d1ce 100644 --- a/packages/nx/src/config/to-project-name.spec.ts +++ b/packages/nx/src/config/to-project-name.spec.ts @@ -65,7 +65,6 @@ describe('Workspaces', () => { }, "name": "my-package", "root": "packages/my-package", - "sourceRoot": "packages/my-package", "tags": [ "npm:public", ], diff --git a/packages/nx/src/plugins/package-json/create-nodes.spec.ts b/packages/nx/src/plugins/package-json/create-nodes.spec.ts index d4edb67811..28571a7103 100644 --- a/packages/nx/src/plugins/package-json/create-nodes.spec.ts +++ b/packages/nx/src/plugins/package-json/create-nodes.spec.ts @@ -69,7 +69,6 @@ describe('nx package.json workspaces plugin', () => { }, "name": "root", "root": ".", - "sourceRoot": ".", "tags": [ "npm:public", ], @@ -123,7 +122,6 @@ describe('nx package.json workspaces plugin', () => { }, "name": "lib-a", "root": "packages/lib-a", - "sourceRoot": "packages/lib-a", "tags": [ "npm:public", ], @@ -185,7 +183,6 @@ describe('nx package.json workspaces plugin', () => { }, "name": "lib-b", "root": "packages/lib-b", - "sourceRoot": "packages/lib-b", "tags": [ "npm:public", ], @@ -290,7 +287,6 @@ describe('nx package.json workspaces plugin', () => { }, "name": "vite", "root": "packages/vite", - "sourceRoot": "packages/vite", "tags": [ "npm:public", ], @@ -394,7 +390,6 @@ describe('nx package.json workspaces plugin', () => { }, "name": "vite", "root": "packages/vite", - "sourceRoot": "packages/vite", "tags": [ "npm:public", ], @@ -494,7 +489,6 @@ describe('nx package.json workspaces plugin', () => { }, "name": "vite", "root": "packages/vite", - "sourceRoot": "packages/vite", "tags": [ "npm:public", ], @@ -582,7 +576,6 @@ describe('nx package.json workspaces plugin', () => { }, "name": "root", "root": "packages/a", - "sourceRoot": "packages/a", "tags": [ "npm:public", ], @@ -667,7 +660,6 @@ describe('nx package.json workspaces plugin', () => { }, "name": "root", "root": "packages/a", - "sourceRoot": "packages/a", "tags": [ "npm:public", ], @@ -753,7 +745,6 @@ describe('nx package.json workspaces plugin', () => { }, "name": "root", "root": "packages/a", - "sourceRoot": "packages/a", "tags": [ "npm:public", ], @@ -939,7 +930,6 @@ describe('nx package.json workspaces plugin', () => { }, "name": "lib-a", "root": "packages/lib-a", - "sourceRoot": "packages/lib-a", "tags": [ "npm:public", ], @@ -990,7 +980,6 @@ describe('nx package.json workspaces plugin', () => { }, "name": "lib-b", "root": "libs/lib-b", - "sourceRoot": "libs/lib-b", "tags": [ "npm:public", ], diff --git a/packages/nx/src/plugins/package-json/create-nodes.ts b/packages/nx/src/plugins/package-json/create-nodes.ts index 2fd168dc2e..34bf2593bc 100644 --- a/packages/nx/src/plugins/package-json/create-nodes.ts +++ b/packages/nx/src/plugins/package-json/create-nodes.ts @@ -143,6 +143,9 @@ export function createNodeFromPackageJson( ...json, root: projectRoot, isInPackageManagerWorkspaces, + // change this to bust the cache when making changes that result in different + // results for the same hash + bust: 1, }); const cached = cache[hash]; @@ -209,7 +212,6 @@ export function buildProjectConfigurationFromPackageJson( const projectConfiguration: ProjectConfiguration & { name: string } = { root: projectRoot, - sourceRoot: projectRoot, name, ...packageJson.nx, targets: readTargetsFromPackageJson(packageJson, nxJson), diff --git a/packages/playwright/src/generators/configuration/configuration.ts b/packages/playwright/src/generators/configuration/configuration.ts index c8565a3912..ba70913ff0 100644 --- a/packages/playwright/src/generators/configuration/configuration.ts +++ b/packages/playwright/src/generators/configuration/configuration.ts @@ -179,10 +179,10 @@ export async function configurationGeneratorInternal( name: importPath, version: '0.0.1', private: true, - nx: { - name: options.project, - }, }; + if (options.project !== importPath) { + packageJson.nx = { name: options.project }; + } writeJson(tree, packageJsonPath, packageJson); } diff --git a/packages/plugin/src/generators/e2e-project/e2e.ts b/packages/plugin/src/generators/e2e-project/e2e.ts index 90c0609ca4..68e1ec81d2 100644 --- a/packages/plugin/src/generators/e2e-project/e2e.ts +++ b/packages/plugin/src/generators/e2e-project/e2e.ts @@ -17,10 +17,7 @@ import { type ProjectConfiguration, type Tree, } from '@nx/devkit'; -import { - determineProjectNameAndRootOptions, - resolveImportPath, -} from '@nx/devkit/src/generators/project-name-and-root-utils'; +import { determineProjectNameAndRootOptions } from '@nx/devkit/src/generators/project-name-and-root-utils'; import { LinterType, lintProjectGenerator } from '@nx/eslint'; import { addPropertyToJestConfig, configurationGenerator } from '@nx/jest'; import { getRelativePathToRootTsConfig } from '@nx/js'; @@ -106,12 +103,14 @@ function addFiles(host: Tree, options: NormalizedSchema) { join(projectConfiguration.root, 'package.json') ); + const simplePluginName = options.pluginName.split('/').pop(); generateFiles(host, join(__dirname, './files'), options.projectRoot, { ...options, tmpl: '', rootTsConfigPath: getRelativePathToRootTsConfig(host, options.projectRoot), packageManagerCommands: getPackageManagerCommand(), pluginPackageName, + simplePluginName, }); } @@ -129,7 +128,7 @@ async function addJest(host: Tree, options: NormalizedSchema) { host, joinPathFragments(options.projectRoot, 'package.json'), { - name: resolveImportPath(host, options.projectName, options.projectRoot), + name: options.projectName, version: '0.0.1', private: true, } diff --git a/packages/plugin/src/generators/e2e-project/files/src/__pluginName__.spec.ts__tmpl__ b/packages/plugin/src/generators/e2e-project/files/src/__simplePluginName__.spec.ts__tmpl__ similarity index 100% rename from packages/plugin/src/generators/e2e-project/files/src/__pluginName__.spec.ts__tmpl__ rename to packages/plugin/src/generators/e2e-project/files/src/__simplePluginName__.spec.ts__tmpl__ diff --git a/packages/plugin/src/generators/plugin/plugin.spec.ts b/packages/plugin/src/generators/plugin/plugin.spec.ts index a82e7e4f20..0a2653e126 100644 --- a/packages/plugin/src/generators/plugin/plugin.spec.ts +++ b/packages/plugin/src/generators/plugin/plugin.spec.ts @@ -354,6 +354,7 @@ describe('NxPlugin Plugin Generator', () => { getSchema({ directory: 'my-plugin', unitTestRunner: 'jest', + useProjectJson: false, }) ); @@ -530,5 +531,50 @@ describe('NxPlugin Plugin Generator', () => { } `); }); + + it('should set "nx.name" in package.json when the user provides a name that is different than the package name and "useProjectJson" is "false"', async () => { + await pluginGenerator(tree, { + directory: 'my-plugin', + name: 'my-plugin', // import path contains the npm scope, so it would be different + useProjectJson: false, + skipFormat: true, + }); + + expect(readJson(tree, 'my-plugin/package.json').nx.name).toBe( + 'my-plugin' + ); + }); + + it('should not set "nx.name" in package.json when the provided name matches the package name', async () => { + await pluginGenerator(tree, { + directory: 'my-plugin', + name: '@proj/my-plugin', + useProjectJson: false, + skipFormat: true, + }); + + expect(readJson(tree, 'my-plugin/package.json').nx.name).toBeUndefined(); + }); + + it('should not set "nx" in package.json when "useProjectJson" is "true"', async () => { + await pluginGenerator(tree, { + directory: 'my-plugin', + name: '@proj/my-plugin', + useProjectJson: true, + skipFormat: true, + }); + + expect(readJson(tree, 'my-plugin/package.json').nx).toBeUndefined(); + }); + + it('should not set "nx.name" in package.json when the user does not provide a name', async () => { + await pluginGenerator(tree, { + directory: 'my-plugin', + useProjectJson: false, + skipFormat: true, + }); + + expect(readJson(tree, 'my-plugin/package.json').nx.name).toBeUndefined(); + }); }); }); diff --git a/packages/plugin/src/generators/plugin/plugin.ts b/packages/plugin/src/generators/plugin/plugin.ts index 68ea2ed5e9..e479df8929 100644 --- a/packages/plugin/src/generators/plugin/plugin.ts +++ b/packages/plugin/src/generators/plugin/plugin.ts @@ -94,7 +94,7 @@ export async function pluginGeneratorInternal(host: Tree, schema: Schema) { config: 'project', bundler: options.bundler, publishable: options.publishable, - importPath: options.npmPackageName, + importPath: options.importPath, linter: options.linter, unitTestRunner: options.unitTestRunner, useProjectJson: options.useProjectJson, @@ -149,9 +149,9 @@ export async function pluginGeneratorInternal(host: Tree, schema: Schema) { projectDirectory: options.projectDirectory, pluginOutputPath: joinPathFragments( 'dist', - options.rootProject ? options.name : options.projectRoot + options.rootProject ? options.projectName : options.projectRoot ), - npmPackageName: options.npmPackageName, + npmPackageName: options.importPath, skipFormat: true, rootProject: options.rootProject, linter: options.linter, diff --git a/packages/plugin/src/generators/plugin/utils/normalize-schema.ts b/packages/plugin/src/generators/plugin/utils/normalize-schema.ts index b27d4519b6..b41af88b28 100644 --- a/packages/plugin/src/generators/plugin/utils/normalize-schema.ts +++ b/packages/plugin/src/generators/plugin/utils/normalize-schema.ts @@ -1,7 +1,7 @@ import { readNxJson, type Tree } from '@nx/devkit'; import { determineProjectNameAndRootOptions, - ensureProjectName, + ensureRootProjectName, } from '@nx/devkit/src/generators/project-name-and-root-utils'; import type { LinterType } from '@nx/eslint'; import { @@ -10,16 +10,14 @@ import { } from '@nx/js/src/utils/generator-prompts'; import { isUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup'; import type { Schema } from '../schema'; -import { getImportPath } from '@nx/js/src/utils/get-import-path'; export interface NormalizedSchema extends Schema { - name: string; projectName: string; fileName: string; projectRoot: string; projectDirectory: string; parsedTags: string[]; - npmPackageName: string; + importPath: string; bundler: 'swc' | 'tsc'; publishable: boolean; unitTestRunner: 'jest' | 'vitest' | 'none'; @@ -48,18 +46,15 @@ export async function normalizeOptions( process.env.NX_ADD_PLUGINS !== 'false' && nxJson.useInferencePlugins !== false); - await ensureProjectName(host, options, 'application'); - const { - projectName, - projectRoot, - importPath: npmPackageName, - } = await determineProjectNameAndRootOptions(host, { - name: options.name, - projectType: 'library', - directory: options.directory, - importPath: options.importPath, - rootProject: options.rootProject, - }); + await ensureRootProjectName(options, 'library'); + const { projectName, projectRoot, importPath } = + await determineProjectNameAndRootOptions(host, { + name: options.name, + projectType: 'library', + directory: options.directory, + importPath: options.importPath, + rootProject: options.rootProject, + }); options.rootProject = projectRoot === '.'; const projectDirectory = projectRoot; @@ -72,14 +67,11 @@ export async function normalizeOptions( ...options, bundler: options.compiler ?? 'tsc', fileName: projectName, - name: projectName, - projectName: isTsSolutionSetup - ? getImportPath(host, projectName) - : projectName, + projectName: isTsSolutionSetup && !options.name ? importPath : projectName, projectRoot, projectDirectory, parsedTags, - npmPackageName, + importPath, publishable: options.publishable ?? false, linter, unitTestRunner, diff --git a/packages/react-native/src/generators/application/application.spec.ts b/packages/react-native/src/generators/application/application.spec.ts index c0b0552c62..fa31d4c359 100644 --- a/packages/react-native/src/generators/application/application.spec.ts +++ b/packages/react-native/src/generators/application/application.spec.ts @@ -253,8 +253,10 @@ describe('app', () => { }); describe('TS solution setup', () => { - it('should add project references when using TS solution', async () => { - const tree = createTreeWithEmptyWorkspace(); + let tree: Tree; + + beforeEach(() => { + tree = createTreeWithEmptyWorkspace(); tree.write('.gitignore', ''); updateJson(tree, 'package.json', (json) => { json.workspaces = ['packages/*', 'apps/*']; @@ -271,7 +273,9 @@ describe('app', () => { files: [], references: [], }); + }); + it('should add project references when using TS solution', async () => { await reactNativeApplicationGenerator(tree, { directory: 'my-app', displayName: 'myApp', @@ -291,9 +295,11 @@ describe('app', () => { }, ] `); + const packageJson = readJson(tree, 'my-app/package.json'); + expect(packageJson.name).toBe('@proj/my-app'); + expect(packageJson.nx.name).toBeUndefined(); // Make sure keys are in idiomatic order - expect(Object.keys(readJson(tree, 'my-app/package.json'))) - .toMatchInlineSnapshot(` + expect(Object.keys(packageJson)).toMatchInlineSnapshot(` [ "name", "version", @@ -403,5 +409,33 @@ describe('app', () => { } `); }); + + it('should respect the provided name', async () => { + await reactNativeApplicationGenerator(tree, { + directory: 'my-app', + name: 'my-app', + displayName: 'myApp', + tags: 'one,two', + linter: Linter.EsLint, + e2eTestRunner: 'none', + install: false, + unitTestRunner: 'jest', + bundler: 'vite', + addPlugin: true, + }); + + const packageJson = readJson(tree, 'my-app/package.json'); + expect(packageJson.name).toBe('@proj/my-app'); + expect(packageJson.nx.name).toBe('my-app'); + // Make sure keys are in idiomatic order + expect(Object.keys(packageJson)).toMatchInlineSnapshot(` + [ + "name", + "version", + "private", + "nx", + ] + `); + }); }); }); diff --git a/packages/react-native/src/generators/application/application.ts b/packages/react-native/src/generators/application/application.ts index 41e0244709..dc995537a5 100644 --- a/packages/react-native/src/generators/application/application.ts +++ b/packages/react-native/src/generators/application/application.ts @@ -66,6 +66,12 @@ export async function reactNativeApplicationGeneratorInternal( await createApplicationFiles(host, options); addProject(host, options); + // If we are using the new TS solution + // We need to update the workspace file (package.json or pnpm-workspaces.yaml) to include the new project + if (options.isTsSolutionSetup) { + addProjectToTsSolutionWorkspace(host, options.appProjectRoot); + } + const lintTask = await addLinting(host, { ...options, projectRoot: options.appProjectRoot, @@ -149,12 +155,6 @@ export async function reactNativeApplicationGeneratorInternal( : undefined ); - // If we are using the new TS solution - // We need to update the workspace file (package.json or pnpm-workspaces.yaml) to include the new project - if (options.useTsSolution) { - addProjectToTsSolutionWorkspace(host, options.appProjectRoot); - } - sortPackageJsonFields(host, options.appProjectRoot); if (!options.skipFormat) { diff --git a/packages/react-native/src/generators/application/lib/add-e2e.ts b/packages/react-native/src/generators/application/lib/add-e2e.ts index a1be66b8be..72b5d3b6a7 100644 --- a/packages/react-native/src/generators/application/lib/add-e2e.ts +++ b/packages/react-native/src/generators/application/lib/add-e2e.ts @@ -1,5 +1,5 @@ import { addE2e as addE2eReact } from '@nx/react/src/generators/application/lib/add-e2e'; -import { GeneratorCallback, Tree, ensurePackage } from '@nx/devkit'; +import { GeneratorCallback, Tree, ensurePackage, names } from '@nx/devkit'; import { nxVersion } from '../../../utils/versions'; @@ -18,6 +18,7 @@ export async function addE2e( styledModule: null, hasStyles: false, unitTestRunner: 'none', + names: names(options.name), }); case 'playwright': return addE2eReact(host, { @@ -27,6 +28,7 @@ export async function addE2e( styledModule: null, hasStyles: false, unitTestRunner: 'none', + names: names(options.name), }); case 'detox': const { detoxApplicationGenerator } = ensurePackage< diff --git a/packages/react-native/src/generators/application/lib/add-project.ts b/packages/react-native/src/generators/application/lib/add-project.ts index 3ffb114836..f55a0ef501 100644 --- a/packages/react-native/src/generators/application/lib/add-project.ts +++ b/packages/react-native/src/generators/application/lib/add-project.ts @@ -9,7 +9,7 @@ import { } from '@nx/devkit'; import { NormalizedSchema } from './normalize-options'; import { isUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup'; -import { getImportPath } from '@nx/js/src/utils/get-import-path'; +import type { PackageJson } from 'nx/src/utils/package-json'; export function addProject(host: Tree, options: NormalizedSchema) { const nxJson = readNxJson(host); @@ -28,15 +28,29 @@ export function addProject(host: Tree, options: NormalizedSchema) { }; if (isUsingTsSolutionSetup(host)) { - writeJson(host, joinPathFragments(options.appProjectRoot, 'package.json'), { - name: options.projectName, + const packageJson: PackageJson = { + name: options.importPath, version: '0.0.1', private: true, - nx: { - targets: hasPlugin ? {} : getTargets(options), - tags: options.parsedTags?.length ? options.parsedTags : undefined, - }, - }); + }; + + if (options.projectName !== options.importPath) { + packageJson.nx = { name: options.projectName }; + } + if (!hasPlugin) { + packageJson.nx ??= {}; + packageJson.nx.targets = getTargets(options); + } + if (options.parsedTags?.length) { + packageJson.nx ??= {}; + packageJson.nx.tags = options.parsedTags; + } + + writeJson( + host, + joinPathFragments(options.appProjectRoot, 'package.json'), + packageJson + ); } else { addProjectConfiguration(host, options.projectName, { ...project, diff --git a/packages/react-native/src/generators/application/lib/normalize-options.spec.ts b/packages/react-native/src/generators/application/lib/normalize-options.spec.ts index d482db00ac..b876d115aa 100644 --- a/packages/react-native/src/generators/application/lib/normalize-options.spec.ts +++ b/packages/react-native/src/generators/application/lib/normalize-options.spec.ts @@ -25,6 +25,7 @@ describe('Normalize Options', () => { addPlugin: true, androidProjectRoot: 'my-app/android', appProjectRoot: 'my-app', + importPath: '@proj/my-app', fileName: 'my-app', className: 'MyApp', directory: 'my-app', @@ -61,6 +62,7 @@ describe('Normalize Options', () => { addPlugin: true, androidProjectRoot: 'myApp/android', appProjectRoot: 'myApp', + importPath: '@proj/myApp', className: 'MyApp', fileName: 'my-app', directory: 'myApp', @@ -98,6 +100,7 @@ describe('Normalize Options', () => { addPlugin: true, androidProjectRoot: 'directory/my-app/android', appProjectRoot: 'directory/my-app', + importPath: '@proj/my-app', className: 'MyApp', fileName: 'my-app', directory: 'directory/my-app', @@ -134,6 +137,7 @@ describe('Normalize Options', () => { addPlugin: true, androidProjectRoot: 'directory/my-app/android', appProjectRoot: 'directory/my-app', + importPath: '@proj/my-app', className: 'MyApp', directory: 'directory/my-app', fileName: 'my-app', @@ -171,6 +175,7 @@ describe('Normalize Options', () => { addPlugin: true, androidProjectRoot: 'my-app/android', appProjectRoot: 'my-app', + importPath: '@proj/my-app', className: 'MyApp', fileName: 'my-app', directory: 'my-app', diff --git a/packages/react-native/src/generators/application/lib/normalize-options.ts b/packages/react-native/src/generators/application/lib/normalize-options.ts index d032572866..3925901a56 100644 --- a/packages/react-native/src/generators/application/lib/normalize-options.ts +++ b/packages/react-native/src/generators/application/lib/normalize-options.ts @@ -1,13 +1,12 @@ import { joinPathFragments, names, readNxJson, Tree } from '@nx/devkit'; import { determineProjectNameAndRootOptions, - ensureProjectName, + ensureRootProjectName, } from '@nx/devkit/src/generators/project-name-and-root-utils'; import { Schema } from '../schema'; import { isUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup'; -import { getImportPath } from '@nx/js/src/utils/get-import-path'; -export interface NormalizedSchema extends Schema { +export interface NormalizedSchema extends Omit { className: string; // app name in class case fileName: string; // app name in file class projectName: string; // directory + app name, case based on user input @@ -20,6 +19,7 @@ export interface NormalizedSchema extends Schema { rootProject: boolean; e2eProjectName: string; e2eProjectRoot: string; + importPath: string; isTsSolutionSetup: boolean; } @@ -27,11 +27,12 @@ export async function normalizeOptions( host: Tree, options: Schema ): Promise { - await ensureProjectName(host, options, 'application'); + await ensureRootProjectName(options, 'application'); const { - projectName: appProjectName, + projectName, names: projectNames, projectRoot: appProjectRoot, + importPath, } = await determineProjectNameAndRootOptions(host, { name: options.name, projectType: 'application', @@ -43,11 +44,15 @@ export async function normalizeOptions( nxJson.useInferencePlugins !== false; options.addPlugin ??= addPluginDefault; - const { className, fileName } = names(options.name); + const { className, fileName } = names(projectNames.projectSimpleName); const iosProjectRoot = joinPathFragments(appProjectRoot, 'ios'); const androidProjectRoot = joinPathFragments(appProjectRoot, 'android'); const rootProject = appProjectRoot === '.'; + const isTsSolutionSetup = isUsingTsSolutionSetup(host); + const appProjectName = + !isTsSolutionSetup || options.name ? projectName : importPath; + const e2eProjectName = rootProject ? 'e2e' : `${appProjectName}-e2e`; const e2eProjectRoot = rootProject ? 'e2e' : `${appProjectRoot}-e2e`; @@ -57,8 +62,6 @@ export async function normalizeOptions( const entryFile = options.js ? 'src/main.js' : 'src/main.tsx'; - const isTsSolutionSetup = isUsingTsSolutionSetup(host); - return { ...options, name: projectNames.projectSimpleName, @@ -66,10 +69,9 @@ export async function normalizeOptions( fileName, lowerCaseName: className.toLowerCase(), displayName: options.displayName || className, - projectName: isTsSolutionSetup - ? getImportPath(host, appProjectName) - : appProjectName, + projectName: appProjectName, appProjectRoot, + importPath, iosProjectRoot, androidProjectRoot, parsedTags, diff --git a/packages/react-native/src/generators/library/lib/normalize-options.ts b/packages/react-native/src/generators/library/lib/normalize-options.ts index ae8f110ea0..4aa4093821 100644 --- a/packages/react-native/src/generators/library/lib/normalize-options.ts +++ b/packages/react-native/src/generators/library/lib/normalize-options.ts @@ -1,16 +1,16 @@ import { readNxJson, Tree } from '@nx/devkit'; import { determineProjectNameAndRootOptions, - ensureProjectName, + ensureRootProjectName, } from '@nx/devkit/src/generators/project-name-and-root-utils'; import { Schema } from '../schema'; import { isUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup'; -import { getImportPath } from '@nx/js/src/utils/get-import-path'; export interface NormalizedSchema extends Schema { name: string; fileName: string; projectRoot: string; + importPath: string; routePath: string; parsedTags: string[]; appMain?: string; @@ -22,7 +22,7 @@ export async function normalizeOptions( host: Tree, options: Schema ): Promise { - await ensureProjectName(host, options, 'library'); + await ensureRootProjectName(options, 'library'); const { projectName, names: projectNames, @@ -50,9 +50,7 @@ export async function normalizeOptions( ...options, fileName: projectName, routePath: `/${projectNames.projectSimpleName}`, - name: isUsingTsSolutionConfig - ? options.importPath ?? getImportPath(host, projectName) - : projectName, + name: isUsingTsSolutionConfig && !options.name ? importPath : projectName, projectRoot, parsedTags, importPath, diff --git a/packages/react-native/src/generators/library/library.spec.ts b/packages/react-native/src/generators/library/library.spec.ts index 7e60fc1def..f487d66ff3 100644 --- a/packages/react-native/src/generators/library/library.spec.ts +++ b/packages/react-native/src/generators/library/library.spec.ts @@ -450,7 +450,7 @@ describe('lib', () => { }); describe('TS solution setup', () => { - it('should add project references when using TS solution', async () => { + beforeEach(() => { updateJson(appTree, 'package.json', (json) => { json.workspaces = ['packages/*', 'apps/*']; return json; @@ -466,7 +466,9 @@ describe('lib', () => { files: [], references: [], }); + }); + it('should add project references when using TS solution', async () => { await libraryGenerator(appTree, { ...defaultSchema, }); @@ -492,7 +494,6 @@ describe('lib', () => { }, "main": "./src/index.ts", "name": "@proj/my-lib", - "nx": {}, "peerDependencies": { "react": "~18.3.1", "react-native": "~0.76.3", @@ -593,5 +594,39 @@ describe('lib', () => { } `); }); + + it('should set "nx.name" in package.json when the user provides a name that is different than the package name', async () => { + await libraryGenerator(appTree, { + ...defaultSchema, + directory: 'my-lib', + name: 'my-lib', // import path contains the npm scope, so it would be different + skipFormat: true, + }); + + expect(readJson(appTree, 'my-lib/package.json').nx).toStrictEqual({ + name: 'my-lib', + }); + }); + + it('should not set "nx.name" in package.json when the provided name matches the package name', async () => { + await libraryGenerator(appTree, { + ...defaultSchema, + directory: 'my-lib', + name: '@proj/my-lib', + skipFormat: true, + }); + + expect(readJson(appTree, 'my-lib/package.json').nx).toBeUndefined(); + }); + + it('should not set "nx.name" in package.json when the user does not provide a name', async () => { + await libraryGenerator(appTree, { + ...defaultSchema, + directory: 'my-lib', + skipFormat: true, + }); + + expect(readJson(appTree, 'my-lib/package.json').nx).toBeUndefined(); + }); }); }); diff --git a/packages/react-native/src/generators/library/library.ts b/packages/react-native/src/generators/library/library.ts index 45bbac96e7..703ff477d0 100644 --- a/packages/react-native/src/generators/library/library.ts +++ b/packages/react-native/src/generators/library/library.ts @@ -87,6 +87,10 @@ export async function reactNativeLibraryGeneratorInternal( tasks.push(addProjectTask); } + if (options.isUsingTsSolutionConfig) { + addProjectToTsSolutionWorkspace(host, options.projectRoot); + } + const lintTask = await addLinting(host, { ...options, projectName: options.name, @@ -146,10 +150,6 @@ export async function reactNativeLibraryGeneratorInternal( : undefined ); - if (options.isUsingTsSolutionConfig) { - addProjectToTsSolutionWorkspace(host, options.projectRoot); - } - sortPackageJsonFields(host, options.projectRoot); if (!options.skipFormat) { @@ -181,19 +181,30 @@ async function addProject( }; if (options.isUsingTsSolutionConfig) { - writeJson(host, joinPathFragments(options.projectRoot, 'package.json'), { - name: options.name, + const packageJson: PackageJson = { + name: options.importPath, version: '0.0.1', ...determineEntryFields(options), - nx: { - tags: options.parsedTags?.length ? options.parsedTags : undefined, - }, files: options.publishable ? ['dist', '!**/*.tsbuildinfo'] : undefined, peerDependencies: { react: reactVersion, 'react-native': reactNativeVersion, }, - }); + }; + + if (options.name !== options.importPath) { + packageJson.nx = { name: options.name }; + } + if (options.parsedTags?.length) { + packageJson.nx ??= {}; + packageJson.nx.tags = options.parsedTags; + } + + writeJson( + host, + joinPathFragments(options.projectRoot, 'package.json'), + packageJson + ); } else { addProjectConfiguration(host, options.name, project); } diff --git a/packages/react/src/generators/application/application.spec.ts b/packages/react/src/generators/application/application.spec.ts index 2bdb712b9e..b1930114d5 100644 --- a/packages/react/src/generators/application/application.spec.ts +++ b/packages/react/src/generators/application/application.spec.ts @@ -1330,9 +1330,11 @@ describe('app', () => { }, ] `); + const packageJson = readJson(appTree, 'myapp/package.json'); + expect(packageJson.name).toBe('@proj/myapp'); + expect(packageJson.nx).toBeUndefined(); // Make sure keys are in idiomatic order - expect(Object.keys(readJson(appTree, 'myapp/package.json'))) - .toMatchInlineSnapshot(` + expect(Object.keys(packageJson)).toMatchInlineSnapshot(` [ "name", "version", @@ -1473,6 +1475,32 @@ describe('app', () => { `); }); + it('should respect the provided name', async () => { + await applicationGenerator(appTree, { + directory: 'myapp', + name: 'myapp', + addPlugin: true, + linter: Linter.EsLint, + style: 'none', + bundler: 'vite', + unitTestRunner: 'vitest', + e2eTestRunner: 'playwright', + }); + + const packageJson = readJson(appTree, 'myapp/package.json'); + expect(packageJson.name).toBe('@proj/myapp'); + expect(packageJson.nx.name).toBe('myapp'); + // Make sure keys are in idiomatic order + expect(Object.keys(packageJson)).toMatchInlineSnapshot(` + [ + "name", + "version", + "private", + "nx", + ] + `); + }); + it('should add project to workspaces when using TS solution (npm, yarn, bun)', async () => { await applicationGenerator(appTree, { directory: 'myapp', diff --git a/packages/react/src/generators/application/lib/add-e2e.ts b/packages/react/src/generators/application/lib/add-e2e.ts index 9c02dde733..b75182e6c2 100644 --- a/packages/react/src/generators/application/lib/add-e2e.ts +++ b/packages/react/src/generators/application/lib/add-e2e.ts @@ -111,8 +111,6 @@ export async function addE2e( version: '0.0.1', private: true, nx: { - projectType: 'application', - sourceRoot: joinPathFragments(options.e2eProjectRoot, 'src'), implicitDependencies: [options.projectName], }, } @@ -209,8 +207,6 @@ export async function addE2e( version: '0.0.1', private: true, nx: { - projectType: 'application', - sourceRoot: joinPathFragments(options.e2eProjectRoot, 'src'), implicitDependencies: [options.projectName], }, } diff --git a/packages/react/src/generators/application/lib/add-project.ts b/packages/react/src/generators/application/lib/add-project.ts index 675da5d2a0..0b9f951489 100644 --- a/packages/react/src/generators/application/lib/add-project.ts +++ b/packages/react/src/generators/application/lib/add-project.ts @@ -11,6 +11,7 @@ import { import { hasWebpackPlugin } from '../../../utils/has-webpack-plugin'; import { maybeJs } from '../../../utils/maybe-js'; import { hasRspackPlugin } from '../../../utils/has-rspack-plugin'; +import type { PackageJson } from 'nx/src/utils/package-json'; export function addProject(host: Tree, options: NormalizedSchema) { const project: ProjectConfiguration = { @@ -39,11 +40,29 @@ export function addProject(host: Tree, options: NormalizedSchema) { } if (options.isUsingTsSolutionConfig) { - writeJson(host, joinPathFragments(options.appProjectRoot, 'package.json'), { - name: options.projectName, + const packageJson: PackageJson = { + name: options.importPath, version: '0.0.1', private: true, - }); + }; + + if (options.projectName !== options.importPath) { + packageJson.nx = { name: options.projectName }; + } + if (Object.keys(project.targets).length) { + packageJson.nx ??= {}; + packageJson.nx.targets = project.targets; + } + if (options.parsedTags?.length) { + packageJson.nx ??= {}; + packageJson.nx.tags = options.parsedTags; + } + + writeJson( + host, + joinPathFragments(options.appProjectRoot, 'package.json'), + packageJson + ); } if (!options.isUsingTsSolutionConfig || options.alwaysGenerateProjectJson) { diff --git a/packages/react/src/generators/application/lib/create-application-files.ts b/packages/react/src/generators/application/lib/create-application-files.ts index 54c1bf8559..5455434008 100644 --- a/packages/react/src/generators/application/lib/create-application-files.ts +++ b/packages/react/src/generators/application/lib/create-application-files.ts @@ -1,7 +1,6 @@ import { generateFiles, joinPathFragments, - names, offsetFromRoot, toJS, Tree, @@ -59,7 +58,7 @@ export async function createApplicationFiles( ); const appTests = getAppTests(options); const templateVariables = { - ...names(options.name), + ...options.names, ...options, js: !!options.js, // Ensure this is defined in template tmpl: '', diff --git a/packages/react/src/generators/application/lib/normalize-options.ts b/packages/react/src/generators/application/lib/normalize-options.ts index f9cc72b8fe..aebb0c78a6 100644 --- a/packages/react/src/generators/application/lib/normalize-options.ts +++ b/packages/react/src/generators/application/lib/normalize-options.ts @@ -1,38 +1,29 @@ -import { Tree, extractLayoutDirectory, names, readNxJson } from '@nx/devkit'; +import { Tree, names, readNxJson } from '@nx/devkit'; import { determineProjectNameAndRootOptions, - ensureProjectName, + ensureRootProjectName, } from '@nx/devkit/src/generators/project-name-and-root-utils'; import { assertValidStyle } from '../../../utils/assertion'; import { NormalizedSchema, Schema } from '../schema'; import { findFreePort } from './find-free-port'; import { isUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup'; -import { getImportPath } from '@nx/js/src/utils/get-import-path'; - -export function normalizeDirectory(options: Schema) { - options.directory = options.directory?.replace(/\\{1,2}/g, '/'); - const { projectDirectory } = extractLayoutDirectory(options.directory); - return projectDirectory - ? `${names(projectDirectory).fileName}/${names(options.name).fileName}` - : names(options.name).fileName; -} - -export function normalizeProjectName(options: Schema) { - return normalizeDirectory(options).replace(new RegExp('/', 'g'), '-'); -} export async function normalizeOptions( host: Tree, options: Schema ): Promise> { - await ensureProjectName(host, options, 'application'); - const { projectName: appProjectName, projectRoot: appProjectRoot } = - await determineProjectNameAndRootOptions(host, { - name: options.name, - projectType: 'application', - directory: options.directory, - rootProject: options.rootProject, - }); + await ensureRootProjectName(options, 'application'); + const { + projectName, + names: projectNames, + projectRoot: appProjectRoot, + importPath, + } = await determineProjectNameAndRootOptions(host, { + name: options.name, + projectType: 'application', + directory: options.directory, + rootProject: options.rootProject, + }); const nxJson = readNxJson(host); const addPlugin = @@ -43,6 +34,10 @@ export async function normalizeOptions( options.rootProject = appProjectRoot === '.'; + const isUsingTsSolutionConfig = isUsingTsSolutionSetup(host); + const appProjectName = + !isUsingTsSolutionConfig || options.name ? projectName : importPath; + const e2eProjectName = options.rootProject ? 'e2e' : `${appProjectName}-e2e`; const e2eProjectRoot = options.rootProject ? 'e2e' : `${appProjectRoot}-e2e`; @@ -58,20 +53,18 @@ export async function normalizeOptions( assertValidStyle(options.style); - const isUsingTsSolutionConfig = isUsingTsSolutionSetup(host); const normalized = { ...options, - name: appProjectName, - projectName: isUsingTsSolutionConfig - ? getImportPath(host, appProjectName) - : appProjectName, + projectName: appProjectName, appProjectRoot, + importPath, e2eProjectName, e2eProjectRoot, parsedTags, fileName, styledModule, hasStyles: options.style !== 'none', + names: names(projectNames.projectSimpleName), isUsingTsSolutionConfig, } as NormalizedSchema; diff --git a/packages/react/src/generators/application/schema.d.ts b/packages/react/src/generators/application/schema.d.ts index 55e28d1f11..e46cc0d88c 100644 --- a/packages/react/src/generators/application/schema.d.ts +++ b/packages/react/src/generators/application/schema.d.ts @@ -1,3 +1,4 @@ +import type { names } from '@nx/devkit'; import type { Linter, LinterType } from '@nx/eslint'; import type { SupportedStyles } from '../../../typings/style'; @@ -38,11 +39,13 @@ export interface NormalizedSchema extends T { appProjectRoot: string; e2eProjectName: string; e2eProjectRoot: string; + importPath: string; parsedTags: string[]; fileName: string; styledModule: null | SupportedStyles; hasStyles: boolean; unitTestRunner: 'jest' | 'vitest' | 'none'; addPlugin?: boolean; + names: ReturnType; isUsingTsSolutionConfig?: boolean; } diff --git a/packages/react/src/generators/federate-module/federate-module.ts b/packages/react/src/generators/federate-module/federate-module.ts index daa96c85b1..f4e6cfd89c 100644 --- a/packages/react/src/generators/federate-module/federate-module.ts +++ b/packages/react/src/generators/federate-module/federate-module.ts @@ -13,10 +13,7 @@ import { Schema } from './schema'; import { remoteGenerator } from '../remote/remote'; import { addPathToExposes, checkRemoteExists } from './lib/utils'; -import { - determineProjectNameAndRootOptions, - ensureProjectName, -} from '@nx/devkit/src/generators/project-name-and-root-utils'; +import { determineProjectNameAndRootOptions } from '@nx/devkit/src/generators/project-name-and-root-utils'; import { addTsConfigPath, getRootTsConfigPathInTree } from '@nx/js'; export async function federateModuleGenerator(tree: Tree, schema: Schema) { diff --git a/packages/react/src/generators/host/host.ts b/packages/react/src/generators/host/host.ts index d1d6ab0b9f..ec7a3177c3 100644 --- a/packages/react/src/generators/host/host.ts +++ b/packages/react/src/generators/host/host.ts @@ -27,7 +27,7 @@ import { moduleFederationEnhancedVersion, nxVersion, } from '../../utils/versions'; -import { ensureProjectName } from '@nx/devkit/src/generators/project-name-and-root-utils'; +import { ensureRootProjectName } from '@nx/devkit/src/generators/project-name-and-root-utils'; import { updateModuleFederationTsconfig } from './lib/update-module-federation-tsconfig'; export async function hostGenerator( @@ -36,7 +36,10 @@ export async function hostGenerator( ): Promise { const tasks: GeneratorCallback[] = []; const options: NormalizedSchema = { - ...(await normalizeOptions(host, schema)), + ...(await normalizeOptions(host, { + ...schema, + alwaysGenerateProjectJson: true, + })), js: schema.js ?? false, typescriptConfiguration: schema.js ? false @@ -60,7 +63,7 @@ export async function hostGenerator( }); } - await ensureProjectName(host, options, 'application'); + await ensureRootProjectName(options, 'application'); const initTask = await applicationGenerator(host, { ...options, directory: options.appProjectRoot, diff --git a/packages/react/src/generators/library/__snapshots__/library.spec.ts.snap b/packages/react/src/generators/library/__snapshots__/library.spec.ts.snap index 5373769439..4dbc32859b 100644 --- a/packages/react/src/generators/library/__snapshots__/library.spec.ts.snap +++ b/packages/react/src/generators/library/__snapshots__/library.spec.ts.snap @@ -126,7 +126,7 @@ export default defineConfig(() => ({ fileName: 'index', // Change this to the formats you want to support. // Don't forget to update your package.json as well. - formats: ['es'], + formats: ['es' as const], }, rollupOptions: { // External packages that should not be bundled into your library. diff --git a/packages/react/src/generators/library/lib/normalize-options.ts b/packages/react/src/generators/library/lib/normalize-options.ts index 11743e5989..3adc0ce4ae 100644 --- a/packages/react/src/generators/library/lib/normalize-options.ts +++ b/packages/react/src/generators/library/lib/normalize-options.ts @@ -8,11 +8,15 @@ import { } from '@nx/devkit'; import { determineProjectNameAndRootOptions, - ensureProjectName, + ensureRootProjectName, } from '@nx/devkit/src/generators/project-name-and-root-utils'; import { assertValidStyle } from '../../../utils/assertion'; import { NormalizedSchema, Schema } from '../schema'; -import { isUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup'; +import { + getProjectSourceRoot, + getProjectType, + isUsingTsSolutionSetup, +} from '@nx/js/src/utils/typescript/ts-solution-setup'; export async function normalizeOptions( host: Tree, @@ -20,7 +24,7 @@ export async function normalizeOptions( ): Promise { const isUsingTsSolutionConfig = isUsingTsSolutionSetup(host); - await ensureProjectName(host, options, 'library'); + await ensureRootProjectName(options, 'library'); const { projectName, names: projectNames, @@ -70,7 +74,7 @@ export async function normalizeOptions( bundler, fileName, routePath: `/${projectNames.projectSimpleName}`, - name: isUsingTsSolutionConfig ? importPath : projectName, + name: isUsingTsSolutionConfig && !options.name ? importPath : projectName, projectRoot, parsedTags, importPath, @@ -85,17 +89,28 @@ export async function normalizeOptions( if (options.appProject) { const appProjectConfig = getProjects(host).get(options.appProject); + const appProjectType = getProjectType( + host, + appProjectConfig.root, + appProjectConfig.projectType + ); - if (appProjectConfig.projectType !== 'application') { + if (appProjectType !== 'application') { throw new Error( - `appProject expected type of "application" but got "${appProjectConfig.projectType}"` + `appProject expected type of "application" but got "${appProjectType}"` ); } + const appSourceRoot = getProjectSourceRoot( + host, + appProjectConfig.sourceRoot, + appProjectConfig.root + ); + normalized.appMain = appProjectConfig.targets.build?.options?.main ?? findMainEntry(host, appProjectConfig.root); - normalized.appSourceRoot = normalizePath(appProjectConfig.sourceRoot); + normalized.appSourceRoot = normalizePath(appSourceRoot); // TODO(jack): We should use appEntryFile instead of appProject so users can directly set it rather than us inferring it. if (!normalized.appMain) { diff --git a/packages/react/src/generators/library/library.spec.ts b/packages/react/src/generators/library/library.spec.ts index 2db24acd83..e4dacf188e 100644 --- a/packages/react/src/generators/library/library.spec.ts +++ b/packages/react/src/generators/library/library.spec.ts @@ -988,7 +988,7 @@ module.exports = withNx( fileName: 'index', // Change this to the formats you want to support. // Don't forget to update your package.json as well. - formats: ['es'] + formats: ['es' as const] }, rollupOptions: { // External packages that should not be bundled into your library. @@ -1275,5 +1275,39 @@ module.exports = withNx( expect(pnpmWorkspaceFile.packages).toEqual(['mylib']); }); + + it('should set "nx.name" in package.json when the user provides a name that is different than the package name', async () => { + await libraryGenerator(tree, { + ...defaultSchema, + directory: 'libs/my-lib', + name: 'my-lib', // import path contains the npm scope, so it would be different + skipFormat: true, + }); + + expect(readJson(tree, 'libs/my-lib/package.json').nx).toStrictEqual({ + name: 'my-lib', + }); + }); + + it('should not set "nx.name" in package.json when the provided name matches the package name', async () => { + await libraryGenerator(tree, { + ...defaultSchema, + directory: 'libs/my-lib', + name: '@proj/my-lib', + skipFormat: true, + }); + + expect(readJson(tree, 'libs/my-lib/package.json').nx).toBeUndefined(); + }); + + it('should not set "nx.name" in package.json when the user does not provide a name', async () => { + await libraryGenerator(tree, { + ...defaultSchema, + directory: 'libs/my-lib', + skipFormat: true, + }); + + expect(readJson(tree, 'libs/my-lib/package.json').nx).toBeUndefined(); + }); }); }); diff --git a/packages/react/src/generators/library/library.ts b/packages/react/src/generators/library/library.ts index aaa3598f32..35889e456d 100644 --- a/packages/react/src/generators/library/library.ts +++ b/packages/react/src/generators/library/library.ts @@ -41,6 +41,7 @@ import { addReleaseConfigForTsSolution, releaseTasks, } from '@nx/js/src/generators/library/utils/add-release-config'; +import type { PackageJson } from 'nx/src/utils/package-json'; export async function libraryGenerator(host: Tree, schema: Schema) { return await libraryGeneratorInternal(host, { @@ -80,17 +81,22 @@ export async function libraryGeneratorInternal(host: Tree, schema: Schema) { tasks.push(initTask); if (options.isUsingTsSolutionConfig) { - writeJson(host, `${options.projectRoot}/package.json`, { - name: options.importPath ?? options.name, + const packageJson: PackageJson = { + name: options.importPath, version: '0.0.1', ...determineEntryFields(options), - nx: options.parsedTags?.length - ? { - tags: options.parsedTags, - } - : undefined, files: options.publishable ? ['dist', '!**/*.tsbuildinfo'] : undefined, - }); + }; + + if (options.name !== options.importPath) { + packageJson.nx = { name: options.name }; + } + if (options.parsedTags?.length) { + packageJson.nx ??= {}; + packageJson.nx.tags = options.parsedTags; + } + + writeJson(host, `${options.projectRoot}/package.json`, packageJson); } else { addProjectConfiguration(host, options.name, { root: options.projectRoot, diff --git a/packages/react/src/generators/library/schema.d.ts b/packages/react/src/generators/library/schema.d.ts index 0b23eb2f16..78db3d81b8 100644 --- a/packages/react/src/generators/library/schema.d.ts +++ b/packages/react/src/generators/library/schema.d.ts @@ -36,6 +36,7 @@ export interface NormalizedSchema extends Schema { projectRoot: string; routePath: string; parsedTags: string[]; + importPath: string; appMain?: string; appSourceRoot?: string; unitTestRunner: 'jest' | 'vitest' | 'none'; diff --git a/packages/react/src/generators/remote/remote.ts b/packages/react/src/generators/remote/remote.ts index 9e1e923b12..96d8e14bbe 100644 --- a/packages/react/src/generators/remote/remote.ts +++ b/packages/react/src/generators/remote/remote.ts @@ -30,7 +30,7 @@ import { moduleFederationEnhancedVersion, nxVersion, } from '../../utils/versions'; -import { ensureProjectName } from '@nx/devkit/src/generators/project-name-and-root-utils'; +import { ensureRootProjectName } from '@nx/devkit/src/generators/project-name-and-root-utils'; export function addModuleFederationFiles( host: Tree, @@ -96,7 +96,10 @@ export function addModuleFederationFiles( export async function remoteGenerator(host: Tree, schema: Schema) { const tasks: GeneratorCallback[] = []; const options: NormalizedSchema = { - ...(await normalizeOptions(host, schema)), + ...(await normalizeOptions(host, { + ...schema, + alwaysGenerateProjectJson: true, + })), // when js is set to true, we want to use the js configuration js: schema.js ?? false, typescriptConfiguration: schema.js @@ -111,20 +114,20 @@ export async function remoteGenerator(host: Tree, schema: Schema) { if (options.dynamic) { // Dynamic remotes generate with library { type: 'var' } by default. // We need to ensure that the remote name is a valid variable name. - const isValidRemote = isValidVariable(options.name); + const isValidRemote = isValidVariable(options.projectName); if (!isValidRemote.isValid) { throw new Error( - `Invalid remote name provided: ${options.name}. ${isValidRemote.message}` + `Invalid remote name provided: ${options.projectName}. ${isValidRemote.message}` ); } } - await ensureProjectName(host, options, 'application'); + await ensureRootProjectName(options, 'application'); const REMOTE_NAME_REGEX = '^[a-zA-Z_$][a-zA-Z_$0-9]*$'; const remoteNameRegex = new RegExp(REMOTE_NAME_REGEX); - if (!remoteNameRegex.test(options.name)) { + if (!remoteNameRegex.test(options.projectName)) { throw new Error( - stripIndents`Invalid remote name: ${options.name}. Remote project names must: + stripIndents`Invalid remote name: ${options.projectName}. Remote project names must: - Start with a letter, dollar sign ($) or underscore (_) - Followed by any valid character (letters, digits, underscores, or dollar signs) The regular expression used is ${REMOTE_NAME_REGEX}.` @@ -132,14 +135,14 @@ export async function remoteGenerator(host: Tree, schema: Schema) { } const initAppTask = await applicationGenerator(host, { ...options, - name: options.name, + name: options.projectName, skipFormat: true, alwaysGenerateProjectJson: true, }); tasks.push(initAppTask); if (options.host) { - updateHostWithRemote(host, options.host, options.name); + updateHostWithRemote(host, options.host, options.projectName); } // Module federation requires bootstrap code to be dynamically imported. diff --git a/packages/remix/src/generators/application/application.impl.spec.ts b/packages/remix/src/generators/application/application.impl.spec.ts index 945c8c6893..2c454f35a5 100644 --- a/packages/remix/src/generators/application/application.impl.spec.ts +++ b/packages/remix/src/generators/application/application.impl.spec.ts @@ -462,9 +462,9 @@ describe('Remix Application', () => { tags: 'foo', }); + const packageJson = readJson(tree, 'myapp/package.json'); // Make sure keys are in idiomatic order - expect(Object.keys(readJson(tree, 'myapp/package.json'))) - .toMatchInlineSnapshot(` + expect(Object.keys(packageJson)).toMatchInlineSnapshot(` [ "name", "private", @@ -477,7 +477,7 @@ describe('Remix Application', () => { "devDependencies", ] `); - expect(readJson(tree, 'myapp/package.json')).toMatchInlineSnapshot(` + expect(packageJson).toMatchInlineSnapshot(` { "dependencies": { "@remix-run/node": "^2.15.0", @@ -655,6 +655,35 @@ describe('Remix Application', () => { `); }); + it('should respect the provided name', async () => { + await applicationGenerator(tree, { + directory: 'myapp', + name: 'myapp', + e2eTestRunner: 'playwright', + unitTestRunner: 'jest', + addPlugin: true, + tags: 'foo', + }); + + const packageJson = readJson(tree, 'myapp/package.json'); + expect(packageJson.name).toBe('@proj/myapp'); + expect(packageJson.nx.name).toBe('myapp'); + // Make sure keys are in idiomatic order + expect(Object.keys(packageJson)).toMatchInlineSnapshot(` + [ + "name", + "private", + "type", + "scripts", + "engines", + "sideEffects", + "nx", + "dependencies", + "devDependencies", + ] + `); + }); + it('should skip nx property in package.json when no tags are provided', async () => { await applicationGenerator(tree, { directory: 'apps/myapp', diff --git a/packages/remix/src/generators/application/application.impl.ts b/packages/remix/src/generators/application/application.impl.ts index b1613d716b..31cc86c26d 100644 --- a/packages/remix/src/generators/application/application.impl.ts +++ b/packages/remix/src/generators/application/application.impl.ts @@ -161,6 +161,21 @@ export async function remixApplicationGeneratorInternal( options.projectRoot, vars ); + + updateJson( + tree, + joinPathFragments(options.projectRoot, 'package.json'), + (json) => { + if (options.projectName !== options.importPath) { + json.nx = { name: options.projectName }; + } + if (options.parsedTags?.length) { + json.nx ??= {}; + json.nx.tags = options.parsedTags; + } + return json; + } + ); } if (options.unitTestRunner !== 'none') { diff --git a/packages/remix/src/generators/application/files/ts-solution/package.json__tmpl__ b/packages/remix/src/generators/application/files/ts-solution/package.json__tmpl__ index 961e49c286..18a8f71e9a 100644 --- a/packages/remix/src/generators/application/files/ts-solution/package.json__tmpl__ +++ b/packages/remix/src/generators/application/files/ts-solution/package.json__tmpl__ @@ -1,6 +1,6 @@ { "private": true, - "name": "<%= projectName %>", + "name": "<%= importPath %>", "scripts": {}, "type": "module", "dependencies": { @@ -19,8 +19,5 @@ "engines": { "node": ">=20" }, - "sideEffects": false<% if (isUsingTsSolutionConfig && parsedTags?.length) { %>, - "nx": { - "tags": <%- JSON.stringify(parsedTags) %> - }<% } %> + "sideEffects": false } diff --git a/packages/remix/src/generators/application/lib/add-e2e.ts b/packages/remix/src/generators/application/lib/add-e2e.ts index ee46632d59..83d3a6fad2 100644 --- a/packages/remix/src/generators/application/lib/add-e2e.ts +++ b/packages/remix/src/generators/application/lib/add-e2e.ts @@ -41,8 +41,6 @@ export async function addE2E(tree: Tree, options: NormalizedSchema) { version: '0.0.1', private: true, nx: { - projectType: 'application', - sourceRoot: joinPathFragments(options.e2eProjectRoot, 'src'), implicitDependencies: [options.projectName], }, } @@ -121,8 +119,6 @@ export async function addE2E(tree: Tree, options: NormalizedSchema) { version: '0.0.1', private: true, nx: { - projectType: 'application', - sourceRoot: joinPathFragments(options.e2eProjectRoot, 'src'), implicitDependencies: [options.projectName], }, } diff --git a/packages/remix/src/generators/application/lib/normalize-options.ts b/packages/remix/src/generators/application/lib/normalize-options.ts index 0140e7bfae..83ee4a22b8 100644 --- a/packages/remix/src/generators/application/lib/normalize-options.ts +++ b/packages/remix/src/generators/application/lib/normalize-options.ts @@ -1,16 +1,16 @@ import { readNxJson, type Tree } from '@nx/devkit'; import { determineProjectNameAndRootOptions, - ensureProjectName, + ensureRootProjectName, } from '@nx/devkit/src/generators/project-name-and-root-utils'; import { Linter } from '@nx/eslint'; import { isUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup'; import { type NxRemixGeneratorSchema } from '../schema'; -import { getImportPath } from '@nx/js/src/utils/get-import-path'; export interface NormalizedSchema extends NxRemixGeneratorSchema { projectName: string; projectRoot: string; + importPath: string; e2eProjectName: string; e2eProjectRoot: string; parsedTags: string[]; @@ -21,16 +21,14 @@ export async function normalizeOptions( tree: Tree, options: NxRemixGeneratorSchema ): Promise { - await ensureProjectName(tree, options, 'application'); - const { projectName, projectRoot } = await determineProjectNameAndRootOptions( - tree, - { + await ensureRootProjectName(options, 'application'); + const { projectName, projectRoot, importPath } = + await determineProjectNameAndRootOptions(tree, { name: options.name, projectType: 'application', directory: options.directory, rootProject: options.rootProject, - } - ); + }); options.rootProject = projectRoot === '.'; const nxJson = readNxJson(tree); const addPluginDefault = @@ -38,25 +36,27 @@ export async function normalizeOptions( nxJson.useInferencePlugins !== false; options.addPlugin ??= addPluginDefault; - const e2eProjectName = options.rootProject ? 'e2e' : `${projectName}-e2e`; + const isUsingTsSolutionConfig = isUsingTsSolutionSetup(tree); + const appProjectName = + !isUsingTsSolutionConfig || options.name ? projectName : importPath; + + const e2eProjectName = options.rootProject ? 'e2e' : `${appProjectName}-e2e`; const e2eProjectRoot = options.rootProject ? 'e2e' : `${projectRoot}-e2e`; const parsedTags = options.tags ? options.tags.split(',').map((s) => s.trim()) : []; - const isUsingTsSolutionConfig = isUsingTsSolutionSetup(tree); return { ...options, linter: options.linter ?? Linter.EsLint, - projectName: isUsingTsSolutionConfig - ? getImportPath(tree, projectName) - : projectName, + projectName: appProjectName, projectRoot, + importPath, e2eProjectName, e2eProjectRoot, parsedTags, - useTsSolution: options.useTsSolution ?? isUsingTsSolutionConfig, + useTsSolution: isUsingTsSolutionConfig, isUsingTsSolutionConfig, }; } diff --git a/packages/remix/src/generators/library/lib/add-tsconfig-entry-points.ts b/packages/remix/src/generators/library/lib/add-tsconfig-entry-points.ts index eb6ac2d24d..d0af8849ed 100644 --- a/packages/remix/src/generators/library/lib/add-tsconfig-entry-points.ts +++ b/packages/remix/src/generators/library/lib/add-tsconfig-entry-points.ts @@ -5,8 +5,8 @@ import { updateJson, } from '@nx/devkit'; import { getRootTsConfigPathInTree } from '@nx/js'; +import { getProjectSourceRoot } from '@nx/js/src/utils/typescript/ts-solution-setup'; import type { RemixLibraryOptions } from './normalize-options'; -import { resolveImportPath } from '@nx/devkit/src/generators/project-name-and-root-utils'; export function addTsconfigEntryPoints( tree: Tree, @@ -16,10 +16,8 @@ export function addTsconfigEntryPoints( tree, options.projectName ); - const serverFilePath = joinPathFragments( - ...(sourceRoot ? [sourceRoot] : [projectRoot, 'src']), - 'server.ts' - ); + const projectSourceRoot = getProjectSourceRoot(tree, sourceRoot, projectRoot); + const serverFilePath = joinPathFragments(projectSourceRoot, 'server.ts'); tree.write( serverFilePath, @@ -27,13 +25,14 @@ export function addTsconfigEntryPoints( ); const baseTsConfig = getRootTsConfigPathInTree(tree); - // Use same logic as `determineProjectNameAndRootOptions` to get the import path - const importPath = resolveImportPath(tree, options.name, projectRoot); updateJson(tree, baseTsConfig, (json) => { - if (json.compilerOptions.paths && json.compilerOptions.paths[importPath]) { - json.compilerOptions.paths[joinPathFragments(importPath, 'server')] = [ - serverFilePath, - ]; + if ( + json.compilerOptions.paths && + json.compilerOptions.paths[options.importPath] + ) { + json.compilerOptions.paths[ + joinPathFragments(options.importPath, 'server') + ] = [serverFilePath]; } return json; diff --git a/packages/remix/src/generators/library/lib/normalize-options.ts b/packages/remix/src/generators/library/lib/normalize-options.ts index a94199dec3..ecd3db542f 100644 --- a/packages/remix/src/generators/library/lib/normalize-options.ts +++ b/packages/remix/src/generators/library/lib/normalize-options.ts @@ -1,11 +1,10 @@ import { readNxJson, type Tree } from '@nx/devkit'; import { determineProjectNameAndRootOptions, - ensureProjectName, + ensureRootProjectName, } from '@nx/devkit/src/generators/project-name-and-root-utils'; import type { NxRemixGeneratorSchema } from '../schema'; import { isUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup'; -import { getImportPath } from '@nx/js/src/utils/get-import-path'; export interface RemixLibraryOptions extends NxRemixGeneratorSchema { projectName: string; @@ -17,15 +16,13 @@ export async function normalizeOptions( tree: Tree, options: NxRemixGeneratorSchema ): Promise { - await ensureProjectName(tree, options, 'application'); - const { projectName, projectRoot } = await determineProjectNameAndRootOptions( - tree, - { + await ensureRootProjectName(options, 'application'); + const { projectName, projectRoot, importPath } = + await determineProjectNameAndRootOptions(tree, { name: options.name, projectType: 'library', directory: options.directory, - } - ); + }); const nxJson = readNxJson(tree); const addPluginDefault = @@ -37,10 +34,10 @@ export async function normalizeOptions( return { ...options, unitTestRunner: options.unitTestRunner ?? 'vitest', - projectName: isUsingTsSolutionConfig - ? getImportPath(tree, projectName) - : projectName, + projectName: + isUsingTsSolutionConfig && !options.name ? importPath : projectName, projectRoot, + importPath, isUsingTsSolutionConfig, }; } diff --git a/packages/remix/src/generators/library/lib/update-buildable-config.ts b/packages/remix/src/generators/library/lib/update-buildable-config.ts index 402d4a03de..98a4d28c8c 100644 --- a/packages/remix/src/generators/library/lib/update-buildable-config.ts +++ b/packages/remix/src/generators/library/lib/update-buildable-config.ts @@ -22,7 +22,7 @@ export function updateBuildableConfig( format: ['cjs'], outputPath: joinPathFragments(project.root, 'dist'), }; - updateProjectConfiguration(tree, options.name, project); + updateProjectConfiguration(tree, options.projectName, project); // Point to nested dist for yarn/npm/pnpm workspaces updateJson(tree, joinPathFragments(project.root, 'package.json'), (json) => { diff --git a/packages/remix/src/generators/library/library.impl.spec.ts b/packages/remix/src/generators/library/library.impl.spec.ts index 4c7950ac61..6f8c50cb27 100644 --- a/packages/remix/src/generators/library/library.impl.spec.ts +++ b/packages/remix/src/generators/library/library.impl.spec.ts @@ -135,6 +135,7 @@ describe('Remix Library Generator', () => { expect(pkgJson.main).toEqual('./dist/index.cjs.js'); expect(pkgJson.typings).toEqual('./dist/index.d.ts'); }); + describe('TS solution setup', () => { let tree: Tree; @@ -210,5 +211,42 @@ describe('Remix Library Generator', () => { ] `); }, 25_000); + + it('should set "nx.name" in package.json when the user provides a name that is different than the package name', async () => { + await libraryGenerator(tree, { + directory: 'packages/my-lib', + name: 'my-lib', // import path contains the npm scope, so it would be different + style: 'css', + addPlugin: true, + skipFormat: true, + }); + + expect(readJson(tree, 'packages/my-lib/package.json').nx).toStrictEqual({ + name: 'my-lib', + }); + }); + + it('should not set "nx.name" in package.json when the provided name matches the package name', async () => { + await libraryGenerator(tree, { + directory: 'packages/my-lib', + name: '@proj/my-lib', + style: 'css', + addPlugin: true, + skipFormat: true, + }); + + expect(readJson(tree, 'packages/my-lib/package.json').nx).toBeUndefined(); + }); + + it('should not set "nx.name" in package.json when the user does not provide a name', async () => { + await libraryGenerator(tree, { + directory: 'packages/my-lib', + style: 'css', + addPlugin: true, + skipFormat: true, + }); + + expect(readJson(tree, 'packages/my-lib/package.json').nx).toBeUndefined(); + }); }); }); diff --git a/packages/rspack/src/generators/application/lib/normalize-options.ts b/packages/rspack/src/generators/application/lib/normalize-options.ts index f2ac7f5d02..95caca77ee 100644 --- a/packages/rspack/src/generators/application/lib/normalize-options.ts +++ b/packages/rspack/src/generators/application/lib/normalize-options.ts @@ -1,31 +1,38 @@ -import { names, Tree } from '@nx/devkit'; +import { Tree } from '@nx/devkit'; import { ApplicationGeneratorSchema, NormalizedSchema } from '../schema'; import { determineProjectNameAndRootOptions, - ensureProjectName, + ensureRootProjectName, } from '@nx/devkit/src/generators/project-name-and-root-utils'; +import { isUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup'; export async function normalizeOptions( host: Tree, options: ApplicationGeneratorSchema ): Promise { - await ensureProjectName(host, options, 'application'); - const { projectName: appProjectName, projectRoot: appProjectRoot } = - await determineProjectNameAndRootOptions(host, { - ...options, - projectType: 'application', - }); + await ensureRootProjectName(options, 'application'); + const { + projectName, + projectRoot: appProjectRoot, + importPath, + } = await determineProjectNameAndRootOptions(host, { + ...options, + projectType: 'application', + }); // --monorepo takes precedence over --rootProject // This won't be needed once we add --bundler=rspack to the @nx/react:app preset const rootProject = !options.monorepo && options.rootProject; - const e2eProjectName = options.rootProject - ? 'e2e' - : `${names(appProjectName).fileName}-e2e`; + + const isTsSolutionSetup = isUsingTsSolutionSetup(host); + const appProjectName = + !isTsSolutionSetup || options.name ? projectName : importPath; + + const e2eProjectName = options.rootProject ? 'e2e' : `${projectName}-e2e`; const normalized = { ...options, rootProject, - name: names(options.name).fileName, + name: appProjectName, projectName: appProjectName, appProjectRoot, e2eProjectName, diff --git a/packages/storybook/src/generators/configuration/configuration.spec.ts b/packages/storybook/src/generators/configuration/configuration.spec.ts index dc139f0ae0..fdeb3be5cf 100644 --- a/packages/storybook/src/generators/configuration/configuration.spec.ts +++ b/packages/storybook/src/generators/configuration/configuration.spec.ts @@ -822,6 +822,7 @@ describe('@nx/storybook:configuration for Storybook v7', () => { bundler: 'none', skipFormat: true, addPlugin: true, + useProjectJson: false, }); await configurationGenerator(tree, { diff --git a/packages/vite/src/generators/configuration/__snapshots__/configuration.spec.ts.snap b/packages/vite/src/generators/configuration/__snapshots__/configuration.spec.ts.snap index d7df311b91..371e53398f 100644 --- a/packages/vite/src/generators/configuration/__snapshots__/configuration.spec.ts.snap +++ b/packages/vite/src/generators/configuration/__snapshots__/configuration.spec.ts.snap @@ -39,7 +39,7 @@ export default defineConfig(() => ({ fileName: 'index', // Change this to the formats you want to support. // Don't forget to update your package.json as well. - formats: ['es'], + formats: ['es' as const], }, rollupOptions: { // External packages that should not be bundled into your library. @@ -210,7 +210,7 @@ export default defineConfig(() => ({ fileName: 'index', // Change this to the formats you want to support. // Don't forget to update your package.json as well. - formats: ['es'], + formats: ['es' as const], }, rollupOptions: { // External packages that should not be bundled into your library. @@ -262,7 +262,7 @@ export default defineConfig(() => ({ fileName: 'index', // Change this to the formats you want to support. // Don't forget to update your package.json as well. - formats: ['es'], + formats: ['es' as const], }, rollupOptions: { // External packages that should not be bundled into your library. @@ -309,7 +309,7 @@ export default defineConfig({ fileName: 'index', // Change this to the formats you want to support. // Don't forget to update your package.json as well. - formats: ['es'], + formats: ['es' as const], }, rollupOptions: { // External packages that should not be bundled into your library. diff --git a/packages/vite/src/generators/vitest/vitest-generator.ts b/packages/vite/src/generators/vitest/vitest-generator.ts index a2f773d29f..735a5df71a 100644 --- a/packages/vite/src/generators/vitest/vitest-generator.ts +++ b/packages/vite/src/generators/vitest/vitest-generator.ts @@ -20,6 +20,7 @@ import { getProjectType, isUsingTsSolutionSetup, } from '@nx/js/src/utils/typescript/ts-solution-setup'; +import { typesNodeVersion } from '@nx/js/src/utils/versions'; import { join } from 'path'; import { ensureDependencies } from '../../utils/ensure-dependencies'; import { @@ -174,17 +175,18 @@ getTestBed().initTestEnvironment( updateNxJson(tree, nxJson); } - const coverageProviderDependency = await getCoverageProviderDependency( + const devDependencies = await getCoverageProviderDependency( tree, schema.coverageProvider ); + devDependencies['@types/node'] = typesNodeVersion; - const installCoverageProviderTask = addDependenciesToPackageJson( + const installDependenciesTask = addDependenciesToPackageJson( tree, {}, - coverageProviderDependency + devDependencies ); - tasks.push(installCoverageProviderTask); + tasks.push(installDependenciesTask); // Setup workspace config file (https://vitest.dev/guide/workspace.html) if ( @@ -351,7 +353,7 @@ function createFiles( async function getCoverageProviderDependency( tree: Tree, coverageProvider: VitestGeneratorSchema['coverageProvider'] -) { +): Promise> { const { vitestCoverageV8, vitestCoverageIstanbul } = await getVitestDependenciesVersionsToInstall(tree); switch (coverageProvider) { diff --git a/packages/vite/src/utils/generator-utils.spec.ts b/packages/vite/src/utils/generator-utils.spec.ts index 6e27a735c9..a9ba434400 100644 --- a/packages/vite/src/utils/generator-utils.spec.ts +++ b/packages/vite/src/utils/generator-utils.spec.ts @@ -168,7 +168,7 @@ describe('generator utils', () => { fileName: 'index', // Change this to the formats you want to support. // Don't forget to update your package.json as well. - formats: ['es'] + formats: ['es' as const] }, rollupOptions: { // External packages that should not be bundled into your library. diff --git a/packages/vite/src/utils/generator-utils.ts b/packages/vite/src/utils/generator-utils.ts index e180391479..ffe7eac0cb 100644 --- a/packages/vite/src/utils/generator-utils.ts +++ b/packages/vite/src/utils/generator-utils.ts @@ -420,7 +420,7 @@ export function createOrEditViteConfig( fileName: 'index', // Change this to the formats you want to support. // Don't forget to update your package.json as well. - formats: ['es'] + formats: ['es' as const] }, rollupOptions: { // External packages that should not be bundled into your library. diff --git a/packages/vite/src/utils/vite-config-edit-utils.spec.ts b/packages/vite/src/utils/vite-config-edit-utils.spec.ts index 1e52b3cb63..ac46875f0a 100644 --- a/packages/vite/src/utils/vite-config-edit-utils.spec.ts +++ b/packages/vite/src/utils/vite-config-edit-utils.spec.ts @@ -126,7 +126,12 @@ describe('ensureViteConfigIsCorrect', () => { build: { 'my': 'option', - 'lib': {"entry":"src/index.ts","name":"my-app","fileName":"index","formats":["es"]}, + 'lib': { + 'entry': "src/index.ts", + 'name': "my-app", + 'fileName': "index", + 'formats': ['es' as const], + }, 'rollupOptions': {"external":["react","react-dom","react/jsx-runtime"]}, } @@ -433,7 +438,12 @@ describe('ensureViteConfigIsCorrect', () => { build: { 'my': 'option', - 'lib': {"entry":"src/index.ts","name":"my-app","fileName":"index","formats":["es"]}, + 'lib': { + 'entry': "src/index.ts", + 'name': "my-app", + 'fileName': "index", + 'formats': ['es' as const], + }, 'rollupOptions': {"external":["react","react-dom","react/jsx-runtime"]}, } diff --git a/packages/vite/src/utils/vite-config-edit-utils.ts b/packages/vite/src/utils/vite-config-edit-utils.ts index ac817c01c8..55607440ce 100644 --- a/packages/vite/src/utils/vite-config-edit-utils.ts +++ b/packages/vite/src/utils/vite-config-edit-utils.ts @@ -110,6 +110,19 @@ function handleBuildOrTestNode( } propString += `}`; updatedPropsString += `${propString}\n`; + } else if (propName === 'lib') { + let propString = ` '${propName}': {\n`; + for (const [pName, pValue] of Object.entries(propValue)) { + if (pName === 'formats') { + propString += ` '${pName}': [${pValue + .map((format: string) => `'${format}' as const`) + .join(', ')}],\n`; + } else { + propString += ` '${pName}': ${JSON.stringify(pValue)},\n`; + } + } + propString += ` },`; + updatedPropsString += `${propString}\n`; } else { updatedPropsString += ` '${propName}': ${JSON.stringify( propValue diff --git a/packages/vue/src/generators/application/application.spec.ts b/packages/vue/src/generators/application/application.spec.ts index ac312ef387..cd97e2fa21 100644 --- a/packages/vue/src/generators/application/application.spec.ts +++ b/packages/vue/src/generators/application/application.spec.ts @@ -188,6 +188,7 @@ describe('application generator', () => { ...options, style: 'none', linter: 'eslint', + addPlugin: true, }); expect(tree.read('test/vite.config.ts', 'utf-8')).toMatchInlineSnapshot(` @@ -244,14 +245,15 @@ describe('application generator', () => { }, ] `); + const packageJson = readJson(tree, 'test/package.json'); + expect(packageJson.name).toBe('@proj/test'); + expect(packageJson.nx).toBeUndefined(); // Make sure keys are in idiomatic order - expect(Object.keys(readJson(tree, 'test/package.json'))) - .toMatchInlineSnapshot(` + expect(Object.keys(packageJson)).toMatchInlineSnapshot(` [ "name", "version", "private", - "nx", ] `); expect(readJson(tree, 'test/tsconfig.json')).toMatchInlineSnapshot(` @@ -355,6 +357,28 @@ describe('application generator', () => { } `); }); + + it('should respect the provided name', async () => { + await applicationGenerator(tree, { + ...options, + name: 'myapp', + addPlugin: true, + skipFormat: true, + }); + + const packageJson = readJson(tree, 'test/package.json'); + expect(packageJson.name).toBe('@proj/myapp'); + expect(packageJson.nx.name).toBe('myapp'); + // Make sure keys are in idiomatic order + expect(Object.keys(packageJson)).toMatchInlineSnapshot(` + [ + "name", + "version", + "private", + "nx", + ] + `); + }); }); }); diff --git a/packages/vue/src/generators/application/application.ts b/packages/vue/src/generators/application/application.ts index 9869402fb1..426fefaf3e 100644 --- a/packages/vue/src/generators/application/application.ts +++ b/packages/vue/src/generators/application/application.ts @@ -22,12 +22,12 @@ import { addRsbuild } from './lib/add-rsbuild'; import { extractTsConfigBase } from '../../utils/create-ts-config'; import { ensureDependencies } from '../../utils/ensure-dependencies'; import { logShowProjectCommand } from '@nx/devkit/src/utils/log-show-project-command'; -import { getImportPath } from '@nx/js/src/utils/get-import-path'; import { addProjectToTsSolutionWorkspace, updateTsconfigFiles, } from '@nx/js/src/utils/typescript/ts-solution-setup'; import { sortPackageJsonFields } from '@nx/js/src/utils/package-json/sort-fields'; +import type { PackageJson } from 'nx/src/utils/package-json'; export function applicationGenerator(tree: Tree, options: Schema) { return applicationGeneratorInternal(tree, { addPlugin: false, ...options }); @@ -66,16 +66,25 @@ export async function applicationGeneratorInternal( nxJson.useInferencePlugins !== false; if (options.isUsingTsSolutionConfig) { - writeJson(tree, joinPathFragments(options.appProjectRoot, 'package.json'), { - name: options.projectName, + const packageJson: PackageJson = { + name: options.importPath, version: '0.0.1', private: true, - nx: options.parsedTags?.length - ? { - tags: options.parsedTags, - } - : undefined, - }); + }; + + if (options.projectName !== options.importPath) { + packageJson.nx = { name: options.projectName }; + } + if (options.parsedTags?.length) { + packageJson.nx ??= {}; + packageJson.nx.tags = options.parsedTags; + } + + writeJson( + tree, + joinPathFragments(options.appProjectRoot, 'package.json'), + packageJson + ); } else { addProjectConfiguration(tree, options.projectName, { root: options.appProjectRoot, diff --git a/packages/vue/src/generators/application/lib/add-e2e.ts b/packages/vue/src/generators/application/lib/add-e2e.ts index 53fe8fc22a..32ff6bfe2f 100644 --- a/packages/vue/src/generators/application/lib/add-e2e.ts +++ b/packages/vue/src/generators/application/lib/add-e2e.ts @@ -4,6 +4,7 @@ import { ensurePackage, joinPathFragments, readNxJson, + writeJson, } from '@nx/devkit'; import { webStaticServeGenerator } from '@nx/web'; @@ -72,14 +73,29 @@ export async function addE2e( const { configurationGenerator } = ensurePackage< typeof import('@nx/cypress') >('@nx/cypress', nxVersion); - addProjectConfiguration(tree, options.e2eProjectName, { - projectType: 'application', - root: options.e2eProjectRoot, - sourceRoot: joinPathFragments(options.e2eProjectRoot, 'src'), - targets: {}, - tags: [], - implicitDependencies: [options.projectName], - }); + if (options.isUsingTsSolutionConfig) { + writeJson( + tree, + joinPathFragments(options.e2eProjectRoot, 'package.json'), + { + name: options.e2eProjectName, + version: '0.0.1', + private: true, + nx: { + implicitDependencies: [options.projectName], + }, + } + ); + } else { + addProjectConfiguration(tree, options.e2eProjectName, { + projectType: 'application', + root: options.e2eProjectRoot, + sourceRoot: joinPathFragments(options.e2eProjectRoot, 'src'), + targets: {}, + tags: [], + implicitDependencies: [options.projectName], + }); + } const e2eTask = await configurationGenerator(tree, { ...options, project: options.e2eProjectName, @@ -140,13 +156,28 @@ export async function addE2e( const { configurationGenerator } = ensurePackage< typeof import('@nx/playwright') >('@nx/playwright', nxVersion); - addProjectConfiguration(tree, options.e2eProjectName, { - projectType: 'application', - root: options.e2eProjectRoot, - sourceRoot: joinPathFragments(options.e2eProjectRoot, 'src'), - targets: {}, - implicitDependencies: [options.projectName], - }); + if (options.isUsingTsSolutionConfig) { + writeJson( + tree, + joinPathFragments(options.e2eProjectRoot, 'package.json'), + { + name: options.e2eProjectName, + version: '0.0.1', + private: true, + nx: { + implicitDependencies: [options.projectName], + }, + } + ); + } else { + addProjectConfiguration(tree, options.e2eProjectName, { + projectType: 'application', + root: options.e2eProjectRoot, + sourceRoot: joinPathFragments(options.e2eProjectRoot, 'src'), + targets: {}, + implicitDependencies: [options.projectName], + }); + } const e2eTask = await configurationGenerator(tree, { ...options, project: options.e2eProjectName, diff --git a/packages/vue/src/generators/application/lib/normalize-options.ts b/packages/vue/src/generators/application/lib/normalize-options.ts index d5cac49596..bb6092803a 100644 --- a/packages/vue/src/generators/application/lib/normalize-options.ts +++ b/packages/vue/src/generators/application/lib/normalize-options.ts @@ -1,26 +1,32 @@ import { Tree } from '@nx/devkit'; import { determineProjectNameAndRootOptions, - ensureProjectName, + ensureRootProjectName, } from '@nx/devkit/src/generators/project-name-and-root-utils'; import { NormalizedSchema, Schema } from '../schema'; import { isUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup'; -import { getImportPath } from '@nx/js/src/utils/get-import-path'; export async function normalizeOptions( host: Tree, options: Schema ): Promise { - await ensureProjectName(host, options, 'application'); - const { projectName: appProjectName, projectRoot: appProjectRoot } = - await determineProjectNameAndRootOptions(host, { - name: options.name, - projectType: 'application', - directory: options.directory, - rootProject: options.rootProject, - }); + await ensureRootProjectName(options, 'application'); + const { + projectName, + projectRoot: appProjectRoot, + importPath, + } = await determineProjectNameAndRootOptions(host, { + name: options.name, + projectType: 'application', + directory: options.directory, + rootProject: options.rootProject, + }); options.rootProject = appProjectRoot === '.'; + const isUsingTsSolutionConfig = isUsingTsSolutionSetup(host); + const appProjectName = + !isUsingTsSolutionConfig || options.name ? projectName : importPath; + const e2eProjectName = options.rootProject ? 'e2e' : `${appProjectName}-e2e`; const e2eProjectRoot = options.rootProject ? 'e2e' : `${appProjectRoot}-e2e`; @@ -28,14 +34,11 @@ export async function normalizeOptions( ? options.tags.split(',').map((s) => s.trim()) : []; - const isUsingTsSolutionConfig = isUsingTsSolutionSetup(host); - const normalized = { ...options, - projectName: isUsingTsSolutionConfig - ? getImportPath(host, appProjectName) - : appProjectName, + projectName: appProjectName, appProjectRoot, + importPath, e2eProjectName, e2eProjectRoot, parsedTags, diff --git a/packages/vue/src/generators/application/schema.d.ts b/packages/vue/src/generators/application/schema.d.ts index d4210348ad..20dd74143f 100644 --- a/packages/vue/src/generators/application/schema.d.ts +++ b/packages/vue/src/generators/application/schema.d.ts @@ -23,9 +23,10 @@ export interface Schema { useTsSolution?: boolean; } -export interface NormalizedSchema extends Schema { +export interface NormalizedSchema extends Omit { projectName: string; appProjectRoot: string; + importPath: string; e2eProjectName: string; e2eProjectRoot: string; parsedTags: string[]; diff --git a/packages/vue/src/generators/library/__snapshots__/library.spec.ts.snap b/packages/vue/src/generators/library/__snapshots__/library.spec.ts.snap index d279032ad3..b5e6dd0068 100644 --- a/packages/vue/src/generators/library/__snapshots__/library.spec.ts.snap +++ b/packages/vue/src/generators/library/__snapshots__/library.spec.ts.snap @@ -41,7 +41,7 @@ export default defineConfig(() => ({ fileName: 'index', // Change this to the formats you want to support. // Don't forget to update your package.json as well. - formats: ['es'], + formats: ['es' as const], }, rollupOptions: { // External packages that should not be bundled into your library. @@ -132,7 +132,7 @@ export default defineConfig(() => ({ fileName: 'index', // Change this to the formats you want to support. // Don't forget to update your package.json as well. - formats: ['es'], + formats: ['es' as const], }, rollupOptions: { // External packages that should not be bundled into your library. @@ -171,6 +171,7 @@ exports[`library should add vue, vite and vitest to package.json 1`] = ` "@swc-node/register": "~1.9.1", "@swc/core": "~1.5.7", "@swc/helpers": "~0.5.11", + "@types/node": "18.16.9", "@typescript-eslint/eslint-plugin": "^7.16.0", "@typescript-eslint/parser": "^7.16.0", "@vitejs/plugin-vue": "^4.5.0", diff --git a/packages/vue/src/generators/library/lib/normalize-options.ts b/packages/vue/src/generators/library/lib/normalize-options.ts index 1a22e103df..75bccf0534 100644 --- a/packages/vue/src/generators/library/lib/normalize-options.ts +++ b/packages/vue/src/generators/library/lib/normalize-options.ts @@ -7,17 +7,20 @@ import { } from '@nx/devkit'; import { determineProjectNameAndRootOptions, - ensureProjectName, + ensureRootProjectName, } from '@nx/devkit/src/generators/project-name-and-root-utils'; import { NormalizedSchema, Schema } from '../schema'; -import { isUsingTsSolutionSetup } from '@nx/js/src/utils/typescript/ts-solution-setup'; -import { getImportPath } from '@nx/js/src/utils/get-import-path'; +import { + getProjectSourceRoot, + getProjectType, + isUsingTsSolutionSetup, +} from '@nx/js/src/utils/typescript/ts-solution-setup'; export async function normalizeOptions( host: Tree, options: Schema ): Promise { - await ensureProjectName(host, options, 'library'); + await ensureRootProjectName(options, 'library'); const { projectName, names: projectNames, @@ -57,9 +60,8 @@ export async function normalizeOptions( const normalized = { addPlugin, ...options, - projectName: isUsingTsSolutionConfig - ? importPath ?? getImportPath(host, projectName) - : projectName, + projectName: + isUsingTsSolutionConfig && !options.name ? importPath : projectName, bundler, fileName, routePath: `/${projectNames.projectFileName}`, @@ -78,16 +80,27 @@ export async function normalizeOptions( if (options.appProject) { const appProjectConfig = getProjects(host).get(options.appProject); + const appProjectType = getProjectType( + host, + appProjectConfig.root, + appProjectConfig.projectType + ); - if (appProjectConfig.projectType !== 'application') { + if (appProjectType !== 'application') { throw new Error( - `appProject expected type of "application" but got "${appProjectConfig.projectType}"` + `appProject expected type of "application" but got "${appProjectType}"` ); } + const appSourceRoot = getProjectSourceRoot( + host, + appProjectConfig.sourceRoot, + appProjectConfig.root + ); + try { normalized.appMain = appProjectConfig.targets.build.options.main; - normalized.appSourceRoot = normalizePath(appProjectConfig.sourceRoot); + normalized.appSourceRoot = normalizePath(appSourceRoot); } catch (e) { throw new Error( `Could not locate project main for ${options.appProject}` diff --git a/packages/vue/src/generators/library/library.spec.ts b/packages/vue/src/generators/library/library.spec.ts index 26f9b6937c..9d15dbd394 100644 --- a/packages/vue/src/generators/library/library.spec.ts +++ b/packages/vue/src/generators/library/library.spec.ts @@ -509,6 +509,7 @@ module.exports = [ expect(eslintConfig.overrides[0].files).toContain('*.vue'); }); }); + describe('TS solution setup', () => { beforeEach(() => { tree = createTreeWithEmptyWorkspace(); @@ -687,5 +688,42 @@ module.exports = [ } `); }); + + it('should set "nx.name" in package.json when the user provides a name that is different than the package name', async () => { + await libraryGenerator(tree, { + ...defaultSchema, + directory: 'my-lib', + name: 'my-lib', // import path contains the npm scope, so it would be different + addPlugin: true, + skipFormat: true, + }); + + expect(readJson(tree, 'my-lib/package.json').nx).toStrictEqual({ + name: 'my-lib', + }); + }); + + it('should not set "nx.name" in package.json when the provided name matches the package name', async () => { + await libraryGenerator(tree, { + ...defaultSchema, + directory: 'my-lib', + name: '@proj/my-lib', + addPlugin: true, + skipFormat: true, + }); + + expect(readJson(tree, 'my-lib/package.json').nx).toBeUndefined(); + }); + + it('should not set "nx.name" in package.json when the user does not provide a name', async () => { + await libraryGenerator(tree, { + ...defaultSchema, // defaultSchema has no name + directory: 'my-lib', + addPlugin: true, + skipFormat: true, + }); + + expect(readJson(tree, 'my-lib/package.json').nx).toBeUndefined(); + }); }); }); diff --git a/packages/vue/src/generators/library/library.ts b/packages/vue/src/generators/library/library.ts index 53f93ff3f5..a77ef14f18 100644 --- a/packages/vue/src/generators/library/library.ts +++ b/packages/vue/src/generators/library/library.ts @@ -35,6 +35,7 @@ import { addReleaseConfigForTsSolution, releaseTasks, } from '@nx/js/src/generators/library/utils/add-release-config'; +import type { PackageJson } from 'nx/src/utils/package-json'; export function libraryGenerator(tree: Tree, schema: Schema) { return libraryGeneratorInternal(tree, { addPlugin: false, ...schema }); @@ -59,17 +60,26 @@ export async function libraryGeneratorInternal(tree: Tree, schema: Schema) { } if (options.isUsingTsSolutionConfig) { - writeJson(tree, joinPathFragments(options.projectRoot, 'package.json'), { - name: options.projectName, + const packageJson: PackageJson = { + name: options.importPath, version: '0.0.1', ...determineEntryFields(options), files: options.publishable ? ['dist', '!**/*.tsbuildinfo'] : undefined, - nx: options.parsedTags?.length - ? { - tags: options.parsedTags, - } - : undefined, - }); + }; + + if (options.projectName !== options.importPath) { + packageJson.nx = { name: options.projectName }; + } + if (options.parsedTags?.length) { + packageJson.nx ??= {}; + packageJson.nx.tags = options.parsedTags; + } + + writeJson( + tree, + joinPathFragments(options.projectRoot, 'package.json'), + packageJson + ); } else { addProjectConfiguration(tree, options.projectName, { root: options.projectRoot, diff --git a/packages/web/src/generators/application/application.spec.ts b/packages/web/src/generators/application/application.spec.ts index bac0de2447..af35c3865a 100644 --- a/packages/web/src/generators/application/application.spec.ts +++ b/packages/web/src/generators/application/application.spec.ts @@ -780,6 +780,17 @@ describe('app', () => { }, ] `); + const packageJson = readJson(tree, 'apps/myapp/package.json'); + expect(packageJson.name).toBe('@proj/myapp'); + expect(packageJson.nx).toBeUndefined(); + // Make sure keys are in idiomatic order + expect(Object.keys(packageJson)).toMatchInlineSnapshot(` + [ + "name", + "version", + "private", + ] + `); expect(readJson(tree, 'apps/myapp/tsconfig.json')).toMatchInlineSnapshot(` { "extends": "../../tsconfig.base.json", @@ -934,5 +945,31 @@ describe('app', () => { " `); }); + + it('should respect the provided name', async () => { + await applicationGenerator(tree, { + directory: 'apps/myapp', + name: 'myapp', + addPlugin: true, + linter: 'none', + style: 'none', + bundler: 'vite', + unitTestRunner: 'vitest', + e2eTestRunner: 'playwright', + }); + + const packageJson = readJson(tree, 'apps/myapp/package.json'); + expect(packageJson.name).toBe('@proj/myapp'); + expect(packageJson.nx.name).toBe('myapp'); + // Make sure keys are in idiomatic order + expect(Object.keys(packageJson)).toMatchInlineSnapshot(` + [ + "name", + "version", + "private", + "nx", + ] + `); + }); }); }); diff --git a/packages/web/src/generators/application/application.ts b/packages/web/src/generators/application/application.ts index 65351b9853..f6d2cc260f 100644 --- a/packages/web/src/generators/application/application.ts +++ b/packages/web/src/generators/application/application.ts @@ -21,7 +21,7 @@ import { } from '@nx/devkit'; import { determineProjectNameAndRootOptions, - ensureProjectName, + ensureRootProjectName, } from '@nx/devkit/src/generators/project-name-and-root-utils'; import { getRelativePathToRootTsConfig, @@ -53,13 +53,15 @@ import { isUsingTsSolutionSetup, updateTsconfigFiles, } from '@nx/js/src/utils/typescript/ts-solution-setup'; -import { getImportPath } from '@nx/js/src/utils/get-import-path'; +import type { PackageJson } from 'nx/src/utils/package-json'; interface NormalizedSchema extends Schema { projectName: string; + importPath: string; appProjectRoot: string; e2eProjectName: string; e2eProjectRoot: string; + names: ReturnType; parsedTags: string[]; isUsingTsSolutionConfig: boolean; } @@ -76,7 +78,6 @@ function createApplicationFiles(tree: Tree, options: NormalizedSchema) { options.appProjectRoot, { ...options, - ...names(options.name), tmpl: '', offsetFromRoot: offsetFromRoot(options.appProjectRoot), rootTsConfigPath, @@ -90,7 +91,6 @@ function createApplicationFiles(tree: Tree, options: NormalizedSchema) { options.appProjectRoot, { ...options, - ...names(options.name), tmpl: '', offsetFromRoot: rootOffset, rootTsConfigPath, @@ -247,16 +247,25 @@ async function setupBundler(tree: Tree, options: NormalizedSchema) { async function addProject(tree: Tree, options: NormalizedSchema) { if (options.isUsingTsSolutionConfig) { - writeJson(tree, joinPathFragments(options.appProjectRoot, 'package.json'), { - name: getImportPath(tree, options.name), + const packageJson: PackageJson = { + name: options.importPath, version: '0.0.1', private: true, - nx: options.parsedTags?.length - ? { - tags: options.parsedTags, - } - : undefined, - }); + }; + + if (options.projectName !== options.importPath) { + packageJson.nx = { name: options.projectName }; + } + if (options.parsedTags?.length) { + packageJson.nx ??= {}; + packageJson.nx.tags = options.parsedTags; + } + + writeJson( + tree, + joinPathFragments(options.appProjectRoot, 'package.json'), + packageJson + ); } else { addProjectConfiguration(tree, options.projectName, { projectType: 'application', @@ -475,8 +484,6 @@ export async function applicationGeneratorInternal(host: Tree, schema: Schema) { version: '0.0.1', private: true, nx: { - projectType: 'application', - sourceRoot: joinPathFragments(options.e2eProjectRoot, 'src'), implicitDependencies: [options.projectName], }, } @@ -553,8 +560,6 @@ export async function applicationGeneratorInternal(host: Tree, schema: Schema) { version: '0.0.1', private: true, nx: { - projectType: 'application', - sourceRoot: joinPathFragments(options.e2eProjectRoot, 'src'), implicitDependencies: [options.projectName], }, } @@ -688,22 +693,28 @@ async function normalizeOptions( host: Tree, options: Schema ): Promise { - await ensureProjectName(host, options, 'application'); - const { projectName: appProjectName, projectRoot: appProjectRoot } = - await determineProjectNameAndRootOptions(host, { - name: options.name, - projectType: 'application', - directory: options.directory, - }); + await ensureRootProjectName(options, 'application'); + const { + projectName, + projectRoot: appProjectRoot, + importPath, + } = await determineProjectNameAndRootOptions(host, { + name: options.name, + projectType: 'application', + directory: options.directory, + }); const nxJson = readNxJson(host); const addPluginDefault = process.env.NX_ADD_PLUGINS !== 'false' && nxJson.useInferencePlugins !== false; options.addPlugin ??= addPluginDefault; + const isUsingTsSolutionConfig = isUsingTsSolutionSetup(host); + const appProjectName = + !isUsingTsSolutionConfig || options.name ? projectName : importPath; + const e2eProjectName = `${appProjectName}-e2e`; const e2eProjectRoot = `${appProjectRoot}-e2e`; - const isUsingTsSolutionConfig = isUsingTsSolutionSetup(host); const npmScope = getNpmScope(host); @@ -719,17 +730,16 @@ async function normalizeOptions( return { ...options, prefix: options.prefix ?? npmScope ?? 'app', - name: names(options.name).fileName, compiler: options.compiler ?? 'babel', bundler: options.bundler ?? 'webpack', - projectName: isUsingTsSolutionConfig - ? getImportPath(host, appProjectName) - : appProjectName, + projectName: appProjectName, + importPath, strict: options.strict ?? true, appProjectRoot, e2eProjectRoot, e2eProjectName, parsedTags, + names: names(projectName), isUsingTsSolutionConfig, }; } diff --git a/packages/web/src/generators/application/files/app-vite/index.html__tmpl__ b/packages/web/src/generators/application/files/app-vite/index.html__tmpl__ index 4252e3f12d..6e3b133d9a 100644 --- a/packages/web/src/generators/application/files/app-vite/index.html__tmpl__ +++ b/packages/web/src/generators/application/files/app-vite/index.html__tmpl__ @@ -2,7 +2,7 @@ - <%= className %> + <%= names.className %> diff --git a/packages/web/src/generators/application/files/app-webpack/src/index.html b/packages/web/src/generators/application/files/app-webpack/src/index.html index 42ece406e8..6483948203 100644 --- a/packages/web/src/generators/application/files/app-webpack/src/index.html +++ b/packages/web/src/generators/application/files/app-webpack/src/index.html @@ -2,7 +2,7 @@ - <%= className %> + <%= names.className %> diff --git a/packages/webpack/src/plugins/nx-webpack-plugin/lib/normalize-options.ts b/packages/webpack/src/plugins/nx-webpack-plugin/lib/normalize-options.ts index 0909ad9375..7ab5e2c753 100644 --- a/packages/webpack/src/plugins/nx-webpack-plugin/lib/normalize-options.ts +++ b/packages/webpack/src/plugins/nx-webpack-plugin/lib/normalize-options.ts @@ -1,5 +1,5 @@ import { basename, dirname, join, parse, relative, resolve } from 'path'; -import { statSync } from 'fs'; +import { existsSync, statSync } from 'fs'; import { normalizePath, parseTargetString, @@ -74,7 +74,11 @@ export function normalizeOptions( ); } - const sourceRoot = projectNode.data.sourceRoot ?? projectNode.data.root; + const sourceRoot = + projectNode.data.sourceRoot ?? + (existsSync(join(workspaceRoot, projectNode.data.root, 'src')) + ? join(projectNode.data.root, 'src') + : projectNode.data.root); if (!combinedPluginAndMaybeExecutorOptions.main) { throw new Error( diff --git a/packages/workspace/src/generators/npm-package/npm-package.ts b/packages/workspace/src/generators/npm-package/npm-package.ts index 0f69d707a6..1df6584d9f 100644 --- a/packages/workspace/src/generators/npm-package/npm-package.ts +++ b/packages/workspace/src/generators/npm-package/npm-package.ts @@ -7,10 +7,9 @@ import { } from '@nx/devkit'; import { determineProjectNameAndRootOptions, - ensureProjectName, + ensureRootProjectName, } from '@nx/devkit/src/generators/project-name-and-root-utils'; import { join } from 'path'; -import { getImportPath } from '../../utilities/get-import-path'; export interface ProjectOptions { directory: string; @@ -19,33 +18,37 @@ export interface ProjectOptions { interface NormalizedProjectOptions extends ProjectOptions { projectRoot: string; + importPath: string; } async function normalizeOptions( tree: Tree, options: ProjectOptions ): Promise { - await ensureProjectName(tree, options, 'library'); - const { projectName, projectRoot } = await determineProjectNameAndRootOptions( - tree, - { + await ensureRootProjectName(options, 'library'); + const { projectName, projectRoot, importPath } = + await determineProjectNameAndRootOptions(tree, { name: options.name, projectType: 'library', directory: options.directory, - } - ); + }); return { ...options, name: projectName, projectRoot, + importPath, }; } -function addFiles(projectRoot: string, tree: Tree, options: ProjectOptions) { +function addFiles( + projectRoot: string, + tree: Tree, + options: NormalizedProjectOptions +) { const packageJsonPath = join(projectRoot, 'package.json'); writeJson(tree, packageJsonPath, { - name: getImportPath(tree, options.name), + name: options.importPath, version: '0.0.0', scripts: { test: 'node index.js',