feat(misc): support new format to determine new project name and destination in move generators (#18878)

Co-authored-by: Jason Jean <jasonjean1993@gmail.com>
This commit is contained in:
Leosvel Pérez Espinosa 2023-09-05 14:40:16 +01:00 committed by GitHub
parent 6b272ed4c4
commit 8564d9ba12
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
45 changed files with 1382 additions and 820 deletions

View File

@ -1,6 +1,6 @@
{ {
"name": "move", "name": "move",
"factory": "./src/generators/move/move#angularMoveGenerator", "factory": "./src/generators/move/move#angularMoveGeneratorInternal",
"schema": { "schema": {
"$schema": "http://json-schema.org/schema", "$schema": "http://json-schema.org/schema",
"$id": "NxAngularMove", "$id": "NxAngularMove",
@ -22,12 +22,24 @@
"x-dropdown": "projects", "x-dropdown": "projects",
"x-priority": "important" "x-priority": "important"
}, },
"newProjectName": {
"type": "string",
"alias": "project",
"description": "The new name of the project after the move.",
"pattern": "(?:^@[a-zA-Z0-9-*~][a-zA-Z0-9-*._~]*\\/[a-zA-Z0-9-~][a-zA-Z0-9-._~]*|^[a-zA-Z][^:]*)$",
"x-priority": "important"
},
"destination": { "destination": {
"type": "string", "type": "string",
"description": "The folder to move the Angular project into.", "description": "The folder to move the Angular project into.",
"$default": { "$source": "argv", "index": 0 }, "$default": { "$source": "argv", "index": 0 },
"x-priority": "important" "x-priority": "important"
}, },
"projectNameAndRootFormat": {
"description": "Whether to generate the new project name and destination as provided (`as-provided`) or generate them composing their values and taking the configured layout into account (`derived`).",
"type": "string",
"enum": ["as-provided", "derived"]
},
"importPath": { "importPath": {
"type": "string", "type": "string",
"description": "The new import path to use in the `tsconfig.base.json`." "description": "The new import path to use in the `tsconfig.base.json`."
@ -50,7 +62,8 @@
}, },
"aliases": ["mv"], "aliases": ["mv"],
"description": "Moves an Angular application or library to another folder within the workspace and updates the project configuration.", "description": "Moves an Angular application or library to another folder within the workspace and updates the project configuration.",
"implementation": "/packages/angular/src/generators/move/move#angularMoveGenerator.ts", "x-deprecated": "Use the `@nx/workspace:move` generator instead. This generator will be removed in Nx v18.",
"implementation": "/packages/angular/src/generators/move/move#angularMoveGeneratorInternal.ts",
"hidden": false, "hidden": false,
"path": "/packages/angular/src/generators/move/schema.json", "path": "/packages/angular/src/generators/move/schema.json",
"type": "generator" "type": "generator"

View File

@ -1,6 +1,6 @@
{ {
"name": "move", "name": "move",
"factory": "./src/generators/move/move#moveGenerator", "factory": "./src/generators/move/move#moveGeneratorInternal",
"schema": { "schema": {
"$schema": "http://json-schema.org/schema", "$schema": "http://json-schema.org/schema",
"$id": "NxWorkspaceMove", "$id": "NxWorkspaceMove",
@ -21,11 +21,23 @@
"description": "The name of the project to move.", "description": "The name of the project to move.",
"x-dropdown": "projects" "x-dropdown": "projects"
}, },
"newProjectName": {
"type": "string",
"alias": "project",
"description": "The new name of the project after the move.",
"pattern": "(?:^@[a-zA-Z0-9-*~][a-zA-Z0-9-*._~]*\\/[a-zA-Z0-9-~][a-zA-Z0-9-._~]*|^[a-zA-Z][^:]*)$",
"x-priority": "important"
},
"destination": { "destination": {
"type": "string", "type": "string",
"description": "The folder to move the project into.", "description": "The folder to move the project into.",
"$default": { "$source": "argv", "index": 0 } "$default": { "$source": "argv", "index": 0 }
}, },
"projectNameAndRootFormat": {
"description": "Whether to generate the new project name and destination as provided (`as-provided`) or generate them composing their values and taking the configured layout into account (`derived`).",
"type": "string",
"enum": ["as-provided", "derived"]
},
"importPath": { "importPath": {
"type": "string", "type": "string",
"description": "The new import path to use in the `tsconfig.base.json`." "description": "The new import path to use in the `tsconfig.base.json`."
@ -48,7 +60,7 @@
}, },
"aliases": ["mv"], "aliases": ["mv"],
"description": "Move an application or library to another folder.", "description": "Move an application or library to another folder.",
"implementation": "/packages/workspace/src/generators/move/move#moveGenerator.ts", "implementation": "/packages/workspace/src/generators/move/move#moveGeneratorInternal.ts",
"hidden": false, "hidden": false,
"path": "/packages/workspace/src/generators/move/schema.json", "path": "/packages/workspace/src/generators/move/schema.json",
"type": "generator" "type": "generator"

View File

@ -20,7 +20,9 @@ describe('Move Angular Project', () => {
app1 = uniq('app1'); app1 = uniq('app1');
app2 = uniq('app2'); app2 = uniq('app2');
newPath = `subfolder/${app2}`; newPath = `subfolder/${app2}`;
runCLI(`generate @nx/angular:app ${app1} --no-interactive`); runCLI(
`generate @nx/angular:app ${app1} --project-name-and-root-format=as-provided --no-interactive`
);
}); });
afterAll(() => cleanupProject()); afterAll(() => cleanupProject());
@ -30,28 +32,26 @@ describe('Move Angular Project', () => {
*/ */
it('should work for apps', () => { it('should work for apps', () => {
const moveOutput = runCLI( const moveOutput = runCLI(
`generate @nx/angular:move --project ${app1} ${newPath}` `generate @nx/angular:move --project ${app1} ${newPath} --project-name-and-root-format=as-provided`
); );
// just check the output // just check the output
expect(moveOutput).toContain(`DELETE apps/${app1}`); expect(moveOutput).toContain(`DELETE ${app1}`);
expect(moveOutput).toContain(`CREATE apps/${newPath}/jest.config.ts`); expect(moveOutput).toContain(`CREATE ${newPath}/jest.config.ts`);
expect(moveOutput).toContain(`CREATE apps/${newPath}/tsconfig.app.json`); expect(moveOutput).toContain(`CREATE ${newPath}/tsconfig.app.json`);
expect(moveOutput).toContain(`CREATE apps/${newPath}/tsconfig.json`); expect(moveOutput).toContain(`CREATE ${newPath}/tsconfig.json`);
expect(moveOutput).toContain(`CREATE apps/${newPath}/tsconfig.spec.json`); expect(moveOutput).toContain(`CREATE ${newPath}/tsconfig.spec.json`);
expect(moveOutput).toContain(`CREATE apps/${newPath}/.eslintrc.json`); expect(moveOutput).toContain(`CREATE ${newPath}/.eslintrc.json`);
expect(moveOutput).toContain(`CREATE apps/${newPath}/src/favicon.ico`); expect(moveOutput).toContain(`CREATE ${newPath}/src/favicon.ico`);
expect(moveOutput).toContain(`CREATE apps/${newPath}/src/index.html`); expect(moveOutput).toContain(`CREATE ${newPath}/src/index.html`);
expect(moveOutput).toContain(`CREATE apps/${newPath}/src/main.ts`); expect(moveOutput).toContain(`CREATE ${newPath}/src/main.ts`);
expect(moveOutput).toContain(`CREATE apps/${newPath}/src/styles.css`); expect(moveOutput).toContain(`CREATE ${newPath}/src/styles.css`);
expect(moveOutput).toContain(`CREATE apps/${newPath}/src/test-setup.ts`); expect(moveOutput).toContain(`CREATE ${newPath}/src/test-setup.ts`);
expect(moveOutput).toContain( expect(moveOutput).toContain(
`CREATE apps/${newPath}/src/app/app.component.html` `CREATE ${newPath}/src/app/app.component.html`
); );
expect(moveOutput).toContain( expect(moveOutput).toContain(`CREATE ${newPath}/src/app/app.module.ts`);
`CREATE apps/${newPath}/src/app/app.module.ts` expect(moveOutput).toContain(`CREATE ${newPath}/src/assets/.gitkeep`);
);
expect(moveOutput).toContain(`CREATE apps/${newPath}/src/assets/.gitkeep`);
}); });
/** /**
@ -61,7 +61,7 @@ describe('Move Angular Project', () => {
// by default the cypress config doesn't contain any app specific paths // by default the cypress config doesn't contain any app specific paths
// create a custom config with some app specific paths // create a custom config with some app specific paths
updateFile( updateFile(
`apps/${app1}-e2e/cypress.config.ts`, `${app1}-e2e/cypress.config.ts`,
` `
import { defineConfig } from 'cypress'; import { defineConfig } from 'cypress';
import { nxE2EPreset } from '@nx/cypress/plugins/cypress-preset'; import { nxE2EPreset } from '@nx/cypress/plugins/cypress-preset';
@ -69,27 +69,25 @@ describe('Move Angular Project', () => {
export default defineConfig({ export default defineConfig({
e2e: { e2e: {
...nxE2EPreset(__dirname), ...nxE2EPreset(__dirname),
videosFolder: '../../dist/cypress/apps/${app1}-e2e/videos', videosFolder: '../dist/cypress/${app1}-e2e/videos',
screenshotsFolder: '../../dist/cypress/apps/${app1}-e2e/screenshots', screenshotsFolder: '../dist/cypress/${app1}-e2e/screenshots',
}, },
}); });
` `
); );
const moveOutput = runCLI( const moveOutput = runCLI(
`generate @nx/angular:move --projectName=${app1}-e2e --destination=${newPath}-e2e` `generate @nx/angular:move --projectName=${app1}-e2e --destination=${newPath}-e2e --project-name-and-root-format=as-provided`
); );
// just check that the cypress.config.ts is updated correctly // just check that the cypress.config.ts is updated correctly
const cypressConfigPath = `apps/${newPath}-e2e/cypress.config.ts`; const cypressConfigPath = `${newPath}-e2e/cypress.config.ts`;
expect(moveOutput).toContain(`CREATE ${cypressConfigPath}`); expect(moveOutput).toContain(`CREATE ${cypressConfigPath}`);
checkFilesExist(cypressConfigPath); checkFilesExist(cypressConfigPath);
const cypressConfig = readFile(cypressConfigPath); const cypressConfig = readFile(cypressConfigPath);
expect(cypressConfig).toContain(`../../dist/cypress/${newPath}-e2e/videos`);
expect(cypressConfig).toContain( expect(cypressConfig).toContain(
`../../../dist/cypress/apps/${newPath}-e2e/videos` `../../dist/cypress/${newPath}-e2e/screenshots`
);
expect(cypressConfig).toContain(
`../../../dist/cypress/apps/${newPath}-e2e/screenshots`
); );
}); });
@ -99,13 +97,73 @@ describe('Move Angular Project', () => {
it('should work for libraries', () => { it('should work for libraries', () => {
const lib1 = uniq('mylib'); const lib1 = uniq('mylib');
const lib2 = uniq('mylib'); const lib2 = uniq('mylib');
runCLI(`generate @nx/angular:lib ${lib1} --no-interactive`); runCLI(
`generate @nx/angular:lib ${lib1} --project-name-and-root-format=as-provided --no-interactive`
);
/** /**
* Create a library which imports the module from the other lib * Create a library which imports the module from the other lib
*/ */
runCLI(`generate @nx/angular:lib ${lib2} --no-interactive`); runCLI(
`generate @nx/angular:lib ${lib2} --project-name-and-root-format=as-provided --no-interactive`
);
updateFile(
`${lib2}/src/lib/${lib2}.module.ts`,
`import { ${classify(lib1)}Module } from '@${proj}/${lib1}';
export class ExtendedModule extends ${classify(lib1)}Module { }`
);
const moveOutput = runCLI(
`generate @nx/angular:move --projectName=${lib1} --destination=shared/${lib1} --newProjectName=shared-${lib1} --project-name-and-root-format=as-provided`
);
const newPath = `shared/${lib1}`;
const newModule = `Shared${classify(lib1)}Module`;
const testSetupPath = `${newPath}/src/test-setup.ts`;
expect(moveOutput).toContain(`CREATE ${testSetupPath}`);
checkFilesExist(testSetupPath);
const modulePath = `${newPath}/src/lib/shared-${lib1}.module.ts`;
expect(moveOutput).toContain(`CREATE ${modulePath}`);
checkFilesExist(modulePath);
const moduleFile = readFile(modulePath);
expect(moduleFile).toContain(`export class ${newModule}`);
const indexPath = `${newPath}/src/index.ts`;
expect(moveOutput).toContain(`CREATE ${indexPath}`);
checkFilesExist(indexPath);
const index = readFile(indexPath);
expect(index).toContain(`export * from './lib/shared-${lib1}.module'`);
/**
* Check that the import in lib2 has been updated
*/
const lib2FilePath = `${lib2}/src/lib/${lib2}.module.ts`;
const lib2File = readFile(lib2FilePath);
expect(lib2File).toContain(
`import { ${newModule} } from '@${proj}/shared-${lib1}';`
);
expect(lib2File).toContain(`extends ${newModule}`);
});
it('should move projects correctly with --project-name-and-root-format=derived', () => {
const lib1 = uniq('mylib');
const lib2 = uniq('mylib');
runCLI(
`generate @nx/angular:lib ${lib1} --project-name-and-root-format=derived --no-interactive`
);
/**
* Create a library which imports the module from the other lib
*/
runCLI(
`generate @nx/angular:lib ${lib2} --project-name-and-root-format=derived --no-interactive`
);
updateFile( updateFile(
`libs/${lib2}/src/lib/${lib2}.module.ts`, `libs/${lib2}/src/lib/${lib2}.module.ts`,
@ -115,7 +173,7 @@ describe('Move Angular Project', () => {
); );
const moveOutput = runCLI( const moveOutput = runCLI(
`generate @nx/angular:move --projectName=${lib1} --destination=shared/${lib1}` `generate @nx/angular:move --projectName=${lib1} --destination=shared/${lib1} --project-name-and-root-format=derived`
); );
const newPath = `libs/shared/${lib1}`; const newPath = `libs/shared/${lib1}`;

View File

@ -83,15 +83,17 @@ describe('Workspace Tests', () => {
const lib1 = uniq('mylib'); const lib1 = uniq('mylib');
const lib2 = uniq('mylib'); const lib2 = uniq('mylib');
const lib3 = uniq('mylib'); const lib3 = uniq('mylib');
runCLI(`generate @nx/js:lib ${lib1}/data-access --unitTestRunner=jest`); runCLI(
`generate @nx/js:lib ${lib1}-data-access --directory=${lib1}/data-access --unitTestRunner=jest --project-name-and-root-format=as-provided`
);
updateFile( updateFile(
`libs/${lib1}/data-access/src/lib/${lib1}-data-access.ts`, `${lib1}/data-access/src/lib/${lib1}-data-access.ts`,
`export function fromLibOne() { console.log('This is completely pointless'); }` `export function fromLibOne() { console.log('This is completely pointless'); }`
); );
updateFile( updateFile(
`libs/${lib1}/data-access/src/index.ts`, `${lib1}/data-access/src/index.ts`,
`export * from './lib/${lib1}-data-access.ts'` `export * from './lib/${lib1}-data-access.ts'`
); );
@ -99,11 +101,13 @@ describe('Workspace Tests', () => {
* Create a library which imports a class from lib1 * Create a library which imports a class from lib1
*/ */
runCLI(`generate @nx/js:lib ${lib2}/ui --unitTestRunner=jest`); runCLI(
`generate @nx/js:lib ${lib2}-ui --directory=${lib2}/ui --unitTestRunner=jest --project-name-and-root-format=as-provided`
);
updateFile( updateFile(
`libs/${lib2}/ui/src/lib/${lib2}-ui.ts`, `${lib2}/ui/src/lib/${lib2}-ui.ts`,
`import { fromLibOne } from '@${proj}/${lib1}/data-access'; `import { fromLibOne } from '@${proj}/${lib1}-data-access';
export const fromLibTwo = () => fromLibOne();` export const fromLibTwo = () => fromLibOne();`
); );
@ -112,7 +116,9 @@ describe('Workspace Tests', () => {
* Create a library which has an implicit dependency on lib1 * Create a library which has an implicit dependency on lib1
*/ */
runCLI(`generate @nx/js:lib ${lib3} --unitTestRunner=jest`); runCLI(
`generate @nx/js:lib ${lib3} --unitTestRunner=jest --project-name-and-root-format=as-provided`
);
await updateProjectConfig(lib3, (config) => { await updateProjectConfig(lib3, (config) => {
config.implicitDependencies = [`${lib1}-data-access`]; config.implicitDependencies = [`${lib1}-data-access`];
return config; return config;
@ -123,13 +129,13 @@ describe('Workspace Tests', () => {
*/ */
const moveOutput = runCLI( const moveOutput = runCLI(
`generate @nx/workspace:move --project ${lib1}-data-access shared/${lib1}/data-access` `generate @nx/workspace:move --project ${lib1}-data-access shared/${lib1}/data-access --newProjectName=shared-${lib1}-data-access --project-name-and-root-format=as-provided`
); );
expect(moveOutput).toContain(`DELETE libs/${lib1}/data-access`); expect(moveOutput).toContain(`DELETE ${lib1}/data-access`);
expect(exists(`libs/${lib1}/data-access`)).toBeFalsy(); expect(exists(`${lib1}/data-access`)).toBeFalsy();
const newPath = `libs/shared/${lib1}/data-access`; const newPath = `shared/${lib1}/data-access`;
const newName = `shared-${lib1}-data-access`; const newName = `shared-${lib1}-data-access`;
const readmePath = `${newPath}/README.md`; const readmePath = `${newPath}/README.md`;
@ -141,8 +147,8 @@ describe('Workspace Tests', () => {
checkFilesExist(jestConfigPath); checkFilesExist(jestConfigPath);
const jestConfig = readFile(jestConfigPath); const jestConfig = readFile(jestConfigPath);
expect(jestConfig).toContain(`displayName: 'shared-${lib1}-data-access'`); expect(jestConfig).toContain(`displayName: 'shared-${lib1}-data-access'`);
expect(jestConfig).toContain(`preset: '../../../../jest.preset.js'`); expect(jestConfig).toContain(`preset: '../../../jest.preset.js'`);
expect(jestConfig).toContain(`'../../../../coverage/${newPath}'`); expect(jestConfig).toContain(`'../../../coverage/${newPath}'`);
const tsConfigPath = `${newPath}/tsconfig.json`; const tsConfigPath = `${newPath}/tsconfig.json`;
expect(moveOutput).toContain(`CREATE ${tsConfigPath}`); expect(moveOutput).toContain(`CREATE ${tsConfigPath}`);
@ -153,7 +159,7 @@ describe('Workspace Tests', () => {
checkFilesExist(tsConfigLibPath); checkFilesExist(tsConfigLibPath);
const tsConfigLib = readJson(tsConfigLibPath); const tsConfigLib = readJson(tsConfigLibPath);
expect(tsConfigLib.compilerOptions.outDir).toEqual( expect(tsConfigLib.compilerOptions.outDir).toEqual(
'../../../../dist/out-tsc' '../../../dist/out-tsc'
); );
const tsConfigSpecPath = `${newPath}/tsconfig.spec.json`; const tsConfigSpecPath = `${newPath}/tsconfig.spec.json`;
@ -161,7 +167,7 @@ describe('Workspace Tests', () => {
checkFilesExist(tsConfigSpecPath); checkFilesExist(tsConfigSpecPath);
const tsConfigSpec = readJson(tsConfigSpecPath); const tsConfigSpec = readJson(tsConfigSpecPath);
expect(tsConfigSpec.compilerOptions.outDir).toEqual( expect(tsConfigSpec.compilerOptions.outDir).toEqual(
'../../../../dist/out-tsc' '../../../dist/out-tsc'
); );
const indexPath = `${newPath}/src/index.ts`; const indexPath = `${newPath}/src/index.ts`;
@ -186,13 +192,13 @@ describe('Workspace Tests', () => {
expect(moveOutput).toContain('UPDATE tsconfig.base.json'); expect(moveOutput).toContain('UPDATE tsconfig.base.json');
const rootTsConfig = readJson('tsconfig.base.json'); const rootTsConfig = readJson('tsconfig.base.json');
expect( expect(
rootTsConfig.compilerOptions.paths[`@${proj}/${lib1}/data-access`] rootTsConfig.compilerOptions.paths[`@${proj}/${lib1}-data-access`]
).toBeUndefined(); ).toBeUndefined();
expect( expect(
rootTsConfig.compilerOptions.paths[ rootTsConfig.compilerOptions.paths[
`@${proj}/shared/${lib1}/data-access` `@${proj}/shared-${lib1}-data-access`
] ]
).toEqual([`libs/shared/${lib1}/data-access/src/index.ts`]); ).toEqual([`shared/${lib1}/data-access/src/index.ts`]);
projects = await readResolvedConfiguration(); projects = await readResolvedConfiguration();
expect(projects[`${lib1}-data-access`]).toBeUndefined(); expect(projects[`${lib1}-data-access`]).toBeUndefined();
@ -200,17 +206,17 @@ describe('Workspace Tests', () => {
expect(project).toBeTruthy(); expect(project).toBeTruthy();
expect(project.sourceRoot).toBe(`${newPath}/src`); expect(project.sourceRoot).toBe(`${newPath}/src`);
expect(project.targets.lint.options.lintFilePatterns).toEqual([ expect(project.targets.lint.options.lintFilePatterns).toEqual([
`libs/shared/${lib1}/data-access/**/*.ts`, `shared/${lib1}/data-access/**/*.ts`,
`libs/shared/${lib1}/data-access/package.json`, `shared/${lib1}/data-access/package.json`,
]); ]);
/** /**
* Check that the import in lib2 has been updated * Check that the import in lib2 has been updated
*/ */
const lib2FilePath = `libs/${lib2}/ui/src/lib/${lib2}-ui.ts`; const lib2FilePath = `${lib2}/ui/src/lib/${lib2}-ui.ts`;
const lib2File = readFile(lib2FilePath); const lib2File = readFile(lib2FilePath);
expect(lib2File).toContain( expect(lib2File).toContain(
`import { fromLibOne } from '@${proj}/shared/${lib1}/data-access';` `import { fromLibOne } from '@${proj}/shared-${lib1}-data-access';`
); );
}); });
@ -220,16 +226,16 @@ describe('Workspace Tests', () => {
const lib2 = uniq('mylib'); const lib2 = uniq('mylib');
const lib3 = uniq('mylib'); const lib3 = uniq('mylib');
runCLI( runCLI(
`generate @nx/js:lib ${lib1}/data-access --importPath=${importPath} --unitTestRunner=jest` `generate @nx/js:lib ${lib1}-data-access --directory=${lib1}/data-access --importPath=${importPath} --unitTestRunner=jest --project-name-and-root-format=as-provided`
); );
updateFile( updateFile(
`libs/${lib1}/data-access/src/lib/${lib1}-data-access.ts`, `${lib1}/data-access/src/lib/${lib1}-data-access.ts`,
`export function fromLibOne() { console.log('This is completely pointless'); }` `export function fromLibOne() { console.log('This is completely pointless'); }`
); );
updateFile( updateFile(
`libs/${lib1}/data-access/src/index.ts`, `${lib1}/data-access/src/index.ts`,
`export * from './lib/${lib1}-data-access.ts'` `export * from './lib/${lib1}-data-access.ts'`
); );
@ -237,10 +243,12 @@ describe('Workspace Tests', () => {
* Create a library which imports a class from lib1 * Create a library which imports a class from lib1
*/ */
runCLI(`generate @nx/js:lib ${lib2}/ui --unitTestRunner=jest`); runCLI(
`generate @nx/js:lib ${lib2}-ui --directory=${lib2}/ui --unitTestRunner=jest --project-name-and-root-format=as-provided`
);
updateFile( updateFile(
`libs/${lib2}/ui/src/lib/${lib2}-ui.ts`, `${lib2}/ui/src/lib/${lib2}-ui.ts`,
`import { fromLibOne } from '${importPath}'; `import { fromLibOne } from '${importPath}';
export const fromLibTwo = () => fromLibOne();` export const fromLibTwo = () => fromLibOne();`
@ -250,7 +258,9 @@ describe('Workspace Tests', () => {
* Create a library which has an implicit dependency on lib1 * Create a library which has an implicit dependency on lib1
*/ */
runCLI(`generate @nx/js:lib ${lib3} --unitTestRunner=jest`); runCLI(
`generate @nx/js:lib ${lib3} --unitTestRunner=jest --project-name-and-root-format=as-provided`
);
await updateProjectConfig(lib3, (config) => { await updateProjectConfig(lib3, (config) => {
config.implicitDependencies = [`${lib1}-data-access`]; config.implicitDependencies = [`${lib1}-data-access`];
return config; return config;
@ -261,13 +271,13 @@ describe('Workspace Tests', () => {
*/ */
const moveOutput = runCLI( const moveOutput = runCLI(
`generate @nx/workspace:move --project ${lib1}-data-access shared/${lib1}/data-access` `generate @nx/workspace:move --project ${lib1}-data-access shared/${lib1}/data-access --newProjectName=shared-${lib1}-data-access --project-name-and-root-format=as-provided`
); );
expect(moveOutput).toContain(`DELETE libs/${lib1}/data-access`); expect(moveOutput).toContain(`DELETE ${lib1}/data-access`);
expect(exists(`libs/${lib1}/data-access`)).toBeFalsy(); expect(exists(`${lib1}/data-access`)).toBeFalsy();
const newPath = `libs/shared/${lib1}/data-access`; const newPath = `shared/${lib1}/data-access`;
const newName = `shared-${lib1}-data-access`; const newName = `shared-${lib1}-data-access`;
const readmePath = `${newPath}/README.md`; const readmePath = `${newPath}/README.md`;
@ -279,8 +289,8 @@ describe('Workspace Tests', () => {
checkFilesExist(jestConfigPath); checkFilesExist(jestConfigPath);
const jestConfig = readFile(jestConfigPath); const jestConfig = readFile(jestConfigPath);
expect(jestConfig).toContain(`displayName: 'shared-${lib1}-data-access'`); expect(jestConfig).toContain(`displayName: 'shared-${lib1}-data-access'`);
expect(jestConfig).toContain(`preset: '../../../../jest.preset.js'`); expect(jestConfig).toContain(`preset: '../../../jest.preset.js'`);
expect(jestConfig).toContain(`'../../../../coverage/${newPath}'`); expect(jestConfig).toContain(`'../../../coverage/${newPath}'`);
const tsConfigPath = `${newPath}/tsconfig.json`; const tsConfigPath = `${newPath}/tsconfig.json`;
expect(moveOutput).toContain(`CREATE ${tsConfigPath}`); expect(moveOutput).toContain(`CREATE ${tsConfigPath}`);
@ -291,7 +301,7 @@ describe('Workspace Tests', () => {
checkFilesExist(tsConfigLibPath); checkFilesExist(tsConfigLibPath);
const tsConfigLib = readJson(tsConfigLibPath); const tsConfigLib = readJson(tsConfigLibPath);
expect(tsConfigLib.compilerOptions.outDir).toEqual( expect(tsConfigLib.compilerOptions.outDir).toEqual(
'../../../../dist/out-tsc' '../../../dist/out-tsc'
); );
const tsConfigSpecPath = `${newPath}/tsconfig.spec.json`; const tsConfigSpecPath = `${newPath}/tsconfig.spec.json`;
@ -299,7 +309,7 @@ describe('Workspace Tests', () => {
checkFilesExist(tsConfigSpecPath); checkFilesExist(tsConfigSpecPath);
const tsConfigSpec = readJson(tsConfigSpecPath); const tsConfigSpec = readJson(tsConfigSpecPath);
expect(tsConfigSpec.compilerOptions.outDir).toEqual( expect(tsConfigSpec.compilerOptions.outDir).toEqual(
'../../../../dist/out-tsc' '../../../dist/out-tsc'
); );
const indexPath = `${newPath}/src/index.ts`; const indexPath = `${newPath}/src/index.ts`;
@ -313,13 +323,13 @@ describe('Workspace Tests', () => {
expect(moveOutput).toContain('UPDATE tsconfig.base.json'); expect(moveOutput).toContain('UPDATE tsconfig.base.json');
const rootTsConfig = readJson('tsconfig.base.json'); const rootTsConfig = readJson('tsconfig.base.json');
expect( expect(
rootTsConfig.compilerOptions.paths[`@${proj}/${lib1}/data-access`] rootTsConfig.compilerOptions.paths[`@${proj}/${lib1}-data-access`]
).toBeUndefined(); ).toBeUndefined();
expect( expect(
rootTsConfig.compilerOptions.paths[ rootTsConfig.compilerOptions.paths[
`@${proj}/shared/${lib1}/data-access` `@${proj}/shared-${lib1}-data-access`
] ]
).toEqual([`libs/shared/${lib1}/data-access/src/index.ts`]); ).toEqual([`shared/${lib1}/data-access/src/index.ts`]);
const projects = await readResolvedConfiguration(); const projects = await readResolvedConfiguration();
expect(projects[`${lib1}-data-access`]).toBeUndefined(); expect(projects[`${lib1}-data-access`]).toBeUndefined();
@ -331,21 +341,21 @@ describe('Workspace Tests', () => {
expect(lib3Config.implicitDependencies).toEqual([newName]); expect(lib3Config.implicitDependencies).toEqual([newName]);
expect(project.targets.lint.options.lintFilePatterns).toEqual([ expect(project.targets.lint.options.lintFilePatterns).toEqual([
`libs/shared/${lib1}/data-access/**/*.ts`, `shared/${lib1}/data-access/**/*.ts`,
`libs/shared/${lib1}/data-access/package.json`, `shared/${lib1}/data-access/package.json`,
]); ]);
/** /**
* Check that the import in lib2 has been updated * Check that the import in lib2 has been updated
*/ */
const lib2FilePath = `libs/${lib2}/ui/src/lib/${lib2}-ui.ts`; const lib2FilePath = `${lib2}/ui/src/lib/${lib2}-ui.ts`;
const lib2File = readFile(lib2FilePath); const lib2File = readFile(lib2FilePath);
expect(lib2File).toContain( expect(lib2File).toContain(
`import { fromLibOne } from '@${proj}/shared/${lib1}/data-access';` `import { fromLibOne } from '@${proj}/shared-${lib1}-data-access';`
); );
}); });
it('should work for custom workspace layouts', async () => { it('should work for custom workspace layouts with --project-name-and-root-format=derived', async () => {
const lib1 = uniq('mylib'); const lib1 = uniq('mylib');
const lib2 = uniq('mylib'); const lib2 = uniq('mylib');
const lib3 = uniq('mylib'); const lib3 = uniq('mylib');
@ -354,7 +364,9 @@ describe('Workspace Tests', () => {
nxJson.workspaceLayout = { libsDir: 'packages' }; nxJson.workspaceLayout = { libsDir: 'packages' };
updateFile('nx.json', JSON.stringify(nxJson)); updateFile('nx.json', JSON.stringify(nxJson));
runCLI(`generate @nx/js:lib ${lib1}/data-access --unitTestRunner=jest`); runCLI(
`generate @nx/js:lib ${lib1}/data-access --unitTestRunner=jest --project-name-and-root-format=derived`
);
updateFile( updateFile(
`packages/${lib1}/data-access/src/lib/${lib1}-data-access.ts`, `packages/${lib1}/data-access/src/lib/${lib1}-data-access.ts`,
@ -370,7 +382,9 @@ describe('Workspace Tests', () => {
* Create a library which imports a class from lib1 * Create a library which imports a class from lib1
*/ */
runCLI(`generate @nx/js:lib ${lib2}/ui --unitTestRunner=jest`); runCLI(
`generate @nx/js:lib ${lib2}/ui --unitTestRunner=jest --project-name-and-root-format=derived`
);
updateFile( updateFile(
`packages/${lib2}/ui/src/lib/${lib2}-ui.ts`, `packages/${lib2}/ui/src/lib/${lib2}-ui.ts`,
@ -383,7 +397,9 @@ describe('Workspace Tests', () => {
* Create a library which has an implicit dependency on lib1 * Create a library which has an implicit dependency on lib1
*/ */
runCLI(`generate @nx/js:lib ${lib3} --unitTestRunner=jest`); runCLI(
`generate @nx/js:lib ${lib3} --unitTestRunner=jest --project-name-and-root-format=derived`
);
await updateProjectConfig(lib3, (config) => { await updateProjectConfig(lib3, (config) => {
config.implicitDependencies = [`${lib1}-data-access`]; config.implicitDependencies = [`${lib1}-data-access`];
return config; return config;
@ -394,7 +410,7 @@ describe('Workspace Tests', () => {
*/ */
const moveOutput = runCLI( const moveOutput = runCLI(
`generate @nx/workspace:move --project ${lib1}-data-access shared/${lib1}/data-access` `generate @nx/workspace:move --project ${lib1}-data-access shared/${lib1}/data-access --project-name-and-root-format=derived`
); );
expect(moveOutput).toContain(`DELETE packages/${lib1}/data-access`); expect(moveOutput).toContain(`DELETE packages/${lib1}/data-access`);
@ -483,26 +499,27 @@ describe('Workspace Tests', () => {
const lib1 = uniq('lib1'); const lib1 = uniq('lib1');
const lib2 = uniq('lib2'); const lib2 = uniq('lib2');
const lib3 = uniq('lib3'); const lib3 = uniq('lib3');
runCLI(`generate @nx/js:lib ${lib1} --unitTestRunner=jest`); runCLI(
`generate @nx/js:lib ${lib1} --unitTestRunner=jest --project-name-and-root-format=as-provided`
);
updateFile( updateFile(
`libs/${lib1}/src/lib/${lib1}.ts`, `${lib1}/src/lib/${lib1}.ts`,
`export function fromLibOne() { console.log('This is completely pointless'); }` `export function fromLibOne() { console.log('This is completely pointless'); }`
); );
updateFile( updateFile(`${lib1}/src/index.ts`, `export * from './lib/${lib1}.ts'`);
`libs/${lib1}/src/index.ts`,
`export * from './lib/${lib1}.ts'`
);
/** /**
* Create a library which imports a class from lib1 * Create a library which imports a class from lib1
*/ */
runCLI(`generate @nx/js:lib ${lib2}/ui --unitTestRunner=jest`); runCLI(
`generate @nx/js:lib ${lib2}-ui --directory=${lib2}/ui --unitTestRunner=jest --project-name-and-root-format=as-provided`
);
updateFile( updateFile(
`libs/${lib2}/ui/src/lib/${lib2}-ui.ts`, `${lib2}/ui/src/lib/${lib2}-ui.ts`,
`import { fromLibOne } from '@${proj}/${lib1}'; `import { fromLibOne } from '@${proj}/${lib1}';
export const fromLibTwo = () => fromLibOne();` export const fromLibTwo = () => fromLibOne();`
@ -512,7 +529,9 @@ describe('Workspace Tests', () => {
* Create a library which has an implicit dependency on lib1 * Create a library which has an implicit dependency on lib1
*/ */
runCLI(`generate @nx/js:lib ${lib3} --unitTestRunner=jest`); runCLI(
`generate @nx/js:lib ${lib3} --unitTestRunner=jest --project-name-and-root-format=as-provided`
);
await updateProjectConfig(lib3, (config) => { await updateProjectConfig(lib3, (config) => {
config.implicitDependencies = [lib1]; config.implicitDependencies = [lib1];
return config; return config;
@ -523,13 +542,13 @@ describe('Workspace Tests', () => {
*/ */
const moveOutput = runCLI( const moveOutput = runCLI(
`generate @nx/workspace:move --project ${lib1} ${lib1}/data-access` `generate @nx/workspace:move --project ${lib1} ${lib1}/data-access --newProjectName=${lib1}-data-access --project-name-and-root-format=as-provided`
); );
expect(moveOutput).toContain(`DELETE libs/${lib1}/project.json`); expect(moveOutput).toContain(`DELETE ${lib1}/project.json`);
expect(exists(`libs/${lib1}/project.json`)).toBeFalsy(); expect(exists(`${lib1}/project.json`)).toBeFalsy();
const newPath = `libs/${lib1}/data-access`; const newPath = `${lib1}/data-access`;
const newName = `${lib1}-data-access`; const newName = `${lib1}-data-access`;
const readmePath = `${newPath}/README.md`; const readmePath = `${newPath}/README.md`;
@ -541,8 +560,8 @@ describe('Workspace Tests', () => {
checkFilesExist(jestConfigPath); checkFilesExist(jestConfigPath);
const jestConfig = readFile(jestConfigPath); const jestConfig = readFile(jestConfigPath);
expect(jestConfig).toContain(`displayName: '${lib1}-data-access'`); expect(jestConfig).toContain(`displayName: '${lib1}-data-access'`);
expect(jestConfig).toContain(`preset: '../../../jest.preset.js'`); expect(jestConfig).toContain(`preset: '../../jest.preset.js'`);
expect(jestConfig).toContain(`'../../../coverage/${newPath}'`); expect(jestConfig).toContain(`'../../coverage/${newPath}'`);
const tsConfigPath = `${newPath}/tsconfig.json`; const tsConfigPath = `${newPath}/tsconfig.json`;
expect(moveOutput).toContain(`CREATE ${tsConfigPath}`); expect(moveOutput).toContain(`CREATE ${tsConfigPath}`);
@ -552,17 +571,13 @@ describe('Workspace Tests', () => {
expect(moveOutput).toContain(`CREATE ${tsConfigLibPath}`); expect(moveOutput).toContain(`CREATE ${tsConfigLibPath}`);
checkFilesExist(tsConfigLibPath); checkFilesExist(tsConfigLibPath);
const tsConfigLib = readJson(tsConfigLibPath); const tsConfigLib = readJson(tsConfigLibPath);
expect(tsConfigLib.compilerOptions.outDir).toEqual( expect(tsConfigLib.compilerOptions.outDir).toEqual('../../dist/out-tsc');
'../../../dist/out-tsc'
);
const tsConfigSpecPath = `${newPath}/tsconfig.spec.json`; const tsConfigSpecPath = `${newPath}/tsconfig.spec.json`;
expect(moveOutput).toContain(`CREATE ${tsConfigSpecPath}`); expect(moveOutput).toContain(`CREATE ${tsConfigSpecPath}`);
checkFilesExist(tsConfigSpecPath); checkFilesExist(tsConfigSpecPath);
const tsConfigSpec = readJson(tsConfigSpecPath); const tsConfigSpec = readJson(tsConfigSpecPath);
expect(tsConfigSpec.compilerOptions.outDir).toEqual( expect(tsConfigSpec.compilerOptions.outDir).toEqual('../../dist/out-tsc');
'../../../dist/out-tsc'
);
const indexPath = `${newPath}/src/index.ts`; const indexPath = `${newPath}/src/index.ts`;
expect(moveOutput).toContain(`CREATE ${indexPath}`); expect(moveOutput).toContain(`CREATE ${indexPath}`);
@ -587,8 +602,8 @@ describe('Workspace Tests', () => {
rootTsConfig.compilerOptions.paths[`@${proj}/${lib1}`] rootTsConfig.compilerOptions.paths[`@${proj}/${lib1}`]
).toBeUndefined(); ).toBeUndefined();
expect( expect(
rootTsConfig.compilerOptions.paths[`@${proj}/${lib1}/data-access`] rootTsConfig.compilerOptions.paths[`@${proj}/${lib1}-data-access`]
).toEqual([`libs/${lib1}/data-access/src/index.ts`]); ).toEqual([`${lib1}/data-access/src/index.ts`]);
projects = await readResolvedConfiguration(); projects = await readResolvedConfiguration();
expect(projects[lib1]).toBeUndefined(); expect(projects[lib1]).toBeUndefined();
@ -596,17 +611,17 @@ describe('Workspace Tests', () => {
expect(project).toBeTruthy(); expect(project).toBeTruthy();
expect(project.sourceRoot).toBe(`${newPath}/src`); expect(project.sourceRoot).toBe(`${newPath}/src`);
expect(project.targets.lint.options.lintFilePatterns).toEqual([ expect(project.targets.lint.options.lintFilePatterns).toEqual([
`libs/${lib1}/data-access/**/*.ts`, `${lib1}/data-access/**/*.ts`,
`libs/${lib1}/data-access/package.json`, `${lib1}/data-access/package.json`,
]); ]);
/** /**
* Check that the import in lib2 has been updated * Check that the import in lib2 has been updated
*/ */
const lib2FilePath = `libs/${lib2}/ui/src/lib/${lib2}-ui.ts`; const lib2FilePath = `${lib2}/ui/src/lib/${lib2}-ui.ts`;
const lib2File = readFile(lib2FilePath); const lib2File = readFile(lib2FilePath);
expect(lib2File).toContain( expect(lib2File).toContain(
`import { fromLibOne } from '@${proj}/${lib1}/data-access';` `import { fromLibOne } from '@${proj}/${lib1}-data-access';`
); );
}); });
@ -618,22 +633,24 @@ describe('Workspace Tests', () => {
const lib1 = uniq('mylib'); const lib1 = uniq('mylib');
const lib2 = uniq('mylib'); const lib2 = uniq('mylib');
const lib3 = uniq('mylib'); const lib3 = uniq('mylib');
runCLI(`generate @nx/js:lib ${lib1}/data-access --unitTestRunner=jest`); runCLI(
`generate @nx/js:lib ${lib1}-data-access --directory=${lib1}/data-access --unitTestRunner=jest --project-name-and-root-format=as-provided`
);
let rootTsConfig = readJson('tsconfig.base.json'); let rootTsConfig = readJson('tsconfig.base.json');
expect( expect(
rootTsConfig.compilerOptions.paths[`@${proj}/${lib1}/data-access`] rootTsConfig.compilerOptions.paths[`@${proj}/${lib1}-data-access`]
).toBeUndefined(); ).toBeUndefined();
expect( expect(
rootTsConfig.compilerOptions.paths[`${lib1}/data-access`] rootTsConfig.compilerOptions.paths[`${lib1}-data-access`]
).toBeDefined(); ).toBeDefined();
updateFile( updateFile(
`libs/${lib1}/data-access/src/lib/${lib1}-data-access.ts`, `${lib1}/data-access/src/lib/${lib1}-data-access.ts`,
`export function fromLibOne() { console.log('This is completely pointless'); }` `export function fromLibOne() { console.log('This is completely pointless'); }`
); );
updateFile( updateFile(
`libs/${lib1}/data-access/src/index.ts`, `${lib1}/data-access/src/index.ts`,
`export * from './lib/${lib1}-data-access.ts'` `export * from './lib/${lib1}-data-access.ts'`
); );
@ -641,11 +658,13 @@ describe('Workspace Tests', () => {
* Create a library which imports a class from lib1 * Create a library which imports a class from lib1
*/ */
runCLI(`generate @nx/js:lib ${lib2}/ui --unitTestRunner=jest`); runCLI(
`generate @nx/js:lib ${lib2}-ui --directory=${lib2}/ui --unitTestRunner=jest --project-name-and-root-format=as-provided`
);
updateFile( updateFile(
`libs/${lib2}/ui/src/lib/${lib2}-ui.ts`, `${lib2}/ui/src/lib/${lib2}-ui.ts`,
`import { fromLibOne } from '${lib1}/data-access'; `import { fromLibOne } from '${lib1}-data-access';
export const fromLibTwo = () => fromLibOne();` export const fromLibTwo = () => fromLibOne();`
); );
@ -654,7 +673,9 @@ describe('Workspace Tests', () => {
* Create a library which has an implicit dependency on lib1 * Create a library which has an implicit dependency on lib1
*/ */
runCLI(`generate @nx/js:lib ${lib3} --unitTestRunner=jest`); runCLI(
`generate @nx/js:lib ${lib3} --unitTestRunner=jest --project-name-and-root-format=as-provided`
);
await updateProjectConfig(lib3, (config) => { await updateProjectConfig(lib3, (config) => {
config.implicitDependencies = [`${lib1}-data-access`]; config.implicitDependencies = [`${lib1}-data-access`];
return config; return config;
@ -665,13 +686,13 @@ describe('Workspace Tests', () => {
*/ */
const moveOutput = runCLI( const moveOutput = runCLI(
`generate @nx/workspace:move --project ${lib1}-data-access shared/${lib1}/data-access` `generate @nx/workspace:move --project ${lib1}-data-access shared/${lib1}/data-access --newProjectName=shared-${lib1}-data-access --project-name-and-root-format=as-provided`
); );
expect(moveOutput).toContain(`DELETE libs/${lib1}/data-access`); expect(moveOutput).toContain(`DELETE ${lib1}/data-access`);
expect(exists(`libs/${lib1}/data-access`)).toBeFalsy(); expect(exists(`${lib1}/data-access`)).toBeFalsy();
const newPath = `libs/shared/${lib1}/data-access`; const newPath = `shared/${lib1}/data-access`;
const newName = `shared-${lib1}-data-access`; const newName = `shared-${lib1}-data-access`;
const readmePath = `${newPath}/README.md`; const readmePath = `${newPath}/README.md`;
@ -698,11 +719,11 @@ describe('Workspace Tests', () => {
expect(moveOutput).toContain('UPDATE tsconfig.base.json'); expect(moveOutput).toContain('UPDATE tsconfig.base.json');
rootTsConfig = readJson('tsconfig.base.json'); rootTsConfig = readJson('tsconfig.base.json');
expect( expect(
rootTsConfig.compilerOptions.paths[`${lib1}/data-access`] rootTsConfig.compilerOptions.paths[`${lib1}-data-access`]
).toBeUndefined(); ).toBeUndefined();
expect( expect(
rootTsConfig.compilerOptions.paths[`shared/${lib1}/data-access`] rootTsConfig.compilerOptions.paths[`shared-${lib1}-data-access`]
).toEqual([`libs/shared/${lib1}/data-access/src/index.ts`]); ).toEqual([`shared/${lib1}/data-access/src/index.ts`]);
const projects = await readResolvedConfiguration(); const projects = await readResolvedConfiguration();
expect(projects[`${lib1}-data-access`]).toBeUndefined(); expect(projects[`${lib1}-data-access`]).toBeUndefined();
@ -710,17 +731,17 @@ describe('Workspace Tests', () => {
expect(project).toBeTruthy(); expect(project).toBeTruthy();
expect(project.sourceRoot).toBe(`${newPath}/src`); expect(project.sourceRoot).toBe(`${newPath}/src`);
expect(project.targets.lint.options.lintFilePatterns).toEqual([ expect(project.targets.lint.options.lintFilePatterns).toEqual([
`libs/shared/${lib1}/data-access/**/*.ts`, `shared/${lib1}/data-access/**/*.ts`,
`libs/shared/${lib1}/data-access/package.json`, `shared/${lib1}/data-access/package.json`,
]); ]);
/** /**
* Check that the import in lib2 has been updated * Check that the import in lib2 has been updated
*/ */
const lib2FilePath = `libs/${lib2}/ui/src/lib/${lib2}-ui.ts`; const lib2FilePath = `${lib2}/ui/src/lib/${lib2}-ui.ts`;
const lib2File = readFile(lib2FilePath); const lib2File = readFile(lib2FilePath);
expect(lib2File).toContain( expect(lib2File).toContain(
`import { fromLibOne } from 'shared/${lib1}/data-access';` `import { fromLibOne } from 'shared-${lib1}-data-access';`
); );
}); });
}); });

View File

@ -74,7 +74,8 @@
"factory": "./src/generators/move/compat", "factory": "./src/generators/move/compat",
"schema": "./src/generators/move/schema.json", "schema": "./src/generators/move/schema.json",
"aliases": ["mv"], "aliases": ["mv"],
"description": "Moves an Angular application or library to another folder within the workspace and updates the project configuration." "description": "Moves an Angular application or library to another folder within the workspace and updates the project configuration.",
"x-deprecated": "Use the `@nx/workspace:move` generator instead. This generator will be removed in Nx v18."
}, },
"convert-to-with-mf": { "convert-to-with-mf": {
"factory": "./src/generators/convert-to-with-mf/convert-to-with-mf.compat", "factory": "./src/generators/convert-to-with-mf/convert-to-with-mf.compat",
@ -230,10 +231,11 @@
"description": "Generate a Remote Angular Module Federation Application." "description": "Generate a Remote Angular Module Federation Application."
}, },
"move": { "move": {
"factory": "./src/generators/move/move#angularMoveGenerator", "factory": "./src/generators/move/move#angularMoveGeneratorInternal",
"schema": "./src/generators/move/schema.json", "schema": "./src/generators/move/schema.json",
"aliases": ["mv"], "aliases": ["mv"],
"description": "Moves an Angular application or library to another folder within the workspace and updates the project configuration." "description": "Moves an Angular application or library to another folder within the workspace and updates the project configuration.",
"x-deprecated": "Use the `@nx/workspace:move` generator instead. This generator will be removed in Nx v18."
}, },
"convert-to-with-mf": { "convert-to-with-mf": {
"factory": "./src/generators/convert-to-with-mf/convert-to-with-mf", "factory": "./src/generators/convert-to-with-mf/convert-to-with-mf",

View File

@ -26,6 +26,7 @@
"./src/utils": "./src/utils/public-api.js", "./src/utils": "./src/utils/public-api.js",
"./plugins/component-testing": "./plugins/component-testing.js", "./plugins/component-testing": "./plugins/component-testing.js",
"./src/generators/utils": "./src/generators/utils/index.js", "./src/generators/utils": "./src/generators/utils/index.js",
"./src/generators/move/move-impl": "./src/generators/move/move-impl.js",
"./src/builders/*/schema.json": "./src/builders/*/schema.json", "./src/builders/*/schema.json": "./src/builders/*/schema.json",
"./src/builders/*.impl": "./src/builders/*.impl.js", "./src/builders/*.impl": "./src/builders/*.impl.js",
"./src/executors/*/schema.json": "./src/executors/*/schema.json", "./src/executors/*/schema.json": "./src/executors/*/schema.json",

View File

@ -1,4 +1,3 @@
export * from './normalize-schema';
export * from './update-module-name'; export * from './update-module-name';
export * from './update-ng-package'; export * from './update-ng-package';
export * from './update-secondary-entry-points'; export * from './update-secondary-entry-points';

View File

@ -1,15 +0,0 @@
import type { Tree } from '@nx/devkit';
import { readProjectConfiguration } from '@nx/devkit';
import type { NormalizedSchema, Schema } from '../schema';
import { getNewProjectName } from '../../utils/get-new-project-name';
export function normalizeSchema(tree: Tree, schema: Schema): NormalizedSchema {
const newProjectName = getNewProjectName(schema.destination);
const { root } = readProjectConfiguration(tree, schema.projectName);
return {
...schema,
newProjectName,
oldProjectRoot: root,
};
}

View File

@ -0,0 +1,4 @@
export type MoveImplOptions = {
oldProjectName: string;
newProjectName: string;
};

View File

@ -1,301 +0,0 @@
import { Tree } from '@nx/devkit';
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
import { Linter } from '@nx/linter';
import { moveGenerator } from '@nx/workspace/generators';
import { UnitTestRunner } from '../../../utils/test-runners';
import { generateTestLibrary } from '../../utils/testing';
import { NormalizedSchema } from '../schema';
import { updateModuleName } from './update-module-name';
describe('updateModuleName Rule', () => {
let tree: Tree;
beforeEach(() => {
tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
});
it('should handle nesting resulting in the same project name', async () => {
const updatedModulePath = '/libs/my/first/src/lib/my-first.module.ts';
await generateTestLibrary(tree, {
name: 'my-first',
simpleName: true,
});
const schema: NormalizedSchema = {
projectName: 'my-first',
destination: 'my/first',
updateImportPath: true,
newProjectName: 'my-first',
oldProjectRoot: 'my-first',
};
await moveGenerator(tree, schema);
updateModuleName(tree, { ...schema, destination: 'my/first' });
expect(tree.exists(updatedModulePath)).toBe(true);
const moduleFile = tree.read(updatedModulePath, 'utf-8');
expect(moduleFile).toContain(`export class MyFirstModule {}`);
});
describe('move to subfolder', () => {
const updatedModulePath =
'/libs/shared/my-first/src/lib/shared-my-first.module.ts';
const updatedModuleSpecPath =
'/libs/shared/my-first/src/lib/shared-my-first.module.spec.ts';
const indexPath = '/libs/shared/my-first/src/index.ts';
const secondModulePath = 'my-second/src/lib/my-second.module.ts';
const schema: NormalizedSchema = {
projectName: 'my-first',
destination: 'shared/my-first',
updateImportPath: true,
newProjectName: 'shared-my-first',
oldProjectRoot: 'my-first',
};
beforeEach(async () => {
await generateTestLibrary(tree, {
name: 'my-first',
buildable: false,
linter: Linter.EsLint,
publishable: false,
simpleName: true,
skipFormat: false,
unitTestRunner: UnitTestRunner.Jest,
});
await generateTestLibrary(tree, {
name: 'my-second',
buildable: false,
linter: Linter.EsLint,
publishable: false,
simpleName: true,
skipFormat: false,
unitTestRunner: UnitTestRunner.Jest,
});
tree.write(
'my-first/src/lib/my-first.module.ts',
`import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
@NgModule({
imports: [CommonModule]
})
export class MyFirstModule {}`
);
tree.write(
'my-first/src/lib/my-first.module.spec.ts',
`import { async, TestBed } from '@angular/core/testing';
import { MyFirstModule } from './my-first.module';
describe('MyFirstModule', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [MyFirstModule]
}).compileComponents();
}));
it('should create', () => {
expect(MyFirstModule).toBeDefined();
});
});`
);
tree.write(
secondModulePath,
`import { MyFirstModule } from '@proj/my-first';
export class MySecondModule extends MyFirstModule {}
`
);
await moveGenerator(tree, schema);
});
it('should rename the module files and update the module name', async () => {
updateModuleName(tree, schema);
expect(tree.exists(updatedModulePath)).toBe(true);
expect(tree.exists(updatedModuleSpecPath)).toBe(true);
const moduleFile = tree.read(updatedModulePath, 'utf-8');
expect(moduleFile).toContain(`export class SharedMyFirstModule {}`);
const moduleSpecFile = tree.read(updatedModuleSpecPath, 'utf-8');
expect(moduleSpecFile).toContain(
`import { SharedMyFirstModule } from './shared-my-first.module';`
);
expect(moduleSpecFile).toContain(
`describe('SharedMyFirstModule', () => {`
);
expect(moduleSpecFile).toContain(`imports: [SharedMyFirstModule]`);
expect(moduleSpecFile).toContain(
`expect(SharedMyFirstModule).toBeDefined();`
);
});
it('should update any references to the module', async () => {
updateModuleName(tree, schema);
const importerFile = tree.read(secondModulePath, 'utf-8');
expect(importerFile).toContain(
`import { SharedMyFirstModule } from '@proj/shared/my-first';`
);
expect(importerFile).toContain(
`export class MySecondModule extends SharedMyFirstModule {}`
);
});
it('should update the index.ts file which exports the module', async () => {
updateModuleName(tree, schema);
const indexFile = tree.read(indexPath, 'utf-8');
expect(indexFile).toContain(
`export * from './lib/shared-my-first.module';`
);
});
});
describe('rename', () => {
const schema: NormalizedSchema = {
projectName: 'my-source',
destination: 'my-destination',
updateImportPath: true,
newProjectName: 'my-destination',
oldProjectRoot: 'my-source',
};
const modulePath = 'my-destination/src/lib/my-destination.module.ts';
const moduleSpecPath =
'my-destination/src/lib/my-destination.module.spec.ts';
const indexPath = 'my-destination/src/index.ts';
const importerPath = 'my-importer/src/lib/my-importing-file.ts';
beforeEach(async () => {
// fake a mid-move tree:
await generateTestLibrary(tree, {
name: 'my-destination',
buildable: false,
linter: Linter.EsLint,
publishable: false,
simpleName: true,
skipFormat: false,
unitTestRunner: UnitTestRunner.Jest,
});
tree.write(
'my-destination/src/lib/my-source.module.ts',
`import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
@NgModule({
imports: [CommonModule]
})
export class MySourceModule {}`
);
tree.write(
'my-destination/src/lib/my-source.module.spec.ts',
`import { async, TestBed } from '@angular/core/testing';
import { MySourceModule } from './my-source.module';
describe('MySourceModule', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [MySourceModule]
}).compileComponents();
}));
it('should create', () => {
expect(MySourceModule).toBeDefined();
});
});`
);
tree.write(
indexPath,
`export * from './lib/my-source.module';
`
);
tree.delete(modulePath);
tree.delete(moduleSpecPath);
await generateTestLibrary(tree, {
name: 'my-importer',
buildable: false,
linter: Linter.EsLint,
publishable: false,
simpleName: true,
skipFormat: false,
unitTestRunner: UnitTestRunner.Jest,
});
tree.write(
importerPath,
`import { MySourceModule } from '@proj/my-destination';
export class MyExtendedSourceModule extends MySourceModule {}
`
);
});
it('should rename the module files and update the module name', async () => {
updateModuleName(tree, schema);
expect(tree.exists(modulePath)).toBe(true);
expect(tree.exists(moduleSpecPath)).toBe(true);
const moduleFile = tree.read(modulePath, 'utf-8');
expect(moduleFile).toContain(`export class MyDestinationModule {}`);
const moduleSpecFile = tree.read(moduleSpecPath, 'utf-8');
expect(moduleSpecFile).toContain(
`import { MyDestinationModule } from './my-destination.module';`
);
expect(moduleSpecFile).toContain(
`describe('MyDestinationModule', () => {`
);
expect(moduleSpecFile).toContain(`imports: [MyDestinationModule]`);
expect(moduleSpecFile).toContain(
`expect(MyDestinationModule).toBeDefined();`
);
});
it('should update any references to the module', async () => {
updateModuleName(tree, schema);
const importerFile = tree.read(importerPath, 'utf-8');
expect(importerFile).toContain(
`import { MyDestinationModule } from '@proj/my-destination';`
);
expect(importerFile).toContain(
`export class MyExtendedSourceModule extends MyDestinationModule {}`
);
});
it('should update the index.ts file which exports the module', async () => {
updateModuleName(tree, schema);
const indexFile = tree.read(indexPath, 'utf-8');
expect(indexFile).toContain(
`export * from './lib/my-destination.module';`
);
});
it('should not rename unrelated symbols with similar name in different projects', async () => {
// create different project whose main module name starts with the same
// name of the project we're moving
await generateTestLibrary(tree, {
name: 'my-source-demo',
buildable: false,
linter: Linter.EsLint,
publishable: false,
simpleName: true,
skipFormat: false,
unitTestRunner: UnitTestRunner.Jest,
});
updateModuleName(tree, schema);
const moduleFile = tree.read(
'my-source-demo/src/lib/my-source-demo.module.ts',
'utf-8'
);
expect(moduleFile).toContain(`export class MySourceDemoModule {}`);
});
});
});

View File

@ -7,7 +7,7 @@ import {
Tree, Tree,
visitNotIgnoredFiles, visitNotIgnoredFiles,
} from '@nx/devkit'; } from '@nx/devkit';
import type { NormalizedSchema } from '../schema'; import type { MoveImplOptions } from './types';
/** /**
* Updates the Angular module name (including the spec file and index.ts) * Updates the Angular module name (including the spec file and index.ts)
@ -19,8 +19,16 @@ import type { NormalizedSchema } from '../schema';
*/ */
export function updateModuleName( export function updateModuleName(
tree: Tree, tree: Tree,
{ projectName: oldProjectName, newProjectName }: NormalizedSchema { oldProjectName, newProjectName }: MoveImplOptions
): void { ): void {
const unscopedNewProjectName = newProjectName.startsWith('@')
? newProjectName.split('/')[1]
: newProjectName;
if (oldProjectName === unscopedNewProjectName) {
return;
}
const project = readProjectConfiguration(tree, newProjectName); const project = readProjectConfiguration(tree, newProjectName);
if (project.projectType === 'application') { if (project.projectType === 'application') {
@ -31,14 +39,14 @@ export function updateModuleName(
const moduleName = { const moduleName = {
from: `${names(oldProjectName).className}Module`, from: `${names(oldProjectName).className}Module`,
to: `${names(newProjectName).className}Module`, to: `${names(unscopedNewProjectName).className}Module`,
}; };
const findModuleName = new RegExp(`\\b${moduleName.from}`, 'g'); const findModuleName = new RegExp(`\\b${moduleName.from}`, 'g');
const moduleFile = { const moduleFile = {
from: `${oldProjectName}.module`, from: `${oldProjectName}.module`,
to: `${newProjectName}.module`, to: `${unscopedNewProjectName}.module`,
}; };
const findFileName = new RegExp(`\\b${moduleFile.from}`, 'g'); const findFileName = new RegExp(`\\b${moduleFile.from}`, 'g');

View File

@ -7,9 +7,9 @@ import {
workspaceRoot, workspaceRoot,
} from '@nx/devkit'; } from '@nx/devkit';
import { join, relative } from 'path'; import { join, relative } from 'path';
import type { NormalizedSchema } from '../schema'; import type { MoveImplOptions } from './types';
export function updateNgPackage(tree: Tree, schema: NormalizedSchema): void { export function updateNgPackage(tree: Tree, schema: MoveImplOptions): void {
const project = readProjectConfiguration(tree, schema.newProjectName); const project = readProjectConfiguration(tree, schema.newProjectName);
if (project.projectType === 'application') { if (project.projectType === 'application') {

View File

@ -6,7 +6,7 @@ import {
visitNotIgnoredFiles, visitNotIgnoredFiles,
} from '@nx/devkit'; } from '@nx/devkit';
import { basename, dirname } from 'path'; import { basename, dirname } from 'path';
import type { NormalizedSchema } from '../schema'; import type { MoveImplOptions } from './types';
const libraryExecutors = [ const libraryExecutors = [
'@angular-devkit/build-angular:ng-packagr', '@angular-devkit/build-angular:ng-packagr',
@ -19,8 +19,12 @@ const libraryExecutors = [
export function updateSecondaryEntryPoints( export function updateSecondaryEntryPoints(
tree: Tree, tree: Tree,
schema: NormalizedSchema schema: MoveImplOptions
): void { ): void {
if (schema.oldProjectName === schema.newProjectName) {
return;
}
const project = readProjectConfiguration(tree, schema.newProjectName); const project = readProjectConfiguration(tree, schema.newProjectName);
if (project.projectType !== 'library') { if (project.projectType !== 'library') {
@ -47,7 +51,7 @@ export function updateSecondaryEntryPoints(
updateReadme( updateReadme(
tree, tree,
dirname(filePath), dirname(filePath),
schema.projectName, schema.oldProjectName,
schema.newProjectName schema.newProjectName
); );
}); });

View File

@ -0,0 +1,35 @@
import { createProjectGraphAsync, type Tree } from '@nx/devkit';
import {
updateModuleName,
updateNgPackage,
updateSecondaryEntryPoints,
} from './lib';
import type { MoveImplOptions } from './lib/types';
/**
* Angular-specific logic to move a project to another directory.
* This is invoked by the `@nx/workspace:move` generator.
*/
export async function move(
tree: Tree,
options: MoveImplOptions
): Promise<void> {
// while the project has already being moved at this point, the changes are
// still in the virtual tree and haven't been committed, so the project graph
// still contains the old project name
if (!(await isAngularProject(options.oldProjectName))) {
return;
}
updateModuleName(tree, options);
updateNgPackage(tree, options);
updateSecondaryEntryPoints(tree, options);
}
async function isAngularProject(project: string): Promise<boolean> {
const projectGraph = await createProjectGraphAsync();
return projectGraph.dependencies[project]?.some(
(dependency) => dependency.target === 'npm:@angular/core'
);
}

View File

@ -1,20 +1,38 @@
import * as devkit from '@nx/devkit'; import * as devkit from '@nx/devkit';
import { readJson, Tree } from '@nx/devkit'; import { ProjectGraph, readJson, Tree } from '@nx/devkit';
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing'; import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
import { Linter } from '@nx/linter'; import { Linter } from '@nx/linter';
import { UnitTestRunner } from '../../utils/test-runners'; import { UnitTestRunner } from '../../utils/test-runners';
import librarySecondaryEntryPointGenerator from '../library-secondary-entry-point/library-secondary-entry-point'; import { librarySecondaryEntryPointGenerator } from '../library-secondary-entry-point/library-secondary-entry-point';
import { generateTestLibrary } from '../utils/testing'; import { generateTestLibrary } from '../utils/testing';
import { angularMoveGenerator } from './move'; import { angularMoveGenerator } from './move';
describe('@nx/angular:move', () => { describe('@nx/angular:move', () => {
let tree: Tree; let tree: Tree;
let projectGraph: ProjectGraph;
function addProjectToGraph(project: string): void {
projectGraph = {
dependencies: {
[project]: [
{ source: project, target: 'npm:@angular/core', type: 'static' },
],
},
nodes: {
[project]: {
name: project,
type: 'lib',
data: { root: project, targets: {} },
},
},
};
}
beforeEach(async () => { beforeEach(async () => {
tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' }); tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
await generateTestLibrary(tree, { await generateTestLibrary(tree, {
name: 'mylib', name: 'my-lib',
buildable: false, buildable: false,
linter: Linter.EsLint, linter: Linter.EsLint,
publishable: false, publishable: false,
@ -23,32 +41,38 @@ describe('@nx/angular:move', () => {
unitTestRunner: UnitTestRunner.Jest, unitTestRunner: UnitTestRunner.Jest,
}); });
jest.clearAllMocks(); jest
.spyOn(devkit, 'createProjectGraphAsync')
.mockImplementation(() => Promise.resolve(projectGraph));
}); });
it('should move a project', async () => { it('should move a project', async () => {
addProjectToGraph('my-lib');
await angularMoveGenerator(tree, { await angularMoveGenerator(tree, {
projectName: 'mylib', projectName: 'my-lib',
newProjectName: 'mynewlib',
destination: 'mynewlib', destination: 'mynewlib',
updateImportPath: true, updateImportPath: true,
projectNameAndRootFormat: 'as-provided',
}); });
expect(tree.exists('libs/mynewlib/src/lib/mynewlib.module.ts')).toEqual( expect(tree.exists('mynewlib/src/lib/mynewlib.module.ts')).toEqual(true);
true
);
}); });
it('should update ng-package.json dest property', async () => { it('should update ng-package.json dest property', async () => {
await generateTestLibrary(tree, { name: 'mylib2', buildable: true }); await generateTestLibrary(tree, { name: 'mylib2', buildable: true });
addProjectToGraph('mylib2');
await angularMoveGenerator(tree, { await angularMoveGenerator(tree, {
projectName: 'mylib2', projectName: 'mylib2',
destination: 'mynewlib2', destination: 'mynewlib2',
updateImportPath: true, updateImportPath: true,
projectNameAndRootFormat: 'as-provided',
}); });
const ngPackageJson = readJson(tree, 'libs/mynewlib2/ng-package.json'); const ngPackageJson = readJson(tree, 'mynewlib2/ng-package.json');
expect(ngPackageJson.dest).toEqual('../../dist/libs/mynewlib2'); expect(ngPackageJson.dest).toEqual('../dist/mynewlib2');
}); });
it('should update secondary entry points readme file', async () => { it('should update secondary entry points readme file', async () => {
@ -57,14 +81,17 @@ describe('@nx/angular:move', () => {
library: 'mylib2', library: 'mylib2',
name: 'testing', name: 'testing',
}); });
addProjectToGraph('mylib2');
await angularMoveGenerator(tree, { await angularMoveGenerator(tree, {
projectName: 'mylib2', projectName: 'mylib2',
newProjectName: 'mynewlib2',
destination: 'mynewlib2', destination: 'mynewlib2',
updateImportPath: true, updateImportPath: true,
projectNameAndRootFormat: 'as-provided',
}); });
const readme = tree.read('libs/mynewlib2/testing/README.md', 'utf-8'); const readme = tree.read('mynewlib2/testing/README.md', 'utf-8');
expect(readme).toMatchInlineSnapshot(` expect(readme).toMatchInlineSnapshot(`
"# @proj/mynewlib2/testing "# @proj/mynewlib2/testing
@ -73,28 +100,275 @@ describe('@nx/angular:move', () => {
`); `);
}); });
it('should format files', async () => { it('should handle nesting resulting in the same project name', async () => {
jest.spyOn(devkit, 'formatFiles'); addProjectToGraph('my-lib');
await angularMoveGenerator(tree, { await angularMoveGenerator(tree, {
projectName: 'mylib', projectName: 'my-lib',
destination: 'mynewlib', destination: 'my/lib',
updateImportPath: true, updateImportPath: true,
projectNameAndRootFormat: 'as-provided',
}); });
expect(devkit.formatFiles).toHaveBeenCalled(); expect(tree.exists('my/lib/src/lib/my-lib.module.ts')).toBe(true);
const moduleFile = tree.read('my/lib/src/lib/my-lib.module.ts', 'utf-8');
expect(moduleFile).toContain(`export class MyLibModule {}`);
}); });
it('should not format files when --skipFormat=true', async () => { describe('move to subfolder', () => {
jest.spyOn(devkit, 'formatFiles'); beforeEach(async () => {
await generateTestLibrary(tree, {
name: 'my-lib2',
buildable: false,
linter: Linter.EsLint,
publishable: false,
simpleName: true,
skipFormat: false,
unitTestRunner: UnitTestRunner.Jest,
});
tree.write(
'my-lib/src/lib/my-lib.module.ts',
`import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
await angularMoveGenerator(tree, { @NgModule({
projectName: 'mylib', imports: [CommonModule]
destination: 'mynewlib', })
updateImportPath: true, export class MyLibModule {}`
skipFormat: true, );
tree.write(
'my-lib/src/lib/my-lib.module.spec.ts',
`import { async, TestBed } from '@angular/core/testing';
import { MyLibModule } from './my-lib.module';
describe('MyLibModule', () => {
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [MyLibModule]
}).compileComponents();
}));
it('should create', () => {
expect(MyLibModule).toBeDefined();
});
});`
);
tree.write(
'my-lib2/src/lib/my-lib2.module.ts',
`import { MyLibModule } from '@proj/my-lib';
export class MyLib2Module extends MyLibModule {}
`
);
}); });
expect(devkit.formatFiles).not.toHaveBeenCalled(); it('should rename the module files and update the module name', async () => {
addProjectToGraph('my-lib');
await angularMoveGenerator(tree, {
projectName: 'my-lib',
newProjectName: 'shared-my-lib',
destination: 'shared/my-lib',
updateImportPath: true,
projectNameAndRootFormat: 'as-provided',
});
expect(tree.exists('shared/my-lib/src/lib/shared-my-lib.module.ts')).toBe(
true
);
expect(
tree.exists('shared/my-lib/src/lib/shared-my-lib.module.spec.ts')
).toBe(true);
const moduleFile = tree.read(
'shared/my-lib/src/lib/shared-my-lib.module.ts',
'utf-8'
);
expect(moduleFile).toContain(`export class SharedMyLibModule {}`);
const moduleSpecFile = tree.read(
'shared/my-lib/src/lib/shared-my-lib.module.spec.ts',
'utf-8'
);
expect(moduleSpecFile).toContain(
`import { SharedMyLibModule } from './shared-my-lib.module';`
);
expect(moduleSpecFile).toContain(`describe('SharedMyLibModule', () => {`);
expect(moduleSpecFile).toContain(`imports: [SharedMyLibModule]`);
expect(moduleSpecFile).toContain(
`expect(SharedMyLibModule).toBeDefined();`
);
});
it('should update any references to the module', async () => {
addProjectToGraph('my-lib');
await angularMoveGenerator(tree, {
projectName: 'my-lib',
newProjectName: 'shared-my-lib',
destination: 'shared/my-lib',
updateImportPath: true,
projectNameAndRootFormat: 'as-provided',
});
const importerFile = tree.read(
'my-lib2/src/lib/my-lib2.module.ts',
'utf-8'
);
expect(importerFile).toContain(
`import { SharedMyLibModule } from '@proj/shared-my-lib';`
);
expect(importerFile).toContain(
`export class MyLib2Module extends SharedMyLibModule {}`
);
});
it('should update the index.ts file which exports the module', async () => {
addProjectToGraph('my-lib');
await angularMoveGenerator(tree, {
projectName: 'my-lib',
newProjectName: 'shared-my-lib',
destination: 'shared/my-lib',
updateImportPath: true,
projectNameAndRootFormat: 'as-provided',
});
const indexFile = tree.read('shared/my-lib/src/index.ts', 'utf-8');
expect(indexFile).toContain(
`export * from './lib/shared-my-lib.module';`
);
});
});
describe('rename', () => {
beforeEach(async () => {
await generateTestLibrary(tree, {
name: 'my-importer',
buildable: false,
linter: Linter.EsLint,
publishable: false,
simpleName: true,
skipFormat: false,
unitTestRunner: UnitTestRunner.Jest,
});
tree.write(
'my-importer/src/lib/my-importing-file.ts',
`import { MyLibModule } from '@proj/my-lib';
export class MyExtendedLibModule extends MyLibModule {}
`
);
});
it('should rename the module file and update the module name', async () => {
addProjectToGraph('my-lib');
await angularMoveGenerator(tree, {
projectName: 'my-lib',
newProjectName: 'my-destination',
destination: 'my-destination',
updateImportPath: true,
projectNameAndRootFormat: 'as-provided',
});
expect(
tree.exists('my-destination/src/lib/my-destination.module.ts')
).toBe(true);
const moduleFile = tree.read(
'my-destination/src/lib/my-destination.module.ts',
'utf-8'
);
expect(moduleFile).toContain(`export class MyDestinationModule {}`);
});
it('should update any references to the module', async () => {
addProjectToGraph('my-lib');
await angularMoveGenerator(tree, {
projectName: 'my-lib',
newProjectName: 'my-destination',
destination: 'my-destination',
updateImportPath: true,
projectNameAndRootFormat: 'as-provided',
});
const importerFile = tree.read(
'my-importer/src/lib/my-importing-file.ts',
'utf-8'
);
expect(importerFile).toContain(
`import { MyDestinationModule } from '@proj/my-destination';`
);
expect(importerFile).toContain(
`export class MyExtendedLibModule extends MyDestinationModule {}`
);
});
it('should update the index.ts file which exports the module', async () => {
addProjectToGraph('my-lib');
await angularMoveGenerator(tree, {
projectName: 'my-lib',
newProjectName: 'my-destination',
destination: 'my-destination',
updateImportPath: true,
projectNameAndRootFormat: 'as-provided',
});
const indexFile = tree.read('my-destination/src/index.ts', 'utf-8');
expect(indexFile).toContain(
`export * from './lib/my-destination.module';`
);
});
it('should not rename unrelated symbols with similar name in different projects', async () => {
// create different project whose main module name starts with the same
// name of the project we're moving
await generateTestLibrary(tree, {
name: 'my-lib-demo',
buildable: false,
linter: Linter.EsLint,
publishable: false,
simpleName: true,
skipFormat: false,
unitTestRunner: UnitTestRunner.Jest,
});
addProjectToGraph('my-lib');
await angularMoveGenerator(tree, {
projectName: 'my-lib',
newProjectName: 'my-destination',
destination: 'my-destination',
updateImportPath: true,
projectNameAndRootFormat: 'as-provided',
});
const moduleFile = tree.read(
'my-lib-demo/src/lib/my-lib-demo.module.ts',
'utf-8'
);
expect(moduleFile).toContain(`export class MyLibDemoModule {}`);
});
});
it('should move project correctly when --project-name-and-root-format=derived', async () => {
await generateTestLibrary(tree, { name: 'mylib2', buildable: true });
addProjectToGraph('mylib2');
await angularMoveGenerator(tree, {
projectName: 'mylib2',
destination: 'mynewlib',
updateImportPath: true,
projectNameAndRootFormat: 'derived',
});
expect(tree.exists('libs/mynewlib/src/lib/mynewlib.module.ts')).toEqual(
true
);
const ngPackageJson = readJson(tree, 'libs/mynewlib/ng-package.json');
expect(ngPackageJson.dest).toEqual('../../dist/libs/mynewlib');
}); });
}); });

View File

@ -1,32 +1,21 @@
import { formatFiles, Tree } from '@nx/devkit'; import type { Tree } from '@nx/devkit';
import { moveGenerator } from '@nx/workspace/generators'; import { moveGeneratorInternal } from '@nx/workspace/src/generators/move/move';
import {
normalizeSchema,
updateModuleName,
updateNgPackage,
updateSecondaryEntryPoints,
} from './lib';
import type { Schema } from './schema'; import type { Schema } from './schema';
/**
* Moves an Angular lib/app to another folder (and renames it in the process)
*
* @remarks It's important to note that `updateModuleName` is done after the update
* to the workspace, so it can't use the same tricks as the `@nx/workspace` rules
* to get the before and after names and paths.
*/
export async function angularMoveGenerator( export async function angularMoveGenerator(
tree: Tree, tree: Tree,
schema: Schema schema: Schema
): Promise<void> { ): Promise<void> {
const normalizedSchema = normalizeSchema(tree, schema); await angularMoveGeneratorInternal(tree, {
projectNameAndRootFormat: 'derived',
await moveGenerator(tree, { ...schema, skipFormat: true }); ...schema,
updateModuleName(tree, normalizedSchema); });
updateNgPackage(tree, normalizedSchema); }
updateSecondaryEntryPoints(tree, normalizedSchema);
export async function angularMoveGeneratorInternal(
if (!normalizedSchema.skipFormat) { tree: Tree,
await formatFiles(tree); schema: Schema
} ): Promise<void> {
process.env.NX_ANGULAR_MOVE_INVOKED = 'true';
await moveGeneratorInternal(tree, schema);
} }

View File

@ -1,12 +1,11 @@
import { ProjectNameAndRootFormat } from '@nx/devkit/src/generators/project-name-and-root-utils';
export interface Schema { export interface Schema {
projectName: string; projectName: string;
destination: string; destination: string;
updateImportPath: boolean; updateImportPath: boolean;
importPath?: string; importPath?: string;
skipFormat?: boolean; skipFormat?: boolean;
} newProjectName?: string;
projectNameAndRootFormat?: ProjectNameAndRootFormat;
export interface NormalizedSchema extends Schema {
oldProjectRoot: string;
newProjectName: string;
} }

View File

@ -19,6 +19,13 @@
"x-dropdown": "projects", "x-dropdown": "projects",
"x-priority": "important" "x-priority": "important"
}, },
"newProjectName": {
"type": "string",
"alias": "project",
"description": "The new name of the project after the move.",
"pattern": "(?:^@[a-zA-Z0-9-*~][a-zA-Z0-9-*._~]*\\/[a-zA-Z0-9-~][a-zA-Z0-9-._~]*|^[a-zA-Z][^:]*)$",
"x-priority": "important"
},
"destination": { "destination": {
"type": "string", "type": "string",
"description": "The folder to move the Angular project into.", "description": "The folder to move the Angular project into.",
@ -28,6 +35,11 @@
}, },
"x-priority": "important" "x-priority": "important"
}, },
"projectNameAndRootFormat": {
"description": "Whether to generate the new project name and destination as provided (`as-provided`) or generate them composing their values and taking the configured layout into account (`derived`).",
"type": "string",
"enum": ["as-provided", "derived"]
},
"importPath": { "importPath": {
"type": "string", "type": "string",
"description": "The new import path to use in the `tsconfig.base.json`." "description": "The new import path to use in the `tsconfig.base.json`."

View File

@ -47,7 +47,7 @@
"hidden": true "hidden": true
}, },
"move": { "move": {
"factory": "./src/generators/move/move#moveGenerator", "factory": "./src/generators/move/move#moveGeneratorInternal",
"schema": "./src/generators/move/schema.json", "schema": "./src/generators/move/schema.json",
"aliases": ["mv"], "aliases": ["mv"],
"description": "Move an application or library to another folder." "description": "Move an application or library to another folder."

View File

@ -61,12 +61,13 @@
} }
}, },
"dependencies": { "dependencies": {
"@nx/devkit": "file:../devkit",
"chalk": "^4.1.0", "chalk": "^4.1.0",
"enquirer": "~2.3.6",
"ignore": "^5.0.4", "ignore": "^5.0.4",
"rxjs": "^7.8.0", "rxjs": "^7.8.0",
"tslib": "^2.3.0", "tslib": "^2.3.0",
"yargs-parser": "21.1.1", "yargs-parser": "21.1.1"
"@nx/devkit": "file:../devkit"
}, },
"publishConfig": { "publishConfig": {
"access": "public" "access": "public"

View File

@ -45,8 +45,8 @@ export async function monorepoGenerator(tree: Tree, options: {}) {
libsDir, libsDir,
project.root === '.' ? project.name : project.root project.root === '.' ? project.name : project.root
), ),
destinationRelativeToRoot: true,
updateImportPath: project.projectType === 'library', updateImportPath: project.projectType === 'library',
projectNameAndRootFormat: 'as-provided',
}); });
} }
} }

View File

@ -4,7 +4,7 @@ import {
Tree, Tree,
} from '@nx/devkit'; } from '@nx/devkit';
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing'; import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
import { Schema } from '../schema'; import { NormalizedSchema } from '../schema';
import { checkDestination } from './check-destination'; import { checkDestination } from './check-destination';
// nx-ignore-next-line // nx-ignore-next-line
@ -16,20 +16,24 @@ describe('checkDestination', () => {
beforeEach(async () => { beforeEach(async () => {
tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' }); tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
await libraryGenerator(tree, { name: 'my-lib' }); await libraryGenerator(tree, {
name: 'my-lib',
projectNameAndRootFormat: 'as-provided',
});
projectConfig = readProjectConfiguration(tree, 'my-lib'); projectConfig = readProjectConfiguration(tree, 'my-lib');
}); });
it('should throw an error if the path is not explicit', async () => { it('should throw an error if the path is not explicit', async () => {
const schema: Schema = { const schema: NormalizedSchema = {
projectName: 'my-lib', projectName: 'my-lib',
destination: '../apps/not-an-app', destination: '../apps/not-an-app',
importPath: undefined, importPath: undefined,
updateImportPath: true, updateImportPath: true,
relativeToRootDestination: '',
}; };
expect(() => { expect(() => {
checkDestination(tree, schema, projectConfig); checkDestination(tree, schema, schema.destination);
}).toThrow( }).toThrow(
`Invalid destination: [${schema.destination}] - Please specify explicit path.` `Invalid destination: [${schema.destination}] - Please specify explicit path.`
); );
@ -38,32 +42,35 @@ describe('checkDestination', () => {
it('should throw an error if the path already exists', async () => { it('should throw an error if the path already exists', async () => {
await libraryGenerator(tree, { await libraryGenerator(tree, {
name: 'my-other-lib', name: 'my-other-lib',
projectNameAndRootFormat: 'as-provided',
}); });
const schema: Schema = { const schema: NormalizedSchema = {
projectName: 'my-lib', projectName: 'my-lib',
destination: 'my-other-lib', destination: 'my-other-lib',
importPath: undefined, importPath: undefined,
updateImportPath: true, updateImportPath: true,
relativeToRootDestination: 'my-other-lib',
}; };
expect(() => { expect(() => {
checkDestination(tree, schema, projectConfig); checkDestination(tree, schema, schema.destination);
}).toThrow( }).toThrow(
`Invalid destination: [${schema.destination}] - Path is not empty.` `Invalid destination: [${schema.destination}] - Path is not empty.`
); );
}); });
it('should NOT throw an error if the path is available', async () => { it('should NOT throw an error if the path is available', async () => {
const schema: Schema = { const schema: NormalizedSchema = {
projectName: 'my-lib', projectName: 'my-lib',
destination: 'my-other-lib', destination: 'my-other-lib',
importPath: undefined, importPath: undefined,
updateImportPath: true, updateImportPath: true,
relativeToRootDestination: 'my-other-lib',
}; };
expect(() => { expect(() => {
checkDestination(tree, schema, projectConfig); checkDestination(tree, schema, schema.destination);
}).not.toThrow(); }).not.toThrow();
}); });
}); });

View File

@ -1,6 +1,5 @@
import { ProjectConfiguration, Tree } from '@nx/devkit'; import type { Tree } from '@nx/devkit';
import { Schema } from '../schema'; import type { NormalizedSchema } from '../schema';
import { getDestination } from './utils';
/** /**
* Checks whether the destination folder is valid * Checks whether the destination folder is valid
@ -12,18 +11,16 @@ import { getDestination } from './utils';
*/ */
export function checkDestination( export function checkDestination(
tree: Tree, tree: Tree,
schema: Schema, schema: NormalizedSchema,
projectConfig: ProjectConfiguration providedDestination: string
) { ) {
const INVALID_DESTINATION = `Invalid destination: [${schema.destination}]`; const INVALID_DESTINATION = `Invalid destination: [${providedDestination}]`;
if (schema.destination.includes('..')) { if (providedDestination.includes('..')) {
throw new Error(`${INVALID_DESTINATION} - Please specify explicit path.`); throw new Error(`${INVALID_DESTINATION} - Please specify explicit path.`);
} }
const destination = getDestination(tree, schema, projectConfig); if (tree.children(schema.relativeToRootDestination).length > 0) {
if (tree.children(destination).length > 0) {
throw new Error(`${INVALID_DESTINATION} - Path is not empty.`); throw new Error(`${INVALID_DESTINATION} - Path is not empty.`);
} }
} }

View File

@ -16,7 +16,10 @@ describe('moveProject', () => {
beforeEach(async () => { beforeEach(async () => {
tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' }); tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
await libraryGenerator(tree, { name: 'my-lib' }); await libraryGenerator(tree, {
name: 'my-lib',
projectNameAndRootFormat: 'as-provided',
});
projectConfig = readProjectConfiguration(tree, 'my-lib'); projectConfig = readProjectConfiguration(tree, 'my-lib');
}); });
@ -27,14 +30,13 @@ describe('moveProject', () => {
importPath: '@proj/my-destination', importPath: '@proj/my-destination',
updateImportPath: true, updateImportPath: true,
newProjectName: 'my-destination', newProjectName: 'my-destination',
relativeToRootDestination: 'libs/my-destination', relativeToRootDestination: 'my-destination',
}; };
moveProjectFiles(tree, schema, projectConfig); moveProjectFiles(tree, schema, projectConfig);
const destinationChildren = tree.children('libs/my-destination'); const destinationChildren = tree.children('my-destination');
expect(destinationChildren.length).toBeGreaterThan(0); expect(destinationChildren.length).toBeGreaterThan(0);
expect(tree.exists('libs/my-lib')).toBeFalsy(); expect(tree.exists('my-lib')).toBeFalsy();
expect(tree.children('libs')).not.toContain('my-lib');
}); });
}); });

View File

@ -31,7 +31,7 @@ describe('normalizeSchema', () => {
projectConfiguration = readProjectConfiguration(tree, schema.projectName); projectConfiguration = readProjectConfiguration(tree, schema.projectName);
}); });
it('should calculate importPath, projectName and relativeToRootDestination correctly', () => { it('should calculate importPath, projectName and relativeToRootDestination correctly', async () => {
const expected: NormalizedSchema = { const expected: NormalizedSchema = {
destination: 'my/library', destination: 'my/library',
importPath: '@proj/my/library', importPath: '@proj/my/library',
@ -41,12 +41,12 @@ describe('normalizeSchema', () => {
updateImportPath: true, updateImportPath: true,
}; };
const result = normalizeSchema(tree, schema, projectConfiguration); const result = await normalizeSchema(tree, schema, projectConfiguration);
expect(result).toEqual(expected); expect(result).toEqual(expected);
}); });
it('should normalize destination and derive projectName correctly', () => { it('should normalize destination and derive projectName correctly', async () => {
const expected: NormalizedSchema = { const expected: NormalizedSchema = {
destination: 'my/library', destination: 'my/library',
importPath: '@proj/my/library', importPath: '@proj/my/library',
@ -56,7 +56,7 @@ describe('normalizeSchema', () => {
updateImportPath: true, updateImportPath: true,
}; };
const result = normalizeSchema( const result = await normalizeSchema(
tree, tree,
{ ...schema, destination: './my/library' }, { ...schema, destination: './my/library' },
projectConfiguration projectConfiguration
@ -65,7 +65,7 @@ describe('normalizeSchema', () => {
expect(result).toEqual(expected); expect(result).toEqual(expected);
}); });
it('should use provided import path', () => { it('should use provided import path', async () => {
const expected: NormalizedSchema = { const expected: NormalizedSchema = {
destination: 'my/library', destination: 'my/library',
importPath: '@proj/my-awesome-library', importPath: '@proj/my-awesome-library',
@ -75,7 +75,7 @@ describe('normalizeSchema', () => {
updateImportPath: true, updateImportPath: true,
}; };
const result = normalizeSchema( const result = await normalizeSchema(
tree, tree,
{ ...schema, importPath: expected.importPath }, { ...schema, importPath: expected.importPath },
projectConfiguration projectConfiguration
@ -90,7 +90,7 @@ describe('normalizeSchema', () => {
return json; return json;
}); });
const result = normalizeSchema(tree, schema, projectConfiguration); const result = await normalizeSchema(tree, schema, projectConfiguration);
expect(result.relativeToRootDestination).toEqual('packages/my/library'); expect(result.relativeToRootDestination).toEqual('packages/my/library');
}); });

View File

@ -1,32 +1,267 @@
import { ProjectConfiguration, Tree } from '@nx/devkit'; import {
ProjectConfiguration,
Tree,
logger,
names,
readNxJson,
stripIndents,
updateNxJson,
} from '@nx/devkit';
import { ProjectNameAndRootFormat } from '@nx/devkit/src/generators/project-name-and-root-utils';
import { prompt } from 'enquirer';
import { getImportPath, getNpmScope } from '../../../utilities/get-import-path';
import type { NormalizedSchema, Schema } from '../schema'; import type { NormalizedSchema, Schema } from '../schema';
import { import {
getDestination, getDestination,
getNewProjectName, getNewProjectName,
normalizePathSlashes, normalizePathSlashes,
} from './utils'; } from './utils';
import { getImportPath } from '../../../utilities/get-import-path';
export function normalizeSchema( export async function normalizeSchema(
tree: Tree, tree: Tree,
schema: Schema, schema: Schema,
projectConfiguration: ProjectConfiguration projectConfiguration: ProjectConfiguration
): NormalizedSchema { ): Promise<NormalizedSchema> {
const destination = normalizePathSlashes(schema.destination); const { destination, newProjectName, importPath } =
const newProjectName = await determineProjectNameAndRootOptions(
schema.newProjectName ?? getNewProjectName(destination);
return {
...schema,
destination,
importPath:
schema.importPath ??
normalizePathSlashes(getImportPath(tree, destination)),
newProjectName,
relativeToRootDestination: getDestination(
tree, tree,
schema, schema,
projectConfiguration projectConfiguration
), );
return {
...schema,
destination: normalizePathSlashes(schema.destination),
importPath,
newProjectName,
relativeToRootDestination: destination,
}; };
} }
type ProjectNameAndRootOptions = {
destination: string;
newProjectName: string;
importPath?: string;
};
type ProjectNameAndRootFormats = {
'as-provided': ProjectNameAndRootOptions;
derived?: ProjectNameAndRootOptions;
};
async function determineProjectNameAndRootOptions(
tree: Tree,
options: Schema,
projectConfiguration: ProjectConfiguration
): Promise<ProjectNameAndRootOptions> {
validateName(
options.newProjectName,
options.projectNameAndRootFormat,
projectConfiguration
);
const formats = getProjectNameAndRootFormats(
tree,
options,
projectConfiguration
);
const format =
options.projectNameAndRootFormat ??
(await determineFormat(tree, formats, options));
return formats[format];
}
function validateName(
name: string | undefined,
projectNameAndRootFormat: ProjectNameAndRootFormat | undefined,
projectConfiguration: ProjectConfiguration
): void {
if (!name) {
return;
}
if (projectNameAndRootFormat === 'derived' && name.startsWith('@')) {
throw new Error(
`The new project name "${name}" cannot start with "@" when the "projectNameAndRootFormat" is "derived".`
);
}
/**
* Matches two types of project names:
*
* 1. Valid npm package names (e.g., '@scope/name' or 'name').
* 2. Names starting with a letter and can contain any character except whitespace and ':'.
*
* The second case is to support the legacy behavior (^[a-zA-Z].*$) with the difference
* that it doesn't allow the ":" character. It was wrong to allow it because it would
* conflict with the notation for tasks.
*/
const libraryPattern =
'(?:^@[a-zA-Z0-9-*~][a-zA-Z0-9-*._~]*\\/[a-zA-Z0-9-~][a-zA-Z0-9-._~]*|^[a-zA-Z][^:]*)$';
const appPattern = '^[a-zA-Z][^:]*$';
if (projectConfiguration.projectType === 'application') {
const validationRegex = new RegExp(appPattern);
if (!validationRegex.test(name)) {
throw new Error(
`The new project name should match the pattern "${appPattern}". The provided value "${name}" does not match.`
);
}
} else if (projectConfiguration.projectType === 'library') {
const validationRegex = new RegExp(libraryPattern);
if (!validationRegex.test(name)) {
throw new Error(
`The new project name should match the pattern "${libraryPattern}". The provided value "${name}" does not match.`
);
}
}
}
function getProjectNameAndRootFormats(
tree: Tree,
schema: Schema,
projectConfiguration: ProjectConfiguration
): ProjectNameAndRootFormats {
let destination = normalizePathSlashes(schema.destination);
const normalizedNewProjectName = schema.newProjectName
? names(schema.newProjectName).fileName
: undefined;
const asProvidedProjectName = normalizedNewProjectName ?? schema.projectName;
const asProvidedDestination = destination;
if (normalizedNewProjectName?.startsWith('@')) {
return {
'as-provided': {
destination: asProvidedDestination,
importPath:
schema.importPath ??
// keep the existing import path if the name didn't change
(normalizedNewProjectName &&
schema.projectName !== normalizedNewProjectName
? asProvidedProjectName
: undefined),
newProjectName: asProvidedProjectName,
},
};
}
let npmScope: string;
let asProvidedImportPath = schema.importPath;
if (
!asProvidedImportPath &&
schema.newProjectName &&
projectConfiguration.projectType === 'library'
) {
npmScope = getNpmScope(tree);
asProvidedImportPath = npmScope
? `${npmScope === '@' ? '' : '@'}${npmScope}/${asProvidedProjectName}`
: asProvidedProjectName;
}
const derivedProjectName =
schema.newProjectName ?? getNewProjectName(destination);
const derivedDestination = getDestination(tree, schema, projectConfiguration);
let derivedImportPath: string;
if (projectConfiguration.projectType === 'library') {
derivedImportPath =
schema.importPath ??
normalizePathSlashes(getImportPath(tree, destination));
}
return {
'as-provided': {
destination: asProvidedDestination,
newProjectName: asProvidedProjectName,
importPath: asProvidedImportPath,
},
derived: {
destination: derivedDestination,
newProjectName: derivedProjectName,
importPath: derivedImportPath,
},
};
}
async function determineFormat(
tree: Tree,
formats: ProjectNameAndRootFormats,
schema: Schema
): Promise<ProjectNameAndRootFormat> {
if (!formats.derived) {
return 'as-provided';
}
if (process.env.NX_INTERACTIVE !== 'true' || !isTTY()) {
return 'derived';
}
const asProvidedDescription = `As provided:
Name: ${formats['as-provided'].newProjectName}
Destination: ${formats['as-provided'].destination}`;
const asProvidedSelectedValue = `${formats['as-provided'].newProjectName} @ ${formats['as-provided'].destination}`;
const derivedDescription = `Derived:
Name: ${formats['derived'].newProjectName}
Destination: ${formats['derived'].destination}`;
const derivedSelectedValue = `${formats['derived'].newProjectName} @ ${formats['derived'].destination}`;
const result = await prompt<{ format: ProjectNameAndRootFormat }>({
type: 'select',
name: 'format',
message:
'What should be the new project name and where should it be moved to?',
choices: [
{
message: asProvidedDescription,
name: asProvidedSelectedValue,
},
{
message: derivedDescription,
name: derivedSelectedValue,
},
],
initial: 'as-provided' as any,
}).then(({ format }) =>
format === asProvidedSelectedValue ? 'as-provided' : 'derived'
);
const callingGenerator =
process.env.NX_ANGULAR_MOVE_INVOKED === 'true'
? '@nx/angular:move'
: '@nx/workspace:move';
const deprecationWarning = stripIndents`
In Nx 18, the project name and destination will no longer be derived.
Please provide the exact new project name and destination in the future.`;
if (result === 'as-provided') {
const { saveDefault } = await prompt<{ saveDefault: boolean }>({
type: 'confirm',
message: `Would you like to configure Nx to always take the project name and destination as provided for ${callingGenerator}?`,
name: 'saveDefault',
initial: true,
});
if (saveDefault) {
const nxJson = readNxJson(tree);
nxJson.generators ??= {};
nxJson.generators[callingGenerator] ??= {};
nxJson.generators[callingGenerator].projectNameAndRootFormat = result;
updateNxJson(tree, nxJson);
} else {
logger.warn(deprecationWarning);
}
} else {
const example =
`Example: nx g ${callingGenerator} --projectName ${schema.projectName} --destination ${formats[result].destination}` +
(schema.projectName !== formats[result].newProjectName
? ` --newProjectName ${formats[result].newProjectName}`
: '');
logger.warn(deprecationWarning + '\n' + example);
}
return result;
}
function isTTY(): boolean {
return !!process.stdout.isTTY && process.env['CI'] !== 'true';
}

View File

@ -0,0 +1,24 @@
import type { Tree } from '@nx/devkit';
import type { Schema } from '../schema';
type PluginOptions = {
oldProjectName: string;
newProjectName: string;
};
export async function runAngularPlugin(tree: Tree, schema: Schema) {
let move: (tree: Tree, schema: PluginOptions) => Promise<void>;
try {
// nx-ignore-next-line
move = require('@nx/angular/src/generators/move/move-impl').move;
} catch {}
if (!move) {
return;
}
await move(tree, {
oldProjectName: schema.projectName,
newProjectName: schema.newProjectName,
});
}

View File

@ -24,11 +24,14 @@ describe('updateCypressConfig', () => {
importPath: '@proj/my-destination', importPath: '@proj/my-destination',
updateImportPath: true, updateImportPath: true,
newProjectName: 'my-destination', newProjectName: 'my-destination',
relativeToRootDestination: 'libs/my-destination', relativeToRootDestination: 'my-destination',
}; };
tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' }); tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
await libraryGenerator(tree, { name: 'my-lib' }); await libraryGenerator(tree, {
name: 'my-lib',
projectNameAndRootFormat: 'as-provided',
});
projectConfig = readProjectConfiguration(tree, 'my-lib'); projectConfig = readProjectConfiguration(tree, 'my-lib');
}); });
@ -46,18 +49,18 @@ describe('updateCypressConfig', () => {
pluginsFile: './src/plugins/index', pluginsFile: './src/plugins/index',
supportFile: false, supportFile: false,
video: true, video: true,
videosFolder: '../../dist/cypress/libs/my-lib/videos', videosFolder: '../../dist/cypress/my-lib/videos',
screenshotsFolder: '../../dist/cypress/libs/my-lib/screenshots', screenshotsFolder: '../../dist/cypress/my-lib/screenshots',
chromeWebSecurity: false, chromeWebSecurity: false,
}; };
writeJson(tree, '/libs/my-destination/cypress.json', cypressJson); writeJson(tree, 'my-destination/cypress.json', cypressJson);
updateCypressConfig(tree, schema, projectConfig); updateCypressConfig(tree, schema, projectConfig);
expect(readJson(tree, '/libs/my-destination/cypress.json')).toEqual({ expect(readJson(tree, 'my-destination/cypress.json')).toEqual({
...cypressJson, ...cypressJson,
videosFolder: '../../dist/cypress/libs/my-destination/videos', videosFolder: '../../dist/cypress/my-destination/videos',
screenshotsFolder: '../../dist/cypress/libs/my-destination/screenshots', screenshotsFolder: '../../dist/cypress/my-destination/screenshots',
}); });
}); });
@ -71,18 +74,16 @@ describe('updateCypressConfig', () => {
video: false, video: false,
chromeWebSecurity: false, chromeWebSecurity: false,
}; };
writeJson(tree, '/libs/my-destination/cypress.json', cypressJson); writeJson(tree, 'my-destination/cypress.json', cypressJson);
updateCypressConfig(tree, schema, projectConfig); updateCypressConfig(tree, schema, projectConfig);
expect(readJson(tree, '/libs/my-destination/cypress.json')).toEqual( expect(readJson(tree, 'my-destination/cypress.json')).toEqual(cypressJson);
cypressJson
);
}); });
it('should handle updating cypress.config.ts', async () => { it('should handle updating cypress.config.ts', async () => {
tree.write( tree.write(
'/libs/my-destination/cypress.config.ts', 'my-destination/cypress.config.ts',
` `
import { defineConfig } from 'cypress'; import { defineConfig } from 'cypress';
import { nxE2EPreset } from '@nx/cypress/plugins/cypress-preset'; import { nxE2EPreset } from '@nx/cypress/plugins/cypress-preset';
@ -90,23 +91,20 @@ import { nxE2EPreset } from '@nx/cypress/plugins/cypress-preset';
export default defineConfig({ export default defineConfig({
e2e: { e2e: {
nxE2EPreset(__dirname), nxE2EPreset(__dirname),
videosFolder: '../../dist/cypress/libs/my-lib/videos', videosFolder: '../../dist/cypress/my-lib/videos',
screenshotsFolder: '../../dist/cypress/libs/my-lib/screenshots', screenshotsFolder: '../../dist/cypress/my-lib/screenshots',
} }
}); });
` `
); );
updateCypressConfig(tree, schema, projectConfig); updateCypressConfig(tree, schema, projectConfig);
const fileContent = tree.read( const fileContent = tree.read('my-destination/cypress.config.ts', 'utf-8');
'/libs/my-destination/cypress.config.ts', expect(fileContent).toContain(
'utf-8' `videosFolder: '../../dist/cypress/my-destination/videos'`
); );
expect(fileContent).toContain( expect(fileContent).toContain(
`videosFolder: '../../dist/cypress/libs/my-destination/videos'` `screenshotsFolder: '../../dist/cypress/my-destination/screenshots'`
);
expect(fileContent).toContain(
`screenshotsFolder: '../../dist/cypress/libs/my-destination/screenshots'`
); );
}); });
}); });

View File

@ -25,7 +25,7 @@ describe('updateEslint', () => {
importPath: '@proj/shared-my-destination', importPath: '@proj/shared-my-destination',
updateImportPath: true, updateImportPath: true,
newProjectName: 'shared-my-destination', newProjectName: 'shared-my-destination',
relativeToRootDestination: 'libs/shared/my-destination', relativeToRootDestination: 'shared/my-destination',
}; };
tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' }); tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
@ -35,6 +35,7 @@ describe('updateEslint', () => {
await libraryGenerator(tree, { await libraryGenerator(tree, {
name: 'my-lib', name: 'my-lib',
linter: Linter.None, linter: Linter.None,
projectNameAndRootFormat: 'as-provided',
}); });
const projectConfig = readProjectConfiguration(tree, 'my-lib'); const projectConfig = readProjectConfiguration(tree, 'my-lib');
@ -48,33 +49,33 @@ describe('updateEslint', () => {
await libraryGenerator(tree, { await libraryGenerator(tree, {
name: 'my-lib', name: 'my-lib',
linter: Linter.EsLint, linter: Linter.EsLint,
projectNameAndRootFormat: 'as-provided',
}); });
// This step is usually handled elsewhere // This step is usually handled elsewhere
tree.rename( tree.rename(
'libs/my-lib/.eslintrc.json', 'my-lib/.eslintrc.json',
'libs/shared/my-destination/.eslintrc.json' 'shared/my-destination/.eslintrc.json'
); );
const projectConfig = readProjectConfiguration(tree, 'my-lib'); const projectConfig = readProjectConfiguration(tree, 'my-lib');
updateEslintConfig(tree, schema, projectConfig); updateEslintConfig(tree, schema, projectConfig);
expect( expect(readJson(tree, 'shared/my-destination/.eslintrc.json')).toEqual(
readJson(tree, '/libs/shared/my-destination/.eslintrc.json')
).toEqual(
expect.objectContaining({ expect.objectContaining({
extends: ['../../../.eslintrc.json'], extends: ['../../.eslintrc.json'],
}) })
); );
}); });
it('should update .eslintrc.json extends path when project is moved from subdirectory', async () => { it('should update .eslintrc.json extends path when project is moved from subdirectory', async () => {
await libraryGenerator(tree, { await libraryGenerator(tree, {
name: 'test', name: 'api-test',
directory: 'api', directory: 'api/test',
linter: Linter.EsLint, linter: Linter.EsLint,
projectNameAndRootFormat: 'as-provided',
}); });
// This step is usually handled elsewhere // This step is usually handled elsewhere
tree.rename('libs/api/test/.eslintrc.json', 'libs/test/.eslintrc.json'); tree.rename('api/test/.eslintrc.json', 'test/.eslintrc.json');
const projectConfig = readProjectConfiguration(tree, 'api-test'); const projectConfig = readProjectConfiguration(tree, 'api-test');
const newSchema = { const newSchema = {
@ -83,14 +84,14 @@ describe('updateEslint', () => {
importPath: '@proj/test', importPath: '@proj/test',
updateImportPath: true, updateImportPath: true,
newProjectName: 'test', newProjectName: 'test',
relativeToRootDestination: 'libs/test', relativeToRootDestination: 'test',
}; };
updateEslintConfig(tree, newSchema, projectConfig); updateEslintConfig(tree, newSchema, projectConfig);
expect(readJson(tree, '/libs/test/.eslintrc.json')).toEqual( expect(readJson(tree, 'test/.eslintrc.json')).toEqual(
expect.objectContaining({ expect.objectContaining({
extends: ['../../.eslintrc.json'], extends: ['../.eslintrc.json'],
}) })
); );
}); });
@ -99,31 +100,30 @@ describe('updateEslint', () => {
await libraryGenerator(tree, { await libraryGenerator(tree, {
name: 'my-lib', name: 'my-lib',
linter: Linter.EsLint, linter: Linter.EsLint,
projectNameAndRootFormat: 'as-provided',
}); });
updateJson(tree, 'libs/my-lib/.eslintrc.json', (eslintRcJson) => { updateJson(tree, 'my-lib/.eslintrc.json', (eslintRcJson) => {
eslintRcJson.extends = [ eslintRcJson.extends = [
'plugin:@nx/react', 'plugin:@nx/react',
'../../.eslintrc.json', '../.eslintrc.json',
'./customrc.json', './customrc.json',
]; ];
return eslintRcJson; return eslintRcJson;
}); });
// This step is usually handled elsewhere // This step is usually handled elsewhere
tree.rename( tree.rename(
'libs/my-lib/.eslintrc.json', 'my-lib/.eslintrc.json',
'libs/shared/my-destination/.eslintrc.json' 'shared/my-destination/.eslintrc.json'
); );
const projectConfig = readProjectConfiguration(tree, 'my-lib'); const projectConfig = readProjectConfiguration(tree, 'my-lib');
updateEslintConfig(tree, schema, projectConfig); updateEslintConfig(tree, schema, projectConfig);
expect( expect(readJson(tree, 'shared/my-destination/.eslintrc.json')).toEqual(
readJson(tree, '/libs/shared/my-destination/.eslintrc.json')
).toEqual(
expect.objectContaining({ expect.objectContaining({
extends: [ extends: [
'plugin:@nx/react', 'plugin:@nx/react',
'../../../.eslintrc.json', '../../.eslintrc.json',
'./customrc.json', './customrc.json',
], ],
}) })
@ -135,24 +135,23 @@ describe('updateEslint', () => {
name: 'my-lib', name: 'my-lib',
linter: Linter.EsLint, linter: Linter.EsLint,
setParserOptionsProject: true, setParserOptionsProject: true,
projectNameAndRootFormat: 'as-provided',
}); });
// This step is usually handled elsewhere // This step is usually handled elsewhere
tree.rename( tree.rename(
'libs/my-lib/.eslintrc.json', 'my-lib/.eslintrc.json',
'libs/shared/my-destination/.eslintrc.json' 'shared/my-destination/.eslintrc.json'
); );
const projectConfig = readProjectConfiguration(tree, 'my-lib'); const projectConfig = readProjectConfiguration(tree, 'my-lib');
updateEslintConfig(tree, schema, projectConfig); updateEslintConfig(tree, schema, projectConfig);
expect( expect(readJson(tree, 'shared/my-destination/.eslintrc.json')).toEqual(
readJson(tree, '/libs/shared/my-destination/.eslintrc.json')
).toEqual(
expect.objectContaining({ expect.objectContaining({
overrides: expect.arrayContaining([ overrides: expect.arrayContaining([
expect.objectContaining({ expect.objectContaining({
parserOptions: expect.objectContaining({ parserOptions: expect.objectContaining({
project: ['libs/shared/my-destination/tsconfig.*?.json'], project: ['shared/my-destination/tsconfig.*?.json'],
}), }),
}), }),
]), ]),
@ -165,36 +164,35 @@ describe('updateEslint', () => {
name: 'my-lib', name: 'my-lib',
linter: Linter.EsLint, linter: Linter.EsLint,
setParserOptionsProject: true, setParserOptionsProject: true,
projectNameAndRootFormat: 'as-provided',
}); });
// Add another parser project to eslint.json // Add another parser project to eslint.json
const storybookProject = '.storybook/tsconfig.json'; const storybookProject = '.storybook/tsconfig.json';
updateJson(tree, '/libs/my-lib/.eslintrc.json', (eslintRcJson) => { updateJson(tree, 'my-lib/.eslintrc.json', (eslintRcJson) => {
eslintRcJson.overrides[0].parserOptions.project.push( eslintRcJson.overrides[0].parserOptions.project.push(
`libs/my-lib/${storybookProject}` `my-lib/${storybookProject}`
); );
return eslintRcJson; return eslintRcJson;
}); });
// This step is usually handled elsewhere // This step is usually handled elsewhere
tree.rename( tree.rename(
'libs/my-lib/.eslintrc.json', 'my-lib/.eslintrc.json',
'libs/shared/my-destination/.eslintrc.json' 'shared/my-destination/.eslintrc.json'
); );
const projectConfig = readProjectConfiguration(tree, 'my-lib'); const projectConfig = readProjectConfiguration(tree, 'my-lib');
updateEslintConfig(tree, schema, projectConfig); updateEslintConfig(tree, schema, projectConfig);
expect( expect(readJson(tree, 'shared/my-destination/.eslintrc.json')).toEqual(
readJson(tree, '/libs/shared/my-destination/.eslintrc.json')
).toEqual(
expect.objectContaining({ expect.objectContaining({
overrides: expect.arrayContaining([ overrides: expect.arrayContaining([
expect.objectContaining({ expect.objectContaining({
parserOptions: expect.objectContaining({ parserOptions: expect.objectContaining({
project: [ project: [
'libs/shared/my-destination/tsconfig.*?.json', 'shared/my-destination/tsconfig.*?.json',
`libs/shared/my-destination/${storybookProject}`, `shared/my-destination/${storybookProject}`,
], ],
}), }),
}), }),
@ -208,28 +206,29 @@ describe('updateEslint', () => {
name: 'my-lib', name: 'my-lib',
linter: Linter.EsLint, linter: Linter.EsLint,
setParserOptionsProject: true, setParserOptionsProject: true,
projectNameAndRootFormat: 'as-provided',
}); });
// Add another parser project to eslint.json // Add another parser project to eslint.json
const storybookProject = '.storybook/tsconfig.json'; const storybookProject = '.storybook/tsconfig.json';
updateJson(tree, '/libs/my-lib/.eslintrc.json', (eslintRcJson) => { updateJson(tree, 'my-lib/.eslintrc.json', (eslintRcJson) => {
eslintRcJson.overrides[0].parserOptions.project = `libs/my-lib/${storybookProject}`; eslintRcJson.overrides[0].parserOptions.project = `my-lib/${storybookProject}`;
return eslintRcJson; return eslintRcJson;
}); });
// This step is usually handled elsewhere // This step is usually handled elsewhere
tree.rename( tree.rename(
'libs/my-lib/.eslintrc.json', 'my-lib/.eslintrc.json',
'libs/shared/my-destination/.eslintrc.json' 'shared/my-destination/.eslintrc.json'
); );
const projectConfig = readProjectConfiguration(tree, 'my-lib'); const projectConfig = readProjectConfiguration(tree, 'my-lib');
updateEslintConfig(tree, schema, projectConfig); updateEslintConfig(tree, schema, projectConfig);
expect( expect(
readJson(tree, '/libs/shared/my-destination/.eslintrc.json').overrides[0] readJson(tree, 'shared/my-destination/.eslintrc.json').overrides[0]
.parserOptions .parserOptions
).toEqual({ project: `libs/shared/my-destination/${storybookProject}` }); ).toEqual({ project: `shared/my-destination/${storybookProject}` });
}); });
}); });
@ -244,7 +243,7 @@ describe('updateEslint (flat config)', () => {
importPath: '@proj/shared-my-destination', importPath: '@proj/shared-my-destination',
updateImportPath: true, updateImportPath: true,
newProjectName: 'shared-my-destination', newProjectName: 'shared-my-destination',
relativeToRootDestination: 'libs/shared/my-destination', relativeToRootDestination: 'shared/my-destination',
}; };
tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' }); tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
@ -256,6 +255,7 @@ describe('updateEslint (flat config)', () => {
await libraryGenerator(tree, { await libraryGenerator(tree, {
name: 'my-lib', name: 'my-lib',
linter: Linter.None, linter: Linter.None,
projectNameAndRootFormat: 'as-provided',
}); });
const projectConfig = readProjectConfiguration(tree, 'my-lib'); const projectConfig = readProjectConfiguration(tree, 'my-lib');
@ -269,31 +269,33 @@ describe('updateEslint (flat config)', () => {
await libraryGenerator(tree, { await libraryGenerator(tree, {
name: 'my-lib', name: 'my-lib',
linter: Linter.EsLint, linter: Linter.EsLint,
projectNameAndRootFormat: 'as-provided',
}); });
convertToFlat(tree, 'libs/my-lib'); convertToFlat(tree, 'my-lib');
// This step is usually handled elsewhere // This step is usually handled elsewhere
tree.rename( tree.rename(
'libs/my-lib/eslint.config.js', 'my-lib/eslint.config.js',
'libs/shared/my-destination/eslint.config.js' 'shared/my-destination/eslint.config.js'
); );
const projectConfig = readProjectConfiguration(tree, 'my-lib'); const projectConfig = readProjectConfiguration(tree, 'my-lib');
updateEslintConfig(tree, schema, projectConfig); updateEslintConfig(tree, schema, projectConfig);
expect( expect(
tree.read('libs/shared/my-destination/eslint.config.js', 'utf-8') tree.read('shared/my-destination/eslint.config.js', 'utf-8')
).toEqual(expect.stringContaining(`require('../../../eslint.config.js')`)); ).toEqual(expect.stringContaining(`require('../../eslint.config.js')`));
}); });
it('should update config extends path when project is moved from subdirectory', async () => { it('should update config extends path when project is moved from subdirectory', async () => {
await libraryGenerator(tree, { await libraryGenerator(tree, {
name: 'test', name: 'api-test',
directory: 'api', directory: 'api/test',
linter: Linter.EsLint, linter: Linter.EsLint,
projectNameAndRootFormat: 'as-provided',
}); });
convertToFlat(tree, 'libs/api/test'); convertToFlat(tree, 'api/test');
// This step is usually handled elsewhere // This step is usually handled elsewhere
tree.rename('libs/api/test/eslint.config.js', 'libs/test/eslint.config.js'); tree.rename('api/test/eslint.config.js', 'test/eslint.config.js');
const projectConfig = readProjectConfiguration(tree, 'api-test'); const projectConfig = readProjectConfiguration(tree, 'api-test');
@ -303,13 +305,13 @@ describe('updateEslint (flat config)', () => {
importPath: '@proj/test', importPath: '@proj/test',
updateImportPath: true, updateImportPath: true,
newProjectName: 'test', newProjectName: 'test',
relativeToRootDestination: 'libs/test', relativeToRootDestination: 'test',
}; };
updateEslintConfig(tree, newSchema, projectConfig); updateEslintConfig(tree, newSchema, projectConfig);
expect(tree.read('libs/test/eslint.config.js', 'utf-8')).toEqual( expect(tree.read('test/eslint.config.js', 'utf-8')).toEqual(
expect.stringContaining(`require('../../eslint.config.js')`) expect.stringContaining(`require('../eslint.config.js')`)
); );
}); });
@ -318,22 +320,23 @@ describe('updateEslint (flat config)', () => {
name: 'my-lib', name: 'my-lib',
linter: Linter.EsLint, linter: Linter.EsLint,
setParserOptionsProject: true, setParserOptionsProject: true,
projectNameAndRootFormat: 'as-provided',
}); });
convertToFlat(tree, 'libs/my-lib', { hasParser: true }); convertToFlat(tree, 'my-lib', { hasParser: true });
// This step is usually handled elsewhere // This step is usually handled elsewhere
tree.rename( tree.rename(
'libs/my-lib/eslint.config.js', 'my-lib/eslint.config.js',
'libs/shared/my-destination/eslint.config.js' 'shared/my-destination/eslint.config.js'
); );
const projectConfig = readProjectConfiguration(tree, 'my-lib'); const projectConfig = readProjectConfiguration(tree, 'my-lib');
updateEslintConfig(tree, schema, projectConfig); updateEslintConfig(tree, schema, projectConfig);
expect( expect(
tree.read('libs/shared/my-destination/eslint.config.js', 'utf-8') tree.read('shared/my-destination/eslint.config.js', 'utf-8')
).toEqual( ).toEqual(
expect.stringContaining( expect.stringContaining(
`project: ["libs/shared/my-destination/tsconfig.*?.json"]` `project: ["shared/my-destination/tsconfig.*?.json"]`
) )
); );
}); });
@ -343,27 +346,28 @@ describe('updateEslint (flat config)', () => {
name: 'my-lib', name: 'my-lib',
linter: Linter.EsLint, linter: Linter.EsLint,
setParserOptionsProject: true, setParserOptionsProject: true,
projectNameAndRootFormat: 'as-provided',
}); });
// Add another parser project to eslint.json // Add another parser project to eslint.json
const storybookProject = '.storybook/tsconfig.json'; const storybookProject = '.storybook/tsconfig.json';
convertToFlat(tree, 'libs/my-lib', { convertToFlat(tree, 'my-lib', {
hasParser: true, hasParser: true,
anotherProject: storybookProject, anotherProject: storybookProject,
}); });
// This step is usually handled elsewhere // This step is usually handled elsewhere
tree.rename( tree.rename(
'libs/my-lib/eslint.config.js', 'my-lib/eslint.config.js',
'libs/shared/my-destination/eslint.config.js' 'shared/my-destination/eslint.config.js'
); );
const projectConfig = readProjectConfiguration(tree, 'my-lib'); const projectConfig = readProjectConfiguration(tree, 'my-lib');
updateEslintConfig(tree, schema, projectConfig); updateEslintConfig(tree, schema, projectConfig);
expect( expect(
tree.read('libs/shared/my-destination/eslint.config.js', 'utf-8') tree.read('shared/my-destination/eslint.config.js', 'utf-8')
).toEqual( ).toEqual(
expect.stringContaining( expect.stringContaining(
`project: ["libs/shared/my-destination/tsconfig.*?.json", "libs/shared/my-destination/${storybookProject}"]` `project: ["shared/my-destination/tsconfig.*?.json", "shared/my-destination/${storybookProject}"]`
) )
); );
}); });
@ -373,23 +377,24 @@ describe('updateEslint (flat config)', () => {
name: 'my-lib', name: 'my-lib',
linter: Linter.EsLint, linter: Linter.EsLint,
setParserOptionsProject: true, setParserOptionsProject: true,
projectNameAndRootFormat: 'as-provided',
}); });
convertToFlat(tree, 'libs/my-lib', { hasParser: true, isString: true }); convertToFlat(tree, 'my-lib', { hasParser: true, isString: true });
// This step is usually handled elsewhere // This step is usually handled elsewhere
tree.rename( tree.rename(
'libs/my-lib/eslint.config.js', 'my-lib/eslint.config.js',
'libs/shared/my-destination/eslint.config.js' 'shared/my-destination/eslint.config.js'
); );
const projectConfig = readProjectConfiguration(tree, 'my-lib'); const projectConfig = readProjectConfiguration(tree, 'my-lib');
updateEslintConfig(tree, schema, projectConfig); updateEslintConfig(tree, schema, projectConfig);
expect( expect(
tree.read('libs/shared/my-destination/eslint.config.js', 'utf-8') tree.read('shared/my-destination/eslint.config.js', 'utf-8')
).toEqual( ).toEqual(
expect.stringContaining( expect.stringContaining(
`project: "libs/shared/my-destination/tsconfig.*?.json"` `project: "shared/my-destination/tsconfig.*?.json"`
) )
); );
}); });

View File

@ -21,8 +21,10 @@ describe('updateImports', () => {
schema = { schema = {
projectName: 'my-source', projectName: 'my-source',
newProjectName: 'my-destination',
destination: 'my-destination', destination: 'my-destination',
updateImportPath: true, updateImportPath: true,
projectNameAndRootFormat: 'as-provided',
}; };
}); });
@ -33,14 +35,17 @@ describe('updateImports', () => {
await libraryGenerator(tree, { await libraryGenerator(tree, {
name: 'my-destination', name: 'my-destination',
config: 'project', config: 'project',
projectNameAndRootFormat: 'as-provided',
}); });
await libraryGenerator(tree, { await libraryGenerator(tree, {
name: 'my-source', name: 'my-source',
projectNameAndRootFormat: 'as-provided',
}); });
await libraryGenerator(tree, { await libraryGenerator(tree, {
name: 'my-importer', name: 'my-importer',
projectNameAndRootFormat: 'as-provided',
}); });
const importerFilePath = 'libs/my-importer/src/importer.ts'; const importerFilePath = 'my-importer/src/importer.ts';
tree.write( tree.write(
importerFilePath, importerFilePath,
` `
@ -53,7 +58,7 @@ describe('updateImports', () => {
updateImports( updateImports(
tree, tree,
normalizeSchema(tree, schema, projectConfig), await normalizeSchema(tree, schema, projectConfig),
projectConfig projectConfig
); );
@ -66,12 +71,19 @@ describe('updateImports', () => {
* be updated. * be updated.
*/ */
it('should not update import paths when they contain a partial match', async () => { it('should not update import paths when they contain a partial match', async () => {
await libraryGenerator(tree, { name: 'table' }); await libraryGenerator(tree, {
await libraryGenerator(tree, { name: 'tab' }); name: 'table',
projectNameAndRootFormat: 'as-provided',
});
await libraryGenerator(tree, {
name: 'tab',
projectNameAndRootFormat: 'as-provided',
});
await libraryGenerator(tree, { await libraryGenerator(tree, {
name: 'my-importer', name: 'my-importer',
projectNameAndRootFormat: 'as-provided',
}); });
const importerFilePath = 'libs/my-importer/src/importer.ts'; const importerFilePath = 'my-importer/src/importer.ts';
tree.write( tree.write(
importerFilePath, importerFilePath,
` `
@ -92,7 +104,7 @@ describe('updateImports', () => {
importPath: '@proj/tabs', importPath: '@proj/tabs',
updateImportPath: true, updateImportPath: true,
newProjectName: 'tabs', newProjectName: 'tabs',
relativeToRootDestination: 'libs/tabs', relativeToRootDestination: 'tabs',
}, },
projectConfig projectConfig
); );
@ -107,12 +119,19 @@ describe('updateImports', () => {
}); });
it('should correctly update deep imports', async () => { it('should correctly update deep imports', async () => {
await libraryGenerator(tree, { name: 'table' }); await libraryGenerator(tree, {
await libraryGenerator(tree, { name: 'tab' }); name: 'table',
projectNameAndRootFormat: 'as-provided',
});
await libraryGenerator(tree, {
name: 'tab',
projectNameAndRootFormat: 'as-provided',
});
await libraryGenerator(tree, { await libraryGenerator(tree, {
name: 'my-importer', name: 'my-importer',
projectNameAndRootFormat: 'as-provided',
}); });
const importerFilePath = 'libs/my-importer/src/importer.ts'; const importerFilePath = 'my-importer/src/importer.ts';
tree.write( tree.write(
importerFilePath, importerFilePath,
` `
@ -133,7 +152,7 @@ describe('updateImports', () => {
importPath: '@proj/tabs', importPath: '@proj/tabs',
updateImportPath: true, updateImportPath: true,
newProjectName: 'tabs', newProjectName: 'tabs',
relativeToRootDestination: 'libs/tabs', relativeToRootDestination: 'tabs',
}, },
projectConfig projectConfig
); );
@ -148,12 +167,19 @@ describe('updateImports', () => {
}); });
it('should update dynamic imports', async () => { it('should update dynamic imports', async () => {
await libraryGenerator(tree, { name: 'table' }); await libraryGenerator(tree, {
await libraryGenerator(tree, { name: 'tab' }); name: 'table',
projectNameAndRootFormat: 'as-provided',
});
await libraryGenerator(tree, {
name: 'tab',
projectNameAndRootFormat: 'as-provided',
});
await libraryGenerator(tree, { await libraryGenerator(tree, {
name: 'my-importer', name: 'my-importer',
projectNameAndRootFormat: 'as-provided',
}); });
const importerFilePath = 'libs/my-importer/src/importer.ts'; const importerFilePath = 'my-importer/src/importer.ts';
tree.write( tree.write(
importerFilePath, importerFilePath,
` `
@ -173,7 +199,7 @@ describe('updateImports', () => {
importPath: '@proj/tabs', importPath: '@proj/tabs',
updateImportPath: true, updateImportPath: true,
newProjectName: 'tabs', newProjectName: 'tabs',
relativeToRootDestination: 'libs/tabs', relativeToRootDestination: 'tabs',
}, },
projectConfig projectConfig
); );
@ -194,12 +220,19 @@ describe('updateImports', () => {
}); });
it('should update require imports', async () => { it('should update require imports', async () => {
await libraryGenerator(tree, { name: 'table' }); await libraryGenerator(tree, {
await libraryGenerator(tree, { name: 'tab' }); name: 'table',
projectNameAndRootFormat: 'as-provided',
});
await libraryGenerator(tree, {
name: 'tab',
projectNameAndRootFormat: 'as-provided',
});
await libraryGenerator(tree, { await libraryGenerator(tree, {
name: 'my-importer', name: 'my-importer',
projectNameAndRootFormat: 'as-provided',
}); });
const importerFilePath = 'libs/my-importer/src/importer.ts'; const importerFilePath = 'my-importer/src/importer.ts';
tree.write( tree.write(
importerFilePath, importerFilePath,
` `
@ -219,7 +252,7 @@ describe('updateImports', () => {
importPath: '@proj/tabs', importPath: '@proj/tabs',
updateImportPath: true, updateImportPath: true,
newProjectName: 'tabs', newProjectName: 'tabs',
relativeToRootDestination: 'libs/tabs', relativeToRootDestination: 'tabs',
}, },
projectConfig projectConfig
); );
@ -244,14 +277,17 @@ describe('updateImports', () => {
// source and destination to make sure that the workspace has libraries with those names. // source and destination to make sure that the workspace has libraries with those names.
await libraryGenerator(tree, { await libraryGenerator(tree, {
name: 'my-destination', name: 'my-destination',
projectNameAndRootFormat: 'as-provided',
}); });
await libraryGenerator(tree, { await libraryGenerator(tree, {
name: 'my-source', name: 'my-source',
projectNameAndRootFormat: 'as-provided',
}); });
await libraryGenerator(tree, { await libraryGenerator(tree, {
name: 'my-importer', name: 'my-importer',
projectNameAndRootFormat: 'as-provided',
}); });
const importerFilePath = 'libs/my-importer/src/importer.ts'; const importerFilePath = 'my-importer/src/importer.ts';
tree.write( tree.write(
importerFilePath, importerFilePath,
`import { MyClass } from '@proj/my-source'; `import { MyClass } from '@proj/my-source';
@ -263,7 +299,7 @@ export MyExtendedClass extends MyClass {};`
updateImports( updateImports(
tree, tree,
normalizeSchema( await normalizeSchema(
tree, tree,
{ {
...schema, ...schema,
@ -282,31 +318,33 @@ export MyExtendedClass extends MyClass {};`
it('should update project ref in the root tsconfig.base.json', async () => { it('should update project ref in the root tsconfig.base.json', async () => {
await libraryGenerator(tree, { await libraryGenerator(tree, {
name: 'my-source', name: 'my-source',
projectNameAndRootFormat: 'as-provided',
}); });
const projectConfig = readProjectConfiguration(tree, 'my-source'); const projectConfig = readProjectConfiguration(tree, 'my-source');
updateImports( updateImports(
tree, tree,
normalizeSchema(tree, schema, projectConfig), await normalizeSchema(tree, schema, projectConfig),
projectConfig projectConfig
); );
const tsConfig = readJson(tree, '/tsconfig.base.json'); const tsConfig = readJson(tree, '/tsconfig.base.json');
expect(tsConfig.compilerOptions.paths).toEqual({ expect(tsConfig.compilerOptions.paths).toEqual({
'@proj/my-destination': ['libs/my-destination/src/index.ts'], '@proj/my-destination': ['my-destination/src/index.ts'],
}); });
}); });
it('should update project ref in the root tsconfig.base.json for secondary entry points', async () => { it('should update project ref in the root tsconfig.base.json for secondary entry points', async () => {
await libraryGenerator(tree, { await libraryGenerator(tree, {
name: 'my-source', name: 'my-source',
projectNameAndRootFormat: 'as-provided',
}); });
updateJson(tree, '/tsconfig.base.json', (json) => { updateJson(tree, '/tsconfig.base.json', (json) => {
json.compilerOptions.paths['@proj/my-source/testing'] = [ json.compilerOptions.paths['@proj/my-source/testing'] = [
'libs/my-source/testing/src/index.ts', 'my-source/testing/src/index.ts',
]; ];
json.compilerOptions.paths['@proj/different-alias'] = [ json.compilerOptions.paths['@proj/different-alias'] = [
'libs/my-source/some-path/src/index.ts', 'my-source/some-path/src/index.ts',
]; ];
return json; return json;
}); });
@ -314,17 +352,15 @@ export MyExtendedClass extends MyClass {};`
updateImports( updateImports(
tree, tree,
normalizeSchema(tree, schema, projectConfig), await normalizeSchema(tree, schema, projectConfig),
projectConfig projectConfig
); );
const tsConfig = readJson(tree, '/tsconfig.base.json'); const tsConfig = readJson(tree, '/tsconfig.base.json');
expect(tsConfig.compilerOptions.paths).toEqual({ expect(tsConfig.compilerOptions.paths).toEqual({
'@proj/my-destination': ['libs/my-destination/src/index.ts'], '@proj/my-destination': ['my-destination/src/index.ts'],
'@proj/my-destination/testing': [ '@proj/my-destination/testing': ['my-destination/testing/src/index.ts'],
'libs/my-destination/testing/src/index.ts', '@proj/different-alias': ['my-destination/some-path/src/index.ts'],
],
'@proj/different-alias': ['libs/my-destination/some-path/src/index.ts'],
}); });
}); });
@ -332,12 +368,13 @@ export MyExtendedClass extends MyClass {};`
tree.delete('libs'); tree.delete('libs');
await libraryGenerator(tree, { await libraryGenerator(tree, {
name: 'my-source', name: 'my-source',
projectNameAndRootFormat: 'as-provided',
}); });
const projectConfig = readProjectConfiguration(tree, 'my-source'); const projectConfig = readProjectConfiguration(tree, 'my-source');
updateImports( updateImports(
tree, tree,
normalizeSchema(tree, schema, projectConfig), await normalizeSchema(tree, schema, projectConfig),
projectConfig projectConfig
); );
@ -351,18 +388,19 @@ export MyExtendedClass extends MyClass {};`
tree.rename('tsconfig.base.json', 'tsconfig.json'); tree.rename('tsconfig.base.json', 'tsconfig.json');
await libraryGenerator(tree, { await libraryGenerator(tree, {
name: 'my-source', name: 'my-source',
projectNameAndRootFormat: 'as-provided',
}); });
const projectConfig = readProjectConfiguration(tree, 'my-source'); const projectConfig = readProjectConfiguration(tree, 'my-source');
updateImports( updateImports(
tree, tree,
normalizeSchema(tree, schema, projectConfig), await normalizeSchema(tree, schema, projectConfig),
projectConfig projectConfig
); );
const tsConfig = readJson(tree, '/tsconfig.json'); const tsConfig = readJson(tree, '/tsconfig.json');
expect(tsConfig.compilerOptions.paths).toEqual({ expect(tsConfig.compilerOptions.paths).toEqual({
'@proj/my-destination': ['libs/my-destination/src/index.ts'], '@proj/my-destination': ['my-destination/src/index.ts'],
}); });
}); });
@ -374,30 +412,32 @@ export MyExtendedClass extends MyClass {};`
); );
await libraryGenerator(tree, { await libraryGenerator(tree, {
name: 'my-source', name: 'my-source',
projectNameAndRootFormat: 'as-provided',
}); });
const projectConfig = readProjectConfiguration(tree, 'my-source'); const projectConfig = readProjectConfiguration(tree, 'my-source');
updateImports( updateImports(
tree, tree,
normalizeSchema(tree, schema, projectConfig), await normalizeSchema(tree, schema, projectConfig),
projectConfig projectConfig
); );
const tsConfig = readJson(tree, '/tsconfig.json'); const tsConfig = readJson(tree, '/tsconfig.json');
expect(tsConfig.compilerOptions.paths).toEqual({ expect(tsConfig.compilerOptions.paths).toEqual({
'@proj/my-destination': ['libs/my-destination/src/index.ts'], '@proj/my-destination': ['my-destination/src/index.ts'],
}); });
}); });
it('should only update the project ref paths in the tsconfig file when --updateImportPath=false', async () => { it('should only update the project ref paths in the tsconfig file when --updateImportPath=false', async () => {
await libraryGenerator(tree, { await libraryGenerator(tree, {
name: 'my-source', name: 'my-source',
projectNameAndRootFormat: 'as-provided',
}); });
const projectConfig = readProjectConfiguration(tree, 'my-source'); const projectConfig = readProjectConfiguration(tree, 'my-source');
updateImports( updateImports(
tree, tree,
normalizeSchema( await normalizeSchema(
tree, tree,
{ {
...schema, ...schema,
@ -411,7 +451,7 @@ export MyExtendedClass extends MyClass {};`
const tsConfig = readJson(tree, '/tsconfig.base.json'); const tsConfig = readJson(tree, '/tsconfig.base.json');
expect(tsConfig.compilerOptions.paths).toEqual({ expect(tsConfig.compilerOptions.paths).toEqual({
'@proj/my-source': ['libs/my-destination/src/index.ts'], '@proj/my-source': ['my-destination/src/index.ts'],
}); });
}); });
}); });

View File

@ -35,7 +35,6 @@ export function updateImports(
project: ProjectConfiguration project: ProjectConfiguration
) { ) {
if (project.projectType === 'application') { if (project.projectType === 'application') {
// These shouldn't be imported anywhere?
return; return;
} }
@ -88,9 +87,10 @@ export function updateImports(
// if the import path doesn't start with the main entry point import path, // if the import path doesn't start with the main entry point import path,
// it's a custom import path we don't know how to update the name, we keep // it's a custom import path we don't know how to update the name, we keep
// it as-is, but we'll update the path it points to // it as-is, but we'll update the path it points to
to: p.startsWith(mainEntryPointImportPath) to:
? p.replace(mainEntryPointImportPath, schema.importPath) schema.importPath && p.startsWith(mainEntryPointImportPath)
: null, ? p.replace(mainEntryPointImportPath, schema.importPath)
: null,
})), })),
]; ];

View File

@ -16,6 +16,7 @@ describe('updateJestConfig', () => {
it('should handle jest config not existing', async () => { it('should handle jest config not existing', async () => {
await libraryGenerator(tree, { await libraryGenerator(tree, {
name: 'my-source', name: 'my-source',
projectNameAndRootFormat: 'as-provided',
}); });
const projectConfig = readProjectConfiguration(tree, 'my-source'); const projectConfig = readProjectConfiguration(tree, 'my-source');
const schema: NormalizedSchema = { const schema: NormalizedSchema = {
@ -24,7 +25,7 @@ describe('updateJestConfig', () => {
importPath: '@proj/my-destination', importPath: '@proj/my-destination',
updateImportPath: true, updateImportPath: true,
newProjectName: 'my-destination', newProjectName: 'my-destination',
relativeToRootDestination: 'libs/my-destination', relativeToRootDestination: 'my-destination',
}; };
expect(() => updateJestConfig(tree, schema, projectConfig)).not.toThrow(); expect(() => updateJestConfig(tree, schema, projectConfig)).not.toThrow();
@ -34,16 +35,17 @@ describe('updateJestConfig', () => {
const jestConfig = `module.exports = { const jestConfig = `module.exports = {
name: 'my-source', name: 'my-source',
preset: '../../jest.config.ts', preset: '../../jest.config.ts',
coverageDirectory: '../../coverage/libs/my-source', coverageDirectory: '../coverage/my-source',
snapshotSerializers: [ snapshotSerializers: [
'jest-preset-angular/AngularSnapshotSerializer.js', 'jest-preset-angular/AngularSnapshotSerializer.js',
'jest-preset-angular/HTMLCommentSerializer.js' 'jest-preset-angular/HTMLCommentSerializer.js'
] ]
};`; };`;
const jestConfigPath = '/libs/my-destination/jest.config.ts'; const jestConfigPath = 'my-destination/jest.config.ts';
const rootJestConfigPath = '/jest.config.ts'; const rootJestConfigPath = '/jest.config.ts';
await libraryGenerator(tree, { await libraryGenerator(tree, {
name: 'my-source', name: 'my-source',
projectNameAndRootFormat: 'as-provided',
}); });
const projectConfig = readProjectConfiguration(tree, 'my-source'); const projectConfig = readProjectConfiguration(tree, 'my-source');
tree.write(jestConfigPath, jestConfig); tree.write(jestConfigPath, jestConfig);
@ -53,7 +55,7 @@ describe('updateJestConfig', () => {
importPath: '@proj/my-destination', importPath: '@proj/my-destination',
updateImportPath: true, updateImportPath: true,
newProjectName: 'my-destination', newProjectName: 'my-destination',
relativeToRootDestination: 'libs/my-destination', relativeToRootDestination: 'my-destination',
}; };
updateJestConfig(tree, schema, projectConfig); updateJestConfig(tree, schema, projectConfig);
@ -62,25 +64,62 @@ describe('updateJestConfig', () => {
const rootJestConfigAfter = tree.read(rootJestConfigPath, 'utf-8'); const rootJestConfigAfter = tree.read(rootJestConfigPath, 'utf-8');
expect(jestConfigAfter).toContain(`name: 'my-destination'`); expect(jestConfigAfter).toContain(`name: 'my-destination'`);
expect(jestConfigAfter).toContain( expect(jestConfigAfter).toContain(
`coverageDirectory: '../../coverage/libs/my-destination'` `coverageDirectory: '../coverage/my-destination'`
); );
expect(rootJestConfigAfter).toContain('getJestProjects()'); expect(rootJestConfigAfter).toContain('getJestProjects()');
}); });
it('should update jest configs properly even if project is in many layers of subfolders', async () => { it('should update the name and dir correctly when moving to a nested dir', async () => {
const jestConfig = `module.exports = { const jestConfig = `module.exports = {
name: 'some-test-dir-my-source', name: 'my-source',
preset: '../../jest.config.ts', preset: '../../jest.config.ts',
coverageDirectory: '../../coverage/libs/some/test/dir/my-source', coverageDirectory: '../coverage/my-source',
snapshotSerializers: [ snapshotSerializers: [
'jest-preset-angular/AngularSnapshotSerializer.js', 'jest-preset-angular/AngularSnapshotSerializer.js',
'jest-preset-angular/HTMLCommentSerializer.js' 'jest-preset-angular/HTMLCommentSerializer.js'
] ]
};`; };`;
const jestConfigPath = '/libs/other/test/dir/my-destination/jest.config.ts'; const jestConfigPath = 'my-source/data-access/jest.config.ts';
await libraryGenerator(tree, {
name: 'my-source',
projectNameAndRootFormat: 'as-provided',
});
const projectConfig = readProjectConfiguration(tree, 'my-source');
tree.write(jestConfigPath, jestConfig);
const schema: NormalizedSchema = {
projectName: 'my-source',
destination: 'my-source/data-access',
importPath: '@proj/my-soource-data-access',
updateImportPath: true,
newProjectName: 'my-source-data-access',
relativeToRootDestination: 'my-source/data-access',
};
updateJestConfig(tree, schema, projectConfig);
const jestConfigAfter = tree.read(jestConfigPath, 'utf-8');
expect(jestConfigAfter).toContain(`name: 'my-source-data-access'`);
expect(jestConfigAfter).toContain(
`coverageDirectory: '../coverage/my-source/data-access'`
);
});
it('should update jest configs properly even if project is in many layers of subfolders', async () => {
const jestConfig = `module.exports = {
name: 'some-test-dir-my-source',
preset: '../jest.config.ts',
coverageDirectory: '../coverage/some/test/dir/my-source',
snapshotSerializers: [
'jest-preset-angular/AngularSnapshotSerializer.js',
'jest-preset-angular/HTMLCommentSerializer.js'
]
};`;
const jestConfigPath = 'other/test/dir/my-destination/jest.config.ts';
const rootJestConfigPath = '/jest.config.ts'; const rootJestConfigPath = '/jest.config.ts';
await libraryGenerator(tree, { await libraryGenerator(tree, {
name: 'some/test/dir/my-source', name: 'some-test-dir-my-source',
directory: 'some/test/dir/my-source',
projectNameAndRootFormat: 'as-provided',
}); });
const projectConfig = readProjectConfiguration( const projectConfig = readProjectConfiguration(
tree, tree,
@ -93,7 +132,7 @@ describe('updateJestConfig', () => {
importPath: '@proj/other-test-dir-my-destination', importPath: '@proj/other-test-dir-my-destination',
updateImportPath: true, updateImportPath: true,
newProjectName: 'other-test-dir-my-destination', newProjectName: 'other-test-dir-my-destination',
relativeToRootDestination: 'libs/other/test/dir/my-destination', relativeToRootDestination: 'other/test/dir/my-destination',
}; };
updateJestConfig(tree, schema, projectConfig); updateJestConfig(tree, schema, projectConfig);
@ -101,7 +140,7 @@ describe('updateJestConfig', () => {
const rootJestConfigAfter = tree.read(rootJestConfigPath, 'utf-8'); const rootJestConfigAfter = tree.read(rootJestConfigPath, 'utf-8');
expect(jestConfigAfter).toContain(`name: 'other-test-dir-my-destination'`); expect(jestConfigAfter).toContain(`name: 'other-test-dir-my-destination'`);
expect(jestConfigAfter).toContain( expect(jestConfigAfter).toContain(
`coverageDirectory: '../../coverage/libs/other/test/dir/my-destination'` `coverageDirectory: '../coverage/other/test/dir/my-destination'`
); );
expect(rootJestConfigAfter).toContain('getJestProjects()'); expect(rootJestConfigAfter).toContain('getJestProjects()');
}); });
@ -109,12 +148,14 @@ describe('updateJestConfig', () => {
it('updates the root config if not using `getJestProjects()`', async () => { it('updates the root config if not using `getJestProjects()`', async () => {
const rootJestConfigPath = '/jest.config.ts'; const rootJestConfigPath = '/jest.config.ts';
await libraryGenerator(tree, { await libraryGenerator(tree, {
name: 'some/test/dir/my-source', name: 'some-test-dir-my-source',
directory: 'some/test/dir/my-source',
projectNameAndRootFormat: 'as-provided',
}); });
tree.write( tree.write(
rootJestConfigPath, rootJestConfigPath,
`module.exports = { `module.exports = {
projects: ['<rootDir>/libs/some/test/dir/my-source'] projects: ['<rootDir>/some/test/dir/my-source']
}; };
` `
); );
@ -128,31 +169,33 @@ describe('updateJestConfig', () => {
importPath: '@proj/other-test-dir-my-destination', importPath: '@proj/other-test-dir-my-destination',
updateImportPath: true, updateImportPath: true,
newProjectName: 'other-test-dir-my-destination', newProjectName: 'other-test-dir-my-destination',
relativeToRootDestination: 'libs/other/test/dir/my-destination', relativeToRootDestination: 'other/test/dir/my-destination',
}; };
updateJestConfig(tree, schema, projectConfig); updateJestConfig(tree, schema, projectConfig);
const rootJestConfigAfter = tree.read(rootJestConfigPath, 'utf-8'); const rootJestConfigAfter = tree.read(rootJestConfigPath, 'utf-8');
expect(rootJestConfigAfter).not.toContain( expect(rootJestConfigAfter).not.toContain(
'<rootDir>/libs/some/test/dir/my-source' '<rootDir>/some/test/dir/my-source'
); );
expect(rootJestConfigAfter).toContain( expect(rootJestConfigAfter).toContain(
'<rootDir>/libs/other/test/dir/my-destination' '<rootDir>/other/test/dir/my-destination'
); );
}); });
it('updates the root config if `getJestProjects()` is used but old path exists', async () => { it('updates the root config if `getJestProjects()` is used but old path exists', async () => {
const rootJestConfigPath = '/jest.config.ts'; const rootJestConfigPath = '/jest.config.ts';
await libraryGenerator(tree, { await libraryGenerator(tree, {
name: 'some/test/dir/my-source', name: 'some-test-dir-my-source',
directory: 'some/test/dir/my-source',
projectNameAndRootFormat: 'as-provided',
}); });
tree.write( tree.write(
rootJestConfigPath, rootJestConfigPath,
`const { getJestProjects } = require('@nx/jest'); `const { getJestProjects } = require('@nx/jest');
module.exports = { module.exports = {
projects: [...getJestProjects(), '<rootDir>/libs/some/test/dir/my-source'] projects: [...getJestProjects(), '<rootDir>/some/test/dir/my-source']
}; };
` `
); );
@ -166,17 +209,17 @@ module.exports = {
importPath: '@proj/other-test-dir-my-destination', importPath: '@proj/other-test-dir-my-destination',
updateImportPath: true, updateImportPath: true,
newProjectName: 'other-test-dir-my-destination', newProjectName: 'other-test-dir-my-destination',
relativeToRootDestination: 'libs/other/test/dir/my-destination', relativeToRootDestination: 'other/test/dir/my-destination',
}; };
updateJestConfig(tree, schema, projectConfig); updateJestConfig(tree, schema, projectConfig);
const rootJestConfigAfter = tree.read(rootJestConfigPath, 'utf-8'); const rootJestConfigAfter = tree.read(rootJestConfigPath, 'utf-8');
expect(rootJestConfigAfter).not.toContain( expect(rootJestConfigAfter).not.toContain(
'<rootDir>/libs/some/test/dir/my-source' '<rootDir>/some/test/dir/my-source'
); );
expect(rootJestConfigAfter).not.toContain( expect(rootJestConfigAfter).not.toContain(
'<rootDir>/libs/other/test/dir/my-destination' '<rootDir>/other/test/dir/my-destination'
); );
expect(rootJestConfigAfter).toContain('getJestProjects()'); expect(rootJestConfigAfter).toContain('getJestProjects()');
}); });
@ -184,14 +227,16 @@ module.exports = {
it('updates the root config if `getJestProjects()` is used with other projects in the array', async () => { it('updates the root config if `getJestProjects()` is used with other projects in the array', async () => {
const rootJestConfigPath = '/jest.config.ts'; const rootJestConfigPath = '/jest.config.ts';
await libraryGenerator(tree, { await libraryGenerator(tree, {
name: 'some/test/dir/my-source', name: 'some-test-dir-my-source',
directory: 'some/test/dir/my-source',
projectNameAndRootFormat: 'as-provided',
}); });
tree.write( tree.write(
rootJestConfigPath, rootJestConfigPath,
`const { getJestProjects } = require('@nx/jest'); `const { getJestProjects } = require('@nx/jest');
module.exports = { module.exports = {
projects: [...getJestProjects(), '<rootDir>/libs/some/test/dir/my-source', '<rootDir>/libs/foo'] projects: [...getJestProjects(), '<rootDir>/some/test/dir/my-source', '<rootDir>/foo']
}; };
` `
); );
@ -205,19 +250,19 @@ module.exports = {
importPath: '@proj/other-test-dir-my-destination', importPath: '@proj/other-test-dir-my-destination',
updateImportPath: true, updateImportPath: true,
newProjectName: 'other-test-dir-my-destination', newProjectName: 'other-test-dir-my-destination',
relativeToRootDestination: 'libs/other/test/dir/my-destination', relativeToRootDestination: 'other/test/dir/my-destination',
}; };
updateJestConfig(tree, schema, projectConfig); updateJestConfig(tree, schema, projectConfig);
const rootJestConfigAfter = tree.read(rootJestConfigPath, 'utf-8'); const rootJestConfigAfter = tree.read(rootJestConfigPath, 'utf-8');
expect(rootJestConfigAfter).not.toContain( expect(rootJestConfigAfter).not.toContain(
'<rootDir>/libs/some/test/dir/my-source' '<rootDir>/some/test/dir/my-source'
); );
expect(rootJestConfigAfter).not.toContain( expect(rootJestConfigAfter).not.toContain(
'<rootDir>/libs/other/test/dir/my-destination' '<rootDir>/other/test/dir/my-destination'
); );
expect(rootJestConfigAfter).toContain('<rootDir>/libs/foo'); expect(rootJestConfigAfter).toContain('<rootDir>/foo');
expect(rootJestConfigAfter).toContain('getJestProjects()'); expect(rootJestConfigAfter).toContain('getJestProjects()');
}); });
}); });

View File

@ -22,16 +22,38 @@ export function updateJestConfig(
if (tree.exists(jestConfigPath)) { if (tree.exists(jestConfigPath)) {
const oldContent = tree.read(jestConfigPath, 'utf-8'); const oldContent = tree.read(jestConfigPath, 'utf-8');
// ensure both single and double quotes are replaced let newContent = oldContent;
const findName = new RegExp( if (schema.projectName !== schema.newProjectName) {
`'${schema.projectName}'|"${schema.projectName}"|\`${schema.projectName}\``, // ensure both single and double quotes are replaced
'g' const findName = new RegExp(
); `'${schema.projectName}'|"${schema.projectName}"|\`${schema.projectName}\``,
const findDir = new RegExp(project.root, 'g'); 'g'
);
newContent = oldContent.replace(findName, `'${schema.newProjectName}'`);
}
let dirRegex = new RegExp(`\\/${project.root}\\/`, 'g');
if (dirRegex.test(newContent)) {
newContent = newContent.replace(
dirRegex,
`/${schema.relativeToRootDestination}/`
);
}
dirRegex = new RegExp(`\\/${project.root}['"\`]`, 'g');
if (dirRegex.test(newContent)) {
newContent = newContent.replace(
dirRegex,
`/${schema.relativeToRootDestination}'`
);
}
dirRegex = new RegExp(`['"\`]${project.root}\\/`, 'g');
if (dirRegex.test(newContent)) {
newContent = newContent.replace(
dirRegex,
`'${schema.relativeToRootDestination}/`
);
}
const newContent = oldContent
.replace(findName, `'${schema.newProjectName}'`)
.replace(findDir, schema.relativeToRootDestination);
tree.write(jestConfigPath, newContent); tree.write(jestConfigPath, newContent);
} }

View File

@ -17,11 +17,14 @@ describe('updatePackageJson', () => {
importPath: '@proj/my-destination', importPath: '@proj/my-destination',
updateImportPath: true, updateImportPath: true,
newProjectName: 'my-destination', newProjectName: 'my-destination',
relativeToRootDestination: 'libs/my-destination', relativeToRootDestination: 'my-destination',
}; };
tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' }); tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
await libraryGenerator(tree, { name: 'my-lib' }); await libraryGenerator(tree, {
name: 'my-lib',
projectNameAndRootFormat: 'as-provided',
});
}); });
it('should handle package.json not existing', async () => { it('should handle package.json not existing', async () => {
@ -34,11 +37,11 @@ describe('updatePackageJson', () => {
const packageJson = { const packageJson = {
name: '@proj/my-lib', name: '@proj/my-lib',
}; };
writeJson(tree, '/libs/my-destination/package.json', packageJson); writeJson(tree, 'my-destination/package.json', packageJson);
updatePackageJson(tree, schema); updatePackageJson(tree, schema);
expect(readJson(tree, '/libs/my-destination/package.json')).toEqual({ expect(readJson(tree, 'my-destination/package.json')).toEqual({
...packageJson, ...packageJson,
name: '@proj/my-destination', name: '@proj/my-destination',
}); });

View File

@ -12,6 +12,10 @@ interface PartialPackageJson {
* @param schema The options provided to the schematic * @param schema The options provided to the schematic
*/ */
export function updatePackageJson(tree: Tree, schema: NormalizedSchema) { export function updatePackageJson(tree: Tree, schema: NormalizedSchema) {
if (!schema.importPath) {
return;
}
const packageJsonPath = path.join( const packageJsonPath = path.join(
schema.relativeToRootDestination, schema.relativeToRootDestination,
'package.json' 'package.json'

View File

@ -16,16 +16,17 @@ describe('updateProjectRootFiles', () => {
it('should update the relative root in files at the root of the project', async () => { it('should update the relative root in files at the root of the project', async () => {
const testFile = `module.exports = { const testFile = `module.exports = {
name: 'my-source', name: 'my-source',
preset: '../../jest.config.js', preset: '../jest.config.js',
coverageDirectory: '../../coverage/libs/my-source', coverageDirectory: '../coverage/my-source',
snapshotSerializers: [ snapshotSerializers: [
'jest-preset-angular/AngularSnapshotSerializer.js', 'jest-preset-angular/AngularSnapshotSerializer.js',
'jest-preset-angular/HTMLCommentSerializer.js' 'jest-preset-angular/HTMLCommentSerializer.js'
] ]
};`; };`;
const testFilePath = '/libs/subfolder/my-destination/jest.config.js'; const testFilePath = 'subfolder/my-destination/jest.config.js';
await libraryGenerator(tree, { await libraryGenerator(tree, {
name: 'my-source', name: 'my-source',
projectNameAndRootFormat: 'as-provided',
}); });
const projectConfig = readProjectConfiguration(tree, 'my-source'); const projectConfig = readProjectConfiguration(tree, 'my-source');
tree.write(testFilePath, testFile); tree.write(testFilePath, testFile);
@ -35,15 +36,15 @@ describe('updateProjectRootFiles', () => {
importPath: '@proj/subfolder-my-destination', importPath: '@proj/subfolder-my-destination',
updateImportPath: true, updateImportPath: true,
newProjectName: 'subfolder-my-destination', newProjectName: 'subfolder-my-destination',
relativeToRootDestination: 'libs/subfolder/my-destination', relativeToRootDestination: 'subfolder/my-destination',
}; };
updateProjectRootFiles(tree, schema, projectConfig); updateProjectRootFiles(tree, schema, projectConfig);
const testFileAfter = tree.read(testFilePath, 'utf-8'); const testFileAfter = tree.read(testFilePath, 'utf-8');
expect(testFileAfter).toContain(`preset: '../../../jest.config.js'`); expect(testFileAfter).toContain(`preset: '../../jest.config.js'`);
expect(testFileAfter).toContain( expect(testFileAfter).toContain(
`coverageDirectory: '../../../coverage/libs/my-source'` `coverageDirectory: '../../coverage/my-source'`
); );
}); });
}); });

View File

@ -18,7 +18,7 @@ describe('updateReadme', () => {
importPath: '@proj/shared-my-destination', importPath: '@proj/shared-my-destination',
updateImportPath: true, updateImportPath: true,
newProjectName: 'shared-my-destination', newProjectName: 'shared-my-destination',
relativeToRootDestination: 'libs/shared/my-destination', relativeToRootDestination: 'shared/my-destination',
}; };
tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' }); tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
@ -27,6 +27,7 @@ describe('updateReadme', () => {
it('should handle README.md not existing', async () => { it('should handle README.md not existing', async () => {
await libraryGenerator(tree, { await libraryGenerator(tree, {
name: 'my-lib', name: 'my-lib',
projectNameAndRootFormat: 'as-provided',
}); });
const readmePath = join(schema.relativeToRootDestination, 'README.md'); const readmePath = join(schema.relativeToRootDestination, 'README.md');
tree.delete(readmePath); tree.delete(readmePath);
@ -39,17 +40,15 @@ describe('updateReadme', () => {
it('should update README.md contents', async () => { it('should update README.md contents', async () => {
await libraryGenerator(tree, { await libraryGenerator(tree, {
name: 'my-lib', name: 'my-lib',
projectNameAndRootFormat: 'as-provided',
}); });
// This step is usually handled elsewhere // This step is usually handled elsewhere
tree.rename( tree.rename('my-lib/README.md', 'shared/my-destination/README.md');
'libs/my-lib/README.md',
'libs/shared/my-destination/README.md'
);
updateReadme(tree, schema); updateReadme(tree, schema);
const content = tree const content = tree
.read('/libs/shared/my-destination/README.md') .read('shared/my-destination/README.md')
.toString('utf8'); .toString('utf8');
expect(content).toMatch('# shared-my-destination'); expect(content).toMatch('# shared-my-destination');
expect(content).toMatch('nx test shared-my-destination'); expect(content).toMatch('nx test shared-my-destination');

View File

@ -16,6 +16,7 @@ describe('updateStorybookConfig', () => {
it('should handle storybook config not existing', async () => { it('should handle storybook config not existing', async () => {
await libraryGenerator(tree, { await libraryGenerator(tree, {
name: 'my-source', name: 'my-source',
projectNameAndRootFormat: 'as-provided',
}); });
const projectConfig = readProjectConfiguration(tree, 'my-source'); const projectConfig = readProjectConfiguration(tree, 'my-source');
const schema: NormalizedSchema = { const schema: NormalizedSchema = {
@ -24,7 +25,7 @@ describe('updateStorybookConfig', () => {
importPath: '@proj/my-destination', importPath: '@proj/my-destination',
updateImportPath: true, updateImportPath: true,
newProjectName: 'my-destination', newProjectName: 'my-destination',
relativeToRootDestination: 'libs/my-destination', relativeToRootDestination: 'my-destination',
}; };
expect(() => expect(() =>
@ -34,14 +35,14 @@ describe('updateStorybookConfig', () => {
it('should update the import path for main.js', async () => { it('should update the import path for main.js', async () => {
const storybookMain = ` const storybookMain = `
const rootMain = require('../../../.storybook/main'); const rootMain = require('../../.storybook/main');
module.exports = rootMain; module.exports = rootMain;
`; `;
const storybookMainPath = const storybookMainPath = 'namespace/my-destination/.storybook/main.js';
'/libs/namespace/my-destination/.storybook/main.js';
await libraryGenerator(tree, { await libraryGenerator(tree, {
name: 'my-source', name: 'my-source',
projectNameAndRootFormat: 'as-provided',
}); });
const projectConfig = readProjectConfiguration(tree, 'my-source'); const projectConfig = readProjectConfiguration(tree, 'my-source');
tree.write(storybookMainPath, storybookMain); tree.write(storybookMainPath, storybookMain);
@ -51,25 +52,26 @@ describe('updateStorybookConfig', () => {
importPath: '@proj/namespace-my-destination', importPath: '@proj/namespace-my-destination',
updateImportPath: true, updateImportPath: true,
newProjectName: 'namespace-my-destination', newProjectName: 'namespace-my-destination',
relativeToRootDestination: 'libs/namespace/my-destination', relativeToRootDestination: 'namespace/my-destination',
}; };
updateStorybookConfig(tree, schema, projectConfig); updateStorybookConfig(tree, schema, projectConfig);
const storybookMainAfter = tree.read(storybookMainPath, 'utf-8'); const storybookMainAfter = tree.read(storybookMainPath, 'utf-8');
expect(storybookMainAfter).toContain( expect(storybookMainAfter).toContain(
`const rootMain = require('../../../../.storybook/main');` `const rootMain = require('../../../.storybook/main');`
); );
}); });
it('should update the import path for webpack.config.json', async () => { it('should update the import path for webpack.config.json', async () => {
const storybookWebpackConfig = ` const storybookWebpackConfig = `
const rootWebpackConfig = require('../../../.storybook/webpack.config'); const rootWebpackConfig = require('../../.storybook/webpack.config');
`; `;
const storybookWebpackConfigPath = const storybookWebpackConfigPath =
'/libs/namespace/my-destination/.storybook/webpack.config.js'; 'namespace/my-destination/.storybook/webpack.config.js';
await libraryGenerator(tree, { await libraryGenerator(tree, {
name: 'my-source', name: 'my-source',
projectNameAndRootFormat: 'as-provided',
}); });
const projectConfig = readProjectConfiguration(tree, 'my-source'); const projectConfig = readProjectConfiguration(tree, 'my-source');
tree.write(storybookWebpackConfigPath, storybookWebpackConfig); tree.write(storybookWebpackConfigPath, storybookWebpackConfig);
@ -79,7 +81,7 @@ describe('updateStorybookConfig', () => {
importPath: '@proj/namespace-my-destination', importPath: '@proj/namespace-my-destination',
updateImportPath: true, updateImportPath: true,
newProjectName: 'namespace-my-destination', newProjectName: 'namespace-my-destination',
relativeToRootDestination: 'libs/namespace/my-destination', relativeToRootDestination: 'namespace/my-destination',
}; };
updateStorybookConfig(tree, schema, projectConfig); updateStorybookConfig(tree, schema, projectConfig);
@ -89,28 +91,28 @@ describe('updateStorybookConfig', () => {
'utf-8' 'utf-8'
); );
expect(storybookWebpackConfigAfter).toContain( expect(storybookWebpackConfigAfter).toContain(
`const rootWebpackConfig = require('../../../../.storybook/webpack.config');` `const rootWebpackConfig = require('../../../.storybook/webpack.config');`
); );
}); });
describe('directory', () => { describe('directory', () => {
it('should update the import path for directory/main.js', async () => { it('should update the import path for directory/main.js', async () => {
const storybookMain = ` const storybookMain = `
const rootMain = require('../../.storybook/main');
module.exports = rootMain;
`;
const storybookMainPath = 'namespace/my-destination/.storybook/main.js';
const storybookNestedMain = `
const rootMain = require('../../../.storybook/main'); const rootMain = require('../../../.storybook/main');
module.exports = rootMain; module.exports = rootMain;
`;
const storybookMainPath =
'/libs/namespace/my-destination/.storybook/main.js';
const storybookNestedMain = `
const rootMain = require('../../../../.storybook/main');
module.exports = rootMain;
`; `;
const storybookNestedMainPath = const storybookNestedMainPath =
'/libs/namespace/my-destination/.storybook/nested/main.js'; 'namespace/my-destination/.storybook/nested/main.js';
await libraryGenerator(tree, { await libraryGenerator(tree, {
name: 'my-source', name: 'my-source',
projectNameAndRootFormat: 'as-provided',
}); });
const projectConfig = readProjectConfiguration(tree, 'my-source'); const projectConfig = readProjectConfiguration(tree, 'my-source');
tree.write(storybookMainPath, storybookMain); tree.write(storybookMainPath, storybookMain);
@ -121,39 +123,40 @@ describe('updateStorybookConfig', () => {
importPath: '@proj/namespace-my-destination', importPath: '@proj/namespace-my-destination',
updateImportPath: true, updateImportPath: true,
newProjectName: 'namespace-my-destination', newProjectName: 'namespace-my-destination',
relativeToRootDestination: 'libs/namespace/my-destination', relativeToRootDestination: 'namespace/my-destination',
}; };
updateStorybookConfig(tree, schema, projectConfig); updateStorybookConfig(tree, schema, projectConfig);
const storybookMainAfter = tree.read(storybookMainPath, 'utf-8'); const storybookMainAfter = tree.read(storybookMainPath, 'utf-8');
expect(storybookMainAfter).toContain( expect(storybookMainAfter).toContain(
`const rootMain = require('../../../../.storybook/main');` `const rootMain = require('../../../.storybook/main');`
); );
const storybookNestedMainAfter = tree.read( const storybookNestedMainAfter = tree.read(
storybookNestedMainPath, storybookNestedMainPath,
'utf-8' 'utf-8'
); );
expect(storybookNestedMainAfter).toContain( expect(storybookNestedMainAfter).toContain(
`const rootMain = require('../../../../../.storybook/main');` `const rootMain = require('../../../../.storybook/main');`
); );
}); });
it('should update the import path for directory/webpack.config.json', async () => { it('should update the import path for directory/webpack.config.json', async () => {
const storybookWebpackConfig = ` const storybookWebpackConfig = `
const rootWebpackConfig = require('../../../.storybook/webpack.config'); const rootWebpackConfig = require('../../.storybook/webpack.config');
`; `;
const storybookWebpackConfigPath = const storybookWebpackConfigPath =
'/libs/namespace/my-destination/.storybook/webpack.config.js'; 'namespace/my-destination/.storybook/webpack.config.js';
const storybookNestedWebpackConfig = ` const storybookNestedWebpackConfig = `
const rootWebpackConfig = require('../../../../.storybook/webpack.config'); const rootWebpackConfig = require('../../../.storybook/webpack.config');
`; `;
const storybookNestedWebpackConfigPath = const storybookNestedWebpackConfigPath =
'/libs/namespace/my-destination/.storybook/nested/webpack.config.js'; 'namespace/my-destination/.storybook/nested/webpack.config.js';
await libraryGenerator(tree, { await libraryGenerator(tree, {
name: 'my-source', name: 'my-source',
projectNameAndRootFormat: 'as-provided',
}); });
const projectConfig = readProjectConfiguration(tree, 'my-source'); const projectConfig = readProjectConfiguration(tree, 'my-source');
tree.write(storybookWebpackConfigPath, storybookWebpackConfig); tree.write(storybookWebpackConfigPath, storybookWebpackConfig);
@ -167,7 +170,7 @@ describe('updateStorybookConfig', () => {
importPath: '@proj/namespace-my-destination', importPath: '@proj/namespace-my-destination',
updateImportPath: true, updateImportPath: true,
newProjectName: 'namespace-my-destination', newProjectName: 'namespace-my-destination',
relativeToRootDestination: 'libs/namespace/my-destination', relativeToRootDestination: 'namespace/my-destination',
}; };
updateStorybookConfig(tree, schema, projectConfig); updateStorybookConfig(tree, schema, projectConfig);
@ -177,7 +180,7 @@ describe('updateStorybookConfig', () => {
'utf-8' 'utf-8'
); );
expect(storybookWebpackConfigAfter).toContain( expect(storybookWebpackConfigAfter).toContain(
`const rootWebpackConfig = require('../../../../.storybook/webpack.config');` `const rootWebpackConfig = require('../../../.storybook/webpack.config');`
); );
const storybookNestedWebpackConfigAfter = tree.read( const storybookNestedWebpackConfigAfter = tree.read(
@ -185,7 +188,7 @@ describe('updateStorybookConfig', () => {
'utf-8' 'utf-8'
); );
expect(storybookNestedWebpackConfigAfter).toContain( expect(storybookNestedWebpackConfigAfter).toContain(
`const rootWebpackConfig = require('../../../../../.storybook/webpack.config');` `const rootWebpackConfig = require('../../../../.storybook/webpack.config');`
); );
}); });
}); });

View File

@ -19,10 +19,6 @@ export function getDestination(
schema: Schema, schema: Schema,
project: ProjectConfiguration project: ProjectConfiguration
): string { ): string {
if (schema.destinationRelativeToRoot) {
return schema.destination;
}
const projectType = project.projectType; const projectType = project.projectType;
const workspaceLayout = getWorkspaceLayout(host); const workspaceLayout = getWorkspaceLayout(host);

View File

@ -12,47 +12,61 @@ describe('move', () => {
}); });
it('should update jest config when moving down directories', async () => { it('should update jest config when moving down directories', async () => {
await libraryGenerator(tree, { name: 'my-lib' }); await libraryGenerator(tree, {
name: 'my-lib',
projectNameAndRootFormat: 'as-provided',
});
await moveGenerator(tree, { await moveGenerator(tree, {
projectName: 'my-lib', projectName: 'my-lib',
importPath: '@proj/shared-mylib', importPath: '@proj/shared-mylib',
updateImportPath: true, updateImportPath: true,
destination: 'shared/my-lib-new', destination: 'shared/my-lib-new',
projectNameAndRootFormat: 'as-provided',
}); });
const jestConfigPath = 'libs/shared/my-lib-new/jest.config.ts';
const jestConfigPath = 'shared/my-lib-new/jest.config.ts';
const afterJestConfig = tree.read(jestConfigPath, 'utf-8'); const afterJestConfig = tree.read(jestConfigPath, 'utf-8');
expect(tree.exists(jestConfigPath)).toBeTruthy(); expect(tree.exists(jestConfigPath)).toBeTruthy();
expect(afterJestConfig).toContain("preset: '../../../jest.preset.js'"); expect(afterJestConfig).toContain("preset: '../../jest.preset.js'");
expect(afterJestConfig).toContain( expect(afterJestConfig).toContain(
"coverageDirectory: '../../../coverage/libs/shared/my-lib-new'" "coverageDirectory: '../../coverage/shared/my-lib-new'"
); );
}); });
it('should update jest config when moving up directories', async () => { it('should update jest config when moving up directories', async () => {
await libraryGenerator(tree, { name: 'shared/my-lib' }); await libraryGenerator(tree, {
name: 'shared-my-lib',
directory: 'shared/my-lib',
projectNameAndRootFormat: 'as-provided',
});
await moveGenerator(tree, { await moveGenerator(tree, {
projectName: 'shared-my-lib', projectName: 'shared-my-lib',
importPath: '@proj/mylib', importPath: '@proj/mylib',
updateImportPath: true, updateImportPath: true,
destination: 'my-lib-new', destination: 'my-lib-new',
projectNameAndRootFormat: 'as-provided',
}); });
const jestConfigPath = 'libs/my-lib-new/jest.config.ts';
const jestConfigPath = 'my-lib-new/jest.config.ts';
const afterJestConfig = tree.read(jestConfigPath, 'utf-8'); const afterJestConfig = tree.read(jestConfigPath, 'utf-8');
expect(tree.exists(jestConfigPath)).toBeTruthy(); expect(tree.exists(jestConfigPath)).toBeTruthy();
expect(afterJestConfig).toContain("preset: '../../jest.preset.js'"); expect(afterJestConfig).toContain("preset: '../jest.preset.js'");
expect(afterJestConfig).toContain( expect(afterJestConfig).toContain(
"coverageDirectory: '../../coverage/libs/my-lib-new'" "coverageDirectory: '../coverage/my-lib-new'"
); );
}); });
it('should update $schema path when move', async () => { it('should update $schema path when move', async () => {
await libraryGenerator(tree, { name: 'my-lib' }); await libraryGenerator(tree, {
name: 'my-lib',
projectNameAndRootFormat: 'as-provided',
});
let projectJson = readJson(tree, 'libs/my-lib/project.json'); let projectJson = readJson(tree, 'my-lib/project.json');
expect(projectJson['$schema']).toEqual( expect(projectJson['$schema']).toEqual(
'../../node_modules/nx/schemas/project-schema.json' '../node_modules/nx/schemas/project-schema.json'
); );
await moveGenerator(tree, { await moveGenerator(tree, {
@ -60,11 +74,12 @@ describe('move', () => {
importPath: '@proj/shared-mylib', importPath: '@proj/shared-mylib',
updateImportPath: true, updateImportPath: true,
destination: 'shared/my-lib-new', destination: 'shared/my-lib-new',
projectNameAndRootFormat: 'as-provided',
}); });
projectJson = readJson(tree, 'libs/shared/my-lib-new/project.json'); projectJson = readJson(tree, 'shared/my-lib-new/project.json');
expect(projectJson['$schema']).toEqual( expect(projectJson['$schema']).toEqual(
'../../../node_modules/nx/schemas/project-schema.json' '../../node_modules/nx/schemas/project-schema.json'
); );
}); });
@ -80,6 +95,7 @@ describe('move', () => {
buildable: true, buildable: true,
unitTestRunner: 'jest', unitTestRunner: 'jest',
linter: 'eslint', linter: 'eslint',
projectNameAndRootFormat: 'as-provided',
}); });
updateJson(tree, 'tsconfig.json', (json) => { updateJson(tree, 'tsconfig.json', (json) => {
@ -100,12 +116,13 @@ describe('move', () => {
importPath: '@proj/my-lib', importPath: '@proj/my-lib',
updateImportPath: true, updateImportPath: true,
destination: 'my-lib', destination: 'my-lib',
projectNameAndRootFormat: 'as-provided',
}); });
expect(readJson(tree, 'libs/my-lib/project.json')).toMatchObject({ expect(readJson(tree, 'my-lib/project.json')).toMatchObject({
name: 'my-lib', name: 'my-lib',
$schema: '../../node_modules/nx/schemas/project-schema.json', $schema: '../node_modules/nx/schemas/project-schema.json',
sourceRoot: 'libs/my-lib/src', sourceRoot: 'my-lib/src',
projectType: 'library', projectType: 'library',
targets: { targets: {
build: { build: {
@ -113,18 +130,15 @@ describe('move', () => {
outputs: ['{options.outputPath}'], outputs: ['{options.outputPath}'],
options: { options: {
outputPath: 'dist/my-lib', outputPath: 'dist/my-lib',
main: 'libs/my-lib/src/index.ts', main: 'my-lib/src/index.ts',
tsConfig: 'libs/my-lib/tsconfig.lib.json', tsConfig: 'my-lib/tsconfig.lib.json',
}, },
}, },
lint: { lint: {
executor: '@nx/linter:eslint', executor: '@nx/linter:eslint',
outputs: ['{options.outputFile}'], outputs: ['{options.outputFile}'],
options: { options: {
lintFilePatterns: [ lintFilePatterns: ['my-lib/**/*.ts', 'my-lib/package.json'],
'libs/my-lib/**/*.ts',
'libs/my-lib/package.json',
],
}, },
}, },
test: { test: {
@ -134,22 +148,22 @@ describe('move', () => {
}, },
}); });
expect(readJson(tree, 'libs/my-lib/tsconfig.json')).toMatchObject({ expect(readJson(tree, 'my-lib/tsconfig.json')).toMatchObject({
extends: '../../tsconfig.base.json', extends: '../tsconfig.base.json',
files: ['../../node_modules/@foo/bar/index.d.ts'], files: ['../node_modules/@foo/bar/index.d.ts'],
references: [ references: [
{ path: './tsconfig.lib.json' }, { path: './tsconfig.lib.json' },
{ path: './tsconfig.spec.json' }, { path: './tsconfig.spec.json' },
], ],
}); });
const jestConfig = tree.read('libs/my-lib/jest.config.lib.ts', 'utf-8'); const jestConfig = tree.read('my-lib/jest.config.lib.ts', 'utf-8');
expect(jestConfig).toContain(`preset: '../../jest.preset.js'`); expect(jestConfig).toContain(`preset: '../jest.preset.js'`);
expect(tree.exists('libs/my-lib/tsconfig.lib.json')).toBeTruthy(); expect(tree.exists('my-lib/tsconfig.lib.json')).toBeTruthy();
expect(tree.exists('libs/my-lib/tsconfig.spec.json')).toBeTruthy(); expect(tree.exists('my-lib/tsconfig.spec.json')).toBeTruthy();
expect(tree.exists('libs/my-lib/.eslintrc.json')).toBeTruthy(); expect(tree.exists('my-lib/.eslintrc.json')).toBeTruthy();
expect(tree.exists('libs/my-lib/src/index.ts')).toBeTruthy(); expect(tree.exists('my-lib/src/index.ts')).toBeTruthy();
// Test that other libs and workspace files are not moved. // Test that other libs and workspace files are not moved.
expect(tree.exists('package.json')).toBeTruthy(); expect(tree.exists('package.json')).toBeTruthy();
@ -162,4 +176,32 @@ describe('move', () => {
expect(tree.exists('jest.config.ts')).toBeTruthy(); expect(tree.exists('jest.config.ts')).toBeTruthy();
expect(tree.exists('.eslintrc.base.json')).toBeTruthy(); expect(tree.exists('.eslintrc.base.json')).toBeTruthy();
}); });
it('should move project correctly when --project-name-and-root-format=derived', async () => {
await libraryGenerator(tree, {
name: 'my-lib',
projectNameAndRootFormat: 'derived',
});
await moveGenerator(tree, {
projectName: 'my-lib',
importPath: '@proj/shared-mylib',
updateImportPath: true,
destination: 'shared/my-lib-new',
projectNameAndRootFormat: 'derived',
});
const projectJson = readJson(tree, 'libs/shared/my-lib-new/project.json');
expect(projectJson['$schema']).toEqual(
'../../../node_modules/nx/schemas/project-schema.json'
);
const afterJestConfig = tree.read(
'libs/shared/my-lib-new/jest.config.ts',
'utf-8'
);
expect(afterJestConfig).toContain("preset: '../../../jest.preset.js'");
expect(afterJestConfig).toContain(
"coverageDirectory: '../../../coverage/libs/shared/my-lib-new'"
);
});
}); });

View File

@ -9,6 +9,7 @@ import { checkDestination } from './lib/check-destination';
import { createProjectConfigurationInNewDestination } from './lib/create-project-configuration-in-new-destination'; import { createProjectConfigurationInNewDestination } from './lib/create-project-configuration-in-new-destination';
import { moveProjectFiles } from './lib/move-project-files'; import { moveProjectFiles } from './lib/move-project-files';
import { normalizeSchema } from './lib/normalize-schema'; import { normalizeSchema } from './lib/normalize-schema';
import { runAngularPlugin } from './lib/run-angular-plugin';
import { updateBuildTargets } from './lib/update-build-targets'; import { updateBuildTargets } from './lib/update-build-targets';
import { updateCypressConfig } from './lib/update-cypress-config'; import { updateCypressConfig } from './lib/update-cypress-config';
import { updateDefaultProject } from './lib/update-default-project'; import { updateDefaultProject } from './lib/update-default-project';
@ -28,9 +29,16 @@ import {
import { Schema } from './schema'; import { Schema } from './schema';
export async function moveGenerator(tree: Tree, rawSchema: Schema) { export async function moveGenerator(tree: Tree, rawSchema: Schema) {
await moveGeneratorInternal(tree, {
projectNameAndRootFormat: 'derived',
...rawSchema,
});
}
export async function moveGeneratorInternal(tree: Tree, rawSchema: Schema) {
let projectConfig = readProjectConfiguration(tree, rawSchema.projectName); let projectConfig = readProjectConfiguration(tree, rawSchema.projectName);
checkDestination(tree, rawSchema, projectConfig); const schema = await normalizeSchema(tree, rawSchema, projectConfig);
const schema = normalizeSchema(tree, rawSchema, projectConfig); checkDestination(tree, schema, rawSchema.destination);
if (projectConfig.root === '.') { if (projectConfig.root === '.') {
maybeExtractTsConfigBase(tree); maybeExtractTsConfigBase(tree);
@ -55,6 +63,8 @@ export async function moveGenerator(tree: Tree, rawSchema: Schema) {
updateDefaultProject(tree, schema); updateDefaultProject(tree, schema);
updateImplicitDependencies(tree, schema); updateImplicitDependencies(tree, schema);
await runAngularPlugin(tree, schema);
if (!schema.skipFormat) { if (!schema.skipFormat) {
await formatFiles(tree); await formatFiles(tree);
} }

View File

@ -1,14 +1,15 @@
import { ProjectNameAndRootFormat } from '@nx/devkit/src/generators/project-name-and-root-utils';
export interface Schema { export interface Schema {
projectName: string; projectName: string;
destination: string; destination: string;
importPath?: string; importPath?: string;
updateImportPath: boolean; updateImportPath: boolean;
skipFormat?: boolean; skipFormat?: boolean;
destinationRelativeToRoot?: boolean;
newProjectName?: string; newProjectName?: string;
projectNameAndRootFormat?: ProjectNameAndRootFormat;
} }
export interface NormalizedSchema extends Schema { export interface NormalizedSchema extends Schema {
importPath: string;
relativeToRootDestination: string; relativeToRootDestination: string;
} }

View File

@ -18,6 +18,13 @@
"description": "The name of the project to move.", "description": "The name of the project to move.",
"x-dropdown": "projects" "x-dropdown": "projects"
}, },
"newProjectName": {
"type": "string",
"alias": "project",
"description": "The new name of the project after the move.",
"pattern": "(?:^@[a-zA-Z0-9-*~][a-zA-Z0-9-*._~]*\\/[a-zA-Z0-9-~][a-zA-Z0-9-._~]*|^[a-zA-Z][^:]*)$",
"x-priority": "important"
},
"destination": { "destination": {
"type": "string", "type": "string",
"description": "The folder to move the project into.", "description": "The folder to move the project into.",
@ -26,6 +33,11 @@
"index": 0 "index": 0
} }
}, },
"projectNameAndRootFormat": {
"description": "Whether to generate the new project name and destination as provided (`as-provided`) or generate them composing their values and taking the configured layout into account (`derived`).",
"type": "string",
"enum": ["as-provided", "derived"]
},
"importPath": { "importPath": {
"type": "string", "type": "string",
"description": "The new import path to use in the `tsconfig.base.json`." "description": "The new import path to use in the `tsconfig.base.json`."

View File

@ -60,7 +60,7 @@ async function moveWorkspaceGeneratorsToLocalPlugin(tree: Tree) {
); );
project = readProjectConfiguration(tree, PROJECT_NAME); project = readProjectConfiguration(tree, PROJECT_NAME);
} }
await updateExistingPlugin(tree, project); updateExistingPlugin(tree, project);
return tasks; return tasks;
} }
@ -187,7 +187,7 @@ async function createNewPlugin(tree: Tree) {
e2eTestRunner: 'none', e2eTestRunner: 'none',
publishable: false, publishable: false,
}); });
getCreateGeneratorsJson()( await getCreateGeneratorsJson()(
tree, tree,
readProjectConfiguration(tree, PROJECT_NAME).root, readProjectConfiguration(tree, PROJECT_NAME).root,
PROJECT_NAME PROJECT_NAME
@ -207,8 +207,8 @@ function moveGeneratedPlugin(
projectName: PROJECT_NAME, projectName: PROJECT_NAME,
newProjectName: PROJECT_NAME, newProjectName: PROJECT_NAME,
updateImportPath: true, updateImportPath: true,
destinationRelativeToRoot: true,
importPath: importPath, importPath: importPath,
projectNameAndRootFormat: 'as-provided',
}); });
} }
} }

View File

@ -9,7 +9,7 @@ export function getImportPath(tree: Tree, projectDirectory: string): string {
: projectDirectory; : projectDirectory;
} }
function getNpmScope(tree: Tree) { export function getNpmScope(tree: Tree) {
const nxJson = readNxJson(tree); const nxJson = readNxJson(tree);
// TODO(v17): Remove reading this from nx.json // TODO(v17): Remove reading this from nx.json