feat(core): don't generate workspace.json for v2 workspaces (#12127)

This commit is contained in:
Craigory Coppola 2022-09-27 19:16:22 -04:00 committed by GitHub
parent beef424029
commit 9b63ce167a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
77 changed files with 572 additions and 453 deletions

3
.gitignore vendored
View File

@ -21,3 +21,6 @@ CHANGELOG.md
# Next.js # Next.js
.next .next
# Local dev files
.env

View File

@ -17,22 +17,6 @@ describe('Angular Config', () => {
beforeAll(() => newProject()); beforeAll(() => newProject());
afterAll(() => cleanupProject()); afterAll(() => cleanupProject());
it('should support workspaces w/o workspace config file', async () => {
if (isNotWindows()) {
const oldWorkspaceJson = readJson('workspace.json');
removeFile('workspace.json');
const myapp = uniq('myapp');
runCLI(`generate @nrwl/angular:app ${myapp} --directory=myDir --routing`);
runCLI(`build my-dir-${myapp} --aot`);
expectTestsPass(await runCLIAsync(`test my-dir-${myapp} --no-watch`));
expect(() =>
checkFilesDoNotExist('workspace.json', 'angular.json')
).not.toThrow();
createFile('workspace.json', JSON.stringify(oldWorkspaceJson, null, 2));
}
}, 1000000);
it('should upgrade the config correctly', async () => { it('should upgrade the config correctly', async () => {
const previousCI = process.env.SELECTED_CLI; const previousCI = process.env.SELECTED_CLI;
process.env.SELECTED_CLI = 'angular'; process.env.SELECTED_CLI = 'angular';

View File

@ -97,7 +97,7 @@ describe('Angular Projects', () => {
} }
}, 1000000); }, 1000000);
it('should build the dependent buildable lib and its child lib, as well as the app', () => { it('should build the dependent buildable lib and its child lib, as well as the app', async () => {
// ARRANGE // ARRANGE
const app = uniq('app'); const app = uniq('app');
const buildableLib = uniq('buildlib1'); const buildableLib = uniq('buildlib1');

View File

@ -15,7 +15,7 @@ describe('Angular Cypress Component Tests', () => {
const usedInAppLibName = uniq('cy-angular-lib'); const usedInAppLibName = uniq('cy-angular-lib');
const buildableLibName = uniq('cy-angular-buildable-lib'); const buildableLibName = uniq('cy-angular-buildable-lib');
beforeAll(() => { beforeAll(async () => {
projectName = newProject({ name: uniq('cy-ng') }); projectName = newProject({ name: uniq('cy-ng') });
runCLI(`generate @nrwl/angular:app ${appName} --no-interactive`); runCLI(`generate @nrwl/angular:app ${appName} --no-interactive`);
runCLI( runCLI(

View File

@ -60,7 +60,6 @@ describe('Move Angular Project', () => {
expect(moveOutput).toContain( expect(moveOutput).toContain(
`CREATE apps/${newPath}/src/environments/environment.ts` `CREATE apps/${newPath}/src/environments/environment.ts`
); );
expect(moveOutput).toContain(`UPDATE workspace.json`);
}); });
/** /**

View File

@ -360,7 +360,7 @@ describe('Tailwind support', () => {
expect(mainBundle).toMatch(expectedStylesRegex); expect(mainBundle).toMatch(expectedStylesRegex);
}; };
it('should build correctly and only output the tailwind utilities used', () => { it('should build correctly and only output the tailwind utilities used', async () => {
const appWithTailwind = uniq('app-with-tailwind'); const appWithTailwind = uniq('app-with-tailwind');
runCLI( runCLI(
`generate @nrwl/angular:app ${appWithTailwind} --add-tailwind --no-interactive` `generate @nrwl/angular:app ${appWithTailwind} --add-tailwind --no-interactive`

View File

@ -528,17 +528,6 @@ exports.FooModel = FooModel;
); );
}, 300000); }, 300000);
it('should support workspaces w/o workspace config file', async () => {
removeFile('workspace.json');
const app2 = uniq('app2');
runCLI(`generate @nrwl/node:app ${app2} --directory=myDir`);
runCLI(`build my-dir-${app2}`);
expect(() =>
checkFilesDoNotExist('workspace.json', 'angular.json')
).not.toThrow();
}, 1000000);
it('should run default jest tests', async () => { it('should run default jest tests', async () => {
await expectJestTestsToPass('@nrwl/node:lib'); await expectJestTestsToPass('@nrwl/node:lib');
}, 100000); }, 100000);

View File

@ -188,7 +188,7 @@ describe('Extra Nx Misc Tests', () => {
expect(resultArgs).toContain('camel: d'); expect(resultArgs).toContain('camel: d');
}, 120000); }, 120000);
it('ttt should fail when a process exits non-zero', () => { it('ttt should fail when a process exits non-zero', async () => {
updateProjectConfig(mylib, (config) => { updateProjectConfig(mylib, (config) => {
config.targets.error = { config.targets.error = {
executor: '@nrwl/workspace:run-commands', executor: '@nrwl/workspace:run-commands',
@ -209,7 +209,7 @@ describe('Extra Nx Misc Tests', () => {
} }
}); });
it('run command should not break if output property is missing in options and arguments', () => { it('run command should not break if output property is missing in options and arguments', async () => {
updateProjectConfig(mylib, (config) => { updateProjectConfig(mylib, (config) => {
config.targets.lint.outputs = ['{options.outputFile}']; config.targets.lint.outputs = ['{options.outputFile}'];
return config; return config;

View File

@ -9,11 +9,12 @@ import {
expectJestTestsToPass, expectJestTestsToPass,
readFile, readFile,
exists, exists,
workspaceConfigName,
renameFile, renameFile,
updateProjectConfig, updateProjectConfig,
readProjectConfig, readProjectConfig,
tmpProjPath, tmpProjPath,
readResolvedWorkspaceConfiguration,
removeFile,
} from '@nrwl/e2e/utils'; } from '@nrwl/e2e/utils';
let proj: string; let proj: string;
@ -23,7 +24,9 @@ describe('Workspace Tests', () => {
proj = newProject(); proj = newProject();
}); });
afterAll(() => cleanupProject()); afterAll(() => {
cleanupProject();
});
describe('@nrwl/workspace:library', () => { describe('@nrwl/workspace:library', () => {
it('should create a library that can be tested and linted', async () => { it('should create a library that can be tested and linted', async () => {
@ -211,13 +214,14 @@ describe('Workspace Tests', () => {
`workspace-generator ${custom} ${workspace} --no-interactive --directory=dir --skipTsConfig=true -d` `workspace-generator ${custom} ${workspace} --no-interactive --directory=dir --skipTsConfig=true -d`
); );
expect(exists(`libs/dir/${workspace}/src/index.ts`)).toEqual(false); expect(exists(`libs/dir/${workspace}/src/index.ts`)).toEqual(false);
expect(dryRunOutput).toContain(`UPDATE ${workspaceConfigName()}`); expect(dryRunOutput).toContain(
`CREATE libs/dir/${workspace}/src/index.ts`
);
const output = runCLI( const output = runCLI(
`workspace-generator ${custom} ${workspace} --no-interactive --directory=dir` `workspace-generator ${custom} ${workspace} --no-interactive --directory=dir`
); );
checkFilesExist(`libs/dir/${workspace}/src/index.ts`); checkFilesExist(`libs/dir/${workspace}/src/index.ts`);
expect(output).toContain(`UPDATE ${workspaceConfigName()}`);
expect(output).not.toContain('UPDATE nx.json'); expect(output).not.toContain('UPDATE nx.json');
const jsonFailing = readJson(`tools/generators/${failing}/schema.json`); const jsonFailing = readJson(`tools/generators/${failing}/schema.json`);
@ -281,7 +285,7 @@ describe('Workspace Tests', () => {
/** /**
* Tries moving a library from ${lib}/data-access -> shared/${lib}/data-access * Tries moving a library from ${lib}/data-access -> shared/${lib}/data-access
*/ */
it('should work for libraries', () => { it('should work for libraries', async () => {
const lib1 = uniq('mylib'); const lib1 = uniq('mylib');
const lib2 = uniq('mylib'); const lib2 = uniq('mylib');
const lib3 = uniq('mylib'); const lib3 = uniq('mylib');
@ -374,8 +378,8 @@ describe('Workspace Tests', () => {
expect(moveOutput).toContain(`CREATE ${rootClassPath}`); expect(moveOutput).toContain(`CREATE ${rootClassPath}`);
checkFilesExist(rootClassPath); checkFilesExist(rootClassPath);
let workspaceJson = readJson(workspaceConfigName()); let workspace = await readResolvedWorkspaceConfiguration();
expect(workspaceJson.projects[`${lib1}-data-access`]).toBeUndefined(); expect(workspace.projects[`${lib1}-data-access`]).toBeUndefined();
const newConfig = readProjectConfig(newName); const newConfig = readProjectConfig(newName);
expect(newConfig).toMatchObject({ expect(newConfig).toMatchObject({
tags: [], tags: [],
@ -396,9 +400,8 @@ describe('Workspace Tests', () => {
] ]
).toEqual([`libs/shared/${lib1}/data-access/src/index.ts`]); ).toEqual([`libs/shared/${lib1}/data-access/src/index.ts`]);
expect(moveOutput).toContain(`UPDATE workspace.json`); workspace = readResolvedWorkspaceConfiguration();
workspaceJson = readJson(workspaceConfigName()); expect(workspace.projects[`${lib1}-data-access`]).toBeUndefined();
expect(workspaceJson.projects[`${lib1}-data-access`]).toBeUndefined();
const project = readProjectConfig(newName); const project = readProjectConfig(newName);
expect(project).toBeTruthy(); expect(project).toBeTruthy();
expect(project.sourceRoot).toBe(`${newPath}/src`); expect(project.sourceRoot).toBe(`${newPath}/src`);
@ -416,7 +419,7 @@ describe('Workspace Tests', () => {
); );
}); });
it('should work for libs created with --importPath', () => { it('should work for libs created with --importPath', async () => {
const importPath = '@wibble/fish'; const importPath = '@wibble/fish';
const lib1 = uniq('mylib'); const lib1 = uniq('mylib');
const lib2 = uniq('mylib'); const lib2 = uniq('mylib');
@ -523,9 +526,8 @@ describe('Workspace Tests', () => {
] ]
).toEqual([`libs/shared/${lib1}/data-access/src/index.ts`]); ).toEqual([`libs/shared/${lib1}/data-access/src/index.ts`]);
expect(moveOutput).toContain(`UPDATE workspace.json`); const workspace = await readResolvedWorkspaceConfiguration();
const workspaceJson = readJson(workspaceConfigName()); expect(workspace.projects[`${lib1}-data-access`]).toBeUndefined();
expect(workspaceJson.projects[`${lib1}-data-access`]).toBeUndefined();
const project = readProjectConfig(newName); const project = readProjectConfig(newName);
expect(project).toBeTruthy(); expect(project).toBeTruthy();
expect(project.sourceRoot).toBe(`${newPath}/src`); expect(project.sourceRoot).toBe(`${newPath}/src`);
@ -547,7 +549,7 @@ describe('Workspace Tests', () => {
); );
}); });
it('should work for custom workspace layouts', () => { it('should work for custom workspace layouts', async () => {
const lib1 = uniq('mylib'); const lib1 = uniq('mylib');
const lib2 = uniq('mylib'); const lib2 = uniq('mylib');
const lib3 = uniq('mylib'); const lib3 = uniq('mylib');
@ -656,9 +658,8 @@ describe('Workspace Tests', () => {
] ]
).toEqual([`packages/shared/${lib1}/data-access/src/index.ts`]); ).toEqual([`packages/shared/${lib1}/data-access/src/index.ts`]);
expect(moveOutput).toContain(`UPDATE workspace.json`); const workspace = await readResolvedWorkspaceConfiguration();
const workspaceJson = readJson(workspaceConfigName()); expect(workspace.projects[`${lib1}-data-access`]).toBeUndefined();
expect(workspaceJson.projects[`${lib1}-data-access`]).toBeUndefined();
const project = readProjectConfig(newName); const project = readProjectConfig(newName);
expect(project).toBeTruthy(); expect(project).toBeTruthy();
expect(project.sourceRoot).toBe(`${newPath}/src`); expect(project.sourceRoot).toBe(`${newPath}/src`);
@ -681,7 +682,7 @@ describe('Workspace Tests', () => {
updateFile('nx.json', JSON.stringify(nxJson)); updateFile('nx.json', JSON.stringify(nxJson));
}); });
it('should work for libraries when scope is unset', () => { it('should work for libraries when scope is unset', async () => {
const json = readJson('nx.json'); const json = readJson('nx.json');
delete json.npmScope; delete json.npmScope;
updateFile('nx.json', JSON.stringify(json)); updateFile('nx.json', JSON.stringify(json));
@ -775,8 +776,7 @@ describe('Workspace Tests', () => {
rootTsConfig.compilerOptions.paths[`shared/${lib1}/data-access`] rootTsConfig.compilerOptions.paths[`shared/${lib1}/data-access`]
).toEqual([`libs/shared/${lib1}/data-access/src/index.ts`]); ).toEqual([`libs/shared/${lib1}/data-access/src/index.ts`]);
expect(moveOutput).toContain(`UPDATE workspace.json`); const workspaceJson = readResolvedWorkspaceConfiguration();
const workspaceJson = readJson(workspaceConfigName());
expect(workspaceJson.projects[`${lib1}-data-access`]).toBeUndefined(); expect(workspaceJson.projects[`${lib1}-data-access`]).toBeUndefined();
const project = readProjectConfig(newName); const project = readProjectConfig(newName);
expect(project).toBeTruthy(); expect(project).toBeTruthy();
@ -800,7 +800,7 @@ describe('Workspace Tests', () => {
/** /**
* Tries creating then deleting a lib * Tries creating then deleting a lib
*/ */
it('should work', () => { it('should work', async () => {
const lib1 = uniq('myliba'); const lib1 = uniq('myliba');
const lib2 = uniq('mylibb'); const lib2 = uniq('mylibb');
@ -848,20 +848,31 @@ describe('Workspace Tests', () => {
expect(exists(tmpProjPath(`libs/${lib1}`))).toBeFalsy(); expect(exists(tmpProjPath(`libs/${lib1}`))).toBeFalsy();
expect(removeOutputForced).not.toContain(`UPDATE nx.json`); expect(removeOutputForced).not.toContain(`UPDATE nx.json`);
const workspaceJson = readJson(workspaceConfigName()); const workspaceJson = readResolvedWorkspaceConfiguration();
expect(workspaceJson.projects[`${lib1}`]).toBeUndefined(); expect(workspaceJson.projects[`${lib1}`]).toBeUndefined();
const lib2Config = readProjectConfig(lib2); const lib2Config = readProjectConfig(lib2);
expect(lib2Config.implicitDependencies).toEqual([]); expect(lib2Config.implicitDependencies).toEqual([]);
expect(removeOutputForced).toContain(`UPDATE workspace.json`);
expect(workspaceJson.projects[`${lib1}`]).toBeUndefined(); expect(workspaceJson.projects[`${lib1}`]).toBeUndefined();
}); });
}); });
describe('workspace-lint', () => { describe('workspace-lint', () => {
it('should identify issues with the workspace', () => { beforeAll(() => {
// Unfortunately, this is required as this test is testing a different workspace layout // Unfortunately, this is required as this test is testing a different workspace layout
// workspace-lint only picks up missing projects and such when workspace.json exists.
newProject(); newProject();
updateFile(
'workspace.json',
JSON.stringify({ version: 2, projects: {} })
);
});
afterAll(() => {
removeFile('workspace.json');
});
it('should identify issues with the workspace', () => {
const appBefore = uniq('before'); const appBefore = uniq('before');
const appAfter = uniq('after'); const appAfter = uniq('after');

View File

@ -286,10 +286,6 @@ describe('Nx Plugin', () => {
}); });
it('should be able to infer projects and targets', async () => { it('should be able to infer projects and targets', async () => {
// Cache workspace json, to test inference and restore afterwards
const workspaceJsonContents = readFile('workspace.json');
removeFile('workspace.json');
// Setup project inference + target inference // Setup project inference + target inference
updateFile( updateFile(
`libs/${plugin}/src/index.ts`, `libs/${plugin}/src/index.ts`,
@ -327,9 +323,6 @@ describe('Nx Plugin', () => {
expect(runCLI(`build ${inferredProject}`)).toContain( expect(runCLI(`build ${inferredProject}`)).toContain(
'custom registered target' 'custom registered target'
); );
// Restore workspace.json
createFile('workspace.json', workspaceJsonContents);
}); });
it('should be able to use local generators and executors', async () => { it('should be able to use local generators and executors', async () => {

View File

@ -2,23 +2,22 @@ import type { NxJsonConfiguration } from '@nrwl/devkit';
import { import {
getPackageManagerCommand, getPackageManagerCommand,
isNotWindows, isNotWindows,
listFiles,
newProject, newProject,
readFile, readFile,
readJson, readJson,
readProjectConfig, readProjectConfig,
cleanupProject, cleanupProject,
rmDist,
runCLI, runCLI,
runCLIAsync, runCLIAsync,
runCommand, runCommand,
uniq, uniq,
updateFile, updateFile,
updateProjectConfig, updateProjectConfig,
workspaceConfigName,
checkFilesExist, checkFilesExist,
isWindows, isWindows,
fileExists, fileExists,
removeFile,
readResolvedWorkspaceConfiguration,
} from '@nrwl/e2e/utils'; } from '@nrwl/e2e/utils';
describe('Nx Affected and Graph Tests', () => { describe('Nx Affected and Graph Tests', () => {
@ -253,11 +252,8 @@ describe('Nx Affected and Graph Tests', () => {
it('should affect all projects by removing projects', () => { it('should affect all projects by removing projects', () => {
generateAll(); generateAll();
updateFile(workspaceConfigName(), (old) => { const root = readResolvedWorkspaceConfiguration().projects[mylib].root;
const workspaceJson = JSON.parse(old); removeFile(root);
delete workspaceJson.projects[mylib];
return JSON.stringify(workspaceJson, null, 2);
});
expect(runCLI('affected:apps')).toContain(myapp); expect(runCLI('affected:apps')).toContain(myapp);
expect(runCLI('affected:apps')).toContain(myapp2); expect(runCLI('affected:apps')).toContain(myapp2);
expect(runCLI('affected:libs')).not.toContain(mylib); expect(runCLI('affected:libs')).not.toContain(mylib);

View File

@ -198,7 +198,7 @@ describe('Nx Running Tests', () => {
env: { ...process.env, NX_DAEMON: 'true' }, env: { ...process.env, NX_DAEMON: 'true' },
}); });
expect(buildAgain).toContain('local cache'); expect(buildAgain).toContain('[local cache]');
}, 10000); }, 10000);
it('should build the project when within the project root', () => { it('should build the project when within the project root', () => {

View File

@ -1,4 +1,5 @@
import { import {
createProjectGraphAsync,
joinPathFragments, joinPathFragments,
parseJson, parseJson,
ProjectConfiguration, ProjectConfiguration,
@ -89,12 +90,19 @@ export function updateProjectConfig(
projectName: string, projectName: string,
callback: (c: ProjectConfiguration) => ProjectConfiguration callback: (c: ProjectConfiguration) => ProjectConfiguration
) { ) {
const root = readJson(workspaceConfigName()).projects[projectName]; const workspace = readResolvedWorkspaceConfiguration();
const root = workspace.projects[projectName].root;
const path = join(root, 'project.json'); const path = join(root, 'project.json');
const current = readJson(path); const current = readJson(path);
updateFile(path, JSON.stringify(callback(current), null, 2)); updateFile(path, JSON.stringify(callback(current), null, 2));
} }
export function readResolvedWorkspaceConfiguration() {
process.env.NX_PROJECT_GLOB_CACHE = 'false';
const ws = new Workspaces(tmpProjPath());
return ws.readWorkspaceConfiguration();
}
/** /**
* Use readProjectConfig or readInlineProjectConfig instead * Use readProjectConfig or readInlineProjectConfig instead
* if you need a project's configuration. * if you need a project's configuration.
@ -109,7 +117,7 @@ export function readWorkspaceConfig(): Omit<
} }
export function readProjectConfig(projectName: string): ProjectConfiguration { export function readProjectConfig(projectName: string): ProjectConfiguration {
const root = readJson(workspaceConfigName()).projects[projectName]; const root = readResolvedWorkspaceConfiguration().projects[projectName].root;
const path = join(root, 'project.json'); const path = join(root, 'project.json');
return readJson(path); return readJson(path);
} }

View File

@ -24,10 +24,10 @@ describe('create-nx-plugin', () => {
}); });
checkFilesExist( checkFilesExist(
'workspace.json',
'package.json', 'package.json',
packageManagerLockFile[packageManager], packageManagerLockFile[packageManager],
`packages/${pluginName}/package.json` `packages/${pluginName}/package.json`,
`packages/${pluginName}/project.json`
); );
expect(() => runCLI(`e2e ${pluginName}-e2e`)).not.toThrow(); expect(() => runCLI(`e2e ${pluginName}-e2e`)).not.toThrow();

View File

@ -29,7 +29,6 @@ describe('create-nx-workspace', () => {
}); });
checkFilesExist( checkFilesExist(
'workspace.json',
'package.json', 'package.json',
packageManagerLockFile[packageManager], packageManagerLockFile[packageManager],
'apps/.gitkeep', 'apps/.gitkeep',
@ -39,7 +38,7 @@ describe('create-nx-workspace', () => {
.filter((pm) => pm !== packageManager) .filter((pm) => pm !== packageManager)
.map((pm) => packageManagerLockFile[pm]); .map((pm) => packageManagerLockFile[pm]);
checkFilesDoNotExist(...foreignLockFiles); checkFilesDoNotExist(...foreignLockFiles, 'workspace.json');
expectNoAngularDevkit(); expectNoAngularDevkit();
}); });

View File

@ -360,9 +360,10 @@ describe('app', () => {
await generateApp(appTree, 'myApp', { directory: 'src/9-websites' }); await generateApp(appTree, 'myApp', { directory: 'src/9-websites' });
// ASSERT // ASSERT
const workspaceJson = readJson(appTree, '/workspace.json');
expect(workspaceJson.projects['src-9-websites-my-app']).toMatchSnapshot(); expect(
readProjectConfiguration(appTree, 'src-9-websites-my-app').root
).toMatchSnapshot();
}); });
it('should generate files', async () => { it('should generate files', async () => {

View File

@ -59,24 +59,9 @@ describe('lib', () => {
it('should default to standalone project for first project', async () => { it('should default to standalone project for first project', async () => {
await runLibraryGeneratorWithOpts(); await runLibraryGeneratorWithOpts();
const workspaceJsonEntry = readJson(tree, 'workspace.json').projects[
'my-lib'
];
const projectConfig = readProjectConfiguration(tree, 'my-lib'); const projectConfig = readProjectConfiguration(tree, 'my-lib');
expect(projectConfig.root).toEqual('libs/my-lib'); expect(projectConfig.root).toEqual('libs/my-lib');
expect(workspaceJsonEntry).toEqual('libs/my-lib'); expect(tree.exists('workspace.json')).toEqual(false);
});
it('should obey standalone === false for first project', async () => {
await runLibraryGeneratorWithOpts({
standaloneConfig: false,
});
const workspaceJsonEntry = readJson(tree, 'workspace.json').projects[
'my-lib'
];
const projectConfig = readProjectConfiguration(tree, 'my-lib');
expect(projectConfig.root).toEqual('libs/my-lib');
expect(projectConfig).toMatchObject(workspaceJsonEntry);
}); });
}); });
@ -732,22 +717,15 @@ describe('lib', () => {
it('should accept numbers in the path', async () => { it('should accept numbers in the path', async () => {
await runLibraryGeneratorWithOpts({ directory: 'src/1-api' }); await runLibraryGeneratorWithOpts({ directory: 'src/1-api' });
// ASSERT expect(readProjectConfiguration(tree, 'src-api-my-lib').root).toEqual(
const workspaceJson = readJson(tree, '/workspace.json');
expect(workspaceJson.projects['src-api-my-lib']).toEqual(
'src/1-api/my-lib' 'src/1-api/my-lib'
); );
}); });
it('should have root relative routes', async () => { it('should have root relative routes', async () => {
await runLibraryGeneratorWithOpts({ directory: 'myDir' }); await runLibraryGeneratorWithOpts({ directory: 'myDir' });
const workspaceJsonEntry = readJson(tree, 'workspace.json').projects[
'my-dir-my-lib'
];
const projectConfig = readProjectConfiguration(tree, 'my-dir-my-lib'); const projectConfig = readProjectConfiguration(tree, 'my-dir-my-lib');
expect(projectConfig.root).toEqual('my-dir/my-lib'); expect(projectConfig.root).toEqual('my-dir/my-lib');
expect(workspaceJsonEntry).toEqual('my-dir/my-lib');
}); });
it('should generate files with correct output paths', async () => { it('should generate files with correct output paths', async () => {

View File

@ -415,6 +415,7 @@ describe('e2e migrator', () => {
const e2eProject = readProjectConfiguration(tree, 'app1-e2e'); const e2eProject = readProjectConfiguration(tree, 'app1-e2e');
expect(e2eProject).toStrictEqual({ expect(e2eProject).toStrictEqual({
$schema: '../../node_modules/nx/schemas/project-schema.json', $schema: '../../node_modules/nx/schemas/project-schema.json',
name: 'app1-e2e',
root: 'apps/app1-e2e', root: 'apps/app1-e2e',
sourceRoot: 'apps/app1-e2e/src', sourceRoot: 'apps/app1-e2e/src',
projectType: 'application', projectType: 'application',
@ -544,6 +545,7 @@ describe('e2e migrator', () => {
const e2eProject = readProjectConfiguration(tree, 'app1-e2e'); const e2eProject = readProjectConfiguration(tree, 'app1-e2e');
expect(e2eProject).toStrictEqual({ expect(e2eProject).toStrictEqual({
$schema: '../../node_modules/nx/schemas/project-schema.json', $schema: '../../node_modules/nx/schemas/project-schema.json',
name: 'app1-e2e',
root: 'apps/app1-e2e', root: 'apps/app1-e2e',
sourceRoot: 'apps/app1-e2e/src', sourceRoot: 'apps/app1-e2e/src',
projectType: 'application', projectType: 'application',
@ -609,6 +611,7 @@ describe('e2e migrator', () => {
const e2eProject = readProjectConfiguration(tree, 'app1-e2e'); const e2eProject = readProjectConfiguration(tree, 'app1-e2e');
expect(e2eProject).toStrictEqual({ expect(e2eProject).toStrictEqual({
$schema: '../../node_modules/nx/schemas/project-schema.json', $schema: '../../node_modules/nx/schemas/project-schema.json',
name: 'app1-e2e',
root: 'apps/app1-e2e', root: 'apps/app1-e2e',
sourceRoot: 'apps/app1-e2e/src', sourceRoot: 'apps/app1-e2e/src',
projectType: 'application', projectType: 'application',
@ -662,6 +665,7 @@ describe('e2e migrator', () => {
const e2eProject = readProjectConfiguration(tree, 'app1-e2e'); const e2eProject = readProjectConfiguration(tree, 'app1-e2e');
expect(e2eProject).toStrictEqual({ expect(e2eProject).toStrictEqual({
$schema: '../../node_modules/nx/schemas/project-schema.json', $schema: '../../node_modules/nx/schemas/project-schema.json',
name: 'app1-e2e',
root: 'apps/app1-e2e', root: 'apps/app1-e2e',
sourceRoot: 'apps/app1-e2e/src', sourceRoot: 'apps/app1-e2e/src',
projectType: 'application', projectType: 'application',

View File

@ -18,7 +18,7 @@ describe('remove-library-generator-style-default migration', () => {
it('should do nothing when angular library generator is not configured', async () => { it('should do nothing when angular library generator is not configured', async () => {
const workspace: WorkspaceConfiguration = { const workspace: WorkspaceConfiguration = {
version: 1, version: 2,
generators: { '@nrwl/angular:application': { style: 'scss' } }, generators: { '@nrwl/angular:application': { style: 'scss' } },
}; };
updateWorkspaceConfiguration(tree, workspace); updateWorkspaceConfiguration(tree, workspace);
@ -30,7 +30,7 @@ describe('remove-library-generator-style-default migration', () => {
it('should do nothing when other vertical library generator is configured with the style entry', async () => { it('should do nothing when other vertical library generator is configured with the style entry', async () => {
const workspace: WorkspaceConfiguration = { const workspace: WorkspaceConfiguration = {
version: 1, version: 2,
generators: { '@nrwl/react:library': { style: 'scss' } }, generators: { '@nrwl/react:library': { style: 'scss' } },
}; };
updateWorkspaceConfiguration(tree, workspace); updateWorkspaceConfiguration(tree, workspace);
@ -43,7 +43,7 @@ describe('remove-library-generator-style-default migration', () => {
describe('collection:generator', () => { describe('collection:generator', () => {
it('should remove style entry when configured', async () => { it('should remove style entry when configured', async () => {
const workspace: WorkspaceConfiguration = { const workspace: WorkspaceConfiguration = {
version: 1, version: 2,
generators: { '@nrwl/angular:library': { style: 'scss' } }, generators: { '@nrwl/angular:library': { style: 'scss' } },
}; };
updateWorkspaceConfiguration(tree, workspace); updateWorkspaceConfiguration(tree, workspace);
@ -51,14 +51,14 @@ describe('remove-library-generator-style-default migration', () => {
await removeLibraryGeneratorStyleDefault(tree); await removeLibraryGeneratorStyleDefault(tree);
expect(readWorkspaceConfiguration(tree)).toStrictEqual({ expect(readWorkspaceConfiguration(tree)).toStrictEqual({
version: 1, version: 2,
generators: { '@nrwl/angular:library': {} }, generators: { '@nrwl/angular:library': {} },
}); });
}); });
it('should do nothing when style is not set', async () => { it('should do nothing when style is not set', async () => {
const workspace: WorkspaceConfiguration = { const workspace: WorkspaceConfiguration = {
version: 1, version: 2,
generators: { '@nrwl/angular:library': { linter: 'eslint' } }, generators: { '@nrwl/angular:library': { linter: 'eslint' } },
}; };
updateWorkspaceConfiguration(tree, workspace); updateWorkspaceConfiguration(tree, workspace);
@ -72,7 +72,7 @@ describe('remove-library-generator-style-default migration', () => {
describe('nested generator', () => { describe('nested generator', () => {
it('should remove style entry when configured', async () => { it('should remove style entry when configured', async () => {
const workspace: WorkspaceConfiguration = { const workspace: WorkspaceConfiguration = {
version: 1, version: 2,
generators: { '@nrwl/angular': { library: { style: 'scss' } } }, generators: { '@nrwl/angular': { library: { style: 'scss' } } },
}; };
updateWorkspaceConfiguration(tree, workspace); updateWorkspaceConfiguration(tree, workspace);
@ -80,14 +80,14 @@ describe('remove-library-generator-style-default migration', () => {
await removeLibraryGeneratorStyleDefault(tree); await removeLibraryGeneratorStyleDefault(tree);
expect(readWorkspaceConfiguration(tree)).toStrictEqual({ expect(readWorkspaceConfiguration(tree)).toStrictEqual({
version: 1, version: 2,
generators: { '@nrwl/angular': { library: {} } }, generators: { '@nrwl/angular': { library: {} } },
}); });
}); });
it('should do nothing when style is not set', async () => { it('should do nothing when style is not set', async () => {
const workspace: WorkspaceConfiguration = { const workspace: WorkspaceConfiguration = {
version: 1, version: 2,
generators: { '@nrwl/angular': { library: { linter: 'eslint' } } }, generators: { '@nrwl/angular': { library: { linter: 'eslint' } } },
}; };
updateWorkspaceConfiguration(tree, workspace); updateWorkspaceConfiguration(tree, workspace);
@ -101,7 +101,7 @@ describe('remove-library-generator-style-default migration', () => {
it('should format files', async () => { it('should format files', async () => {
jest.spyOn(devkit, 'formatFiles'); jest.spyOn(devkit, 'formatFiles');
const workspace: WorkspaceConfiguration = { const workspace: WorkspaceConfiguration = {
version: 1, version: 2,
generators: { '@nrwl/angular:library': { style: 'scss' } }, generators: { '@nrwl/angular:library': { style: 'scss' } },
}; };
updateWorkspaceConfiguration(tree, workspace); updateWorkspaceConfiguration(tree, workspace);

View File

@ -22,6 +22,7 @@ describe('set-build-libs-from-source migration', () => {
it('should not update when not using the @nrwl/angular:webpack-browser executor', async () => { it('should not update when not using the @nrwl/angular:webpack-browser executor', async () => {
const project: ProjectConfiguration = { const project: ProjectConfiguration = {
name: 'app1',
root: 'apps/app1', root: 'apps/app1',
targets: { build: { executor: '@nrwl/angular:package' } }, targets: { build: { executor: '@nrwl/angular:package' } },
}; };
@ -38,6 +39,7 @@ describe('set-build-libs-from-source migration', () => {
it('should set buildLibsFromSource to false', async () => { it('should set buildLibsFromSource to false', async () => {
addProjectConfiguration(tree, 'app1', { addProjectConfiguration(tree, 'app1', {
name: 'app1',
root: 'apps/app1', root: 'apps/app1',
targets: { build: { executor: '@nrwl/angular:webpack-browser' } }, targets: { build: { executor: '@nrwl/angular:webpack-browser' } },
}); });
@ -52,6 +54,7 @@ describe('set-build-libs-from-source migration', () => {
it('should support any target name using @nrwl/angular:webpack-browser', async () => { it('should support any target name using @nrwl/angular:webpack-browser', async () => {
addProjectConfiguration(tree, 'app1', { addProjectConfiguration(tree, 'app1', {
name: 'app1',
root: 'apps/app1', root: 'apps/app1',
targets: { 'build-base': { executor: '@nrwl/angular:webpack-browser' } }, targets: { 'build-base': { executor: '@nrwl/angular:webpack-browser' } },
}); });

View File

@ -4,6 +4,21 @@ const allowedProjectExtensions = [
'configFilePath', 'configFilePath',
'$schema', '$schema',
'generators', 'generators',
'namedInputs',
'name',
];
const allowedWorkspaceExtensions = [
'implicitDependencies',
'affected',
'npmScope',
'tasksRunnerOptions',
'workspaceLayout',
'plugins',
'targetDefaults',
'files',
'generators',
'namedInputs',
]; ];
const possiblePaths = [ const possiblePaths = [
@ -23,6 +38,7 @@ for (const possiblePath of possiblePaths) {
return originalReadJsonWorkspace(path, host, { return originalReadJsonWorkspace(path, host, {
...options, ...options,
allowedProjectExtensions, allowedProjectExtensions,
allowedWorkspaceExtensions,
}); });
}, },
}; };

View File

@ -12,5 +12,4 @@ export function cleanUpFiles(appName: string) {
); );
removeSync('temp-workspace'); removeSync('temp-workspace');
removeSync('workspace.json');
} }

View File

@ -37,8 +37,7 @@ describe('detox application generator', () => {
}); });
it('should add update `workspace.json` file', async () => { it('should add update `workspace.json` file', async () => {
const workspaceJson = readJson(tree, 'workspace.json'); const project = readProjectConfiguration(tree, 'my-app-e2e');
const project = workspaceJson.projects['my-app-e2e'];
expect(project.root).toEqual('apps/my-app-e2e'); expect(project.root).toEqual('apps/my-app-e2e');
}); });
@ -73,8 +72,7 @@ describe('detox application generator', () => {
}); });
it('should add update `workspace.json` file', async () => { it('should add update `workspace.json` file', async () => {
const workspaceJson = readJson(tree, 'workspace.json'); const project = readProjectConfiguration(tree, 'my-dir-my-app-e2e');
const project = workspaceJson.projects['my-dir-my-app-e2e'];
expect(project.root).toEqual('apps/my-dir/my-app-e2e'); expect(project.root).toEqual('apps/my-dir/my-app-e2e');
}); });
@ -108,8 +106,7 @@ describe('detox application generator', () => {
}); });
it('should add update `workspace.json` file', async () => { it('should add update `workspace.json` file', async () => {
const workspaceJson = readJson(tree, 'workspace.json'); const project = readProjectConfiguration(tree, 'my-dir-my-app-e2e');
const project = workspaceJson.projects['my-dir-my-app-e2e'];
expect(project.root).toEqual('apps/my-dir/my-app-e2e'); expect(project.root).toEqual('apps/my-dir/my-app-e2e');
}); });

View File

@ -17,7 +17,7 @@ describe('app', () => {
appTree.write('.gitignore', ''); appTree.write('.gitignore', '');
}); });
it('should update workspace.json', async () => { it('should update workspace', async () => {
await expoApplicationGenerator(appTree, { await expoApplicationGenerator(appTree, {
name: 'myApp', name: 'myApp',
displayName: 'myApp', displayName: 'myApp',
@ -27,11 +27,11 @@ describe('app', () => {
js: false, js: false,
unitTestRunner: 'none', unitTestRunner: 'none',
}); });
const workspaceJson = readWorkspaceConfiguration(appTree); const workspace = readWorkspaceConfiguration(appTree);
const projects = getProjects(appTree); const projects = getProjects(appTree);
expect(projects.get('my-app').root).toEqual('apps/my-app'); expect(projects.get('my-app').root).toEqual('apps/my-app');
expect(workspaceJson.defaultProject).toEqual('my-app'); expect(workspace.defaultProject).toEqual('my-app');
}); });
it('should update nx.json', async () => { it('should update nx.json', async () => {

View File

@ -15,7 +15,7 @@ describe('Add Linting', () => {
}); });
}); });
it('should add update `workspace.json` file properly when eslint is passed', () => { it('should add update `project configuration` file properly when eslint is passed', () => {
addLinting( addLinting(
tree, tree,
'my-lib', 'my-lib',
@ -29,7 +29,7 @@ describe('Add Linting', () => {
expect(project.targets.lint.executor).toEqual('@nrwl/linter:eslint'); expect(project.targets.lint.executor).toEqual('@nrwl/linter:eslint');
}); });
it('should add update `workspace.json` file properly when tslint is passed', () => { it('should add update `project configuration` file properly when tslint is passed', () => {
addLinting( addLinting(
tree, tree,
'my-lib', 'my-lib',

View File

@ -65,7 +65,7 @@ describe('jestProject', () => {
expect(tree.exists('babel.config.json')).toBeTruthy(); expect(tree.exists('babel.config.json')).toBeTruthy();
}); });
it('should alter workspace.json', async () => { it('should alter project configuration', async () => {
await jestProjectGenerator(tree, { await jestProjectGenerator(tree, {
...defaultOptions, ...defaultOptions,
project: 'lib1', project: 'lib1',
@ -160,7 +160,7 @@ describe('jestProject', () => {
expect(jestConfig).toMatchSnapshot(); expect(jestConfig).toMatchSnapshot();
}); });
it('should not list the setup file in workspace.json', async () => { it('should not list the setup file in project configuration', async () => {
await jestProjectGenerator(tree, { await jestProjectGenerator(tree, {
...defaultOptions, ...defaultOptions,
project: 'lib1', project: 'lib1',
@ -191,7 +191,7 @@ describe('jestProject', () => {
expect(tree.exists('src/test-setup.ts')).toBeFalsy(); expect(tree.exists('src/test-setup.ts')).toBeFalsy();
}); });
it('should not list the setup file in workspace.json', async () => { it('should not list the setup file in project configuration', async () => {
await jestProjectGenerator(tree, { await jestProjectGenerator(tree, {
...defaultOptions, ...defaultOptions,
project: 'lib1', project: 'lib1',

View File

@ -51,10 +51,6 @@ describe('lib', () => {
// unitTestRunner property is ignored. // unitTestRunner property is ignored.
// It only works with our executors. // It only works with our executors.
expect(tree.exists('libs/my-lib/src/lib/my-lib.spec.ts')).toBeFalsy(); expect(tree.exists('libs/my-lib/src/lib/my-lib.spec.ts')).toBeFalsy();
const workspaceJson = readJson(tree, '/workspace.json');
// Blocked on Craigory merging optional config PR
// expect(workspaceJson.projects['my-lib']).toBeUndefined();
// expect(tree.exists('libs/my-lib/project.json')).toBeFalsy();
}); });
it('should generate an empty ts lib using --config=project', async () => { it('should generate an empty ts lib using --config=project', async () => {
@ -63,12 +59,8 @@ describe('lib', () => {
name: 'my-lib', name: 'my-lib',
config: 'project', config: 'project',
}); });
const workspaceJsonEntry = readJson(tree, 'workspace.json').projects[
'my-lib'
];
const projectConfig = readProjectConfiguration(tree, 'my-lib'); const projectConfig = readProjectConfiguration(tree, 'my-lib');
expect(projectConfig.root).toEqual('libs/my-lib'); expect(projectConfig.root).toEqual('libs/my-lib');
expect(workspaceJsonEntry).toEqual('libs/my-lib');
}); });
it('should generate an empty ts lib using --config=workspace', async () => { it('should generate an empty ts lib using --config=workspace', async () => {
@ -77,12 +69,8 @@ describe('lib', () => {
name: 'my-lib', name: 'my-lib',
config: 'workspace', config: 'workspace',
}); });
const workspaceJsonEntry = readJson(tree, 'workspace.json').projects[
'my-lib'
];
const projectConfig = readProjectConfiguration(tree, 'my-lib'); const projectConfig = readProjectConfiguration(tree, 'my-lib');
expect(projectConfig.root).toEqual('libs/my-lib'); expect(projectConfig.root).toEqual('libs/my-lib');
expect(projectConfig).toMatchObject(workspaceJsonEntry);
}); });
}); });
@ -225,16 +213,15 @@ describe('lib', () => {
expect(tree.exists(`libs/my-dir/my-lib/package.json`)).toBeFalsy(); expect(tree.exists(`libs/my-dir/my-lib/package.json`)).toBeFalsy();
}); });
it('should update workspace.json', async () => { it('should update project configuration', async () => {
await libraryGenerator(tree, { await libraryGenerator(tree, {
...defaultOptions, ...defaultOptions,
name: 'myLib', name: 'myLib',
directory: 'myDir', directory: 'myDir',
config: 'workspace', config: 'workspace',
}); });
const workspaceJson = readJson(tree, '/workspace.json');
expect(workspaceJson.projects['my-dir-my-lib'].root).toEqual( expect(readProjectConfiguration(tree, 'my-dir-my-lib').root).toEqual(
'libs/my-dir/my-lib' 'libs/my-dir/my-lib'
); );
}); });

View File

@ -141,10 +141,8 @@ function addProject(
} }
} }
if (options.config === 'workspace') { if (options.config === 'workspace' || options.config === 'project') {
addProjectConfiguration(tree, options.name, projectConfiguration, false); addProjectConfiguration(tree, options.name, projectConfiguration);
} else if (options.config === 'project') {
addProjectConfiguration(tree, options.name, projectConfiguration, true);
} else { } else {
addProjectConfiguration( addProjectConfiguration(
tree, tree,

View File

@ -39,7 +39,7 @@ describe('@nrwl/linter:lint-project', () => {
).toMatchSnapshot(); ).toMatchSnapshot();
}); });
it('should configure the target in workspace.json', async () => { it('should configure the target in project configuration', async () => {
await lintProjectGenerator(tree, { await lintProjectGenerator(tree, {
...defaultOptions, ...defaultOptions,
linter: Linter.EsLint, linter: Linter.EsLint,
@ -96,7 +96,7 @@ describe('@nrwl/linter:lint-project', () => {
).toMatchSnapshot(); ).toMatchSnapshot();
}); });
it('should configure the target in workspace.json', async () => { it('should configure the target in project configuration', async () => {
await lintProjectGenerator(tree, { await lintProjectGenerator(tree, {
...defaultOptions, ...defaultOptions,
linter: Linter.TsLint, linter: Linter.TsLint,

View File

@ -76,6 +76,7 @@ describe('@nrwl/linter:workspace-rules-project', () => {
.toMatchInlineSnapshot(` .toMatchInlineSnapshot(`
Object { Object {
"$schema": "../../node_modules/nx/schemas/project-schema.json", "$schema": "../../node_modules/nx/schemas/project-schema.json",
"name": "eslint-rules",
"root": "tools/eslint-rules", "root": "tools/eslint-rules",
"sourceRoot": "tools/eslint-rules", "sourceRoot": "tools/eslint-rules",
"targets": Object { "targets": Object {

View File

@ -73,7 +73,7 @@ describe('Add explicit .json file extension to .eslintrc files', () => {
); );
}); });
it('should rename .eslintrc files to .eslintrc.json and update any workspace.json references', async () => { it('should rename .eslintrc files to .eslintrc.json and update any project configuration references', async () => {
const result = await runMigration('add-json-ext-to-eslintrc', {}, tree); const result = await runMigration('add-json-ext-to-eslintrc', {}, tree);
const workspace = readWorkspace(tree); const workspace = readWorkspace(tree);

View File

@ -77,7 +77,7 @@ function updateReactESLintConfigs(host: Tree) {
/** /**
* There isn't a way to know for sure if a project was started with the Nx * There isn't a way to know for sure if a project was started with the Nx
* original inline React ESLint config (for applications it is easy to know * original inline React ESLint config (for applications it is easy to know
* from the workspace.json, but that is not the case for all libraries). * from the project configuration, but that is not the case for all libraries).
* *
* We therefore try and infer it based on the presence of react eslint plugins * We therefore try and infer it based on the presence of react eslint plugins
* within the config that is currently there. * within the config that is currently there.

View File

@ -421,14 +421,14 @@ export class ProjectConverter {
); );
/** /**
* Update global linter configuration defaults in workspace.json * Update global linter configuration defaults in project configuration
*/ */
const workspace = readWorkspaceConfiguration(this.host); const workspace = readWorkspaceConfiguration(this.host);
this.cleanUpGeneratorsConfig(workspace); this.cleanUpGeneratorsConfig(workspace);
updateWorkspaceConfiguration(this.host, workspace); updateWorkspaceConfiguration(this.host, workspace);
/** /**
* Update project-level linter configuration defaults in workspace.json * Update project-level linter configuration defaults in project configuration
*/ */
const projects = getProjects(this.host); const projects = getProjects(this.host);
for (const [projectName, { generators }] of projects.entries()) { for (const [projectName, { generators }] of projects.entries()) {

View File

@ -15,7 +15,7 @@ describe('lib', () => {
}); });
describe('not nested', () => { describe('not nested', () => {
it('should update workspace.json', async () => { it('should update project configuration', async () => {
await libraryGenerator(tree, { name: libName }); await libraryGenerator(tree, { name: libName });
const workspaceJson = readJson(tree, '/workspace.json'); const workspaceJson = readJson(tree, '/workspace.json');

View File

@ -96,6 +96,7 @@ describe('React default development configuration', () => {
const config = readProjectConfiguration(tree, 'example'); const config = readProjectConfiguration(tree, 'example');
expect(config).toEqual({ expect(config).toEqual({
$schema: '../../node_modules/nx/schemas/project-schema.json', $schema: '../../node_modules/nx/schemas/project-schema.json',
name: 'example',
root: 'apps/example', root: 'apps/example',
projectType: 'application', projectType: 'application',
}); });

View File

@ -26,6 +26,7 @@ const allowedProjectExtensions = [
'$schema', '$schema',
'generators', 'generators',
'namedInputs', 'namedInputs',
'name',
]; ];
const allowedWorkspaceExtensions = [ const allowedWorkspaceExtensions = [

View File

@ -515,7 +515,7 @@ export class NxScopedHost extends virtualFs.ScopedHost<any> {
} }
// project was read from a project.json file // project was read from a project.json file
const configPath = projectConfig.configFilePath; const configPath = projectConfig.configFilePath;
const fileConfigObject = { ...projectConfig }; const fileConfigObject = { name: project, ...projectConfig };
delete fileConfigObject.root; // remove the root before writing delete fileConfigObject.root; // remove the root before writing
delete fileConfigObject.configFilePath; // remove the configFilePath before writing delete fileConfigObject.configFilePath; // remove the configFilePath before writing
const projectJsonWrite = super.write( const projectJsonWrite = super.write(
@ -752,12 +752,7 @@ function findWorkspaceConfigFileChange(host: Tree): WorkspaceConfigFileChange {
function findCreatedProjects(host: Tree): FileChange[] { function findCreatedProjects(host: Tree): FileChange[] {
return host return host
.listChanges() .listChanges()
.filter( .filter((f) => f.type === 'CREATE' && basename(f.path) === 'project.json');
(f) =>
f.type === 'CREATE' &&
(basename(f.path) === 'project.json' ||
basename(f.path) === 'package.json')
);
} }
function findDeletedProjects(host: Tree): FileChange[] { function findDeletedProjects(host: Tree): FileChange[] {
@ -1196,7 +1191,9 @@ function saveWorkspaceConfigurationInWrappedSchematic(
const workspaceJsonExists = host.exists(r.eventPath); const workspaceJsonExists = host.exists(r.eventPath);
const workspace: Omit<AngularJsonConfiguration, 'projects'> & { const workspace: Omit<AngularJsonConfiguration, 'projects'> & {
projects: { projects: {
[key: string]: string | { configFilePath?: string; root: string }; [key: string]:
| string
| ({ configFilePath?: string } & ProjectConfiguration);
}; };
} = parseJson(r.content.toString()); } = parseJson(r.content.toString());
for (const [project, config] of Object.entries(workspace.projects)) { for (const [project, config] of Object.entries(workspace.projects)) {
@ -1206,9 +1203,15 @@ function saveWorkspaceConfigurationInWrappedSchematic(
) { ) {
const path = config.configFilePath || join(config.root, 'project.json'); const path = config.configFilePath || join(config.root, 'project.json');
workspace.projects[project] = normalize(dirname(path)); workspace.projects[project] = normalize(dirname(path));
delete config.root; // remove the root before writing host.write(
delete config.configFilePath; path,
host.write(path, serializeJson(config)); serializeJson({
name: project,
...config,
root: undefined,
configFilePath: undefined,
})
);
} }
} }
const nxJson: NxJsonConfiguration = parseJson( const nxJson: NxJsonConfiguration = parseJson(

View File

@ -148,7 +148,7 @@ async function runExecutorInternal<T extends { success: boolean }>(
mergePluginTargetsWithNxTargets( mergePluginTargetsWithNxTargets(
proj.root, proj.root,
proj.targets, proj.targets,
loadNxPlugins(workspace.plugins) loadNxPlugins(workspace.plugins, [root], root)
)[target]; )[target];
if (!targetConfig) { if (!targetConfig) {

View File

@ -28,7 +28,9 @@ import {
import { PackageJson } from '../utils/package-json'; import { PackageJson } from '../utils/package-json';
import { sortObjectByKeys } from 'nx/src/utils/object-sort'; import { sortObjectByKeys } from 'nx/src/utils/object-sort';
export function workspaceConfigName(root: string) { export function workspaceConfigName(
root: string
): 'angular.json' | 'workspace.json' | null {
if (existsSync(path.join(root, 'angular.json'))) { if (existsSync(path.join(root, 'angular.json'))) {
return 'angular.json'; return 'angular.json';
} else if (existsSync(path.join(root, 'workspace.json'))) { } else if (existsSync(path.join(root, 'workspace.json'))) {
@ -71,8 +73,9 @@ export class Workspaces {
if ( if (
this.cachedWorkspaceConfig && this.cachedWorkspaceConfig &&
process.env.NX_CACHE_WORKSPACE_CONFIG !== 'false' process.env.NX_CACHE_WORKSPACE_CONFIG !== 'false'
) ) {
return this.cachedWorkspaceConfig; return this.cachedWorkspaceConfig;
}
const nxJson = this.readNxJson(); const nxJson = this.readNxJson();
const workspaceFile = workspaceConfigName(this.root); const workspaceFile = workspaceConfigName(this.root);
const workspacePath = workspaceFile const workspacePath = workspaceFile
@ -474,6 +477,7 @@ export function toOldFormatOrNull(w: any) {
renamePropertyWithStableKeys(projectConfig, 'generators', 'schematics'); renamePropertyWithStableKeys(projectConfig, 'generators', 'schematics');
formatted = true; formatted = true;
} }
delete projectConfig.name;
Object.values(projectConfig.architect || {}).forEach((target: any) => { Object.values(projectConfig.architect || {}).forEach((target: any) => {
if (target.executor !== undefined) { if (target.executor !== undefined) {
renamePropertyWithStableKeys(target, 'executor', 'builder'); renamePropertyWithStableKeys(target, 'executor', 'builder');
@ -551,8 +555,12 @@ export function toProjectName(
let projectGlobCache: string[]; let projectGlobCache: string[];
let projectGlobCacheKey: string; let projectGlobCacheKey: string;
function getGlobPatternsFromPlugins(nxJson: NxJsonConfiguration): string[] { export function getGlobPatternsFromPlugins(
const plugins = loadNxPlugins(nxJson?.plugins); nxJson: NxJsonConfiguration,
paths: string[],
root = workspaceRoot
): string[] {
const plugins = loadNxPlugins(nxJson?.plugins, paths, root);
const patterns = []; const patterns = [];
for (const plugin of plugins) { for (const plugin of plugins) {
@ -570,7 +578,9 @@ function getGlobPatternsFromPlugins(nxJson: NxJsonConfiguration): string[] {
/** /**
* Get the package.json globs from package manager workspaces * Get the package.json globs from package manager workspaces
*/ */
function getGlobPatternsFromPackageManagerWorkspaces(root: string): string[] { export function getGlobPatternsFromPackageManagerWorkspaces(
root: string
): string[] {
try { try {
try { try {
const obj = yaml.load(readFileSync(join(root, 'pnpm-workspace.yaml'))); const obj = yaml.load(readFileSync(join(root, 'pnpm-workspace.yaml')));
@ -635,7 +645,9 @@ export function globForProjectFiles(
]; ];
if (!ignorePluginInference) { if (!ignorePluginInference) {
projectGlobPatterns.push(...getGlobPatternsFromPlugins(nxJson)); projectGlobPatterns.push(
...getGlobPatternsFromPlugins(nxJson, [root], root)
);
} }
const combinedProjectGlobPattern = '{' + projectGlobPatterns.join(',') + '}'; const combinedProjectGlobPattern = '{' + projectGlobPatterns.join(',') + '}';

View File

@ -6,7 +6,6 @@ import type { Tree } from 'nx/src/generators/tree';
*/ */
export function createTreeWithEmptyWorkspace(): Tree { export function createTreeWithEmptyWorkspace(): Tree {
const tree = new FsTree('/virtual', false); const tree = new FsTree('/virtual', false);
tree.write('/workspace.json', JSON.stringify({ version: 2, projects: {} }));
return addCommonFiles(tree); return addCommonFiles(tree);
} }

View File

@ -6,7 +6,7 @@ import {
createTreeWithEmptyWorkspace, createTreeWithEmptyWorkspace,
createTreeWithEmptyV1Workspace, createTreeWithEmptyV1Workspace,
} from '../testing-utils/create-tree-with-empty-workspace'; } from '../testing-utils/create-tree-with-empty-workspace';
import { readJson, updateJson } from '../utils/json'; import { readJson, updateJson, writeJson } from '../utils/json';
import { import {
addProjectConfiguration, addProjectConfiguration,
getProjects, getProjects,
@ -20,6 +20,7 @@ import {
} from './project-configuration'; } from './project-configuration';
import * as projectSchema from '../../../schemas/project-schema.json'; import * as projectSchema from '../../../schemas/project-schema.json';
import { joinPathFragments } from 'nx/src/utils/path';
type ProjectConfigurationV1 = Pick< type ProjectConfigurationV1 = Pick<
ProjectConfiguration, ProjectConfiguration,
@ -38,6 +39,7 @@ const baseTestProjectConfigV1: ProjectConfigurationV1 = {
architect: {}, architect: {},
}; };
const baseTestProjectConfigV2: ProjectConfiguration = { const baseTestProjectConfigV2: ProjectConfiguration = {
name: 'test',
root: 'libs/test', root: 'libs/test',
sourceRoot: 'libs/test/src', sourceRoot: 'libs/test/src',
targets: {}, targets: {},
@ -195,11 +197,13 @@ describe('project configuration', () => {
describe('readProjectConfiguration', () => { describe('readProjectConfiguration', () => {
it('should get info from workspace.json', () => { it('should get info from workspace.json', () => {
updateJson(tree, getWorkspacePath(tree), (json) => { writeJson(tree, 'workspace.json', {
json.projects['proj1'] = { version: 2,
projects: {
proj1: {
root: 'proj1', root: 'proj1',
}; },
return json; },
}); });
const config = readProjectConfiguration(tree, 'proj1'); const config = readProjectConfiguration(tree, 'proj1');
@ -209,11 +213,8 @@ describe('project configuration', () => {
}); });
it('should should not fail if projects is not defined in nx.json', () => { it('should should not fail if projects is not defined in nx.json', () => {
updateJson(tree, getWorkspacePath(tree), (json) => { writeJson(tree, 'libs/proj1/project.json', {
json.projects['proj1'] = { name: 'proj1',
root: 'proj1',
};
return json;
}); });
updateJson(tree, 'nx.json', (json) => { updateJson(tree, 'nx.json', (json) => {
delete json.projects; delete json.projects;
@ -222,7 +223,8 @@ describe('project configuration', () => {
const config = readProjectConfiguration(tree, 'proj1'); const config = readProjectConfiguration(tree, 'proj1');
expect(config).toEqual({ expect(config).toEqual({
root: 'proj1', name: 'proj1',
root: 'libs/proj1',
}); });
}); });
}); });
@ -244,19 +246,18 @@ describe('project configuration', () => {
}, },
true true
); );
addProjectConfiguration( addProjectConfiguration(tree, 'project-b', {
tree,
'project-b',
{
root: 'apps/project-b', root: 'apps/project-b',
targets: {}, targets: {},
}, });
true
);
expect(tree.exists('apps/project-b/project.json')).toBeTruthy(); expect(tree.exists('apps/project-b/project.json')).toBeTruthy();
}); });
it("should not create project.json file if any other app in the workspace doesn't use project.json", () => { it("should not create project.json file if any other app in the workspace doesn't use project.json", () => {
writeJson(tree, 'workspace.json', {
version: 2,
projects: {},
});
addProjectConfiguration( addProjectConfiguration(
tree, tree,
'project-a', 'project-a',
@ -275,6 +276,10 @@ describe('project configuration', () => {
}); });
it('should not create project.json file when adding a project if standalone is false', () => { it('should not create project.json file when adding a project if standalone is false', () => {
writeJson(tree, 'workspace.json', {
version: 2,
projects: {},
});
addProjectConfiguration(tree, 'test', baseTestProjectConfigV2, false); addProjectConfiguration(tree, 'test', baseTestProjectConfigV2, false);
expect(tree.exists('libs/test/project.json')).toBeFalsy(); expect(tree.exists('libs/test/project.json')).toBeFalsy();
@ -307,17 +312,21 @@ describe('project configuration', () => {
addProjectConfiguration(tree, 'test', baseTestProjectConfigV2, true); addProjectConfiguration(tree, 'test', baseTestProjectConfigV2, true);
const expectedProjectConfig = { const expectedProjectConfig = {
...baseTestProjectConfigV2, ...baseTestProjectConfigV2,
root: undefined,
targets: { build: { executor: '' } }, targets: { build: { executor: '' } },
}; };
updateProjectConfiguration(tree, 'test', expectedProjectConfig); updateProjectConfiguration(tree, 'test', expectedProjectConfig);
expect(readJson(tree, 'libs/test/project.json')).toEqual( expect(readJson(tree, 'libs/test/project.json')).toEqual({
expectedProjectConfig ...expectedProjectConfig,
); root: undefined,
});
}); });
it('should update workspace.json file when updating an inline project', () => { it('should update workspace.json file when updating an inline project', () => {
writeJson(tree, 'workspace.json', {
version: 2,
projects: {},
});
addProjectConfiguration(tree, 'test', baseTestProjectConfigV2, false); addProjectConfiguration(tree, 'test', baseTestProjectConfigV2, false);
const expectedProjectConfig = { const expectedProjectConfig = {
...baseTestProjectConfigV2, ...baseTestProjectConfigV2,
@ -334,19 +343,25 @@ describe('project configuration', () => {
addProjectConfiguration(tree, 'test', baseTestProjectConfigV2, true); addProjectConfiguration(tree, 'test', baseTestProjectConfigV2, true);
removeProjectConfiguration(tree, 'test'); removeProjectConfiguration(tree, 'test');
expect(readJson(tree, 'workspace.json').projects.test).toBeUndefined();
expect(tree.exists('test/project.json')).toBeFalsy(); expect(tree.exists('test/project.json')).toBeFalsy();
}); });
it('should support workspaces with standalone and inline projects', () => { it('should support workspaces with standalone and inline projects', () => {
writeJson(tree, 'workspace.json', {
version: 2,
projects: {},
});
addProjectConfiguration(tree, 'test', baseTestProjectConfigV2, true); addProjectConfiguration(tree, 'test', baseTestProjectConfigV2, true);
addProjectConfiguration(tree, 'test2', baseTestProjectConfigV2, false); addProjectConfiguration(tree, 'test2', baseTestProjectConfigV2, false);
const configurations = getProjects(tree); const configurations = getProjects(tree);
expect(configurations.get('test')).toEqual({ expect(configurations.get('test')).toEqual({
$schema: '../../node_modules/nx/schemas/project-schema.json', $schema: '../../node_modules/nx/schemas/project-schema.json',
name: 'test',
...baseTestProjectConfigV2,
});
expect(configurations.get('test2')).toEqual({
...baseTestProjectConfigV2, ...baseTestProjectConfigV2,
}); });
expect(configurations.get('test2')).toEqual(baseTestProjectConfigV2);
}); });
describe('JSON schema', () => { describe('JSON schema', () => {
@ -375,6 +390,10 @@ describe('project configuration', () => {
let workspaceConfiguration: WorkspaceConfiguration; let workspaceConfiguration: WorkspaceConfiguration;
beforeEach(() => { beforeEach(() => {
writeJson(tree, 'workspace.json', {
version: 2,
projects: {},
});
workspaceConfiguration = readWorkspaceConfiguration(tree); workspaceConfiguration = readWorkspaceConfiguration(tree);
}); });
@ -447,6 +466,10 @@ describe('project configuration', () => {
}); });
it("should not create project.json file if any other app in the workspace doesn't use project.json", () => { it("should not create project.json file if any other app in the workspace doesn't use project.json", () => {
writeJson(tree, 'workspace.json', {
version: 2,
projects: {},
});
addProjectConfiguration( addProjectConfiguration(
tree, tree,
'project-a', 'project-a',
@ -456,20 +479,19 @@ describe('project configuration', () => {
}, },
false false
); );
addProjectConfiguration( addProjectConfiguration(tree, 'project-b', {
tree,
'project-b',
{
root: 'apps/project-b', root: 'apps/project-b',
targets: {}, targets: {},
}, });
false
);
expect(tree.exists('apps/project-a/project.json')).toBeFalsy(); expect(tree.exists('apps/project-a/project.json')).toBeFalsy();
expect(tree.exists('apps/project-b/project.json')).toBeFalsy(); expect(tree.exists('apps/project-b/project.json')).toBeFalsy();
}); });
it('should not create project.json file when adding a project if standalone is false', () => { it('should not create project.json file when adding a project if standalone is false', () => {
writeJson(tree, 'workspace.json', {
version: 2,
projects: {},
});
addProjectConfiguration(tree, 'test', baseTestProjectConfigV2, false); addProjectConfiguration(tree, 'test', baseTestProjectConfigV2, false);
expect(tree.exists('libs/test/project.json')).toBeFalsy(); expect(tree.exists('libs/test/project.json')).toBeFalsy();
@ -502,17 +524,21 @@ describe('project configuration', () => {
addProjectConfiguration(tree, 'test', baseTestProjectConfigV2, true); addProjectConfiguration(tree, 'test', baseTestProjectConfigV2, true);
const expectedProjectConfig = { const expectedProjectConfig = {
...baseTestProjectConfigV2, ...baseTestProjectConfigV2,
root: undefined,
targets: { build: { executor: '' } }, targets: { build: { executor: '' } },
}; };
updateProjectConfiguration(tree, 'test', expectedProjectConfig); updateProjectConfiguration(tree, 'test', expectedProjectConfig);
expect(readJson(tree, 'libs/test/project.json')).toEqual( expect(readJson(tree, 'libs/test/project.json')).toEqual({
expectedProjectConfig ...expectedProjectConfig,
); root: undefined,
});
}); });
it('should update workspace.json file when updating an inline project', () => { it('should update workspace.json file when updating an inline project', () => {
writeJson(tree, 'workspace.json', {
version: 2,
projects: {},
});
addProjectConfiguration(tree, 'test', baseTestProjectConfigV2, false); addProjectConfiguration(tree, 'test', baseTestProjectConfigV2, false);
const expectedProjectConfig = { const expectedProjectConfig = {
...baseTestProjectConfigV2, ...baseTestProjectConfigV2,
@ -536,20 +562,11 @@ describe('project configuration', () => {
addProjectConfiguration(tree, 'test', baseTestProjectConfigV2, false); addProjectConfiguration(tree, 'test', baseTestProjectConfigV2, false);
removeProjectConfiguration(tree, 'test'); removeProjectConfiguration(tree, 'test');
expect(readJson(tree, 'workspace.json').projects.test).toBeUndefined(); expect(
}); tree.exists(
joinPathFragments(baseTestProjectConfigV2.root, 'project.json')
it('should support workspaces with standalone and inline projects', () => { )
addProjectConfiguration(tree, 'test', baseTestProjectConfigV2, true); ).toBeFalsy();
addProjectConfiguration(tree, 'test2', baseTestProjectConfigV2, false);
const configurations = getProjects(tree);
expect(configurations.get('test')).toEqual({
$schema: '../../node_modules/nx/schemas/project-schema.json',
...baseTestProjectConfigV2,
});
expect(configurations.get('test2')).toEqual(baseTestProjectConfigV2);
}); });
}); });
}); });

View File

@ -228,9 +228,11 @@ export function readProjectConfiguration(
const workspace = readWorkspace(tree); const workspace = readWorkspace(tree);
if (!workspace.projects[projectName]) { if (!workspace.projects[projectName]) {
throw new Error( throw new Error(
`Cannot find configuration for '${projectName}' in ${getWorkspacePath( getWorkspacePath(tree)
? `Cannot find configuration for '${projectName}' in ${getWorkspacePath(
tree tree
)}.` )}.`
: `Cannot find configuration for '${projectName}'`
); );
} }
@ -373,6 +375,7 @@ function addProjectToWorkspaceJson(
// update the project.json file // update the project.json file
writeJson(tree, configFile, { writeJson(tree, configFile, {
...jsonSchema, ...jsonSchema,
name: mode === 'create' ? projectName : project.name,
...project, ...project,
root: undefined, root: undefined,
}); });

View File

@ -1,6 +1,6 @@
import { createTreeWithEmptyWorkspace } from '../../generators/testing-utils/create-tree-with-empty-workspace'; import { createTreeWithEmptyWorkspace } from '../../generators/testing-utils/create-tree-with-empty-workspace';
import type { Tree } from '../../generators/tree'; import type { Tree } from '../../generators/tree';
import { readJson } from '../../generators/utils/json'; import { readJson, writeJson } from '../../generators/utils/json';
import { addProjectConfiguration } from '../../generators/utils/project-configuration'; import { addProjectConfiguration } from '../../generators/utils/project-configuration';
import addJsonSchema from './add-json-schema'; import addJsonSchema from './add-json-schema';
@ -22,6 +22,7 @@ describe('add-json-schema >', () => {
}); });
it('should update workspace.json $schema', async () => { it('should update workspace.json $schema', async () => {
writeJson(tree, 'workspace.json', { version: 2, projects: {} });
const workspaceJson = readJson(tree, 'workspace.json'); const workspaceJson = readJson(tree, 'workspace.json');
delete workspaceJson['$schema']; delete workspaceJson['$schema'];

View File

@ -16,6 +16,9 @@ import { ProjectGraph } from '../../config/project-graph';
import { reverse } from '../operators'; import { reverse } from '../operators';
import { ProjectConfiguration } from '../../config/workspace-json-project-json'; import { ProjectConfiguration } from '../../config/workspace-json-project-json';
import { readNxJson } from '../../config/configuration'; import { readNxJson } from '../../config/configuration';
import { workspaceConfigName } from 'nx/src/config/workspaces';
import { getTouchedProjectsFromProjectGlobChanges } from './locators/project-glob-changes';
import { workspaceRoot } from 'nx/src/utils/workspace-root';
export function filterAffected( export function filterAffected(
graph: ProjectGraph<ProjectConfiguration>, graph: ProjectGraph<ProjectConfiguration>,
@ -29,9 +32,13 @@ export function filterAffected(
getImplicitlyTouchedProjects, getImplicitlyTouchedProjects,
getTouchedNpmPackages, getTouchedNpmPackages,
getImplicitlyTouchedProjectsByJsonChanges, getImplicitlyTouchedProjectsByJsonChanges,
getTouchedProjectsInWorkspaceJson,
getTouchedProjectsFromTsConfig, getTouchedProjectsFromTsConfig,
]; ];
if (workspaceConfigName(workspaceRoot)) {
touchedProjectLocators.push(getTouchedProjectsInWorkspaceJson);
} else {
touchedProjectLocators.push(getTouchedProjectsFromProjectGlobChanges);
}
const touchedProjects = touchedProjectLocators.reduce((acc, f) => { const touchedProjects = touchedProjectLocators.reduce((acc, f) => {
return acc.concat(f(touchedFiles, graph.nodes, nxJson, packageJson, graph)); return acc.concat(f(touchedFiles, graph.nodes, nxJson, packageJson, graph));
}, [] as string[]); }, [] as string[]);

View File

@ -1,11 +1,11 @@
import { getImplicitlyTouchedProjectsByJsonChanges } from './implicit-json-changes'; import { getImplicitlyTouchedProjectsByJsonChanges } from './implicit-json-changes';
import { WholeFileChange } from '../../file-utils'; import { WholeFileChange } from '../../file-utils';
import { DiffType } from '../../../utils/json-diff'; import { JsonDiffType } from '../../../utils/json-diff';
import { NxJsonConfiguration } from '../../../config/nx-json'; import { NxJsonConfiguration } from '../../../config/nx-json';
function getModifiedChange(path: string[]) { function getModifiedChange(path: string[]) {
return { return {
type: DiffType.Modified, type: JsonDiffType.Modified,
path, path,
value: { value: {
lhs: 'before', lhs: 'before',

View File

@ -1,6 +1,6 @@
import { NxJsonConfiguration } from '../../../config/nx-json'; import { NxJsonConfiguration } from '../../../config/nx-json';
import { ProjectGraph } from '../../../config/project-graph'; import { ProjectGraph } from '../../../config/project-graph';
import { DiffType } from '../../../utils/json-diff'; import { JsonDiffType } from '../../../utils/json-diff';
import { WholeFileChange } from '../../file-utils'; import { WholeFileChange } from '../../file-utils';
import { getTouchedNpmPackages } from './npm-packages'; import { getTouchedNpmPackages } from './npm-packages';
@ -76,7 +76,7 @@ describe('getTouchedNpmPackages', () => {
hash: 'some-hash', hash: 'some-hash',
getChanges: () => [ getChanges: () => [
{ {
type: DiffType.Modified, type: JsonDiffType.Modified,
path: ['dependencies', 'happy-nrwl'], path: ['dependencies', 'happy-nrwl'],
value: { value: {
lhs: '0.0.1', lhs: '0.0.1',
@ -106,7 +106,7 @@ describe('getTouchedNpmPackages', () => {
hash: 'some-hash', hash: 'some-hash',
getChanges: () => [ getChanges: () => [
{ {
type: DiffType.Modified, type: JsonDiffType.Modified,
path: ['dependencies', '@types/happy-nrwl'], path: ['dependencies', '@types/happy-nrwl'],
value: { value: {
lhs: '0.0.1', lhs: '0.0.1',
@ -141,7 +141,7 @@ describe('getTouchedNpmPackages', () => {
hash: 'some-hash', hash: 'some-hash',
getChanges: () => [ getChanges: () => [
{ {
type: DiffType.Modified, type: JsonDiffType.Modified,
path: ['dependencies', '@types/happy-nrwl'], path: ['dependencies', '@types/happy-nrwl'],
value: { value: {
lhs: '0.0.1', lhs: '0.0.1',
@ -171,7 +171,7 @@ describe('getTouchedNpmPackages', () => {
hash: 'some-hash', hash: 'some-hash',
getChanges: () => [ getChanges: () => [
{ {
type: DiffType.Deleted, type: JsonDiffType.Deleted,
path: ['dependencies', 'sad-nrwl'], path: ['dependencies', 'sad-nrwl'],
value: { value: {
lhs: '0.0.1', lhs: '0.0.1',
@ -209,7 +209,7 @@ describe('getTouchedNpmPackages', () => {
hash: 'some-hash', hash: 'some-hash',
getChanges: () => [ getChanges: () => [
{ {
type: DiffType.Added, type: JsonDiffType.Added,
path: ['dependencies', 'awesome-nrwl'], path: ['dependencies', 'awesome-nrwl'],
value: { value: {
lhs: undefined, lhs: undefined,

View File

@ -1,5 +1,9 @@
import { isWholeFileChange, WholeFileChange } from '../../file-utils'; import { isWholeFileChange, WholeFileChange } from '../../file-utils';
import { DiffType, isJsonChange, JsonChange } from '../../../utils/json-diff'; import {
JsonDiffType,
isJsonChange,
JsonChange,
} from '../../../utils/json-diff';
import { TouchedProjectLocator } from '../affected-project-graph-models'; import { TouchedProjectLocator } from '../affected-project-graph-models';
export const getTouchedNpmPackages: TouchedProjectLocator< export const getTouchedNpmPackages: TouchedProjectLocator<
@ -20,7 +24,7 @@ export const getTouchedNpmPackages: TouchedProjectLocator<
c.path.length === 2 c.path.length === 2
) { ) {
// A package was deleted so mark all workspace projects as touched. // A package was deleted so mark all workspace projects as touched.
if (c.type === DiffType.Deleted) { if (c.type === JsonDiffType.Deleted) {
touched = Object.keys(projectGraph.nodes); touched = Object.keys(projectGraph.nodes);
break; break;
} else { } else {

View File

@ -0,0 +1,52 @@
import { ProjectGraphProjectNode } from 'nx/src/config/project-graph';
import { ProjectConfiguration } from 'nx/src/config/workspace-json-project-json';
import { JsonDiffType } from '../../../utils/json-diff';
import * as nxPlugin from '../../../utils/nx-plugin';
import { DeletedFileChange, WholeFileChange } from '../../file-utils';
import { getTouchedProjectsFromProjectGlobChanges } from './project-glob-changes';
function makeProjectGraphNode(
name,
configurationFile = 'project.json'
): ProjectGraphProjectNode<ProjectConfiguration> {
return {
data: {
files: [
{
file: `libs/${name}/${configurationFile}`,
hash: 'hash' + Math.floor(Math.random() * 10000),
},
],
root: `libs/${name}`,
},
name,
type: 'lib',
};
}
describe('getTouchedProjectsFromProjectGlobChanges', () => {
beforeEach(() => {
jest.spyOn(nxPlugin, 'loadNxPlugins').mockReturnValue([]);
});
it('should affect all projects if a project is removed', () => {
const result = getTouchedProjectsFromProjectGlobChanges(
[
{
file: 'libs/proj1/project.json',
hash: 'some-hash',
getChanges: () => [new DeletedFileChange()],
},
],
{
proj2: makeProjectGraphNode('proj2'),
proj3: makeProjectGraphNode('proj3'),
},
{
plugins: [],
}
);
expect(result).toEqual(['proj2', 'proj3']);
});
});

View File

@ -0,0 +1,50 @@
import {
DeletedFileChange,
isDeletedFileChange,
WholeFileChange,
} from '../../file-utils';
import { JsonChange } from '../../../utils/json-diff';
import { TouchedProjectLocator } from '../affected-project-graph-models';
import minimatch = require('minimatch');
import {
getGlobPatternsFromPackageManagerWorkspaces,
getGlobPatternsFromPlugins,
} from 'nx/src/config/workspaces';
import { workspaceRoot } from 'nx/src/utils/workspace-root';
export const getTouchedProjectsFromProjectGlobChanges: TouchedProjectLocator<
WholeFileChange | JsonChange | DeletedFileChange
> = (touchedFiles, projectGraphNodes, nxJson): string[] => {
const pluginGlobPatterns = getGlobPatternsFromPlugins(
nxJson,
[workspaceRoot],
workspaceRoot
);
const workspacesGlobPatterns =
getGlobPatternsFromPackageManagerWorkspaces(workspaceRoot);
const patterns = [
'**/project.json',
...pluginGlobPatterns,
...workspacesGlobPatterns,
];
const combinedGlobPattern = '{' + patterns.join(',') + '}';
const touchedProjects = new Set<string>();
for (const touchedFile of touchedFiles) {
const isProjectFile = minimatch(touchedFile.file, combinedGlobPattern);
if (isProjectFile) {
if (
touchedFile.getChanges().some((change) => isDeletedFileChange(change))
) {
// If any project has been deleted, we must assume all projects were affected
return Object.keys(projectGraphNodes);
}
// Modified project config files are under a project's root, and implicitly
// mark it as affected. Thus, we don't need to handle it here.
}
}
return Array.from(touchedProjects);
};

View File

@ -1,5 +1,9 @@
import { WholeFileChange } from '../../file-utils'; import { WholeFileChange } from '../../file-utils';
import { DiffType, isJsonChange, JsonChange } from '../../../utils/json-diff'; import {
JsonDiffType,
isJsonChange,
JsonChange,
} from '../../../utils/json-diff';
import { getRootTsConfigFileName } from '../../../utils/typescript'; import { getRootTsConfigFileName } from '../../../utils/typescript';
import { TouchedProjectLocator } from '../affected-project-graph-models'; import { TouchedProjectLocator } from '../affected-project-graph-models';
import { ProjectGraphProjectNode } from '../../../config/project-graph'; import { ProjectGraphProjectNode } from '../../../config/project-graph';
@ -33,7 +37,7 @@ export const getTouchedProjectsFromTsConfig: TouchedProjectLocator<
} }
// If a path is deleted, everything is touched // If a path is deleted, everything is touched
if (change.type === DiffType.Deleted) { if (change.type === JsonDiffType.Deleted) {
return Object.keys(graph.nodes); return Object.keys(graph.nodes);
} }
touched.push( touched.push(

View File

@ -1,6 +1,6 @@
import { getTouchedProjectsInWorkspaceJson } from './workspace-json-changes'; import { getTouchedProjectsInWorkspaceJson } from './workspace-json-changes';
import { WholeFileChange } from '../../file-utils'; import { WholeFileChange } from '../../file-utils';
import { DiffType } from '../../../utils/json-diff'; import { JsonDiffType } from '../../../utils/json-diff';
describe('getTouchedProjectsInWorkspaceJson', () => { describe('getTouchedProjectsInWorkspaceJson', () => {
it('should not return changes when workspace.json is not touched', () => { it('should not return changes when workspace.json is not touched', () => {
@ -61,7 +61,7 @@ describe('getTouchedProjectsInWorkspaceJson', () => {
hash: 'some-hash', hash: 'some-hash',
getChanges: () => [ getChanges: () => [
{ {
type: DiffType.Modified, type: JsonDiffType.Modified,
path: ['newProjectRoot'], path: ['newProjectRoot'],
value: { value: {
lhs: '', lhs: '',
@ -103,7 +103,7 @@ describe('getTouchedProjectsInWorkspaceJson', () => {
hash: 'some-hash', hash: 'some-hash',
getChanges: () => [ getChanges: () => [
{ {
type: DiffType.Added, type: JsonDiffType.Added,
path: ['projects', 'proj1'], path: ['projects', 'proj1'],
value: { value: {
lhs: undefined, lhs: undefined,
@ -114,7 +114,7 @@ describe('getTouchedProjectsInWorkspaceJson', () => {
}, },
{ {
type: DiffType.Added, type: JsonDiffType.Added,
path: ['projects', 'proj1', 'root'], path: ['projects', 'proj1', 'root'],
value: { value: {
lhs: undefined, lhs: undefined,
@ -147,7 +147,7 @@ describe('getTouchedProjectsInWorkspaceJson', () => {
hash: 'some-hash', hash: 'some-hash',
getChanges: () => [ getChanges: () => [
{ {
type: DiffType.Deleted, type: JsonDiffType.Deleted,
path: ['projects', 'proj3'], path: ['projects', 'proj3'],
value: { value: {
lhs: { lhs: {
@ -191,7 +191,7 @@ describe('getTouchedProjectsInWorkspaceJson', () => {
hash: 'some-hash', hash: 'some-hash',
getChanges: () => [ getChanges: () => [
{ {
type: DiffType.Modified, type: JsonDiffType.Modified,
path: ['projects', 'proj1'], path: ['projects', 'proj1'],
value: { value: {
lhs: { lhs: {
@ -203,7 +203,7 @@ describe('getTouchedProjectsInWorkspaceJson', () => {
}, },
}, },
{ {
type: DiffType.Modified, type: JsonDiffType.Modified,
path: ['projects', 'proj1', 'root'], path: ['projects', 'proj1', 'root'],
value: { value: {
lhs: 'proj3', lhs: 'proj3',

View File

@ -3,7 +3,11 @@ import {
WholeFileChange, WholeFileChange,
workspaceFileName, workspaceFileName,
} from '../../file-utils'; } from '../../file-utils';
import { DiffType, isJsonChange, JsonChange } from '../../../utils/json-diff'; import {
JsonDiffType,
isJsonChange,
JsonChange,
} from '../../../utils/json-diff';
import { TouchedProjectLocator } from '../affected-project-graph-models'; import { TouchedProjectLocator } from '../affected-project-graph-models';
export const getTouchedProjectsInWorkspaceJson: TouchedProjectLocator< export const getTouchedProjectsInWorkspaceJson: TouchedProjectLocator<
@ -45,7 +49,7 @@ export const getTouchedProjectsInWorkspaceJson: TouchedProjectLocator<
} }
switch (change.type) { switch (change.type) {
case DiffType.Deleted: { case JsonDiffType.Deleted: {
// We are not sure which projects used to depend on a deleted project // We are not sure which projects used to depend on a deleted project
// so return all projects to be safe // so return all projects to be safe
return Object.keys(projectGraphNodes); return Object.keys(projectGraphNodes);

View File

@ -50,7 +50,7 @@ export function buildWorkspaceProjectNodes(
p.targets = mergePluginTargetsWithNxTargets( p.targets = mergePluginTargetsWithNxTargets(
p.root, p.root,
p.targets, p.targets,
loadNxPlugins(ctx.workspace.plugins) loadNxPlugins(ctx.workspace.plugins, [p.root], p.root)
); );
const projectType = const projectType =

View File

@ -1,5 +1,10 @@
import { calculateFileChanges, WholeFileChange } from './file-utils'; import {
import { DiffType } from '../utils/json-diff'; calculateFileChanges,
DeletedFileChange,
WholeFileChange,
} from './file-utils';
import * as fs from 'fs';
import { JsonDiffType } from '../utils/json-diff';
import { defaultFileHasher } from '../hasher/file-hasher'; import { defaultFileHasher } from '../hasher/file-hasher';
import ignore from 'ignore'; import ignore from 'ignore';
@ -7,7 +12,8 @@ describe('calculateFileChanges', () => {
beforeEach(() => { beforeEach(() => {
defaultFileHasher.ensureInitialized(); defaultFileHasher.ensureInitialized();
}); });
it('should return a whole file change by default', () => { it('should return a whole file change by default for files that exist', () => {
jest.spyOn(fs, 'existsSync').mockReturnValue(true);
const changes = calculateFileChanges( const changes = calculateFileChanges(
['proj/index.ts'], ['proj/index.ts'],
[], [],
@ -46,7 +52,7 @@ describe('calculateFileChanges', () => {
); );
expect(changes[0].getChanges()).toContainEqual({ expect(changes[0].getChanges()).toContainEqual({
type: DiffType.Modified, type: JsonDiffType.Modified,
path: ['dependencies', 'happy-nrwl'], path: ['dependencies', 'happy-nrwl'],
value: { value: {
lhs: '0.0.1', lhs: '0.0.1',
@ -54,7 +60,7 @@ describe('calculateFileChanges', () => {
}, },
}); });
expect(changes[0].getChanges()).toContainEqual({ expect(changes[0].getChanges()).toContainEqual({
type: DiffType.Deleted, type: JsonDiffType.Deleted,
path: ['dependencies', 'not-awesome-nrwl'], path: ['dependencies', 'not-awesome-nrwl'],
value: { value: {
lhs: '0.0.1', lhs: '0.0.1',
@ -62,7 +68,7 @@ describe('calculateFileChanges', () => {
}, },
}); });
expect(changes[0].getChanges()).toContainEqual({ expect(changes[0].getChanges()).toContainEqual({
type: DiffType.Added, type: JsonDiffType.Added,
path: ['dependencies', 'awesome-nrwl'], path: ['dependencies', 'awesome-nrwl'],
value: { value: {
lhs: undefined, lhs: undefined,
@ -71,6 +77,23 @@ describe('calculateFileChanges', () => {
}); });
}); });
it('should pick up deleted changes for deleted files', () => {
jest.spyOn(fs, 'existsSync').mockReturnValue(false);
const changes = calculateFileChanges(
['i-dont-exist.json'],
[],
{
base: 'sha1',
head: 'sha2',
},
(path, revision) => {
return '';
}
);
expect(changes[0].getChanges()).toEqual([new DeletedFileChange()]);
});
it('should ignore *.md changes', () => { it('should ignore *.md changes', () => {
const ig = ignore(); const ig = ignore();
ig.add('*.md'); ig.add('*.md');

View File

@ -29,10 +29,20 @@ export class WholeFileChange implements Change {
type = 'WholeFileChange'; type = 'WholeFileChange';
} }
export class DeletedFileChange implements Change {
type = 'WholeFileDeleted';
}
export function isWholeFileChange(change: Change): change is WholeFileChange { export function isWholeFileChange(change: Change): change is WholeFileChange {
return change.type === 'WholeFileChange'; return change.type === 'WholeFileChange';
} }
export function isDeletedFileChange(
change: Change
): change is DeletedFileChange {
return change.type === 'WholeFileDeleted';
}
export function readFileIfExisting(path: string) { export function readFileIfExisting(path: string) {
return existsSync(path) ? readFileSync(path, 'utf-8') : ''; return existsSync(path) ? readFileSync(path, 'utf-8') : '';
} }
@ -66,6 +76,10 @@ export function calculateFileChanges(
ext, ext,
hash, hash,
getChanges: (): Change[] => { getChanges: (): Change[] => {
if (!existsSync(join(workspaceRoot, f))) {
return [new DeletedFileChange()];
}
if (!nxArgs) { if (!nxArgs) {
return [new WholeFileChange()]; return [new WholeFileChange()];
} }
@ -77,7 +91,6 @@ export function calculateFileChanges(
case '.json': case '.json':
const atBase = readFileAtRevision(f, nxArgs.base); const atBase = readFileAtRevision(f, nxArgs.base);
const atHead = readFileAtRevision(f, nxArgs.head); const atHead = readFileAtRevision(f, nxArgs.head);
try { try {
return jsonDiff(JSON.parse(atBase), JSON.parse(atHead)); return jsonDiff(JSON.parse(atBase), JSON.parse(atHead));
} catch (e) { } catch (e) {

View File

@ -1,4 +1,4 @@
import { jsonDiff, DiffType } from './json-diff'; import { jsonDiff, JsonDiffType } from './json-diff';
describe('jsonDiff', () => { describe('jsonDiff', () => {
it('should return deep diffs of two JSON objects (including parents of children changes)', () => { it('should return deep diffs of two JSON objects (including parents of children changes)', () => {
@ -10,7 +10,7 @@ describe('jsonDiff', () => {
expect(result).toEqual( expect(result).toEqual(
expect.arrayContaining([ expect.arrayContaining([
{ {
type: DiffType.Modified, type: JsonDiffType.Modified,
path: ['a'], path: ['a'],
value: { value: {
lhs: { lhs: {
@ -27,7 +27,7 @@ describe('jsonDiff', () => {
}, },
}, },
{ {
type: DiffType.Modified, type: JsonDiffType.Modified,
path: ['a', 'b'], path: ['a', 'b'],
value: { value: {
lhs: { lhs: {
@ -40,22 +40,22 @@ describe('jsonDiff', () => {
}, },
}, },
{ {
type: DiffType.Modified, type: JsonDiffType.Modified,
path: ['a', 'b', 'c'], path: ['a', 'b', 'c'],
value: { lhs: 1, rhs: 2 }, value: { lhs: 1, rhs: 2 },
}, },
{ {
type: DiffType.Deleted, type: JsonDiffType.Deleted,
path: ['x'], path: ['x'],
value: { lhs: 1, rhs: undefined }, value: { lhs: 1, rhs: undefined },
}, },
{ {
type: DiffType.Added, type: JsonDiffType.Added,
path: ['y'], path: ['y'],
value: { lhs: undefined, rhs: 2 }, value: { lhs: undefined, rhs: 2 },
}, },
{ {
type: DiffType.Added, type: JsonDiffType.Added,
path: ['a', 'b', 'd'], path: ['a', 'b', 'd'],
value: { lhs: undefined, rhs: 2 }, value: { lhs: undefined, rhs: 2 },
}, },
@ -74,7 +74,7 @@ describe('jsonDiff', () => {
} }
); );
expect(result).toContainEqual({ expect(result).toContainEqual({
type: DiffType.Modified, type: JsonDiffType.Modified,
path: ['a'], path: ['a'],
value: { value: {
lhs: { lhs: {
@ -86,7 +86,7 @@ describe('jsonDiff', () => {
}, },
}); });
expect(result).toContainEqual({ expect(result).toContainEqual({
type: DiffType.Modified, type: JsonDiffType.Modified,
path: ['a', 'b'], path: ['a', 'b'],
value: { value: {
lhs: 0, lhs: 0,
@ -102,7 +102,7 @@ describe('jsonDiff', () => {
} }
); );
expect(result2).toContainEqual({ expect(result2).toContainEqual({
type: DiffType.Added, type: JsonDiffType.Added,
path: ['a'], path: ['a'],
value: { lhs: undefined, rhs: {} }, value: { lhs: undefined, rhs: {} },
}); });
@ -121,7 +121,7 @@ describe('jsonDiff', () => {
expect(result).toEqual( expect(result).toEqual(
expect.arrayContaining([ expect.arrayContaining([
{ {
type: DiffType.Modified, type: JsonDiffType.Modified,
path: ['rules'], path: ['rules'],
value: { value: {
lhs: undefined, lhs: undefined,
@ -129,7 +129,7 @@ describe('jsonDiff', () => {
}, },
}, },
{ {
type: DiffType.Added, type: JsonDiffType.Added,
path: ['rules', '0'], path: ['rules', '0'],
value: { value: {
lhs: undefined, lhs: undefined,
@ -153,7 +153,7 @@ describe('jsonDiff', () => {
expect(result).toEqual( expect(result).toEqual(
expect.arrayContaining([ expect.arrayContaining([
{ {
type: DiffType.Modified, type: JsonDiffType.Modified,
path: ['rules'], path: ['rules'],
value: { value: {
lhs: ['rule1'], lhs: ['rule1'],
@ -161,7 +161,7 @@ describe('jsonDiff', () => {
}, },
}, },
{ {
type: DiffType.Added, type: JsonDiffType.Added,
path: ['rules', '1'], path: ['rules', '1'],
value: { value: {
lhs: undefined, lhs: undefined,
@ -189,19 +189,19 @@ describe('jsonDiff', () => {
); );
expect(result).toContainEqual({ expect(result).toContainEqual({
type: DiffType.Modified, type: JsonDiffType.Modified,
path: ['dependencies', 'happy-nrwl'], path: ['dependencies', 'happy-nrwl'],
value: { lhs: '0.0.1', rhs: '0.0.2' }, value: { lhs: '0.0.1', rhs: '0.0.2' },
}); });
expect(result).toContainEqual({ expect(result).toContainEqual({
type: DiffType.Deleted, type: JsonDiffType.Deleted,
path: ['dependencies', 'not-awesome-nrwl'], path: ['dependencies', 'not-awesome-nrwl'],
value: { lhs: '0.0.1', rhs: undefined }, value: { lhs: '0.0.1', rhs: undefined },
}); });
expect(result).toContainEqual({ expect(result).toContainEqual({
type: DiffType.Added, type: JsonDiffType.Added,
path: ['dependencies', 'awesome-nrwl'], path: ['dependencies', 'awesome-nrwl'],
value: { lhs: undefined, rhs: '0.0.1' }, value: { lhs: undefined, rhs: '0.0.1' },
}); });

View File

@ -1,13 +1,13 @@
import { Change } from '../project-graph/file-utils'; import { Change } from '../project-graph/file-utils';
export enum DiffType { export enum JsonDiffType {
Deleted = 'JsonPropertyDeleted', Deleted = 'JsonPropertyDeleted',
Added = 'JsonPropertyAdded', Added = 'JsonPropertyAdded',
Modified = 'JsonPropertyModified', Modified = 'JsonPropertyModified',
} }
export interface JsonChange extends Change { export interface JsonChange extends Change {
type: DiffType; type: JsonDiffType;
path: string[]; path: string[];
value: { value: {
lhs: any; lhs: any;
@ -17,9 +17,9 @@ export interface JsonChange extends Change {
export function isJsonChange(change: Change): change is JsonChange { export function isJsonChange(change: Change): change is JsonChange {
return ( return (
change.type === DiffType.Added || change.type === JsonDiffType.Added ||
change.type === DiffType.Deleted || change.type === JsonDiffType.Deleted ||
change.type === DiffType.Modified change.type === JsonDiffType.Modified
); );
} }
@ -32,7 +32,7 @@ export function jsonDiff(lhs: any, rhs: any): JsonChange[] {
const rhsValue = getJsonValue(path, rhs); const rhsValue = getJsonValue(path, rhs);
if (rhsValue === undefined) { if (rhsValue === undefined) {
result.push({ result.push({
type: DiffType.Deleted, type: JsonDiffType.Deleted,
path, path,
value: { value: {
lhs: lhsValue, lhs: lhsValue,
@ -41,7 +41,7 @@ export function jsonDiff(lhs: any, rhs: any): JsonChange[] {
}); });
} else if (!deepEquals(lhsValue, rhsValue)) { } else if (!deepEquals(lhsValue, rhsValue)) {
result.push({ result.push({
type: DiffType.Modified, type: JsonDiffType.Modified,
path, path,
value: { value: {
lhs: lhsValue, lhs: lhsValue,
@ -56,7 +56,7 @@ export function jsonDiff(lhs: any, rhs: any): JsonChange[] {
const addedInRhs = !seenInLhs.has(hashArray(path)); const addedInRhs = !seenInLhs.has(hashArray(path));
if (addedInRhs) { if (addedInRhs) {
result.push({ result.push({
type: DiffType.Added, type: JsonDiffType.Added,
path, path,
value: { value: {
lhs: undefined, lhs: undefined,

View File

@ -46,7 +46,8 @@ export interface NxPlugin {
let nxPluginCache: NxPlugin[] = null; let nxPluginCache: NxPlugin[] = null;
export function loadNxPlugins( export function loadNxPlugins(
plugins?: string[], plugins?: string[],
paths = [workspaceRoot] paths = [workspaceRoot],
root = workspaceRoot
): NxPlugin[] { ): NxPlugin[] {
return plugins?.length return plugins?.length
? nxPluginCache || ? nxPluginCache ||
@ -58,14 +59,12 @@ export function loadNxPlugins(
}); });
} catch (e) { } catch (e) {
if (e.code === 'MODULE_NOT_FOUND') { if (e.code === 'MODULE_NOT_FOUND') {
const plugin = resolveLocalNxPlugin(moduleName); const plugin = resolveLocalNxPlugin(moduleName, root);
if (plugin) { if (plugin) {
const main = readPluginMainFromProjectConfiguration( const main = readPluginMainFromProjectConfiguration(
plugin.projectConfig plugin.projectConfig
); );
pluginPath = main pluginPath = main ? path.join(root, main) : plugin.path;
? path.join(workspaceRoot, main)
: plugin.path;
} else { } else {
logger.error( logger.error(
`Plugin listed in \`nx.json\` not found: ${moduleName}` `Plugin listed in \`nx.json\` not found: ${moduleName}`

View File

@ -106,6 +106,7 @@ describe('React default development configuration', () => {
const config = readProjectConfiguration(tree, 'example'); const config = readProjectConfiguration(tree, 'example');
expect(config).toEqual({ expect(config).toEqual({
$schema: '../../node_modules/nx/schemas/project-schema.json', $schema: '../../node_modules/nx/schemas/project-schema.json',
name: 'example',
root: 'apps/example', root: 'apps/example',
projectType: 'application', projectType: 'application',
}); });

View File

@ -33,7 +33,7 @@ describe('convert-to-nx-project', () => {
}); });
it('should throw if project && all are both specified', async () => { it('should throw if project && all are both specified', async () => {
const tree = createTreeWithEmptyWorkspace(); const tree = createTreeWithWorkspaceFile();
await libraryGenerator(tree, { await libraryGenerator(tree, {
name: 'lib', name: 'lib',
@ -47,7 +47,7 @@ describe('convert-to-nx-project', () => {
it('should prompt for a project if neither project nor all are specified', async () => { it('should prompt for a project if neither project nor all are specified', async () => {
const spy = jest.spyOn(enquirer, 'prompt'); const spy = jest.spyOn(enquirer, 'prompt');
const tree = createTreeWithEmptyWorkspace(); const tree = createTreeWithWorkspaceFile();
await libraryGenerator(tree, { await libraryGenerator(tree, {
name: 'lib', name: 'lib',
@ -61,7 +61,7 @@ describe('convert-to-nx-project', () => {
it('should not prompt for a project if all is specified', async () => { it('should not prompt for a project if all is specified', async () => {
const spy = jest.spyOn(enquirer, 'prompt'); const spy = jest.spyOn(enquirer, 'prompt');
const tree = createTreeWithEmptyWorkspace(); const tree = createTreeWithWorkspaceFile();
await libraryGenerator(tree, { await libraryGenerator(tree, {
name: 'lib', name: 'lib',
@ -73,7 +73,7 @@ describe('convert-to-nx-project', () => {
}); });
it('should extract single project configuration to project.json', async () => { it('should extract single project configuration to project.json', async () => {
const tree = createTreeWithEmptyWorkspace(); const tree = createTreeWithWorkspaceFile();
await libraryGenerator(tree, { await libraryGenerator(tree, {
name: 'lib', name: 'lib',
@ -93,7 +93,7 @@ describe('convert-to-nx-project', () => {
}); });
it('should extract all project configurations to project.json', async () => { it('should extract all project configurations to project.json', async () => {
const tree = createTreeWithEmptyWorkspace(); const tree = createTreeWithWorkspaceFile();
await libraryGenerator(tree, { await libraryGenerator(tree, {
name: 'lib', name: 'lib',
@ -122,7 +122,7 @@ describe('convert-to-nx-project', () => {
}); });
it('should include tags in project.json', async () => { it('should include tags in project.json', async () => {
const tree = createTreeWithEmptyWorkspace(); const tree = createTreeWithWorkspaceFile();
await libraryGenerator(tree, { await libraryGenerator(tree, {
name: 'lib', name: 'lib',
@ -142,7 +142,7 @@ describe('convert-to-nx-project', () => {
}); });
it('should set workspace.json to point to the root directory', async () => { it('should set workspace.json to point to the root directory', async () => {
const tree = createTreeWithEmptyWorkspace(); const tree = createTreeWithWorkspaceFile();
await libraryGenerator(tree, { await libraryGenerator(tree, {
name: 'lib', name: 'lib',
standaloneConfig: false, standaloneConfig: false,
@ -171,7 +171,7 @@ describe('convert-to-nx-project', () => {
it('should format files by default', async () => { it('should format files by default', async () => {
jest.spyOn(devkit, 'formatFiles'); jest.spyOn(devkit, 'formatFiles');
const tree = createTreeWithEmptyWorkspace(); const tree = createTreeWithWorkspaceFile();
await libraryGenerator(tree, { await libraryGenerator(tree, {
name: 'lib', name: 'lib',
@ -187,7 +187,7 @@ describe('convert-to-nx-project', () => {
it('should format files when passing skipFormat false', async () => { it('should format files when passing skipFormat false', async () => {
jest.spyOn(devkit, 'formatFiles'); jest.spyOn(devkit, 'formatFiles');
const tree = createTreeWithEmptyWorkspace(); const tree = createTreeWithWorkspaceFile();
await libraryGenerator(tree, { await libraryGenerator(tree, {
name: 'lib', name: 'lib',
@ -203,7 +203,7 @@ describe('convert-to-nx-project', () => {
it('should not format files when passing skipFormat true ', async () => { it('should not format files when passing skipFormat true ', async () => {
jest.spyOn(devkit, 'formatFiles'); jest.spyOn(devkit, 'formatFiles');
const tree = createTreeWithEmptyWorkspace(); const tree = createTreeWithWorkspaceFile();
await libraryGenerator(tree, { await libraryGenerator(tree, {
name: 'lib', name: 'lib',
@ -216,3 +216,9 @@ describe('convert-to-nx-project', () => {
expect(devkit.formatFiles).toHaveBeenCalledTimes(0); expect(devkit.formatFiles).toHaveBeenCalledTimes(0);
}); });
}); });
function createTreeWithWorkspaceFile() {
const tree = createTreeWithEmptyV1Workspace();
tree.write('workspace.json', JSON.stringify({ version: 2, projects: {} }));
return tree;
}

View File

@ -38,52 +38,11 @@ describe('lib', () => {
it('should default to standalone project for first project', async () => { it('should default to standalone project for first project', async () => {
await libraryGenerator(tree, { ...defaultOptions, name: 'my-lib' }); await libraryGenerator(tree, { ...defaultOptions, name: 'my-lib' });
const workspaceJsonEntry = readJson(tree, 'workspace.json').projects[
'my-lib'
];
const projectConfig = readProjectConfiguration(tree, 'my-lib'); const projectConfig = readProjectConfiguration(tree, 'my-lib');
expect(projectConfig.root).toEqual('libs/my-lib'); expect(projectConfig.root).toEqual('libs/my-lib');
expect(workspaceJsonEntry).toEqual('libs/my-lib');
});
it('should obey standalone === false for first project', async () => {
await libraryGenerator(tree, {
...defaultOptions,
name: 'my-lib',
standaloneConfig: false,
});
const workspaceJsonEntry = readJson(tree, 'workspace.json').projects[
'my-lib'
];
const projectConfig = readProjectConfiguration(tree, 'my-lib');
expect(projectConfig.root).toEqual('libs/my-lib');
expect(projectConfig).toMatchObject(workspaceJsonEntry);
}); });
}); });
// describe('workspace v1', () => {
// beforeEach(() => {
// tree = createTreeWithEmptyV1Workspace();
// });
//
// it('should default to inline project for first project', async () => {
// await libraryGenerator(tree, { ...defaultOptions, name: 'my-lib' });
// const workspaceJsonEntry = toNewFormat(readJson(tree, 'workspace.json'))
// .projects['my-lib'];
// const projectConfig = readProjectConfiguration(tree, 'my-lib');
// expect(projectConfig.root).toEqual('libs/my-lib');
// expect(projectConfig).toMatchObject(workspaceJsonEntry);
// });
//
// it('should throw for standaloneConfig === true', async () => {
// const promise = libraryGenerator(tree, {
// standaloneConfig: true,
// name: 'my-lib',
// });
// await expect(promise).rejects.toThrow();
// });
// });
describe('not nested', () => { describe('not nested', () => {
it('should update workspace.json', async () => { it('should update workspace.json', async () => {
await libraryGenerator(tree, { await libraryGenerator(tree, {

View File

@ -230,8 +230,6 @@ describe('moveProjectConfiguration', () => {
expect(() => { expect(() => {
readProjectConfiguration(tree, projectName); readProjectConfiguration(tree, projectName);
}).toThrow(); }).toThrow();
const ws = readJson(tree, 'workspace.json');
expect(typeof ws.projects[newProjectName]).toBe('string');
expect(readProjectConfiguration(tree, newProjectName)).toBeDefined(); expect(readProjectConfiguration(tree, newProjectName)).toBeDefined();
}); });
}); });

View File

@ -12,6 +12,10 @@ export function moveProjectConfiguration(
schema: NormalizedSchema, schema: NormalizedSchema,
projectConfig: ProjectConfiguration projectConfig: ProjectConfiguration
) { ) {
if (projectConfig.name) {
projectConfig.name = schema.newProjectName;
}
const isStandalone = isStandaloneProject(tree, schema.projectName); const isStandalone = isStandaloneProject(tree, schema.projectName);
const projectString = JSON.stringify(projectConfig); const projectString = JSON.stringify(projectConfig);
const newProjectString = projectString.replace( const newProjectString = projectString.replace(

View File

@ -3,9 +3,11 @@ import {
readProjectConfiguration, readProjectConfiguration,
Tree, Tree,
} from '@nrwl/devkit'; } from '@nrwl/devkit';
import * as nxDevkit from '@nrwl/devkit';
import { createTreeWithEmptyV1Workspace } from '@nrwl/devkit/testing'; import { createTreeWithEmptyV1Workspace } from '@nrwl/devkit/testing';
import { NormalizedSchema } from '../schema'; import { NormalizedSchema } from '../schema';
import { updateBuildTargets } from './update-build-targets'; import { updateBuildTargets } from './update-build-targets';
import { array } from 'yargs';
describe('updateBuildTargets', () => { describe('updateBuildTargets', () => {
let tree: Tree; let tree: Tree;
@ -82,4 +84,12 @@ describe('updateBuildTargets', () => {
'subfolder-my-destination:serve' 'subfolder-my-destination:serve'
); );
}); });
it('should NOT attempt to update unrelated projects', async () => {
addProjectConfiguration(tree, 'unrelated', { root: 'libs/unrelated' });
const spy = jest.spyOn(nxDevkit, 'updateProjectConfiguration');
schema.projectName = 'storybook';
updateBuildTargets(tree, schema);
expect(spy.mock.calls.map((x) => x[1])).not.toContain('unrelated');
});
}); });

View File

@ -11,8 +11,10 @@ import { NormalizedSchema } from '../schema';
*/ */
export function updateBuildTargets(tree: Tree, schema: NormalizedSchema) { export function updateBuildTargets(tree: Tree, schema: NormalizedSchema) {
getProjects(tree).forEach((projectConfig, project) => { getProjects(tree).forEach((projectConfig, project) => {
let changed = false;
Object.entries(projectConfig.targets || {}).forEach( Object.entries(projectConfig.targets || {}).forEach(
([target, targetConfig]) => { ([target, targetConfig]) => {
changed =
updateJsonValue(targetConfig, (value) => { updateJsonValue(targetConfig, (value) => {
const [project, target, configuration] = value.split(':'); const [project, target, configuration] = value.split(':');
if (project === schema.projectName && target) { if (project === schema.projectName && target) {
@ -20,33 +22,41 @@ export function updateBuildTargets(tree: Tree, schema: NormalizedSchema) {
? `${schema.newProjectName}:${target}:${configuration}` ? `${schema.newProjectName}:${target}:${configuration}`
: `${schema.newProjectName}:${target}`; : `${schema.newProjectName}:${target}`;
} }
}); }) || changed;
} }
); );
if (changed) {
updateProjectConfiguration(tree, project, projectConfig); updateProjectConfiguration(tree, project, projectConfig);
}
}); });
} }
function updateJsonValue( function updateJsonValue(
config: TargetConfiguration, config: TargetConfiguration,
callback: (x: string) => void | string callback: (x: string) => void | string
) { ): boolean {
function recur(obj, key, value) { function recur(obj, key, value): boolean {
let changed = false;
if (typeof value === 'string') { if (typeof value === 'string') {
const result = callback(value); const result = callback(value);
if (result) { if (result && obj[key] !== result) {
obj[key] = result; obj[key] = result;
changed = true;
} }
} else if (Array.isArray(value)) { } else if (Array.isArray(value)) {
value.forEach((x, idx) => recur(value, idx, x)); value.forEach((x, idx) => recur(value, idx, x));
} else if (typeof value === 'object') { } else if (typeof value === 'object') {
Object.entries(value).forEach(([k, v]) => { Object.entries(value).forEach(([k, v]) => {
recur(value, k, v); changed = recur(value, k, v) || changed;
}); });
} }
return changed;
} }
let changed = false;
Object.entries(config).forEach(([k, v]) => { Object.entries(config).forEach(([k, v]) => {
recur(config, k, v); changed = recur(config, k, v) || changed;
}); });
return changed;
} }

View File

@ -112,11 +112,3 @@ Object {
}, },
} }
`; `;
exports[`new should generate an empty workspace.json 1`] = `
Object {
"$schema": "./node_modules/nx/schemas/workspace-schema.json",
"projects": Object {},
"version": 2,
}
`;

View File

@ -19,7 +19,7 @@ describe('new', () => {
tree = createTree(); tree = createTree();
}); });
it('should generate an empty workspace.json', async () => { it('should not generate a workspace.json', async () => {
await newGenerator(tree, { await newGenerator(tree, {
...defaultOptions, ...defaultOptions,
name: 'my-workspace', name: 'my-workspace',
@ -27,7 +27,7 @@ describe('new', () => {
npmScope: 'npmScope', npmScope: 'npmScope',
appName: 'app', appName: 'app',
}); });
expect(readJson(tree, 'my-workspace/workspace.json')).toMatchSnapshot(); expect(tree.exists('my-workspace/workspace.json')).toBeFalsy();
}); });
it('should generate an empty nx.json', async () => { it('should generate an empty nx.json', async () => {

View File

@ -6,6 +6,7 @@ import {
getWorkspacePath as devkitGetWorkspacePath, getWorkspacePath as devkitGetWorkspacePath,
installPackagesTask, installPackagesTask,
names, names,
NxJsonConfiguration,
PackageManager, PackageManager,
Tree, Tree,
updateJson, updateJson,
@ -284,7 +285,10 @@ function setDefaultLinter(host: Tree, options: Schema) {
* This sets ESLint as the default for any schematics that default to TSLint * This sets ESLint as the default for any schematics that default to TSLint
*/ */
function setESLintDefault(host: Tree, options: Schema) { function setESLintDefault(host: Tree, options: Schema) {
updateJson(host, getWorkspacePath(host, options), (json) => { updateJson<NxJsonConfiguration>(
host,
join(options.directory, 'nx.json'),
(json) => {
setDefault(json, '@nrwl/angular', 'application', 'linter', 'eslint'); setDefault(json, '@nrwl/angular', 'application', 'linter', 'eslint');
setDefault(json, '@nrwl/angular', 'library', 'linter', 'eslint'); setDefault(json, '@nrwl/angular', 'library', 'linter', 'eslint');
setDefault( setDefault(
@ -295,14 +299,18 @@ function setESLintDefault(host: Tree, options: Schema) {
'eslint' 'eslint'
); );
return json; return json;
}); }
);
} }
/** /**
* This sets TSLint as the default for any schematics that default to ESLint * This sets TSLint as the default for any schematics that default to ESLint
*/ */
function setTSLintDefault(host: Tree, options: Schema) { function setTSLintDefault(host: Tree, options: Schema) {
updateJson(host, getWorkspacePath(host, options), (json) => { updateJson<NxJsonConfiguration>(
host,
join(options.directory, 'nx.json'),
(json) => {
setDefault(json, '@nrwl/workspace', 'library', 'linter', 'tslint'); setDefault(json, '@nrwl/workspace', 'library', 'linter', 'tslint');
setDefault(json, '@nrwl/cypress', 'cypress-project', 'linter', 'tslint'); setDefault(json, '@nrwl/cypress', 'cypress-project', 'linter', 'tslint');
setDefault(json, '@nrwl/cypress', 'cypress-project', 'linter', 'tslint'); setDefault(json, '@nrwl/cypress', 'cypress-project', 'linter', 'tslint');
@ -314,30 +322,27 @@ function setTSLintDefault(host: Tree, options: Schema) {
setDefault(json, '@nrwl/express', 'library', 'linter', 'tslint'); setDefault(json, '@nrwl/express', 'library', 'linter', 'tslint');
return json; return json;
}); }
} );
function getWorkspacePath(host: Tree, { directory, cli }: Schema) {
return join(directory, cli === 'angular' ? 'angular.json' : 'workspace.json');
} }
function setDefault( function setDefault(
json: any, json: NxJsonConfiguration,
collectionName: string, collectionName: string,
generatorName: string, generatorName: string,
key: string, key: string,
value: any value: any
) { ) {
if (!json.schematics) json.schematics = {}; if (!json.generators) json.generators = {};
if ( if (
json.schematics[collectionName] && json.generators[collectionName] &&
json.schematics[collectionName][generatorName] json.generators[collectionName][generatorName]
) { ) {
json.schematics[collectionName][generatorName][key] = value; json.generators[collectionName][generatorName][key] = value;
} else if (json.schematics[`${collectionName}:${generatorName}`]) { } else if (json.generators[`${collectionName}:${generatorName}`]) {
json.schematics[`${collectionName}:${generatorName}`][key] = value; json.generators[`${collectionName}:${generatorName}`][key] = value;
} else { } else {
json.schematics[collectionName] = json.schematics[collectionName] || {}; json.generators[collectionName] = json.generators[collectionName] || {};
json.schematics[collectionName][generatorName] = { [key]: value }; json.generators[collectionName][generatorName] = { [key]: value };
} }
} }

View File

@ -36,11 +36,16 @@ export function removeProjectConfig(tree: Tree, schema: Schema) {
// Remove implicit dependencies onto removed project // Remove implicit dependencies onto removed project
getProjects(tree).forEach((project, projectName) => { getProjects(tree).forEach((project, projectName) => {
if (project.implicitDependencies) { if (
project.implicitDependencies &&
project.implicitDependencies.some(
(projectName) => projectName === schema.projectName
)
) {
project.implicitDependencies = project.implicitDependencies.filter( project.implicitDependencies = project.implicitDependencies.filter(
(projectName) => projectName !== schema.projectName (projectName) => projectName !== schema.projectName
); );
}
updateProjectConfiguration(tree, projectName, project); updateProjectConfiguration(tree, projectName, project);
}
}); });
} }

View File

@ -1,5 +0,0 @@
{
"$schema": "./node_modules/nx/schemas/workspace-schema.json",
"version": 2,
"projects": {}
}

View File

@ -23,12 +23,12 @@ describe('@nrwl/workspace:workspace', () => {
defaultBase: 'main', defaultBase: 'main',
}); });
expect(tree.exists('/proj/nx.json')).toBe(true); expect(tree.exists('/proj/nx.json')).toBe(true);
expect(tree.exists('/proj/workspace.json')).toBe(true); expect(tree.exists('/proj/workspace.json')).toBe(false);
expect(tree.exists('/proj/.prettierrc')).toBe(true); expect(tree.exists('/proj/.prettierrc')).toBe(true);
expect(tree.exists('/proj/.prettierignore')).toBe(true); expect(tree.exists('/proj/.prettierignore')).toBe(true);
}); });
it('should create nx.json and workspace.json', async () => { it('should create nx.json', async () => {
const ajv = new Ajv(); const ajv = new Ajv();
await workspaceGenerator(tree, { await workspaceGenerator(tree, {
@ -61,15 +61,6 @@ describe('@nrwl/workspace:workspace', () => {
}); });
const validateNxJson = ajv.compile(nxSchema); const validateNxJson = ajv.compile(nxSchema);
expect(validateNxJson(nxJson)).toEqual(true); expect(validateNxJson(nxJson)).toEqual(true);
const workspaceJson = readJson(tree, '/proj/workspace.json');
expect(workspaceJson).toEqual({
$schema: './node_modules/nx/schemas/workspace-schema.json',
version: 2,
projects: {},
});
const validateWorkspaceJson = ajv.compile(workspaceSchema);
expect(validateWorkspaceJson(workspaceJson)).toEqual(true);
}); });
it('should setup named inputs and target defaults for non-empty presets', async () => { it('should setup named inputs and target defaults for non-empty presets', async () => {

View File

@ -151,26 +151,6 @@ function createYarnrcYml(host: Tree, options: Schema) {
); );
} }
function formatWorkspaceJson(host: Tree, options: Schema) {
const path = join(
options.directory,
options.cli === 'angular' ? 'angular.json' : 'workspace.json'
);
try {
updateJson(host, path, (workspaceJson) => {
const reformatted = reformattedWorkspaceJsonOrNull(workspaceJson);
if (reformatted) {
return reformatted;
}
return workspaceJson;
});
} catch (e) {
console.error(`Failed to format: ${path}`);
console.error(e);
}
}
function addNpmScripts(host: Tree, options: Schema) { function addNpmScripts(host: Tree, options: Schema) {
if (options.cli === 'angular') { if (options.cli === 'angular') {
updateJson(host, join(options.directory, 'package.json'), (json) => { updateJson(host, join(options.directory, 'package.json'), (json) => {
@ -222,7 +202,6 @@ export async function workspaceGenerator(host: Tree, options: Schema) {
createAppsAndLibsFolders(host, options); createAppsAndLibsFolders(host, options);
await formatFiles(host); await formatFiles(host);
formatWorkspaceJson(host, options);
} }
export const workspaceSchematic = convertNxGenerator(workspaceGenerator); export const workspaceSchematic = convertNxGenerator(workspaceGenerator);

View File

@ -16,6 +16,7 @@ describe('update to v13 config locations', () => {
beforeEach(async () => { beforeEach(async () => {
tree = createTreeWithEmptyWorkspace(); tree = createTreeWithEmptyWorkspace();
tree.write('workspace.json', JSON.stringify({ version: 2, projects: {} }));
updateJson(tree, 'workspace.json', (json) => ({ updateJson(tree, 'workspace.json', (json) => ({
...json, ...json,
cli: { cli: {

View File

@ -1,7 +1,5 @@
import { import {
Tree, Tree,
writeJson,
readWorkspaceConfiguration,
addProjectConfiguration, addProjectConfiguration,
getProjects, getProjects,
readJson, readJson,
@ -44,6 +42,7 @@ describe('add `defaultBase` in nx.json', () => {
expect(projects).toEqual({ expect(projects).toEqual({
'tsc-project': { 'tsc-project': {
$schema: '../../node_modules/nx/schemas/project-schema.json', $schema: '../../node_modules/nx/schemas/project-schema.json',
name: 'tsc-project',
root: 'projects/tsc-project', root: 'projects/tsc-project',
targets: { targets: {
build: { build: {
@ -56,6 +55,7 @@ describe('add `defaultBase` in nx.json', () => {
}, },
'other-project': { 'other-project': {
$schema: '../../node_modules/nx/schemas/project-schema.json', $schema: '../../node_modules/nx/schemas/project-schema.json',
name: 'other-project',
root: 'projects/other-project', root: 'projects/other-project',
targets: { targets: {
build: { build: {

View File

@ -33,6 +33,7 @@ describe('changeRunCommandsExecutor', () => {
expect(readProjectConfiguration(tree, 'proj1')).toMatchInlineSnapshot(` expect(readProjectConfiguration(tree, 'proj1')).toMatchInlineSnapshot(`
Object { Object {
"$schema": "../node_modules/nx/schemas/project-schema.json", "$schema": "../node_modules/nx/schemas/project-schema.json",
"name": "proj1",
"root": "proj1", "root": "proj1",
"targets": Object { "targets": Object {
"notScriptTarget": Object { "notScriptTarget": Object {

View File

@ -64,6 +64,7 @@ export function replaceAppNameWithPath(
'tags', 'tags',
'defaultConfiguration', 'defaultConfiguration',
'maximumError', 'maximumError',
'name',
]; // Some of the properties should not be renamed ]; // Some of the properties should not be renamed
return Object.keys(node).reduce( return Object.keys(node).reduce(
(m, c) => ( (m, c) => (

View File

@ -82,8 +82,8 @@ export function formatFiles(
function updateWorkspaceJsonToMatchFormatVersion(host: Tree) { function updateWorkspaceJsonToMatchFormatVersion(host: Tree) {
const workspaceConfigPath = workspaceConfigName(workspaceRoot); const workspaceConfigPath = workspaceConfigName(workspaceRoot);
try {
if (workspaceConfigPath) { if (workspaceConfigPath) {
try {
const workspaceJson = parseJson( const workspaceJson = parseJson(
host.read(workspaceConfigPath).toString() host.read(workspaceConfigPath).toString()
); );
@ -91,9 +91,11 @@ function updateWorkspaceJsonToMatchFormatVersion(host: Tree) {
if (reformatted) { if (reformatted) {
host.overwrite(workspaceConfigPath, serializeJson(reformatted)); host.overwrite(workspaceConfigPath, serializeJson(reformatted));
} }
}
} catch (e) { } catch (e) {
console.error(`Failed to format workspace config: ${workspaceConfigPath}`); console.error(
`Failed to format workspace config: ${workspaceConfigPath}`
);
console.error(e); console.error(e);
} }
}
} }