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:
parent
6b272ed4c4
commit
8564d9ba12
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "move",
|
||||
"factory": "./src/generators/move/move#angularMoveGenerator",
|
||||
"factory": "./src/generators/move/move#angularMoveGeneratorInternal",
|
||||
"schema": {
|
||||
"$schema": "http://json-schema.org/schema",
|
||||
"$id": "NxAngularMove",
|
||||
@ -22,12 +22,24 @@
|
||||
"x-dropdown": "projects",
|
||||
"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": {
|
||||
"type": "string",
|
||||
"description": "The folder to move the Angular project into.",
|
||||
"$default": { "$source": "argv", "index": 0 },
|
||||
"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": {
|
||||
"type": "string",
|
||||
"description": "The new import path to use in the `tsconfig.base.json`."
|
||||
@ -50,7 +62,8 @@
|
||||
},
|
||||
"aliases": ["mv"],
|
||||
"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,
|
||||
"path": "/packages/angular/src/generators/move/schema.json",
|
||||
"type": "generator"
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "move",
|
||||
"factory": "./src/generators/move/move#moveGenerator",
|
||||
"factory": "./src/generators/move/move#moveGeneratorInternal",
|
||||
"schema": {
|
||||
"$schema": "http://json-schema.org/schema",
|
||||
"$id": "NxWorkspaceMove",
|
||||
@ -21,11 +21,23 @@
|
||||
"description": "The name of the project to move.",
|
||||
"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": {
|
||||
"type": "string",
|
||||
"description": "The folder to move the project into.",
|
||||
"$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": {
|
||||
"type": "string",
|
||||
"description": "The new import path to use in the `tsconfig.base.json`."
|
||||
@ -48,7 +60,7 @@
|
||||
},
|
||||
"aliases": ["mv"],
|
||||
"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,
|
||||
"path": "/packages/workspace/src/generators/move/schema.json",
|
||||
"type": "generator"
|
||||
|
||||
@ -20,7 +20,9 @@ describe('Move Angular Project', () => {
|
||||
app1 = uniq('app1');
|
||||
app2 = uniq('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());
|
||||
@ -30,28 +32,26 @@ describe('Move Angular Project', () => {
|
||||
*/
|
||||
it('should work for apps', () => {
|
||||
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
|
||||
expect(moveOutput).toContain(`DELETE apps/${app1}`);
|
||||
expect(moveOutput).toContain(`CREATE apps/${newPath}/jest.config.ts`);
|
||||
expect(moveOutput).toContain(`CREATE apps/${newPath}/tsconfig.app.json`);
|
||||
expect(moveOutput).toContain(`CREATE apps/${newPath}/tsconfig.json`);
|
||||
expect(moveOutput).toContain(`CREATE apps/${newPath}/tsconfig.spec.json`);
|
||||
expect(moveOutput).toContain(`CREATE apps/${newPath}/.eslintrc.json`);
|
||||
expect(moveOutput).toContain(`CREATE apps/${newPath}/src/favicon.ico`);
|
||||
expect(moveOutput).toContain(`CREATE apps/${newPath}/src/index.html`);
|
||||
expect(moveOutput).toContain(`CREATE apps/${newPath}/src/main.ts`);
|
||||
expect(moveOutput).toContain(`CREATE apps/${newPath}/src/styles.css`);
|
||||
expect(moveOutput).toContain(`CREATE apps/${newPath}/src/test-setup.ts`);
|
||||
expect(moveOutput).toContain(`DELETE ${app1}`);
|
||||
expect(moveOutput).toContain(`CREATE ${newPath}/jest.config.ts`);
|
||||
expect(moveOutput).toContain(`CREATE ${newPath}/tsconfig.app.json`);
|
||||
expect(moveOutput).toContain(`CREATE ${newPath}/tsconfig.json`);
|
||||
expect(moveOutput).toContain(`CREATE ${newPath}/tsconfig.spec.json`);
|
||||
expect(moveOutput).toContain(`CREATE ${newPath}/.eslintrc.json`);
|
||||
expect(moveOutput).toContain(`CREATE ${newPath}/src/favicon.ico`);
|
||||
expect(moveOutput).toContain(`CREATE ${newPath}/src/index.html`);
|
||||
expect(moveOutput).toContain(`CREATE ${newPath}/src/main.ts`);
|
||||
expect(moveOutput).toContain(`CREATE ${newPath}/src/styles.css`);
|
||||
expect(moveOutput).toContain(`CREATE ${newPath}/src/test-setup.ts`);
|
||||
expect(moveOutput).toContain(
|
||||
`CREATE apps/${newPath}/src/app/app.component.html`
|
||||
`CREATE ${newPath}/src/app/app.component.html`
|
||||
);
|
||||
expect(moveOutput).toContain(
|
||||
`CREATE apps/${newPath}/src/app/app.module.ts`
|
||||
);
|
||||
expect(moveOutput).toContain(`CREATE apps/${newPath}/src/assets/.gitkeep`);
|
||||
expect(moveOutput).toContain(`CREATE ${newPath}/src/app/app.module.ts`);
|
||||
expect(moveOutput).toContain(`CREATE ${newPath}/src/assets/.gitkeep`);
|
||||
});
|
||||
|
||||
/**
|
||||
@ -61,7 +61,7 @@ describe('Move Angular Project', () => {
|
||||
// by default the cypress config doesn't contain any app specific paths
|
||||
// create a custom config with some app specific paths
|
||||
updateFile(
|
||||
`apps/${app1}-e2e/cypress.config.ts`,
|
||||
`${app1}-e2e/cypress.config.ts`,
|
||||
`
|
||||
import { defineConfig } from 'cypress';
|
||||
import { nxE2EPreset } from '@nx/cypress/plugins/cypress-preset';
|
||||
@ -69,27 +69,25 @@ describe('Move Angular Project', () => {
|
||||
export default defineConfig({
|
||||
e2e: {
|
||||
...nxE2EPreset(__dirname),
|
||||
videosFolder: '../../dist/cypress/apps/${app1}-e2e/videos',
|
||||
screenshotsFolder: '../../dist/cypress/apps/${app1}-e2e/screenshots',
|
||||
videosFolder: '../dist/cypress/${app1}-e2e/videos',
|
||||
screenshotsFolder: '../dist/cypress/${app1}-e2e/screenshots',
|
||||
},
|
||||
});
|
||||
`
|
||||
);
|
||||
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
|
||||
const cypressConfigPath = `apps/${newPath}-e2e/cypress.config.ts`;
|
||||
const cypressConfigPath = `${newPath}-e2e/cypress.config.ts`;
|
||||
expect(moveOutput).toContain(`CREATE ${cypressConfigPath}`);
|
||||
checkFilesExist(cypressConfigPath);
|
||||
const cypressConfig = readFile(cypressConfigPath);
|
||||
|
||||
expect(cypressConfig).toContain(`../../dist/cypress/${newPath}-e2e/videos`);
|
||||
expect(cypressConfig).toContain(
|
||||
`../../../dist/cypress/apps/${newPath}-e2e/videos`
|
||||
);
|
||||
expect(cypressConfig).toContain(
|
||||
`../../../dist/cypress/apps/${newPath}-e2e/screenshots`
|
||||
`../../dist/cypress/${newPath}-e2e/screenshots`
|
||||
);
|
||||
});
|
||||
|
||||
@ -99,13 +97,73 @@ describe('Move Angular Project', () => {
|
||||
it('should work for libraries', () => {
|
||||
const lib1 = 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
|
||||
*/
|
||||
|
||||
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(
|
||||
`libs/${lib2}/src/lib/${lib2}.module.ts`,
|
||||
@ -115,7 +173,7 @@ describe('Move Angular Project', () => {
|
||||
);
|
||||
|
||||
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}`;
|
||||
|
||||
@ -83,15 +83,17 @@ describe('Workspace Tests', () => {
|
||||
const lib1 = uniq('mylib');
|
||||
const lib2 = 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(
|
||||
`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'); }`
|
||||
);
|
||||
|
||||
updateFile(
|
||||
`libs/${lib1}/data-access/src/index.ts`,
|
||||
`${lib1}/data-access/src/index.ts`,
|
||||
`export * from './lib/${lib1}-data-access.ts'`
|
||||
);
|
||||
|
||||
@ -99,11 +101,13 @@ describe('Workspace Tests', () => {
|
||||
* 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(
|
||||
`libs/${lib2}/ui/src/lib/${lib2}-ui.ts`,
|
||||
`import { fromLibOne } from '@${proj}/${lib1}/data-access';
|
||||
`${lib2}/ui/src/lib/${lib2}-ui.ts`,
|
||||
`import { fromLibOne } from '@${proj}/${lib1}-data-access';
|
||||
|
||||
export const fromLibTwo = () => fromLibOne();`
|
||||
);
|
||||
@ -112,7 +116,9 @@ describe('Workspace Tests', () => {
|
||||
* 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) => {
|
||||
config.implicitDependencies = [`${lib1}-data-access`];
|
||||
return config;
|
||||
@ -123,13 +129,13 @@ describe('Workspace Tests', () => {
|
||||
*/
|
||||
|
||||
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(exists(`libs/${lib1}/data-access`)).toBeFalsy();
|
||||
expect(moveOutput).toContain(`DELETE ${lib1}/data-access`);
|
||||
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 readmePath = `${newPath}/README.md`;
|
||||
@ -141,8 +147,8 @@ describe('Workspace Tests', () => {
|
||||
checkFilesExist(jestConfigPath);
|
||||
const jestConfig = readFile(jestConfigPath);
|
||||
expect(jestConfig).toContain(`displayName: 'shared-${lib1}-data-access'`);
|
||||
expect(jestConfig).toContain(`preset: '../../../../jest.preset.js'`);
|
||||
expect(jestConfig).toContain(`'../../../../coverage/${newPath}'`);
|
||||
expect(jestConfig).toContain(`preset: '../../../jest.preset.js'`);
|
||||
expect(jestConfig).toContain(`'../../../coverage/${newPath}'`);
|
||||
|
||||
const tsConfigPath = `${newPath}/tsconfig.json`;
|
||||
expect(moveOutput).toContain(`CREATE ${tsConfigPath}`);
|
||||
@ -153,7 +159,7 @@ describe('Workspace Tests', () => {
|
||||
checkFilesExist(tsConfigLibPath);
|
||||
const tsConfigLib = readJson(tsConfigLibPath);
|
||||
expect(tsConfigLib.compilerOptions.outDir).toEqual(
|
||||
'../../../../dist/out-tsc'
|
||||
'../../../dist/out-tsc'
|
||||
);
|
||||
|
||||
const tsConfigSpecPath = `${newPath}/tsconfig.spec.json`;
|
||||
@ -161,7 +167,7 @@ describe('Workspace Tests', () => {
|
||||
checkFilesExist(tsConfigSpecPath);
|
||||
const tsConfigSpec = readJson(tsConfigSpecPath);
|
||||
expect(tsConfigSpec.compilerOptions.outDir).toEqual(
|
||||
'../../../../dist/out-tsc'
|
||||
'../../../dist/out-tsc'
|
||||
);
|
||||
|
||||
const indexPath = `${newPath}/src/index.ts`;
|
||||
@ -186,13 +192,13 @@ describe('Workspace Tests', () => {
|
||||
expect(moveOutput).toContain('UPDATE tsconfig.base.json');
|
||||
const rootTsConfig = readJson('tsconfig.base.json');
|
||||
expect(
|
||||
rootTsConfig.compilerOptions.paths[`@${proj}/${lib1}/data-access`]
|
||||
rootTsConfig.compilerOptions.paths[`@${proj}/${lib1}-data-access`]
|
||||
).toBeUndefined();
|
||||
expect(
|
||||
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();
|
||||
expect(projects[`${lib1}-data-access`]).toBeUndefined();
|
||||
@ -200,17 +206,17 @@ describe('Workspace Tests', () => {
|
||||
expect(project).toBeTruthy();
|
||||
expect(project.sourceRoot).toBe(`${newPath}/src`);
|
||||
expect(project.targets.lint.options.lintFilePatterns).toEqual([
|
||||
`libs/shared/${lib1}/data-access/**/*.ts`,
|
||||
`libs/shared/${lib1}/data-access/package.json`,
|
||||
`shared/${lib1}/data-access/**/*.ts`,
|
||||
`shared/${lib1}/data-access/package.json`,
|
||||
]);
|
||||
|
||||
/**
|
||||
* 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);
|
||||
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 lib3 = uniq('mylib');
|
||||
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(
|
||||
`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'); }`
|
||||
);
|
||||
|
||||
updateFile(
|
||||
`libs/${lib1}/data-access/src/index.ts`,
|
||||
`${lib1}/data-access/src/index.ts`,
|
||||
`export * from './lib/${lib1}-data-access.ts'`
|
||||
);
|
||||
|
||||
@ -237,10 +243,12 @@ describe('Workspace Tests', () => {
|
||||
* 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(
|
||||
`libs/${lib2}/ui/src/lib/${lib2}-ui.ts`,
|
||||
`${lib2}/ui/src/lib/${lib2}-ui.ts`,
|
||||
`import { fromLibOne } from '${importPath}';
|
||||
|
||||
export const fromLibTwo = () => fromLibOne();`
|
||||
@ -250,7 +258,9 @@ describe('Workspace Tests', () => {
|
||||
* 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) => {
|
||||
config.implicitDependencies = [`${lib1}-data-access`];
|
||||
return config;
|
||||
@ -261,13 +271,13 @@ describe('Workspace Tests', () => {
|
||||
*/
|
||||
|
||||
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(exists(`libs/${lib1}/data-access`)).toBeFalsy();
|
||||
expect(moveOutput).toContain(`DELETE ${lib1}/data-access`);
|
||||
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 readmePath = `${newPath}/README.md`;
|
||||
@ -279,8 +289,8 @@ describe('Workspace Tests', () => {
|
||||
checkFilesExist(jestConfigPath);
|
||||
const jestConfig = readFile(jestConfigPath);
|
||||
expect(jestConfig).toContain(`displayName: 'shared-${lib1}-data-access'`);
|
||||
expect(jestConfig).toContain(`preset: '../../../../jest.preset.js'`);
|
||||
expect(jestConfig).toContain(`'../../../../coverage/${newPath}'`);
|
||||
expect(jestConfig).toContain(`preset: '../../../jest.preset.js'`);
|
||||
expect(jestConfig).toContain(`'../../../coverage/${newPath}'`);
|
||||
|
||||
const tsConfigPath = `${newPath}/tsconfig.json`;
|
||||
expect(moveOutput).toContain(`CREATE ${tsConfigPath}`);
|
||||
@ -291,7 +301,7 @@ describe('Workspace Tests', () => {
|
||||
checkFilesExist(tsConfigLibPath);
|
||||
const tsConfigLib = readJson(tsConfigLibPath);
|
||||
expect(tsConfigLib.compilerOptions.outDir).toEqual(
|
||||
'../../../../dist/out-tsc'
|
||||
'../../../dist/out-tsc'
|
||||
);
|
||||
|
||||
const tsConfigSpecPath = `${newPath}/tsconfig.spec.json`;
|
||||
@ -299,7 +309,7 @@ describe('Workspace Tests', () => {
|
||||
checkFilesExist(tsConfigSpecPath);
|
||||
const tsConfigSpec = readJson(tsConfigSpecPath);
|
||||
expect(tsConfigSpec.compilerOptions.outDir).toEqual(
|
||||
'../../../../dist/out-tsc'
|
||||
'../../../dist/out-tsc'
|
||||
);
|
||||
|
||||
const indexPath = `${newPath}/src/index.ts`;
|
||||
@ -313,13 +323,13 @@ describe('Workspace Tests', () => {
|
||||
expect(moveOutput).toContain('UPDATE tsconfig.base.json');
|
||||
const rootTsConfig = readJson('tsconfig.base.json');
|
||||
expect(
|
||||
rootTsConfig.compilerOptions.paths[`@${proj}/${lib1}/data-access`]
|
||||
rootTsConfig.compilerOptions.paths[`@${proj}/${lib1}-data-access`]
|
||||
).toBeUndefined();
|
||||
expect(
|
||||
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();
|
||||
expect(projects[`${lib1}-data-access`]).toBeUndefined();
|
||||
@ -331,21 +341,21 @@ describe('Workspace Tests', () => {
|
||||
expect(lib3Config.implicitDependencies).toEqual([newName]);
|
||||
|
||||
expect(project.targets.lint.options.lintFilePatterns).toEqual([
|
||||
`libs/shared/${lib1}/data-access/**/*.ts`,
|
||||
`libs/shared/${lib1}/data-access/package.json`,
|
||||
`shared/${lib1}/data-access/**/*.ts`,
|
||||
`shared/${lib1}/data-access/package.json`,
|
||||
]);
|
||||
|
||||
/**
|
||||
* 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);
|
||||
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 lib2 = uniq('mylib');
|
||||
const lib3 = uniq('mylib');
|
||||
@ -354,7 +364,9 @@ describe('Workspace Tests', () => {
|
||||
nxJson.workspaceLayout = { libsDir: 'packages' };
|
||||
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(
|
||||
`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
|
||||
*/
|
||||
|
||||
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(
|
||||
`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
|
||||
*/
|
||||
|
||||
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) => {
|
||||
config.implicitDependencies = [`${lib1}-data-access`];
|
||||
return config;
|
||||
@ -394,7 +410,7 @@ describe('Workspace Tests', () => {
|
||||
*/
|
||||
|
||||
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`);
|
||||
@ -483,26 +499,27 @@ describe('Workspace Tests', () => {
|
||||
const lib1 = uniq('lib1');
|
||||
const lib2 = uniq('lib2');
|
||||
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(
|
||||
`libs/${lib1}/src/lib/${lib1}.ts`,
|
||||
`${lib1}/src/lib/${lib1}.ts`,
|
||||
`export function fromLibOne() { console.log('This is completely pointless'); }`
|
||||
);
|
||||
|
||||
updateFile(
|
||||
`libs/${lib1}/src/index.ts`,
|
||||
`export * from './lib/${lib1}.ts'`
|
||||
);
|
||||
updateFile(`${lib1}/src/index.ts`, `export * from './lib/${lib1}.ts'`);
|
||||
|
||||
/**
|
||||
* 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(
|
||||
`libs/${lib2}/ui/src/lib/${lib2}-ui.ts`,
|
||||
`${lib2}/ui/src/lib/${lib2}-ui.ts`,
|
||||
`import { fromLibOne } from '@${proj}/${lib1}';
|
||||
|
||||
export const fromLibTwo = () => fromLibOne();`
|
||||
@ -512,7 +529,9 @@ describe('Workspace Tests', () => {
|
||||
* 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) => {
|
||||
config.implicitDependencies = [lib1];
|
||||
return config;
|
||||
@ -523,13 +542,13 @@ describe('Workspace Tests', () => {
|
||||
*/
|
||||
|
||||
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(exists(`libs/${lib1}/project.json`)).toBeFalsy();
|
||||
expect(moveOutput).toContain(`DELETE ${lib1}/project.json`);
|
||||
expect(exists(`${lib1}/project.json`)).toBeFalsy();
|
||||
|
||||
const newPath = `libs/${lib1}/data-access`;
|
||||
const newPath = `${lib1}/data-access`;
|
||||
const newName = `${lib1}-data-access`;
|
||||
|
||||
const readmePath = `${newPath}/README.md`;
|
||||
@ -541,8 +560,8 @@ describe('Workspace Tests', () => {
|
||||
checkFilesExist(jestConfigPath);
|
||||
const jestConfig = readFile(jestConfigPath);
|
||||
expect(jestConfig).toContain(`displayName: '${lib1}-data-access'`);
|
||||
expect(jestConfig).toContain(`preset: '../../../jest.preset.js'`);
|
||||
expect(jestConfig).toContain(`'../../../coverage/${newPath}'`);
|
||||
expect(jestConfig).toContain(`preset: '../../jest.preset.js'`);
|
||||
expect(jestConfig).toContain(`'../../coverage/${newPath}'`);
|
||||
|
||||
const tsConfigPath = `${newPath}/tsconfig.json`;
|
||||
expect(moveOutput).toContain(`CREATE ${tsConfigPath}`);
|
||||
@ -552,17 +571,13 @@ describe('Workspace Tests', () => {
|
||||
expect(moveOutput).toContain(`CREATE ${tsConfigLibPath}`);
|
||||
checkFilesExist(tsConfigLibPath);
|
||||
const tsConfigLib = readJson(tsConfigLibPath);
|
||||
expect(tsConfigLib.compilerOptions.outDir).toEqual(
|
||||
'../../../dist/out-tsc'
|
||||
);
|
||||
expect(tsConfigLib.compilerOptions.outDir).toEqual('../../dist/out-tsc');
|
||||
|
||||
const tsConfigSpecPath = `${newPath}/tsconfig.spec.json`;
|
||||
expect(moveOutput).toContain(`CREATE ${tsConfigSpecPath}`);
|
||||
checkFilesExist(tsConfigSpecPath);
|
||||
const tsConfigSpec = readJson(tsConfigSpecPath);
|
||||
expect(tsConfigSpec.compilerOptions.outDir).toEqual(
|
||||
'../../../dist/out-tsc'
|
||||
);
|
||||
expect(tsConfigSpec.compilerOptions.outDir).toEqual('../../dist/out-tsc');
|
||||
|
||||
const indexPath = `${newPath}/src/index.ts`;
|
||||
expect(moveOutput).toContain(`CREATE ${indexPath}`);
|
||||
@ -587,8 +602,8 @@ describe('Workspace Tests', () => {
|
||||
rootTsConfig.compilerOptions.paths[`@${proj}/${lib1}`]
|
||||
).toBeUndefined();
|
||||
expect(
|
||||
rootTsConfig.compilerOptions.paths[`@${proj}/${lib1}/data-access`]
|
||||
).toEqual([`libs/${lib1}/data-access/src/index.ts`]);
|
||||
rootTsConfig.compilerOptions.paths[`@${proj}/${lib1}-data-access`]
|
||||
).toEqual([`${lib1}/data-access/src/index.ts`]);
|
||||
|
||||
projects = await readResolvedConfiguration();
|
||||
expect(projects[lib1]).toBeUndefined();
|
||||
@ -596,17 +611,17 @@ describe('Workspace Tests', () => {
|
||||
expect(project).toBeTruthy();
|
||||
expect(project.sourceRoot).toBe(`${newPath}/src`);
|
||||
expect(project.targets.lint.options.lintFilePatterns).toEqual([
|
||||
`libs/${lib1}/data-access/**/*.ts`,
|
||||
`libs/${lib1}/data-access/package.json`,
|
||||
`${lib1}/data-access/**/*.ts`,
|
||||
`${lib1}/data-access/package.json`,
|
||||
]);
|
||||
|
||||
/**
|
||||
* 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);
|
||||
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 lib2 = 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');
|
||||
expect(
|
||||
rootTsConfig.compilerOptions.paths[`@${proj}/${lib1}/data-access`]
|
||||
rootTsConfig.compilerOptions.paths[`@${proj}/${lib1}-data-access`]
|
||||
).toBeUndefined();
|
||||
expect(
|
||||
rootTsConfig.compilerOptions.paths[`${lib1}/data-access`]
|
||||
rootTsConfig.compilerOptions.paths[`${lib1}-data-access`]
|
||||
).toBeDefined();
|
||||
|
||||
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'); }`
|
||||
);
|
||||
|
||||
updateFile(
|
||||
`libs/${lib1}/data-access/src/index.ts`,
|
||||
`${lib1}/data-access/src/index.ts`,
|
||||
`export * from './lib/${lib1}-data-access.ts'`
|
||||
);
|
||||
|
||||
@ -641,11 +658,13 @@ describe('Workspace Tests', () => {
|
||||
* 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(
|
||||
`libs/${lib2}/ui/src/lib/${lib2}-ui.ts`,
|
||||
`import { fromLibOne } from '${lib1}/data-access';
|
||||
`${lib2}/ui/src/lib/${lib2}-ui.ts`,
|
||||
`import { fromLibOne } from '${lib1}-data-access';
|
||||
|
||||
export const fromLibTwo = () => fromLibOne();`
|
||||
);
|
||||
@ -654,7 +673,9 @@ describe('Workspace Tests', () => {
|
||||
* 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) => {
|
||||
config.implicitDependencies = [`${lib1}-data-access`];
|
||||
return config;
|
||||
@ -665,13 +686,13 @@ describe('Workspace Tests', () => {
|
||||
*/
|
||||
|
||||
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(exists(`libs/${lib1}/data-access`)).toBeFalsy();
|
||||
expect(moveOutput).toContain(`DELETE ${lib1}/data-access`);
|
||||
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 readmePath = `${newPath}/README.md`;
|
||||
@ -698,11 +719,11 @@ describe('Workspace Tests', () => {
|
||||
expect(moveOutput).toContain('UPDATE tsconfig.base.json');
|
||||
rootTsConfig = readJson('tsconfig.base.json');
|
||||
expect(
|
||||
rootTsConfig.compilerOptions.paths[`${lib1}/data-access`]
|
||||
rootTsConfig.compilerOptions.paths[`${lib1}-data-access`]
|
||||
).toBeUndefined();
|
||||
expect(
|
||||
rootTsConfig.compilerOptions.paths[`shared/${lib1}/data-access`]
|
||||
).toEqual([`libs/shared/${lib1}/data-access/src/index.ts`]);
|
||||
rootTsConfig.compilerOptions.paths[`shared-${lib1}-data-access`]
|
||||
).toEqual([`shared/${lib1}/data-access/src/index.ts`]);
|
||||
|
||||
const projects = await readResolvedConfiguration();
|
||||
expect(projects[`${lib1}-data-access`]).toBeUndefined();
|
||||
@ -710,17 +731,17 @@ describe('Workspace Tests', () => {
|
||||
expect(project).toBeTruthy();
|
||||
expect(project.sourceRoot).toBe(`${newPath}/src`);
|
||||
expect(project.targets.lint.options.lintFilePatterns).toEqual([
|
||||
`libs/shared/${lib1}/data-access/**/*.ts`,
|
||||
`libs/shared/${lib1}/data-access/package.json`,
|
||||
`shared/${lib1}/data-access/**/*.ts`,
|
||||
`shared/${lib1}/data-access/package.json`,
|
||||
]);
|
||||
|
||||
/**
|
||||
* 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);
|
||||
expect(lib2File).toContain(
|
||||
`import { fromLibOne } from 'shared/${lib1}/data-access';`
|
||||
`import { fromLibOne } from 'shared-${lib1}-data-access';`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@ -74,7 +74,8 @@
|
||||
"factory": "./src/generators/move/compat",
|
||||
"schema": "./src/generators/move/schema.json",
|
||||
"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": {
|
||||
"factory": "./src/generators/convert-to-with-mf/convert-to-with-mf.compat",
|
||||
@ -230,10 +231,11 @@
|
||||
"description": "Generate a Remote Angular Module Federation Application."
|
||||
},
|
||||
"move": {
|
||||
"factory": "./src/generators/move/move#angularMoveGenerator",
|
||||
"factory": "./src/generators/move/move#angularMoveGeneratorInternal",
|
||||
"schema": "./src/generators/move/schema.json",
|
||||
"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": {
|
||||
"factory": "./src/generators/convert-to-with-mf/convert-to-with-mf",
|
||||
|
||||
@ -26,6 +26,7 @@
|
||||
"./src/utils": "./src/utils/public-api.js",
|
||||
"./plugins/component-testing": "./plugins/component-testing.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/*.impl": "./src/builders/*.impl.js",
|
||||
"./src/executors/*/schema.json": "./src/executors/*/schema.json",
|
||||
|
||||
@ -1,4 +1,3 @@
|
||||
export * from './normalize-schema';
|
||||
export * from './update-module-name';
|
||||
export * from './update-ng-package';
|
||||
export * from './update-secondary-entry-points';
|
||||
|
||||
@ -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,
|
||||
};
|
||||
}
|
||||
4
packages/angular/src/generators/move/lib/types.ts
Normal file
4
packages/angular/src/generators/move/lib/types.ts
Normal file
@ -0,0 +1,4 @@
|
||||
export type MoveImplOptions = {
|
||||
oldProjectName: string;
|
||||
newProjectName: string;
|
||||
};
|
||||
@ -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 {}`);
|
||||
});
|
||||
});
|
||||
});
|
||||
@ -7,7 +7,7 @@ import {
|
||||
Tree,
|
||||
visitNotIgnoredFiles,
|
||||
} 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)
|
||||
@ -19,8 +19,16 @@ import type { NormalizedSchema } from '../schema';
|
||||
*/
|
||||
export function updateModuleName(
|
||||
tree: Tree,
|
||||
{ projectName: oldProjectName, newProjectName }: NormalizedSchema
|
||||
{ oldProjectName, newProjectName }: MoveImplOptions
|
||||
): void {
|
||||
const unscopedNewProjectName = newProjectName.startsWith('@')
|
||||
? newProjectName.split('/')[1]
|
||||
: newProjectName;
|
||||
|
||||
if (oldProjectName === unscopedNewProjectName) {
|
||||
return;
|
||||
}
|
||||
|
||||
const project = readProjectConfiguration(tree, newProjectName);
|
||||
|
||||
if (project.projectType === 'application') {
|
||||
@ -31,14 +39,14 @@ export function updateModuleName(
|
||||
|
||||
const moduleName = {
|
||||
from: `${names(oldProjectName).className}Module`,
|
||||
to: `${names(newProjectName).className}Module`,
|
||||
to: `${names(unscopedNewProjectName).className}Module`,
|
||||
};
|
||||
|
||||
const findModuleName = new RegExp(`\\b${moduleName.from}`, 'g');
|
||||
|
||||
const moduleFile = {
|
||||
from: `${oldProjectName}.module`,
|
||||
to: `${newProjectName}.module`,
|
||||
to: `${unscopedNewProjectName}.module`,
|
||||
};
|
||||
|
||||
const findFileName = new RegExp(`\\b${moduleFile.from}`, 'g');
|
||||
|
||||
@ -7,9 +7,9 @@ import {
|
||||
workspaceRoot,
|
||||
} from '@nx/devkit';
|
||||
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);
|
||||
|
||||
if (project.projectType === 'application') {
|
||||
|
||||
@ -6,7 +6,7 @@ import {
|
||||
visitNotIgnoredFiles,
|
||||
} from '@nx/devkit';
|
||||
import { basename, dirname } from 'path';
|
||||
import type { NormalizedSchema } from '../schema';
|
||||
import type { MoveImplOptions } from './types';
|
||||
|
||||
const libraryExecutors = [
|
||||
'@angular-devkit/build-angular:ng-packagr',
|
||||
@ -19,8 +19,12 @@ const libraryExecutors = [
|
||||
|
||||
export function updateSecondaryEntryPoints(
|
||||
tree: Tree,
|
||||
schema: NormalizedSchema
|
||||
schema: MoveImplOptions
|
||||
): void {
|
||||
if (schema.oldProjectName === schema.newProjectName) {
|
||||
return;
|
||||
}
|
||||
|
||||
const project = readProjectConfiguration(tree, schema.newProjectName);
|
||||
|
||||
if (project.projectType !== 'library') {
|
||||
@ -47,7 +51,7 @@ export function updateSecondaryEntryPoints(
|
||||
updateReadme(
|
||||
tree,
|
||||
dirname(filePath),
|
||||
schema.projectName,
|
||||
schema.oldProjectName,
|
||||
schema.newProjectName
|
||||
);
|
||||
});
|
||||
|
||||
35
packages/angular/src/generators/move/move-impl.ts
Normal file
35
packages/angular/src/generators/move/move-impl.ts
Normal 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'
|
||||
);
|
||||
}
|
||||
@ -1,20 +1,38 @@
|
||||
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 { Linter } from '@nx/linter';
|
||||
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 { angularMoveGenerator } from './move';
|
||||
|
||||
describe('@nx/angular:move', () => {
|
||||
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 () => {
|
||||
tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
|
||||
|
||||
await generateTestLibrary(tree, {
|
||||
name: 'mylib',
|
||||
name: 'my-lib',
|
||||
buildable: false,
|
||||
linter: Linter.EsLint,
|
||||
publishable: false,
|
||||
@ -23,32 +41,38 @@ describe('@nx/angular:move', () => {
|
||||
unitTestRunner: UnitTestRunner.Jest,
|
||||
});
|
||||
|
||||
jest.clearAllMocks();
|
||||
jest
|
||||
.spyOn(devkit, 'createProjectGraphAsync')
|
||||
.mockImplementation(() => Promise.resolve(projectGraph));
|
||||
});
|
||||
|
||||
it('should move a project', async () => {
|
||||
addProjectToGraph('my-lib');
|
||||
|
||||
await angularMoveGenerator(tree, {
|
||||
projectName: 'mylib',
|
||||
projectName: 'my-lib',
|
||||
newProjectName: 'mynewlib',
|
||||
destination: 'mynewlib',
|
||||
updateImportPath: true,
|
||||
projectNameAndRootFormat: 'as-provided',
|
||||
});
|
||||
|
||||
expect(tree.exists('libs/mynewlib/src/lib/mynewlib.module.ts')).toEqual(
|
||||
true
|
||||
);
|
||||
expect(tree.exists('mynewlib/src/lib/mynewlib.module.ts')).toEqual(true);
|
||||
});
|
||||
|
||||
it('should update ng-package.json dest property', async () => {
|
||||
await generateTestLibrary(tree, { name: 'mylib2', buildable: true });
|
||||
addProjectToGraph('mylib2');
|
||||
|
||||
await angularMoveGenerator(tree, {
|
||||
projectName: 'mylib2',
|
||||
destination: 'mynewlib2',
|
||||
updateImportPath: true,
|
||||
projectNameAndRootFormat: 'as-provided',
|
||||
});
|
||||
|
||||
const ngPackageJson = readJson(tree, 'libs/mynewlib2/ng-package.json');
|
||||
expect(ngPackageJson.dest).toEqual('../../dist/libs/mynewlib2');
|
||||
const ngPackageJson = readJson(tree, 'mynewlib2/ng-package.json');
|
||||
expect(ngPackageJson.dest).toEqual('../dist/mynewlib2');
|
||||
});
|
||||
|
||||
it('should update secondary entry points readme file', async () => {
|
||||
@ -57,14 +81,17 @@ describe('@nx/angular:move', () => {
|
||||
library: 'mylib2',
|
||||
name: 'testing',
|
||||
});
|
||||
addProjectToGraph('mylib2');
|
||||
|
||||
await angularMoveGenerator(tree, {
|
||||
projectName: 'mylib2',
|
||||
newProjectName: 'mynewlib2',
|
||||
destination: 'mynewlib2',
|
||||
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(`
|
||||
"# @proj/mynewlib2/testing
|
||||
|
||||
@ -73,28 +100,275 @@ describe('@nx/angular:move', () => {
|
||||
`);
|
||||
});
|
||||
|
||||
it('should format files', async () => {
|
||||
jest.spyOn(devkit, 'formatFiles');
|
||||
it('should handle nesting resulting in the same project name', async () => {
|
||||
addProjectToGraph('my-lib');
|
||||
|
||||
await angularMoveGenerator(tree, {
|
||||
projectName: 'mylib',
|
||||
destination: 'mynewlib',
|
||||
projectName: 'my-lib',
|
||||
destination: 'my/lib',
|
||||
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 () => {
|
||||
jest.spyOn(devkit, 'formatFiles');
|
||||
describe('move to subfolder', () => {
|
||||
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, {
|
||||
projectName: 'mylib',
|
||||
destination: 'mynewlib',
|
||||
updateImportPath: true,
|
||||
skipFormat: true,
|
||||
@NgModule({
|
||||
imports: [CommonModule]
|
||||
})
|
||||
export class MyLibModule {}`
|
||||
);
|
||||
|
||||
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');
|
||||
});
|
||||
});
|
||||
|
||||
@ -1,32 +1,21 @@
|
||||
import { formatFiles, Tree } from '@nx/devkit';
|
||||
import { moveGenerator } from '@nx/workspace/generators';
|
||||
import {
|
||||
normalizeSchema,
|
||||
updateModuleName,
|
||||
updateNgPackage,
|
||||
updateSecondaryEntryPoints,
|
||||
} from './lib';
|
||||
import type { Tree } from '@nx/devkit';
|
||||
import { moveGeneratorInternal } from '@nx/workspace/src/generators/move/move';
|
||||
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(
|
||||
tree: Tree,
|
||||
schema: Schema
|
||||
): Promise<void> {
|
||||
const normalizedSchema = normalizeSchema(tree, schema);
|
||||
|
||||
await moveGenerator(tree, { ...schema, skipFormat: true });
|
||||
updateModuleName(tree, normalizedSchema);
|
||||
updateNgPackage(tree, normalizedSchema);
|
||||
updateSecondaryEntryPoints(tree, normalizedSchema);
|
||||
|
||||
if (!normalizedSchema.skipFormat) {
|
||||
await formatFiles(tree);
|
||||
}
|
||||
await angularMoveGeneratorInternal(tree, {
|
||||
projectNameAndRootFormat: 'derived',
|
||||
...schema,
|
||||
});
|
||||
}
|
||||
|
||||
export async function angularMoveGeneratorInternal(
|
||||
tree: Tree,
|
||||
schema: Schema
|
||||
): Promise<void> {
|
||||
process.env.NX_ANGULAR_MOVE_INVOKED = 'true';
|
||||
await moveGeneratorInternal(tree, schema);
|
||||
}
|
||||
|
||||
@ -1,12 +1,11 @@
|
||||
import { ProjectNameAndRootFormat } from '@nx/devkit/src/generators/project-name-and-root-utils';
|
||||
|
||||
export interface Schema {
|
||||
projectName: string;
|
||||
destination: string;
|
||||
updateImportPath: boolean;
|
||||
importPath?: string;
|
||||
skipFormat?: boolean;
|
||||
}
|
||||
|
||||
export interface NormalizedSchema extends Schema {
|
||||
oldProjectRoot: string;
|
||||
newProjectName: string;
|
||||
newProjectName?: string;
|
||||
projectNameAndRootFormat?: ProjectNameAndRootFormat;
|
||||
}
|
||||
|
||||
@ -19,6 +19,13 @@
|
||||
"x-dropdown": "projects",
|
||||
"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": {
|
||||
"type": "string",
|
||||
"description": "The folder to move the Angular project into.",
|
||||
@ -28,6 +35,11 @@
|
||||
},
|
||||
"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": {
|
||||
"type": "string",
|
||||
"description": "The new import path to use in the `tsconfig.base.json`."
|
||||
|
||||
@ -47,7 +47,7 @@
|
||||
"hidden": true
|
||||
},
|
||||
"move": {
|
||||
"factory": "./src/generators/move/move#moveGenerator",
|
||||
"factory": "./src/generators/move/move#moveGeneratorInternal",
|
||||
"schema": "./src/generators/move/schema.json",
|
||||
"aliases": ["mv"],
|
||||
"description": "Move an application or library to another folder."
|
||||
|
||||
@ -61,12 +61,13 @@
|
||||
}
|
||||
},
|
||||
"dependencies": {
|
||||
"@nx/devkit": "file:../devkit",
|
||||
"chalk": "^4.1.0",
|
||||
"enquirer": "~2.3.6",
|
||||
"ignore": "^5.0.4",
|
||||
"rxjs": "^7.8.0",
|
||||
"tslib": "^2.3.0",
|
||||
"yargs-parser": "21.1.1",
|
||||
"@nx/devkit": "file:../devkit"
|
||||
"yargs-parser": "21.1.1"
|
||||
},
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
|
||||
@ -45,8 +45,8 @@ export async function monorepoGenerator(tree: Tree, options: {}) {
|
||||
libsDir,
|
||||
project.root === '.' ? project.name : project.root
|
||||
),
|
||||
destinationRelativeToRoot: true,
|
||||
updateImportPath: project.projectType === 'library',
|
||||
projectNameAndRootFormat: 'as-provided',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -4,7 +4,7 @@ import {
|
||||
Tree,
|
||||
} from '@nx/devkit';
|
||||
import { createTreeWithEmptyWorkspace } from '@nx/devkit/testing';
|
||||
import { Schema } from '../schema';
|
||||
import { NormalizedSchema } from '../schema';
|
||||
import { checkDestination } from './check-destination';
|
||||
|
||||
// nx-ignore-next-line
|
||||
@ -16,20 +16,24 @@ describe('checkDestination', () => {
|
||||
|
||||
beforeEach(async () => {
|
||||
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');
|
||||
});
|
||||
|
||||
it('should throw an error if the path is not explicit', async () => {
|
||||
const schema: Schema = {
|
||||
const schema: NormalizedSchema = {
|
||||
projectName: 'my-lib',
|
||||
destination: '../apps/not-an-app',
|
||||
importPath: undefined,
|
||||
updateImportPath: true,
|
||||
relativeToRootDestination: '',
|
||||
};
|
||||
|
||||
expect(() => {
|
||||
checkDestination(tree, schema, projectConfig);
|
||||
checkDestination(tree, schema, schema.destination);
|
||||
}).toThrow(
|
||||
`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 () => {
|
||||
await libraryGenerator(tree, {
|
||||
name: 'my-other-lib',
|
||||
projectNameAndRootFormat: 'as-provided',
|
||||
});
|
||||
|
||||
const schema: Schema = {
|
||||
const schema: NormalizedSchema = {
|
||||
projectName: 'my-lib',
|
||||
destination: 'my-other-lib',
|
||||
importPath: undefined,
|
||||
updateImportPath: true,
|
||||
relativeToRootDestination: 'my-other-lib',
|
||||
};
|
||||
|
||||
expect(() => {
|
||||
checkDestination(tree, schema, projectConfig);
|
||||
checkDestination(tree, schema, schema.destination);
|
||||
}).toThrow(
|
||||
`Invalid destination: [${schema.destination}] - Path is not empty.`
|
||||
);
|
||||
});
|
||||
|
||||
it('should NOT throw an error if the path is available', async () => {
|
||||
const schema: Schema = {
|
||||
const schema: NormalizedSchema = {
|
||||
projectName: 'my-lib',
|
||||
destination: 'my-other-lib',
|
||||
importPath: undefined,
|
||||
updateImportPath: true,
|
||||
relativeToRootDestination: 'my-other-lib',
|
||||
};
|
||||
|
||||
expect(() => {
|
||||
checkDestination(tree, schema, projectConfig);
|
||||
checkDestination(tree, schema, schema.destination);
|
||||
}).not.toThrow();
|
||||
});
|
||||
});
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import { ProjectConfiguration, Tree } from '@nx/devkit';
|
||||
import { Schema } from '../schema';
|
||||
import { getDestination } from './utils';
|
||||
import type { Tree } from '@nx/devkit';
|
||||
import type { NormalizedSchema } from '../schema';
|
||||
|
||||
/**
|
||||
* Checks whether the destination folder is valid
|
||||
@ -12,18 +11,16 @@ import { getDestination } from './utils';
|
||||
*/
|
||||
export function checkDestination(
|
||||
tree: Tree,
|
||||
schema: Schema,
|
||||
projectConfig: ProjectConfiguration
|
||||
schema: NormalizedSchema,
|
||||
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.`);
|
||||
}
|
||||
|
||||
const destination = getDestination(tree, schema, projectConfig);
|
||||
|
||||
if (tree.children(destination).length > 0) {
|
||||
if (tree.children(schema.relativeToRootDestination).length > 0) {
|
||||
throw new Error(`${INVALID_DESTINATION} - Path is not empty.`);
|
||||
}
|
||||
}
|
||||
|
||||
@ -16,7 +16,10 @@ describe('moveProject', () => {
|
||||
|
||||
beforeEach(async () => {
|
||||
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');
|
||||
});
|
||||
|
||||
@ -27,14 +30,13 @@ describe('moveProject', () => {
|
||||
importPath: '@proj/my-destination',
|
||||
updateImportPath: true,
|
||||
newProjectName: 'my-destination',
|
||||
relativeToRootDestination: 'libs/my-destination',
|
||||
relativeToRootDestination: 'my-destination',
|
||||
};
|
||||
|
||||
moveProjectFiles(tree, schema, projectConfig);
|
||||
|
||||
const destinationChildren = tree.children('libs/my-destination');
|
||||
const destinationChildren = tree.children('my-destination');
|
||||
expect(destinationChildren.length).toBeGreaterThan(0);
|
||||
expect(tree.exists('libs/my-lib')).toBeFalsy();
|
||||
expect(tree.children('libs')).not.toContain('my-lib');
|
||||
expect(tree.exists('my-lib')).toBeFalsy();
|
||||
});
|
||||
});
|
||||
|
||||
@ -31,7 +31,7 @@ describe('normalizeSchema', () => {
|
||||
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 = {
|
||||
destination: 'my/library',
|
||||
importPath: '@proj/my/library',
|
||||
@ -41,12 +41,12 @@ describe('normalizeSchema', () => {
|
||||
updateImportPath: true,
|
||||
};
|
||||
|
||||
const result = normalizeSchema(tree, schema, projectConfiguration);
|
||||
const result = await normalizeSchema(tree, schema, projectConfiguration);
|
||||
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should normalize destination and derive projectName correctly', () => {
|
||||
it('should normalize destination and derive projectName correctly', async () => {
|
||||
const expected: NormalizedSchema = {
|
||||
destination: 'my/library',
|
||||
importPath: '@proj/my/library',
|
||||
@ -56,7 +56,7 @@ describe('normalizeSchema', () => {
|
||||
updateImportPath: true,
|
||||
};
|
||||
|
||||
const result = normalizeSchema(
|
||||
const result = await normalizeSchema(
|
||||
tree,
|
||||
{ ...schema, destination: './my/library' },
|
||||
projectConfiguration
|
||||
@ -65,7 +65,7 @@ describe('normalizeSchema', () => {
|
||||
expect(result).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should use provided import path', () => {
|
||||
it('should use provided import path', async () => {
|
||||
const expected: NormalizedSchema = {
|
||||
destination: 'my/library',
|
||||
importPath: '@proj/my-awesome-library',
|
||||
@ -75,7 +75,7 @@ describe('normalizeSchema', () => {
|
||||
updateImportPath: true,
|
||||
};
|
||||
|
||||
const result = normalizeSchema(
|
||||
const result = await normalizeSchema(
|
||||
tree,
|
||||
{ ...schema, importPath: expected.importPath },
|
||||
projectConfiguration
|
||||
@ -90,7 +90,7 @@ describe('normalizeSchema', () => {
|
||||
return json;
|
||||
});
|
||||
|
||||
const result = normalizeSchema(tree, schema, projectConfiguration);
|
||||
const result = await normalizeSchema(tree, schema, projectConfiguration);
|
||||
|
||||
expect(result.relativeToRootDestination).toEqual('packages/my/library');
|
||||
});
|
||||
|
||||
@ -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 {
|
||||
getDestination,
|
||||
getNewProjectName,
|
||||
normalizePathSlashes,
|
||||
} from './utils';
|
||||
import { getImportPath } from '../../../utilities/get-import-path';
|
||||
|
||||
export function normalizeSchema(
|
||||
export async function normalizeSchema(
|
||||
tree: Tree,
|
||||
schema: Schema,
|
||||
projectConfiguration: ProjectConfiguration
|
||||
): NormalizedSchema {
|
||||
const destination = normalizePathSlashes(schema.destination);
|
||||
const newProjectName =
|
||||
schema.newProjectName ?? getNewProjectName(destination);
|
||||
|
||||
return {
|
||||
...schema,
|
||||
destination,
|
||||
importPath:
|
||||
schema.importPath ??
|
||||
normalizePathSlashes(getImportPath(tree, destination)),
|
||||
newProjectName,
|
||||
relativeToRootDestination: getDestination(
|
||||
): Promise<NormalizedSchema> {
|
||||
const { destination, newProjectName, importPath } =
|
||||
await determineProjectNameAndRootOptions(
|
||||
tree,
|
||||
schema,
|
||||
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';
|
||||
}
|
||||
|
||||
@ -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,
|
||||
});
|
||||
}
|
||||
@ -24,11 +24,14 @@ describe('updateCypressConfig', () => {
|
||||
importPath: '@proj/my-destination',
|
||||
updateImportPath: true,
|
||||
newProjectName: 'my-destination',
|
||||
relativeToRootDestination: 'libs/my-destination',
|
||||
relativeToRootDestination: 'my-destination',
|
||||
};
|
||||
|
||||
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');
|
||||
});
|
||||
|
||||
@ -46,18 +49,18 @@ describe('updateCypressConfig', () => {
|
||||
pluginsFile: './src/plugins/index',
|
||||
supportFile: false,
|
||||
video: true,
|
||||
videosFolder: '../../dist/cypress/libs/my-lib/videos',
|
||||
screenshotsFolder: '../../dist/cypress/libs/my-lib/screenshots',
|
||||
videosFolder: '../../dist/cypress/my-lib/videos',
|
||||
screenshotsFolder: '../../dist/cypress/my-lib/screenshots',
|
||||
chromeWebSecurity: false,
|
||||
};
|
||||
writeJson(tree, '/libs/my-destination/cypress.json', cypressJson);
|
||||
writeJson(tree, 'my-destination/cypress.json', cypressJson);
|
||||
|
||||
updateCypressConfig(tree, schema, projectConfig);
|
||||
|
||||
expect(readJson(tree, '/libs/my-destination/cypress.json')).toEqual({
|
||||
expect(readJson(tree, 'my-destination/cypress.json')).toEqual({
|
||||
...cypressJson,
|
||||
videosFolder: '../../dist/cypress/libs/my-destination/videos',
|
||||
screenshotsFolder: '../../dist/cypress/libs/my-destination/screenshots',
|
||||
videosFolder: '../../dist/cypress/my-destination/videos',
|
||||
screenshotsFolder: '../../dist/cypress/my-destination/screenshots',
|
||||
});
|
||||
});
|
||||
|
||||
@ -71,18 +74,16 @@ describe('updateCypressConfig', () => {
|
||||
video: false,
|
||||
chromeWebSecurity: false,
|
||||
};
|
||||
writeJson(tree, '/libs/my-destination/cypress.json', cypressJson);
|
||||
writeJson(tree, 'my-destination/cypress.json', cypressJson);
|
||||
|
||||
updateCypressConfig(tree, schema, projectConfig);
|
||||
|
||||
expect(readJson(tree, '/libs/my-destination/cypress.json')).toEqual(
|
||||
cypressJson
|
||||
);
|
||||
expect(readJson(tree, 'my-destination/cypress.json')).toEqual(cypressJson);
|
||||
});
|
||||
|
||||
it('should handle updating cypress.config.ts', async () => {
|
||||
tree.write(
|
||||
'/libs/my-destination/cypress.config.ts',
|
||||
'my-destination/cypress.config.ts',
|
||||
`
|
||||
import { defineConfig } from 'cypress';
|
||||
import { nxE2EPreset } from '@nx/cypress/plugins/cypress-preset';
|
||||
@ -90,23 +91,20 @@ import { nxE2EPreset } from '@nx/cypress/plugins/cypress-preset';
|
||||
export default defineConfig({
|
||||
e2e: {
|
||||
nxE2EPreset(__dirname),
|
||||
videosFolder: '../../dist/cypress/libs/my-lib/videos',
|
||||
screenshotsFolder: '../../dist/cypress/libs/my-lib/screenshots',
|
||||
videosFolder: '../../dist/cypress/my-lib/videos',
|
||||
screenshotsFolder: '../../dist/cypress/my-lib/screenshots',
|
||||
}
|
||||
});
|
||||
`
|
||||
);
|
||||
|
||||
updateCypressConfig(tree, schema, projectConfig);
|
||||
const fileContent = tree.read(
|
||||
'/libs/my-destination/cypress.config.ts',
|
||||
'utf-8'
|
||||
const fileContent = tree.read('my-destination/cypress.config.ts', 'utf-8');
|
||||
expect(fileContent).toContain(
|
||||
`videosFolder: '../../dist/cypress/my-destination/videos'`
|
||||
);
|
||||
expect(fileContent).toContain(
|
||||
`videosFolder: '../../dist/cypress/libs/my-destination/videos'`
|
||||
);
|
||||
expect(fileContent).toContain(
|
||||
`screenshotsFolder: '../../dist/cypress/libs/my-destination/screenshots'`
|
||||
`screenshotsFolder: '../../dist/cypress/my-destination/screenshots'`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@ -25,7 +25,7 @@ describe('updateEslint', () => {
|
||||
importPath: '@proj/shared-my-destination',
|
||||
updateImportPath: true,
|
||||
newProjectName: 'shared-my-destination',
|
||||
relativeToRootDestination: 'libs/shared/my-destination',
|
||||
relativeToRootDestination: 'shared/my-destination',
|
||||
};
|
||||
|
||||
tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
|
||||
@ -35,6 +35,7 @@ describe('updateEslint', () => {
|
||||
await libraryGenerator(tree, {
|
||||
name: 'my-lib',
|
||||
linter: Linter.None,
|
||||
projectNameAndRootFormat: 'as-provided',
|
||||
});
|
||||
|
||||
const projectConfig = readProjectConfiguration(tree, 'my-lib');
|
||||
@ -48,33 +49,33 @@ describe('updateEslint', () => {
|
||||
await libraryGenerator(tree, {
|
||||
name: 'my-lib',
|
||||
linter: Linter.EsLint,
|
||||
projectNameAndRootFormat: 'as-provided',
|
||||
});
|
||||
// This step is usually handled elsewhere
|
||||
tree.rename(
|
||||
'libs/my-lib/.eslintrc.json',
|
||||
'libs/shared/my-destination/.eslintrc.json'
|
||||
'my-lib/.eslintrc.json',
|
||||
'shared/my-destination/.eslintrc.json'
|
||||
);
|
||||
const projectConfig = readProjectConfiguration(tree, 'my-lib');
|
||||
|
||||
updateEslintConfig(tree, schema, projectConfig);
|
||||
|
||||
expect(
|
||||
readJson(tree, '/libs/shared/my-destination/.eslintrc.json')
|
||||
).toEqual(
|
||||
expect(readJson(tree, 'shared/my-destination/.eslintrc.json')).toEqual(
|
||||
expect.objectContaining({
|
||||
extends: ['../../../.eslintrc.json'],
|
||||
extends: ['../../.eslintrc.json'],
|
||||
})
|
||||
);
|
||||
});
|
||||
|
||||
it('should update .eslintrc.json extends path when project is moved from subdirectory', async () => {
|
||||
await libraryGenerator(tree, {
|
||||
name: 'test',
|
||||
directory: 'api',
|
||||
name: 'api-test',
|
||||
directory: 'api/test',
|
||||
linter: Linter.EsLint,
|
||||
projectNameAndRootFormat: 'as-provided',
|
||||
});
|
||||
// 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 newSchema = {
|
||||
@ -83,14 +84,14 @@ describe('updateEslint', () => {
|
||||
importPath: '@proj/test',
|
||||
updateImportPath: true,
|
||||
newProjectName: 'test',
|
||||
relativeToRootDestination: 'libs/test',
|
||||
relativeToRootDestination: 'test',
|
||||
};
|
||||
|
||||
updateEslintConfig(tree, newSchema, projectConfig);
|
||||
|
||||
expect(readJson(tree, '/libs/test/.eslintrc.json')).toEqual(
|
||||
expect(readJson(tree, 'test/.eslintrc.json')).toEqual(
|
||||
expect.objectContaining({
|
||||
extends: ['../../.eslintrc.json'],
|
||||
extends: ['../.eslintrc.json'],
|
||||
})
|
||||
);
|
||||
});
|
||||
@ -99,31 +100,30 @@ describe('updateEslint', () => {
|
||||
await libraryGenerator(tree, {
|
||||
name: 'my-lib',
|
||||
linter: Linter.EsLint,
|
||||
projectNameAndRootFormat: 'as-provided',
|
||||
});
|
||||
updateJson(tree, 'libs/my-lib/.eslintrc.json', (eslintRcJson) => {
|
||||
updateJson(tree, 'my-lib/.eslintrc.json', (eslintRcJson) => {
|
||||
eslintRcJson.extends = [
|
||||
'plugin:@nx/react',
|
||||
'../../.eslintrc.json',
|
||||
'../.eslintrc.json',
|
||||
'./customrc.json',
|
||||
];
|
||||
return eslintRcJson;
|
||||
});
|
||||
// This step is usually handled elsewhere
|
||||
tree.rename(
|
||||
'libs/my-lib/.eslintrc.json',
|
||||
'libs/shared/my-destination/.eslintrc.json'
|
||||
'my-lib/.eslintrc.json',
|
||||
'shared/my-destination/.eslintrc.json'
|
||||
);
|
||||
const projectConfig = readProjectConfiguration(tree, 'my-lib');
|
||||
|
||||
updateEslintConfig(tree, schema, projectConfig);
|
||||
|
||||
expect(
|
||||
readJson(tree, '/libs/shared/my-destination/.eslintrc.json')
|
||||
).toEqual(
|
||||
expect(readJson(tree, 'shared/my-destination/.eslintrc.json')).toEqual(
|
||||
expect.objectContaining({
|
||||
extends: [
|
||||
'plugin:@nx/react',
|
||||
'../../../.eslintrc.json',
|
||||
'../../.eslintrc.json',
|
||||
'./customrc.json',
|
||||
],
|
||||
})
|
||||
@ -135,24 +135,23 @@ describe('updateEslint', () => {
|
||||
name: 'my-lib',
|
||||
linter: Linter.EsLint,
|
||||
setParserOptionsProject: true,
|
||||
projectNameAndRootFormat: 'as-provided',
|
||||
});
|
||||
// This step is usually handled elsewhere
|
||||
tree.rename(
|
||||
'libs/my-lib/.eslintrc.json',
|
||||
'libs/shared/my-destination/.eslintrc.json'
|
||||
'my-lib/.eslintrc.json',
|
||||
'shared/my-destination/.eslintrc.json'
|
||||
);
|
||||
const projectConfig = readProjectConfiguration(tree, 'my-lib');
|
||||
|
||||
updateEslintConfig(tree, schema, projectConfig);
|
||||
|
||||
expect(
|
||||
readJson(tree, '/libs/shared/my-destination/.eslintrc.json')
|
||||
).toEqual(
|
||||
expect(readJson(tree, 'shared/my-destination/.eslintrc.json')).toEqual(
|
||||
expect.objectContaining({
|
||||
overrides: expect.arrayContaining([
|
||||
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',
|
||||
linter: Linter.EsLint,
|
||||
setParserOptionsProject: true,
|
||||
projectNameAndRootFormat: 'as-provided',
|
||||
});
|
||||
|
||||
// Add another parser project to eslint.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(
|
||||
`libs/my-lib/${storybookProject}`
|
||||
`my-lib/${storybookProject}`
|
||||
);
|
||||
return eslintRcJson;
|
||||
});
|
||||
|
||||
// This step is usually handled elsewhere
|
||||
tree.rename(
|
||||
'libs/my-lib/.eslintrc.json',
|
||||
'libs/shared/my-destination/.eslintrc.json'
|
||||
'my-lib/.eslintrc.json',
|
||||
'shared/my-destination/.eslintrc.json'
|
||||
);
|
||||
const projectConfig = readProjectConfiguration(tree, 'my-lib');
|
||||
|
||||
updateEslintConfig(tree, schema, projectConfig);
|
||||
|
||||
expect(
|
||||
readJson(tree, '/libs/shared/my-destination/.eslintrc.json')
|
||||
).toEqual(
|
||||
expect(readJson(tree, 'shared/my-destination/.eslintrc.json')).toEqual(
|
||||
expect.objectContaining({
|
||||
overrides: expect.arrayContaining([
|
||||
expect.objectContaining({
|
||||
parserOptions: expect.objectContaining({
|
||||
project: [
|
||||
'libs/shared/my-destination/tsconfig.*?.json',
|
||||
`libs/shared/my-destination/${storybookProject}`,
|
||||
'shared/my-destination/tsconfig.*?.json',
|
||||
`shared/my-destination/${storybookProject}`,
|
||||
],
|
||||
}),
|
||||
}),
|
||||
@ -208,28 +206,29 @@ describe('updateEslint', () => {
|
||||
name: 'my-lib',
|
||||
linter: Linter.EsLint,
|
||||
setParserOptionsProject: true,
|
||||
projectNameAndRootFormat: 'as-provided',
|
||||
});
|
||||
|
||||
// Add another parser project to eslint.json
|
||||
const storybookProject = '.storybook/tsconfig.json';
|
||||
updateJson(tree, '/libs/my-lib/.eslintrc.json', (eslintRcJson) => {
|
||||
eslintRcJson.overrides[0].parserOptions.project = `libs/my-lib/${storybookProject}`;
|
||||
updateJson(tree, 'my-lib/.eslintrc.json', (eslintRcJson) => {
|
||||
eslintRcJson.overrides[0].parserOptions.project = `my-lib/${storybookProject}`;
|
||||
return eslintRcJson;
|
||||
});
|
||||
|
||||
// This step is usually handled elsewhere
|
||||
tree.rename(
|
||||
'libs/my-lib/.eslintrc.json',
|
||||
'libs/shared/my-destination/.eslintrc.json'
|
||||
'my-lib/.eslintrc.json',
|
||||
'shared/my-destination/.eslintrc.json'
|
||||
);
|
||||
const projectConfig = readProjectConfiguration(tree, 'my-lib');
|
||||
|
||||
updateEslintConfig(tree, schema, projectConfig);
|
||||
|
||||
expect(
|
||||
readJson(tree, '/libs/shared/my-destination/.eslintrc.json').overrides[0]
|
||||
readJson(tree, 'shared/my-destination/.eslintrc.json').overrides[0]
|
||||
.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',
|
||||
updateImportPath: true,
|
||||
newProjectName: 'shared-my-destination',
|
||||
relativeToRootDestination: 'libs/shared/my-destination',
|
||||
relativeToRootDestination: 'shared/my-destination',
|
||||
};
|
||||
|
||||
tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
|
||||
@ -256,6 +255,7 @@ describe('updateEslint (flat config)', () => {
|
||||
await libraryGenerator(tree, {
|
||||
name: 'my-lib',
|
||||
linter: Linter.None,
|
||||
projectNameAndRootFormat: 'as-provided',
|
||||
});
|
||||
|
||||
const projectConfig = readProjectConfiguration(tree, 'my-lib');
|
||||
@ -269,31 +269,33 @@ describe('updateEslint (flat config)', () => {
|
||||
await libraryGenerator(tree, {
|
||||
name: 'my-lib',
|
||||
linter: Linter.EsLint,
|
||||
projectNameAndRootFormat: 'as-provided',
|
||||
});
|
||||
convertToFlat(tree, 'libs/my-lib');
|
||||
convertToFlat(tree, 'my-lib');
|
||||
// This step is usually handled elsewhere
|
||||
tree.rename(
|
||||
'libs/my-lib/eslint.config.js',
|
||||
'libs/shared/my-destination/eslint.config.js'
|
||||
'my-lib/eslint.config.js',
|
||||
'shared/my-destination/eslint.config.js'
|
||||
);
|
||||
const projectConfig = readProjectConfiguration(tree, 'my-lib');
|
||||
|
||||
updateEslintConfig(tree, schema, projectConfig);
|
||||
|
||||
expect(
|
||||
tree.read('libs/shared/my-destination/eslint.config.js', 'utf-8')
|
||||
).toEqual(expect.stringContaining(`require('../../../eslint.config.js')`));
|
||||
tree.read('shared/my-destination/eslint.config.js', 'utf-8')
|
||||
).toEqual(expect.stringContaining(`require('../../eslint.config.js')`));
|
||||
});
|
||||
|
||||
it('should update config extends path when project is moved from subdirectory', async () => {
|
||||
await libraryGenerator(tree, {
|
||||
name: 'test',
|
||||
directory: 'api',
|
||||
name: 'api-test',
|
||||
directory: 'api/test',
|
||||
linter: Linter.EsLint,
|
||||
projectNameAndRootFormat: 'as-provided',
|
||||
});
|
||||
convertToFlat(tree, 'libs/api/test');
|
||||
convertToFlat(tree, 'api/test');
|
||||
// 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');
|
||||
|
||||
@ -303,13 +305,13 @@ describe('updateEslint (flat config)', () => {
|
||||
importPath: '@proj/test',
|
||||
updateImportPath: true,
|
||||
newProjectName: 'test',
|
||||
relativeToRootDestination: 'libs/test',
|
||||
relativeToRootDestination: 'test',
|
||||
};
|
||||
|
||||
updateEslintConfig(tree, newSchema, projectConfig);
|
||||
|
||||
expect(tree.read('libs/test/eslint.config.js', 'utf-8')).toEqual(
|
||||
expect.stringContaining(`require('../../eslint.config.js')`)
|
||||
expect(tree.read('test/eslint.config.js', 'utf-8')).toEqual(
|
||||
expect.stringContaining(`require('../eslint.config.js')`)
|
||||
);
|
||||
});
|
||||
|
||||
@ -318,22 +320,23 @@ describe('updateEslint (flat config)', () => {
|
||||
name: 'my-lib',
|
||||
linter: Linter.EsLint,
|
||||
setParserOptionsProject: true,
|
||||
projectNameAndRootFormat: 'as-provided',
|
||||
});
|
||||
convertToFlat(tree, 'libs/my-lib', { hasParser: true });
|
||||
convertToFlat(tree, 'my-lib', { hasParser: true });
|
||||
// This step is usually handled elsewhere
|
||||
tree.rename(
|
||||
'libs/my-lib/eslint.config.js',
|
||||
'libs/shared/my-destination/eslint.config.js'
|
||||
'my-lib/eslint.config.js',
|
||||
'shared/my-destination/eslint.config.js'
|
||||
);
|
||||
const projectConfig = readProjectConfiguration(tree, 'my-lib');
|
||||
|
||||
updateEslintConfig(tree, schema, projectConfig);
|
||||
|
||||
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(
|
||||
`project: ["libs/shared/my-destination/tsconfig.*?.json"]`
|
||||
`project: ["shared/my-destination/tsconfig.*?.json"]`
|
||||
)
|
||||
);
|
||||
});
|
||||
@ -343,27 +346,28 @@ describe('updateEslint (flat config)', () => {
|
||||
name: 'my-lib',
|
||||
linter: Linter.EsLint,
|
||||
setParserOptionsProject: true,
|
||||
projectNameAndRootFormat: 'as-provided',
|
||||
});
|
||||
// Add another parser project to eslint.json
|
||||
const storybookProject = '.storybook/tsconfig.json';
|
||||
convertToFlat(tree, 'libs/my-lib', {
|
||||
convertToFlat(tree, 'my-lib', {
|
||||
hasParser: true,
|
||||
anotherProject: storybookProject,
|
||||
});
|
||||
// This step is usually handled elsewhere
|
||||
tree.rename(
|
||||
'libs/my-lib/eslint.config.js',
|
||||
'libs/shared/my-destination/eslint.config.js'
|
||||
'my-lib/eslint.config.js',
|
||||
'shared/my-destination/eslint.config.js'
|
||||
);
|
||||
const projectConfig = readProjectConfiguration(tree, 'my-lib');
|
||||
|
||||
updateEslintConfig(tree, schema, projectConfig);
|
||||
|
||||
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(
|
||||
`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',
|
||||
linter: Linter.EsLint,
|
||||
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
|
||||
tree.rename(
|
||||
'libs/my-lib/eslint.config.js',
|
||||
'libs/shared/my-destination/eslint.config.js'
|
||||
'my-lib/eslint.config.js',
|
||||
'shared/my-destination/eslint.config.js'
|
||||
);
|
||||
const projectConfig = readProjectConfiguration(tree, 'my-lib');
|
||||
|
||||
updateEslintConfig(tree, schema, projectConfig);
|
||||
|
||||
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(
|
||||
`project: "libs/shared/my-destination/tsconfig.*?.json"`
|
||||
`project: "shared/my-destination/tsconfig.*?.json"`
|
||||
)
|
||||
);
|
||||
});
|
||||
|
||||
@ -21,8 +21,10 @@ describe('updateImports', () => {
|
||||
|
||||
schema = {
|
||||
projectName: 'my-source',
|
||||
newProjectName: 'my-destination',
|
||||
destination: 'my-destination',
|
||||
updateImportPath: true,
|
||||
projectNameAndRootFormat: 'as-provided',
|
||||
};
|
||||
});
|
||||
|
||||
@ -33,14 +35,17 @@ describe('updateImports', () => {
|
||||
await libraryGenerator(tree, {
|
||||
name: 'my-destination',
|
||||
config: 'project',
|
||||
projectNameAndRootFormat: 'as-provided',
|
||||
});
|
||||
await libraryGenerator(tree, {
|
||||
name: 'my-source',
|
||||
projectNameAndRootFormat: 'as-provided',
|
||||
});
|
||||
await libraryGenerator(tree, {
|
||||
name: 'my-importer',
|
||||
projectNameAndRootFormat: 'as-provided',
|
||||
});
|
||||
const importerFilePath = 'libs/my-importer/src/importer.ts';
|
||||
const importerFilePath = 'my-importer/src/importer.ts';
|
||||
tree.write(
|
||||
importerFilePath,
|
||||
`
|
||||
@ -53,7 +58,7 @@ describe('updateImports', () => {
|
||||
|
||||
updateImports(
|
||||
tree,
|
||||
normalizeSchema(tree, schema, projectConfig),
|
||||
await normalizeSchema(tree, schema, projectConfig),
|
||||
projectConfig
|
||||
);
|
||||
|
||||
@ -66,12 +71,19 @@ describe('updateImports', () => {
|
||||
* be updated.
|
||||
*/
|
||||
it('should not update import paths when they contain a partial match', async () => {
|
||||
await libraryGenerator(tree, { name: 'table' });
|
||||
await libraryGenerator(tree, { name: 'tab' });
|
||||
await libraryGenerator(tree, {
|
||||
name: 'table',
|
||||
projectNameAndRootFormat: 'as-provided',
|
||||
});
|
||||
await libraryGenerator(tree, {
|
||||
name: 'tab',
|
||||
projectNameAndRootFormat: 'as-provided',
|
||||
});
|
||||
await libraryGenerator(tree, {
|
||||
name: 'my-importer',
|
||||
projectNameAndRootFormat: 'as-provided',
|
||||
});
|
||||
const importerFilePath = 'libs/my-importer/src/importer.ts';
|
||||
const importerFilePath = 'my-importer/src/importer.ts';
|
||||
tree.write(
|
||||
importerFilePath,
|
||||
`
|
||||
@ -92,7 +104,7 @@ describe('updateImports', () => {
|
||||
importPath: '@proj/tabs',
|
||||
updateImportPath: true,
|
||||
newProjectName: 'tabs',
|
||||
relativeToRootDestination: 'libs/tabs',
|
||||
relativeToRootDestination: 'tabs',
|
||||
},
|
||||
projectConfig
|
||||
);
|
||||
@ -107,12 +119,19 @@ describe('updateImports', () => {
|
||||
});
|
||||
|
||||
it('should correctly update deep imports', async () => {
|
||||
await libraryGenerator(tree, { name: 'table' });
|
||||
await libraryGenerator(tree, { name: 'tab' });
|
||||
await libraryGenerator(tree, {
|
||||
name: 'table',
|
||||
projectNameAndRootFormat: 'as-provided',
|
||||
});
|
||||
await libraryGenerator(tree, {
|
||||
name: 'tab',
|
||||
projectNameAndRootFormat: 'as-provided',
|
||||
});
|
||||
await libraryGenerator(tree, {
|
||||
name: 'my-importer',
|
||||
projectNameAndRootFormat: 'as-provided',
|
||||
});
|
||||
const importerFilePath = 'libs/my-importer/src/importer.ts';
|
||||
const importerFilePath = 'my-importer/src/importer.ts';
|
||||
tree.write(
|
||||
importerFilePath,
|
||||
`
|
||||
@ -133,7 +152,7 @@ describe('updateImports', () => {
|
||||
importPath: '@proj/tabs',
|
||||
updateImportPath: true,
|
||||
newProjectName: 'tabs',
|
||||
relativeToRootDestination: 'libs/tabs',
|
||||
relativeToRootDestination: 'tabs',
|
||||
},
|
||||
projectConfig
|
||||
);
|
||||
@ -148,12 +167,19 @@ describe('updateImports', () => {
|
||||
});
|
||||
|
||||
it('should update dynamic imports', async () => {
|
||||
await libraryGenerator(tree, { name: 'table' });
|
||||
await libraryGenerator(tree, { name: 'tab' });
|
||||
await libraryGenerator(tree, {
|
||||
name: 'table',
|
||||
projectNameAndRootFormat: 'as-provided',
|
||||
});
|
||||
await libraryGenerator(tree, {
|
||||
name: 'tab',
|
||||
projectNameAndRootFormat: 'as-provided',
|
||||
});
|
||||
await libraryGenerator(tree, {
|
||||
name: 'my-importer',
|
||||
projectNameAndRootFormat: 'as-provided',
|
||||
});
|
||||
const importerFilePath = 'libs/my-importer/src/importer.ts';
|
||||
const importerFilePath = 'my-importer/src/importer.ts';
|
||||
tree.write(
|
||||
importerFilePath,
|
||||
`
|
||||
@ -173,7 +199,7 @@ describe('updateImports', () => {
|
||||
importPath: '@proj/tabs',
|
||||
updateImportPath: true,
|
||||
newProjectName: 'tabs',
|
||||
relativeToRootDestination: 'libs/tabs',
|
||||
relativeToRootDestination: 'tabs',
|
||||
},
|
||||
projectConfig
|
||||
);
|
||||
@ -194,12 +220,19 @@ describe('updateImports', () => {
|
||||
});
|
||||
|
||||
it('should update require imports', async () => {
|
||||
await libraryGenerator(tree, { name: 'table' });
|
||||
await libraryGenerator(tree, { name: 'tab' });
|
||||
await libraryGenerator(tree, {
|
||||
name: 'table',
|
||||
projectNameAndRootFormat: 'as-provided',
|
||||
});
|
||||
await libraryGenerator(tree, {
|
||||
name: 'tab',
|
||||
projectNameAndRootFormat: 'as-provided',
|
||||
});
|
||||
await libraryGenerator(tree, {
|
||||
name: 'my-importer',
|
||||
projectNameAndRootFormat: 'as-provided',
|
||||
});
|
||||
const importerFilePath = 'libs/my-importer/src/importer.ts';
|
||||
const importerFilePath = 'my-importer/src/importer.ts';
|
||||
tree.write(
|
||||
importerFilePath,
|
||||
`
|
||||
@ -219,7 +252,7 @@ describe('updateImports', () => {
|
||||
importPath: '@proj/tabs',
|
||||
updateImportPath: true,
|
||||
newProjectName: 'tabs',
|
||||
relativeToRootDestination: 'libs/tabs',
|
||||
relativeToRootDestination: 'tabs',
|
||||
},
|
||||
projectConfig
|
||||
);
|
||||
@ -244,14 +277,17 @@ describe('updateImports', () => {
|
||||
// source and destination to make sure that the workspace has libraries with those names.
|
||||
await libraryGenerator(tree, {
|
||||
name: 'my-destination',
|
||||
projectNameAndRootFormat: 'as-provided',
|
||||
});
|
||||
await libraryGenerator(tree, {
|
||||
name: 'my-source',
|
||||
projectNameAndRootFormat: 'as-provided',
|
||||
});
|
||||
await libraryGenerator(tree, {
|
||||
name: 'my-importer',
|
||||
projectNameAndRootFormat: 'as-provided',
|
||||
});
|
||||
const importerFilePath = 'libs/my-importer/src/importer.ts';
|
||||
const importerFilePath = 'my-importer/src/importer.ts';
|
||||
tree.write(
|
||||
importerFilePath,
|
||||
`import { MyClass } from '@proj/my-source';
|
||||
@ -263,7 +299,7 @@ export MyExtendedClass extends MyClass {};`
|
||||
|
||||
updateImports(
|
||||
tree,
|
||||
normalizeSchema(
|
||||
await normalizeSchema(
|
||||
tree,
|
||||
{
|
||||
...schema,
|
||||
@ -282,31 +318,33 @@ export MyExtendedClass extends MyClass {};`
|
||||
it('should update project ref in the root tsconfig.base.json', async () => {
|
||||
await libraryGenerator(tree, {
|
||||
name: 'my-source',
|
||||
projectNameAndRootFormat: 'as-provided',
|
||||
});
|
||||
const projectConfig = readProjectConfiguration(tree, 'my-source');
|
||||
|
||||
updateImports(
|
||||
tree,
|
||||
normalizeSchema(tree, schema, projectConfig),
|
||||
await normalizeSchema(tree, schema, projectConfig),
|
||||
projectConfig
|
||||
);
|
||||
|
||||
const tsConfig = readJson(tree, '/tsconfig.base.json');
|
||||
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 () => {
|
||||
await libraryGenerator(tree, {
|
||||
name: 'my-source',
|
||||
projectNameAndRootFormat: 'as-provided',
|
||||
});
|
||||
updateJson(tree, '/tsconfig.base.json', (json) => {
|
||||
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'] = [
|
||||
'libs/my-source/some-path/src/index.ts',
|
||||
'my-source/some-path/src/index.ts',
|
||||
];
|
||||
return json;
|
||||
});
|
||||
@ -314,17 +352,15 @@ export MyExtendedClass extends MyClass {};`
|
||||
|
||||
updateImports(
|
||||
tree,
|
||||
normalizeSchema(tree, schema, projectConfig),
|
||||
await normalizeSchema(tree, schema, projectConfig),
|
||||
projectConfig
|
||||
);
|
||||
|
||||
const tsConfig = readJson(tree, '/tsconfig.base.json');
|
||||
expect(tsConfig.compilerOptions.paths).toEqual({
|
||||
'@proj/my-destination': ['libs/my-destination/src/index.ts'],
|
||||
'@proj/my-destination/testing': [
|
||||
'libs/my-destination/testing/src/index.ts',
|
||||
],
|
||||
'@proj/different-alias': ['libs/my-destination/some-path/src/index.ts'],
|
||||
'@proj/my-destination': ['my-destination/src/index.ts'],
|
||||
'@proj/my-destination/testing': ['my-destination/testing/src/index.ts'],
|
||||
'@proj/different-alias': ['my-destination/some-path/src/index.ts'],
|
||||
});
|
||||
});
|
||||
|
||||
@ -332,12 +368,13 @@ export MyExtendedClass extends MyClass {};`
|
||||
tree.delete('libs');
|
||||
await libraryGenerator(tree, {
|
||||
name: 'my-source',
|
||||
projectNameAndRootFormat: 'as-provided',
|
||||
});
|
||||
const projectConfig = readProjectConfiguration(tree, 'my-source');
|
||||
|
||||
updateImports(
|
||||
tree,
|
||||
normalizeSchema(tree, schema, projectConfig),
|
||||
await normalizeSchema(tree, schema, projectConfig),
|
||||
projectConfig
|
||||
);
|
||||
|
||||
@ -351,18 +388,19 @@ export MyExtendedClass extends MyClass {};`
|
||||
tree.rename('tsconfig.base.json', 'tsconfig.json');
|
||||
await libraryGenerator(tree, {
|
||||
name: 'my-source',
|
||||
projectNameAndRootFormat: 'as-provided',
|
||||
});
|
||||
const projectConfig = readProjectConfiguration(tree, 'my-source');
|
||||
|
||||
updateImports(
|
||||
tree,
|
||||
normalizeSchema(tree, schema, projectConfig),
|
||||
await normalizeSchema(tree, schema, projectConfig),
|
||||
projectConfig
|
||||
);
|
||||
|
||||
const tsConfig = readJson(tree, '/tsconfig.json');
|
||||
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, {
|
||||
name: 'my-source',
|
||||
projectNameAndRootFormat: 'as-provided',
|
||||
});
|
||||
const projectConfig = readProjectConfiguration(tree, 'my-source');
|
||||
|
||||
updateImports(
|
||||
tree,
|
||||
normalizeSchema(tree, schema, projectConfig),
|
||||
await normalizeSchema(tree, schema, projectConfig),
|
||||
projectConfig
|
||||
);
|
||||
|
||||
const tsConfig = readJson(tree, '/tsconfig.json');
|
||||
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 () => {
|
||||
await libraryGenerator(tree, {
|
||||
name: 'my-source',
|
||||
projectNameAndRootFormat: 'as-provided',
|
||||
});
|
||||
const projectConfig = readProjectConfiguration(tree, 'my-source');
|
||||
|
||||
updateImports(
|
||||
tree,
|
||||
normalizeSchema(
|
||||
await normalizeSchema(
|
||||
tree,
|
||||
{
|
||||
...schema,
|
||||
@ -411,7 +451,7 @@ export MyExtendedClass extends MyClass {};`
|
||||
|
||||
const tsConfig = readJson(tree, '/tsconfig.base.json');
|
||||
expect(tsConfig.compilerOptions.paths).toEqual({
|
||||
'@proj/my-source': ['libs/my-destination/src/index.ts'],
|
||||
'@proj/my-source': ['my-destination/src/index.ts'],
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
@ -35,7 +35,6 @@ export function updateImports(
|
||||
project: ProjectConfiguration
|
||||
) {
|
||||
if (project.projectType === 'application') {
|
||||
// These shouldn't be imported anywhere?
|
||||
return;
|
||||
}
|
||||
|
||||
@ -88,9 +87,10 @@ export function updateImports(
|
||||
// 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 as-is, but we'll update the path it points to
|
||||
to: p.startsWith(mainEntryPointImportPath)
|
||||
? p.replace(mainEntryPointImportPath, schema.importPath)
|
||||
: null,
|
||||
to:
|
||||
schema.importPath && p.startsWith(mainEntryPointImportPath)
|
||||
? p.replace(mainEntryPointImportPath, schema.importPath)
|
||||
: null,
|
||||
})),
|
||||
];
|
||||
|
||||
|
||||
@ -16,6 +16,7 @@ describe('updateJestConfig', () => {
|
||||
it('should handle jest config not existing', async () => {
|
||||
await libraryGenerator(tree, {
|
||||
name: 'my-source',
|
||||
projectNameAndRootFormat: 'as-provided',
|
||||
});
|
||||
const projectConfig = readProjectConfiguration(tree, 'my-source');
|
||||
const schema: NormalizedSchema = {
|
||||
@ -24,7 +25,7 @@ describe('updateJestConfig', () => {
|
||||
importPath: '@proj/my-destination',
|
||||
updateImportPath: true,
|
||||
newProjectName: 'my-destination',
|
||||
relativeToRootDestination: 'libs/my-destination',
|
||||
relativeToRootDestination: 'my-destination',
|
||||
};
|
||||
|
||||
expect(() => updateJestConfig(tree, schema, projectConfig)).not.toThrow();
|
||||
@ -34,16 +35,17 @@ describe('updateJestConfig', () => {
|
||||
const jestConfig = `module.exports = {
|
||||
name: 'my-source',
|
||||
preset: '../../jest.config.ts',
|
||||
coverageDirectory: '../../coverage/libs/my-source',
|
||||
coverageDirectory: '../coverage/my-source',
|
||||
snapshotSerializers: [
|
||||
'jest-preset-angular/AngularSnapshotSerializer.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';
|
||||
await libraryGenerator(tree, {
|
||||
name: 'my-source',
|
||||
projectNameAndRootFormat: 'as-provided',
|
||||
});
|
||||
const projectConfig = readProjectConfiguration(tree, 'my-source');
|
||||
tree.write(jestConfigPath, jestConfig);
|
||||
@ -53,7 +55,7 @@ describe('updateJestConfig', () => {
|
||||
importPath: '@proj/my-destination',
|
||||
updateImportPath: true,
|
||||
newProjectName: 'my-destination',
|
||||
relativeToRootDestination: 'libs/my-destination',
|
||||
relativeToRootDestination: 'my-destination',
|
||||
};
|
||||
|
||||
updateJestConfig(tree, schema, projectConfig);
|
||||
@ -62,25 +64,62 @@ describe('updateJestConfig', () => {
|
||||
const rootJestConfigAfter = tree.read(rootJestConfigPath, 'utf-8');
|
||||
expect(jestConfigAfter).toContain(`name: 'my-destination'`);
|
||||
expect(jestConfigAfter).toContain(
|
||||
`coverageDirectory: '../../coverage/libs/my-destination'`
|
||||
`coverageDirectory: '../coverage/my-destination'`
|
||||
);
|
||||
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 = {
|
||||
name: 'some-test-dir-my-source',
|
||||
name: 'my-source',
|
||||
preset: '../../jest.config.ts',
|
||||
coverageDirectory: '../../coverage/libs/some/test/dir/my-source',
|
||||
coverageDirectory: '../coverage/my-source',
|
||||
snapshotSerializers: [
|
||||
'jest-preset-angular/AngularSnapshotSerializer.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';
|
||||
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(
|
||||
tree,
|
||||
@ -93,7 +132,7 @@ describe('updateJestConfig', () => {
|
||||
importPath: '@proj/other-test-dir-my-destination',
|
||||
updateImportPath: true,
|
||||
newProjectName: 'other-test-dir-my-destination',
|
||||
relativeToRootDestination: 'libs/other/test/dir/my-destination',
|
||||
relativeToRootDestination: 'other/test/dir/my-destination',
|
||||
};
|
||||
|
||||
updateJestConfig(tree, schema, projectConfig);
|
||||
@ -101,7 +140,7 @@ describe('updateJestConfig', () => {
|
||||
const rootJestConfigAfter = tree.read(rootJestConfigPath, 'utf-8');
|
||||
expect(jestConfigAfter).toContain(`name: 'other-test-dir-my-destination'`);
|
||||
expect(jestConfigAfter).toContain(
|
||||
`coverageDirectory: '../../coverage/libs/other/test/dir/my-destination'`
|
||||
`coverageDirectory: '../coverage/other/test/dir/my-destination'`
|
||||
);
|
||||
expect(rootJestConfigAfter).toContain('getJestProjects()');
|
||||
});
|
||||
@ -109,12 +148,14 @@ describe('updateJestConfig', () => {
|
||||
it('updates the root config if not using `getJestProjects()`', async () => {
|
||||
const rootJestConfigPath = '/jest.config.ts';
|
||||
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(
|
||||
rootJestConfigPath,
|
||||
`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',
|
||||
updateImportPath: true,
|
||||
newProjectName: 'other-test-dir-my-destination',
|
||||
relativeToRootDestination: 'libs/other/test/dir/my-destination',
|
||||
relativeToRootDestination: 'other/test/dir/my-destination',
|
||||
};
|
||||
|
||||
updateJestConfig(tree, schema, projectConfig);
|
||||
|
||||
const rootJestConfigAfter = tree.read(rootJestConfigPath, 'utf-8');
|
||||
expect(rootJestConfigAfter).not.toContain(
|
||||
'<rootDir>/libs/some/test/dir/my-source'
|
||||
'<rootDir>/some/test/dir/my-source'
|
||||
);
|
||||
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 () => {
|
||||
const rootJestConfigPath = '/jest.config.ts';
|
||||
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(
|
||||
rootJestConfigPath,
|
||||
`const { getJestProjects } = require('@nx/jest');
|
||||
|
||||
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',
|
||||
updateImportPath: true,
|
||||
newProjectName: 'other-test-dir-my-destination',
|
||||
relativeToRootDestination: 'libs/other/test/dir/my-destination',
|
||||
relativeToRootDestination: 'other/test/dir/my-destination',
|
||||
};
|
||||
|
||||
updateJestConfig(tree, schema, projectConfig);
|
||||
|
||||
const rootJestConfigAfter = tree.read(rootJestConfigPath, 'utf-8');
|
||||
expect(rootJestConfigAfter).not.toContain(
|
||||
'<rootDir>/libs/some/test/dir/my-source'
|
||||
'<rootDir>/some/test/dir/my-source'
|
||||
);
|
||||
expect(rootJestConfigAfter).not.toContain(
|
||||
'<rootDir>/libs/other/test/dir/my-destination'
|
||||
'<rootDir>/other/test/dir/my-destination'
|
||||
);
|
||||
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 () => {
|
||||
const rootJestConfigPath = '/jest.config.ts';
|
||||
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(
|
||||
rootJestConfigPath,
|
||||
`const { getJestProjects } = require('@nx/jest');
|
||||
|
||||
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',
|
||||
updateImportPath: true,
|
||||
newProjectName: 'other-test-dir-my-destination',
|
||||
relativeToRootDestination: 'libs/other/test/dir/my-destination',
|
||||
relativeToRootDestination: 'other/test/dir/my-destination',
|
||||
};
|
||||
|
||||
updateJestConfig(tree, schema, projectConfig);
|
||||
|
||||
const rootJestConfigAfter = tree.read(rootJestConfigPath, 'utf-8');
|
||||
expect(rootJestConfigAfter).not.toContain(
|
||||
'<rootDir>/libs/some/test/dir/my-source'
|
||||
'<rootDir>/some/test/dir/my-source'
|
||||
);
|
||||
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()');
|
||||
});
|
||||
});
|
||||
|
||||
@ -22,16 +22,38 @@ export function updateJestConfig(
|
||||
if (tree.exists(jestConfigPath)) {
|
||||
const oldContent = tree.read(jestConfigPath, 'utf-8');
|
||||
|
||||
// ensure both single and double quotes are replaced
|
||||
const findName = new RegExp(
|
||||
`'${schema.projectName}'|"${schema.projectName}"|\`${schema.projectName}\``,
|
||||
'g'
|
||||
);
|
||||
const findDir = new RegExp(project.root, 'g');
|
||||
let newContent = oldContent;
|
||||
if (schema.projectName !== schema.newProjectName) {
|
||||
// ensure both single and double quotes are replaced
|
||||
const findName = new RegExp(
|
||||
`'${schema.projectName}'|"${schema.projectName}"|\`${schema.projectName}\``,
|
||||
'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);
|
||||
}
|
||||
|
||||
|
||||
@ -17,11 +17,14 @@ describe('updatePackageJson', () => {
|
||||
importPath: '@proj/my-destination',
|
||||
updateImportPath: true,
|
||||
newProjectName: 'my-destination',
|
||||
relativeToRootDestination: 'libs/my-destination',
|
||||
relativeToRootDestination: 'my-destination',
|
||||
};
|
||||
|
||||
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 () => {
|
||||
@ -34,11 +37,11 @@ describe('updatePackageJson', () => {
|
||||
const packageJson = {
|
||||
name: '@proj/my-lib',
|
||||
};
|
||||
writeJson(tree, '/libs/my-destination/package.json', packageJson);
|
||||
writeJson(tree, 'my-destination/package.json', packageJson);
|
||||
|
||||
updatePackageJson(tree, schema);
|
||||
|
||||
expect(readJson(tree, '/libs/my-destination/package.json')).toEqual({
|
||||
expect(readJson(tree, 'my-destination/package.json')).toEqual({
|
||||
...packageJson,
|
||||
name: '@proj/my-destination',
|
||||
});
|
||||
|
||||
@ -12,6 +12,10 @@ interface PartialPackageJson {
|
||||
* @param schema The options provided to the schematic
|
||||
*/
|
||||
export function updatePackageJson(tree: Tree, schema: NormalizedSchema) {
|
||||
if (!schema.importPath) {
|
||||
return;
|
||||
}
|
||||
|
||||
const packageJsonPath = path.join(
|
||||
schema.relativeToRootDestination,
|
||||
'package.json'
|
||||
|
||||
@ -16,16 +16,17 @@ describe('updateProjectRootFiles', () => {
|
||||
it('should update the relative root in files at the root of the project', async () => {
|
||||
const testFile = `module.exports = {
|
||||
name: 'my-source',
|
||||
preset: '../../jest.config.js',
|
||||
coverageDirectory: '../../coverage/libs/my-source',
|
||||
preset: '../jest.config.js',
|
||||
coverageDirectory: '../coverage/my-source',
|
||||
snapshotSerializers: [
|
||||
'jest-preset-angular/AngularSnapshotSerializer.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, {
|
||||
name: 'my-source',
|
||||
projectNameAndRootFormat: 'as-provided',
|
||||
});
|
||||
const projectConfig = readProjectConfiguration(tree, 'my-source');
|
||||
tree.write(testFilePath, testFile);
|
||||
@ -35,15 +36,15 @@ describe('updateProjectRootFiles', () => {
|
||||
importPath: '@proj/subfolder-my-destination',
|
||||
updateImportPath: true,
|
||||
newProjectName: 'subfolder-my-destination',
|
||||
relativeToRootDestination: 'libs/subfolder/my-destination',
|
||||
relativeToRootDestination: 'subfolder/my-destination',
|
||||
};
|
||||
|
||||
updateProjectRootFiles(tree, schema, projectConfig);
|
||||
|
||||
const testFileAfter = tree.read(testFilePath, 'utf-8');
|
||||
expect(testFileAfter).toContain(`preset: '../../../jest.config.js'`);
|
||||
expect(testFileAfter).toContain(`preset: '../../jest.config.js'`);
|
||||
expect(testFileAfter).toContain(
|
||||
`coverageDirectory: '../../../coverage/libs/my-source'`
|
||||
`coverageDirectory: '../../coverage/my-source'`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@ -18,7 +18,7 @@ describe('updateReadme', () => {
|
||||
importPath: '@proj/shared-my-destination',
|
||||
updateImportPath: true,
|
||||
newProjectName: 'shared-my-destination',
|
||||
relativeToRootDestination: 'libs/shared/my-destination',
|
||||
relativeToRootDestination: 'shared/my-destination',
|
||||
};
|
||||
|
||||
tree = createTreeWithEmptyWorkspace({ layout: 'apps-libs' });
|
||||
@ -27,6 +27,7 @@ describe('updateReadme', () => {
|
||||
it('should handle README.md not existing', async () => {
|
||||
await libraryGenerator(tree, {
|
||||
name: 'my-lib',
|
||||
projectNameAndRootFormat: 'as-provided',
|
||||
});
|
||||
const readmePath = join(schema.relativeToRootDestination, 'README.md');
|
||||
tree.delete(readmePath);
|
||||
@ -39,17 +40,15 @@ describe('updateReadme', () => {
|
||||
it('should update README.md contents', async () => {
|
||||
await libraryGenerator(tree, {
|
||||
name: 'my-lib',
|
||||
projectNameAndRootFormat: 'as-provided',
|
||||
});
|
||||
// This step is usually handled elsewhere
|
||||
tree.rename(
|
||||
'libs/my-lib/README.md',
|
||||
'libs/shared/my-destination/README.md'
|
||||
);
|
||||
tree.rename('my-lib/README.md', 'shared/my-destination/README.md');
|
||||
|
||||
updateReadme(tree, schema);
|
||||
|
||||
const content = tree
|
||||
.read('/libs/shared/my-destination/README.md')
|
||||
.read('shared/my-destination/README.md')
|
||||
.toString('utf8');
|
||||
expect(content).toMatch('# shared-my-destination');
|
||||
expect(content).toMatch('nx test shared-my-destination');
|
||||
|
||||
@ -16,6 +16,7 @@ describe('updateStorybookConfig', () => {
|
||||
it('should handle storybook config not existing', async () => {
|
||||
await libraryGenerator(tree, {
|
||||
name: 'my-source',
|
||||
projectNameAndRootFormat: 'as-provided',
|
||||
});
|
||||
const projectConfig = readProjectConfiguration(tree, 'my-source');
|
||||
const schema: NormalizedSchema = {
|
||||
@ -24,7 +25,7 @@ describe('updateStorybookConfig', () => {
|
||||
importPath: '@proj/my-destination',
|
||||
updateImportPath: true,
|
||||
newProjectName: 'my-destination',
|
||||
relativeToRootDestination: 'libs/my-destination',
|
||||
relativeToRootDestination: 'my-destination',
|
||||
};
|
||||
|
||||
expect(() =>
|
||||
@ -34,14 +35,14 @@ describe('updateStorybookConfig', () => {
|
||||
|
||||
it('should update the import path for main.js', async () => {
|
||||
const storybookMain = `
|
||||
const rootMain = require('../../../.storybook/main');
|
||||
const rootMain = require('../../.storybook/main');
|
||||
module.exports = rootMain;
|
||||
`;
|
||||
const storybookMainPath =
|
||||
'/libs/namespace/my-destination/.storybook/main.js';
|
||||
const storybookMainPath = 'namespace/my-destination/.storybook/main.js';
|
||||
|
||||
await libraryGenerator(tree, {
|
||||
name: 'my-source',
|
||||
projectNameAndRootFormat: 'as-provided',
|
||||
});
|
||||
const projectConfig = readProjectConfiguration(tree, 'my-source');
|
||||
tree.write(storybookMainPath, storybookMain);
|
||||
@ -51,25 +52,26 @@ describe('updateStorybookConfig', () => {
|
||||
importPath: '@proj/namespace-my-destination',
|
||||
updateImportPath: true,
|
||||
newProjectName: 'namespace-my-destination',
|
||||
relativeToRootDestination: 'libs/namespace/my-destination',
|
||||
relativeToRootDestination: 'namespace/my-destination',
|
||||
};
|
||||
|
||||
updateStorybookConfig(tree, schema, projectConfig);
|
||||
|
||||
const storybookMainAfter = tree.read(storybookMainPath, 'utf-8');
|
||||
expect(storybookMainAfter).toContain(
|
||||
`const rootMain = require('../../../../.storybook/main');`
|
||||
`const rootMain = require('../../../.storybook/main');`
|
||||
);
|
||||
});
|
||||
|
||||
it('should update the import path for webpack.config.json', async () => {
|
||||
const storybookWebpackConfig = `
|
||||
const rootWebpackConfig = require('../../../.storybook/webpack.config');
|
||||
const rootWebpackConfig = require('../../.storybook/webpack.config');
|
||||
`;
|
||||
const storybookWebpackConfigPath =
|
||||
'/libs/namespace/my-destination/.storybook/webpack.config.js';
|
||||
'namespace/my-destination/.storybook/webpack.config.js';
|
||||
await libraryGenerator(tree, {
|
||||
name: 'my-source',
|
||||
projectNameAndRootFormat: 'as-provided',
|
||||
});
|
||||
const projectConfig = readProjectConfiguration(tree, 'my-source');
|
||||
tree.write(storybookWebpackConfigPath, storybookWebpackConfig);
|
||||
@ -79,7 +81,7 @@ describe('updateStorybookConfig', () => {
|
||||
importPath: '@proj/namespace-my-destination',
|
||||
updateImportPath: true,
|
||||
newProjectName: 'namespace-my-destination',
|
||||
relativeToRootDestination: 'libs/namespace/my-destination',
|
||||
relativeToRootDestination: 'namespace/my-destination',
|
||||
};
|
||||
|
||||
updateStorybookConfig(tree, schema, projectConfig);
|
||||
@ -89,28 +91,28 @@ describe('updateStorybookConfig', () => {
|
||||
'utf-8'
|
||||
);
|
||||
expect(storybookWebpackConfigAfter).toContain(
|
||||
`const rootWebpackConfig = require('../../../../.storybook/webpack.config');`
|
||||
`const rootWebpackConfig = require('../../../.storybook/webpack.config');`
|
||||
);
|
||||
});
|
||||
|
||||
describe('directory', () => {
|
||||
it('should update the import path for directory/main.js', async () => {
|
||||
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');
|
||||
module.exports = rootMain;
|
||||
`;
|
||||
const storybookMainPath =
|
||||
'/libs/namespace/my-destination/.storybook/main.js';
|
||||
|
||||
const storybookNestedMain = `
|
||||
const rootMain = require('../../../../.storybook/main');
|
||||
module.exports = rootMain;
|
||||
`;
|
||||
const storybookNestedMainPath =
|
||||
'/libs/namespace/my-destination/.storybook/nested/main.js';
|
||||
'namespace/my-destination/.storybook/nested/main.js';
|
||||
|
||||
await libraryGenerator(tree, {
|
||||
name: 'my-source',
|
||||
projectNameAndRootFormat: 'as-provided',
|
||||
});
|
||||
const projectConfig = readProjectConfiguration(tree, 'my-source');
|
||||
tree.write(storybookMainPath, storybookMain);
|
||||
@ -121,39 +123,40 @@ describe('updateStorybookConfig', () => {
|
||||
importPath: '@proj/namespace-my-destination',
|
||||
updateImportPath: true,
|
||||
newProjectName: 'namespace-my-destination',
|
||||
relativeToRootDestination: 'libs/namespace/my-destination',
|
||||
relativeToRootDestination: 'namespace/my-destination',
|
||||
};
|
||||
|
||||
updateStorybookConfig(tree, schema, projectConfig);
|
||||
|
||||
const storybookMainAfter = tree.read(storybookMainPath, 'utf-8');
|
||||
expect(storybookMainAfter).toContain(
|
||||
`const rootMain = require('../../../../.storybook/main');`
|
||||
`const rootMain = require('../../../.storybook/main');`
|
||||
);
|
||||
const storybookNestedMainAfter = tree.read(
|
||||
storybookNestedMainPath,
|
||||
'utf-8'
|
||||
);
|
||||
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 () => {
|
||||
const storybookWebpackConfig = `
|
||||
const rootWebpackConfig = require('../../../.storybook/webpack.config');
|
||||
const rootWebpackConfig = require('../../.storybook/webpack.config');
|
||||
`;
|
||||
const storybookWebpackConfigPath =
|
||||
'/libs/namespace/my-destination/.storybook/webpack.config.js';
|
||||
'namespace/my-destination/.storybook/webpack.config.js';
|
||||
|
||||
const storybookNestedWebpackConfig = `
|
||||
const rootWebpackConfig = require('../../../../.storybook/webpack.config');
|
||||
const rootWebpackConfig = require('../../../.storybook/webpack.config');
|
||||
`;
|
||||
const storybookNestedWebpackConfigPath =
|
||||
'/libs/namespace/my-destination/.storybook/nested/webpack.config.js';
|
||||
'namespace/my-destination/.storybook/nested/webpack.config.js';
|
||||
|
||||
await libraryGenerator(tree, {
|
||||
name: 'my-source',
|
||||
projectNameAndRootFormat: 'as-provided',
|
||||
});
|
||||
const projectConfig = readProjectConfiguration(tree, 'my-source');
|
||||
tree.write(storybookWebpackConfigPath, storybookWebpackConfig);
|
||||
@ -167,7 +170,7 @@ describe('updateStorybookConfig', () => {
|
||||
importPath: '@proj/namespace-my-destination',
|
||||
updateImportPath: true,
|
||||
newProjectName: 'namespace-my-destination',
|
||||
relativeToRootDestination: 'libs/namespace/my-destination',
|
||||
relativeToRootDestination: 'namespace/my-destination',
|
||||
};
|
||||
|
||||
updateStorybookConfig(tree, schema, projectConfig);
|
||||
@ -177,7 +180,7 @@ describe('updateStorybookConfig', () => {
|
||||
'utf-8'
|
||||
);
|
||||
expect(storybookWebpackConfigAfter).toContain(
|
||||
`const rootWebpackConfig = require('../../../../.storybook/webpack.config');`
|
||||
`const rootWebpackConfig = require('../../../.storybook/webpack.config');`
|
||||
);
|
||||
|
||||
const storybookNestedWebpackConfigAfter = tree.read(
|
||||
@ -185,7 +188,7 @@ describe('updateStorybookConfig', () => {
|
||||
'utf-8'
|
||||
);
|
||||
expect(storybookNestedWebpackConfigAfter).toContain(
|
||||
`const rootWebpackConfig = require('../../../../../.storybook/webpack.config');`
|
||||
`const rootWebpackConfig = require('../../../../.storybook/webpack.config');`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@ -19,10 +19,6 @@ export function getDestination(
|
||||
schema: Schema,
|
||||
project: ProjectConfiguration
|
||||
): string {
|
||||
if (schema.destinationRelativeToRoot) {
|
||||
return schema.destination;
|
||||
}
|
||||
|
||||
const projectType = project.projectType;
|
||||
|
||||
const workspaceLayout = getWorkspaceLayout(host);
|
||||
|
||||
@ -12,47 +12,61 @@ describe('move', () => {
|
||||
});
|
||||
|
||||
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, {
|
||||
projectName: 'my-lib',
|
||||
importPath: '@proj/shared-mylib',
|
||||
updateImportPath: true,
|
||||
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');
|
||||
expect(tree.exists(jestConfigPath)).toBeTruthy();
|
||||
expect(afterJestConfig).toContain("preset: '../../../jest.preset.js'");
|
||||
expect(afterJestConfig).toContain("preset: '../../jest.preset.js'");
|
||||
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 () => {
|
||||
await libraryGenerator(tree, { name: 'shared/my-lib' });
|
||||
await libraryGenerator(tree, {
|
||||
name: 'shared-my-lib',
|
||||
directory: 'shared/my-lib',
|
||||
projectNameAndRootFormat: 'as-provided',
|
||||
});
|
||||
|
||||
await moveGenerator(tree, {
|
||||
projectName: 'shared-my-lib',
|
||||
importPath: '@proj/mylib',
|
||||
updateImportPath: true,
|
||||
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');
|
||||
expect(tree.exists(jestConfigPath)).toBeTruthy();
|
||||
expect(afterJestConfig).toContain("preset: '../../jest.preset.js'");
|
||||
expect(afterJestConfig).toContain("preset: '../jest.preset.js'");
|
||||
expect(afterJestConfig).toContain(
|
||||
"coverageDirectory: '../../coverage/libs/my-lib-new'"
|
||||
"coverageDirectory: '../coverage/my-lib-new'"
|
||||
);
|
||||
});
|
||||
|
||||
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(
|
||||
'../../node_modules/nx/schemas/project-schema.json'
|
||||
'../node_modules/nx/schemas/project-schema.json'
|
||||
);
|
||||
|
||||
await moveGenerator(tree, {
|
||||
@ -60,11 +74,12 @@ describe('move', () => {
|
||||
importPath: '@proj/shared-mylib',
|
||||
updateImportPath: true,
|
||||
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(
|
||||
'../../../node_modules/nx/schemas/project-schema.json'
|
||||
'../../node_modules/nx/schemas/project-schema.json'
|
||||
);
|
||||
});
|
||||
|
||||
@ -80,6 +95,7 @@ describe('move', () => {
|
||||
buildable: true,
|
||||
unitTestRunner: 'jest',
|
||||
linter: 'eslint',
|
||||
projectNameAndRootFormat: 'as-provided',
|
||||
});
|
||||
|
||||
updateJson(tree, 'tsconfig.json', (json) => {
|
||||
@ -100,12 +116,13 @@ describe('move', () => {
|
||||
importPath: '@proj/my-lib',
|
||||
updateImportPath: true,
|
||||
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',
|
||||
$schema: '../../node_modules/nx/schemas/project-schema.json',
|
||||
sourceRoot: 'libs/my-lib/src',
|
||||
$schema: '../node_modules/nx/schemas/project-schema.json',
|
||||
sourceRoot: 'my-lib/src',
|
||||
projectType: 'library',
|
||||
targets: {
|
||||
build: {
|
||||
@ -113,18 +130,15 @@ describe('move', () => {
|
||||
outputs: ['{options.outputPath}'],
|
||||
options: {
|
||||
outputPath: 'dist/my-lib',
|
||||
main: 'libs/my-lib/src/index.ts',
|
||||
tsConfig: 'libs/my-lib/tsconfig.lib.json',
|
||||
main: 'my-lib/src/index.ts',
|
||||
tsConfig: 'my-lib/tsconfig.lib.json',
|
||||
},
|
||||
},
|
||||
lint: {
|
||||
executor: '@nx/linter:eslint',
|
||||
outputs: ['{options.outputFile}'],
|
||||
options: {
|
||||
lintFilePatterns: [
|
||||
'libs/my-lib/**/*.ts',
|
||||
'libs/my-lib/package.json',
|
||||
],
|
||||
lintFilePatterns: ['my-lib/**/*.ts', 'my-lib/package.json'],
|
||||
},
|
||||
},
|
||||
test: {
|
||||
@ -134,22 +148,22 @@ describe('move', () => {
|
||||
},
|
||||
});
|
||||
|
||||
expect(readJson(tree, 'libs/my-lib/tsconfig.json')).toMatchObject({
|
||||
extends: '../../tsconfig.base.json',
|
||||
files: ['../../node_modules/@foo/bar/index.d.ts'],
|
||||
expect(readJson(tree, 'my-lib/tsconfig.json')).toMatchObject({
|
||||
extends: '../tsconfig.base.json',
|
||||
files: ['../node_modules/@foo/bar/index.d.ts'],
|
||||
references: [
|
||||
{ path: './tsconfig.lib.json' },
|
||||
{ path: './tsconfig.spec.json' },
|
||||
],
|
||||
});
|
||||
|
||||
const jestConfig = tree.read('libs/my-lib/jest.config.lib.ts', 'utf-8');
|
||||
expect(jestConfig).toContain(`preset: '../../jest.preset.js'`);
|
||||
const jestConfig = tree.read('my-lib/jest.config.lib.ts', 'utf-8');
|
||||
expect(jestConfig).toContain(`preset: '../jest.preset.js'`);
|
||||
|
||||
expect(tree.exists('libs/my-lib/tsconfig.lib.json')).toBeTruthy();
|
||||
expect(tree.exists('libs/my-lib/tsconfig.spec.json')).toBeTruthy();
|
||||
expect(tree.exists('libs/my-lib/.eslintrc.json')).toBeTruthy();
|
||||
expect(tree.exists('libs/my-lib/src/index.ts')).toBeTruthy();
|
||||
expect(tree.exists('my-lib/tsconfig.lib.json')).toBeTruthy();
|
||||
expect(tree.exists('my-lib/tsconfig.spec.json')).toBeTruthy();
|
||||
expect(tree.exists('my-lib/.eslintrc.json')).toBeTruthy();
|
||||
expect(tree.exists('my-lib/src/index.ts')).toBeTruthy();
|
||||
|
||||
// Test that other libs and workspace files are not moved.
|
||||
expect(tree.exists('package.json')).toBeTruthy();
|
||||
@ -162,4 +176,32 @@ describe('move', () => {
|
||||
expect(tree.exists('jest.config.ts')).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'"
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
@ -9,6 +9,7 @@ import { checkDestination } from './lib/check-destination';
|
||||
import { createProjectConfigurationInNewDestination } from './lib/create-project-configuration-in-new-destination';
|
||||
import { moveProjectFiles } from './lib/move-project-files';
|
||||
import { normalizeSchema } from './lib/normalize-schema';
|
||||
import { runAngularPlugin } from './lib/run-angular-plugin';
|
||||
import { updateBuildTargets } from './lib/update-build-targets';
|
||||
import { updateCypressConfig } from './lib/update-cypress-config';
|
||||
import { updateDefaultProject } from './lib/update-default-project';
|
||||
@ -28,9 +29,16 @@ import {
|
||||
import { Schema } from './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);
|
||||
checkDestination(tree, rawSchema, projectConfig);
|
||||
const schema = normalizeSchema(tree, rawSchema, projectConfig);
|
||||
const schema = await normalizeSchema(tree, rawSchema, projectConfig);
|
||||
checkDestination(tree, schema, rawSchema.destination);
|
||||
|
||||
if (projectConfig.root === '.') {
|
||||
maybeExtractTsConfigBase(tree);
|
||||
@ -55,6 +63,8 @@ export async function moveGenerator(tree: Tree, rawSchema: Schema) {
|
||||
updateDefaultProject(tree, schema);
|
||||
updateImplicitDependencies(tree, schema);
|
||||
|
||||
await runAngularPlugin(tree, schema);
|
||||
|
||||
if (!schema.skipFormat) {
|
||||
await formatFiles(tree);
|
||||
}
|
||||
|
||||
@ -1,14 +1,15 @@
|
||||
import { ProjectNameAndRootFormat } from '@nx/devkit/src/generators/project-name-and-root-utils';
|
||||
|
||||
export interface Schema {
|
||||
projectName: string;
|
||||
destination: string;
|
||||
importPath?: string;
|
||||
updateImportPath: boolean;
|
||||
skipFormat?: boolean;
|
||||
destinationRelativeToRoot?: boolean;
|
||||
newProjectName?: string;
|
||||
projectNameAndRootFormat?: ProjectNameAndRootFormat;
|
||||
}
|
||||
|
||||
export interface NormalizedSchema extends Schema {
|
||||
importPath: string;
|
||||
relativeToRootDestination: string;
|
||||
}
|
||||
|
||||
@ -18,6 +18,13 @@
|
||||
"description": "The name of the project to move.",
|
||||
"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": {
|
||||
"type": "string",
|
||||
"description": "The folder to move the project into.",
|
||||
@ -26,6 +33,11 @@
|
||||
"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": {
|
||||
"type": "string",
|
||||
"description": "The new import path to use in the `tsconfig.base.json`."
|
||||
|
||||
@ -60,7 +60,7 @@ async function moveWorkspaceGeneratorsToLocalPlugin(tree: Tree) {
|
||||
);
|
||||
project = readProjectConfiguration(tree, PROJECT_NAME);
|
||||
}
|
||||
await updateExistingPlugin(tree, project);
|
||||
updateExistingPlugin(tree, project);
|
||||
return tasks;
|
||||
}
|
||||
|
||||
@ -187,7 +187,7 @@ async function createNewPlugin(tree: Tree) {
|
||||
e2eTestRunner: 'none',
|
||||
publishable: false,
|
||||
});
|
||||
getCreateGeneratorsJson()(
|
||||
await getCreateGeneratorsJson()(
|
||||
tree,
|
||||
readProjectConfiguration(tree, PROJECT_NAME).root,
|
||||
PROJECT_NAME
|
||||
@ -207,8 +207,8 @@ function moveGeneratedPlugin(
|
||||
projectName: PROJECT_NAME,
|
||||
newProjectName: PROJECT_NAME,
|
||||
updateImportPath: true,
|
||||
destinationRelativeToRoot: true,
|
||||
importPath: importPath,
|
||||
projectNameAndRootFormat: 'as-provided',
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@ -9,7 +9,7 @@ export function getImportPath(tree: Tree, projectDirectory: string): string {
|
||||
: projectDirectory;
|
||||
}
|
||||
|
||||
function getNpmScope(tree: Tree) {
|
||||
export function getNpmScope(tree: Tree) {
|
||||
const nxJson = readNxJson(tree);
|
||||
|
||||
// TODO(v17): Remove reading this from nx.json
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user