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
# Local dev files
.env

View File

@ -17,22 +17,6 @@ describe('Angular Config', () => {
beforeAll(() => newProject());
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 () => {
const previousCI = process.env.SELECTED_CLI;
process.env.SELECTED_CLI = 'angular';

View File

@ -97,7 +97,7 @@ describe('Angular Projects', () => {
}
}, 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
const app = uniq('app');
const buildableLib = uniq('buildlib1');

View File

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

View File

@ -60,7 +60,6 @@ describe('Move Angular Project', () => {
expect(moveOutput).toContain(
`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);
};
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');
runCLI(
`generate @nrwl/angular:app ${appWithTailwind} --add-tailwind --no-interactive`

View File

@ -528,17 +528,6 @@ exports.FooModel = FooModel;
);
}, 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 () => {
await expectJestTestsToPass('@nrwl/node:lib');
}, 100000);

View File

@ -188,7 +188,7 @@ describe('Extra Nx Misc Tests', () => {
expect(resultArgs).toContain('camel: d');
}, 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) => {
config.targets.error = {
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) => {
config.targets.lint.outputs = ['{options.outputFile}'];
return config;

View File

@ -9,11 +9,12 @@ import {
expectJestTestsToPass,
readFile,
exists,
workspaceConfigName,
renameFile,
updateProjectConfig,
readProjectConfig,
tmpProjPath,
readResolvedWorkspaceConfiguration,
removeFile,
} from '@nrwl/e2e/utils';
let proj: string;
@ -23,7 +24,9 @@ describe('Workspace Tests', () => {
proj = newProject();
});
afterAll(() => cleanupProject());
afterAll(() => {
cleanupProject();
});
describe('@nrwl/workspace:library', () => {
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`
);
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(
`workspace-generator ${custom} ${workspace} --no-interactive --directory=dir`
);
checkFilesExist(`libs/dir/${workspace}/src/index.ts`);
expect(output).toContain(`UPDATE ${workspaceConfigName()}`);
expect(output).not.toContain('UPDATE nx.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
*/
it('should work for libraries', () => {
it('should work for libraries', async () => {
const lib1 = uniq('mylib');
const lib2 = uniq('mylib');
const lib3 = uniq('mylib');
@ -374,8 +378,8 @@ describe('Workspace Tests', () => {
expect(moveOutput).toContain(`CREATE ${rootClassPath}`);
checkFilesExist(rootClassPath);
let workspaceJson = readJson(workspaceConfigName());
expect(workspaceJson.projects[`${lib1}-data-access`]).toBeUndefined();
let workspace = await readResolvedWorkspaceConfiguration();
expect(workspace.projects[`${lib1}-data-access`]).toBeUndefined();
const newConfig = readProjectConfig(newName);
expect(newConfig).toMatchObject({
tags: [],
@ -396,9 +400,8 @@ describe('Workspace Tests', () => {
]
).toEqual([`libs/shared/${lib1}/data-access/src/index.ts`]);
expect(moveOutput).toContain(`UPDATE workspace.json`);
workspaceJson = readJson(workspaceConfigName());
expect(workspaceJson.projects[`${lib1}-data-access`]).toBeUndefined();
workspace = readResolvedWorkspaceConfiguration();
expect(workspace.projects[`${lib1}-data-access`]).toBeUndefined();
const project = readProjectConfig(newName);
expect(project).toBeTruthy();
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 lib1 = uniq('mylib');
const lib2 = uniq('mylib');
@ -523,9 +526,8 @@ describe('Workspace Tests', () => {
]
).toEqual([`libs/shared/${lib1}/data-access/src/index.ts`]);
expect(moveOutput).toContain(`UPDATE workspace.json`);
const workspaceJson = readJson(workspaceConfigName());
expect(workspaceJson.projects[`${lib1}-data-access`]).toBeUndefined();
const workspace = await readResolvedWorkspaceConfiguration();
expect(workspace.projects[`${lib1}-data-access`]).toBeUndefined();
const project = readProjectConfig(newName);
expect(project).toBeTruthy();
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 lib2 = uniq('mylib');
const lib3 = uniq('mylib');
@ -656,9 +658,8 @@ describe('Workspace Tests', () => {
]
).toEqual([`packages/shared/${lib1}/data-access/src/index.ts`]);
expect(moveOutput).toContain(`UPDATE workspace.json`);
const workspaceJson = readJson(workspaceConfigName());
expect(workspaceJson.projects[`${lib1}-data-access`]).toBeUndefined();
const workspace = await readResolvedWorkspaceConfiguration();
expect(workspace.projects[`${lib1}-data-access`]).toBeUndefined();
const project = readProjectConfig(newName);
expect(project).toBeTruthy();
expect(project.sourceRoot).toBe(`${newPath}/src`);
@ -681,7 +682,7 @@ describe('Workspace Tests', () => {
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');
delete json.npmScope;
updateFile('nx.json', JSON.stringify(json));
@ -775,8 +776,7 @@ describe('Workspace Tests', () => {
rootTsConfig.compilerOptions.paths[`shared/${lib1}/data-access`]
).toEqual([`libs/shared/${lib1}/data-access/src/index.ts`]);
expect(moveOutput).toContain(`UPDATE workspace.json`);
const workspaceJson = readJson(workspaceConfigName());
const workspaceJson = readResolvedWorkspaceConfiguration();
expect(workspaceJson.projects[`${lib1}-data-access`]).toBeUndefined();
const project = readProjectConfig(newName);
expect(project).toBeTruthy();
@ -800,7 +800,7 @@ describe('Workspace Tests', () => {
/**
* Tries creating then deleting a lib
*/
it('should work', () => {
it('should work', async () => {
const lib1 = uniq('myliba');
const lib2 = uniq('mylibb');
@ -848,20 +848,31 @@ describe('Workspace Tests', () => {
expect(exists(tmpProjPath(`libs/${lib1}`))).toBeFalsy();
expect(removeOutputForced).not.toContain(`UPDATE nx.json`);
const workspaceJson = readJson(workspaceConfigName());
const workspaceJson = readResolvedWorkspaceConfiguration();
expect(workspaceJson.projects[`${lib1}`]).toBeUndefined();
const lib2Config = readProjectConfig(lib2);
expect(lib2Config.implicitDependencies).toEqual([]);
expect(removeOutputForced).toContain(`UPDATE workspace.json`);
expect(workspaceJson.projects[`${lib1}`]).toBeUndefined();
});
});
describe('workspace-lint', () => {
it('should identify issues with the workspace', () => {
beforeAll(() => {
// 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();
updateFile(
'workspace.json',
JSON.stringify({ version: 2, projects: {} })
);
});
afterAll(() => {
removeFile('workspace.json');
});
it('should identify issues with the workspace', () => {
const appBefore = uniq('before');
const appAfter = uniq('after');

View File

@ -286,10 +286,6 @@ describe('Nx Plugin', () => {
});
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
updateFile(
`libs/${plugin}/src/index.ts`,
@ -327,9 +323,6 @@ describe('Nx Plugin', () => {
expect(runCLI(`build ${inferredProject}`)).toContain(
'custom registered target'
);
// Restore workspace.json
createFile('workspace.json', workspaceJsonContents);
});
it('should be able to use local generators and executors', async () => {

View File

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

View File

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

View File

@ -1,4 +1,5 @@
import {
createProjectGraphAsync,
joinPathFragments,
parseJson,
ProjectConfiguration,
@ -89,12 +90,19 @@ export function updateProjectConfig(
projectName: string,
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 current = readJson(path);
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
* if you need a project's configuration.
@ -109,7 +117,7 @@ export function readWorkspaceConfig(): Omit<
}
export function readProjectConfig(projectName: string): ProjectConfiguration {
const root = readJson(workspaceConfigName()).projects[projectName];
const root = readResolvedWorkspaceConfiguration().projects[projectName].root;
const path = join(root, 'project.json');
return readJson(path);
}

View File

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

View File

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

View File

@ -360,9 +360,10 @@ describe('app', () => {
await generateApp(appTree, 'myApp', { directory: 'src/9-websites' });
// 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 () => {

View File

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

View File

@ -415,6 +415,7 @@ describe('e2e migrator', () => {
const e2eProject = readProjectConfiguration(tree, 'app1-e2e');
expect(e2eProject).toStrictEqual({
$schema: '../../node_modules/nx/schemas/project-schema.json',
name: 'app1-e2e',
root: 'apps/app1-e2e',
sourceRoot: 'apps/app1-e2e/src',
projectType: 'application',
@ -544,6 +545,7 @@ describe('e2e migrator', () => {
const e2eProject = readProjectConfiguration(tree, 'app1-e2e');
expect(e2eProject).toStrictEqual({
$schema: '../../node_modules/nx/schemas/project-schema.json',
name: 'app1-e2e',
root: 'apps/app1-e2e',
sourceRoot: 'apps/app1-e2e/src',
projectType: 'application',
@ -609,6 +611,7 @@ describe('e2e migrator', () => {
const e2eProject = readProjectConfiguration(tree, 'app1-e2e');
expect(e2eProject).toStrictEqual({
$schema: '../../node_modules/nx/schemas/project-schema.json',
name: 'app1-e2e',
root: 'apps/app1-e2e',
sourceRoot: 'apps/app1-e2e/src',
projectType: 'application',
@ -662,6 +665,7 @@ describe('e2e migrator', () => {
const e2eProject = readProjectConfiguration(tree, 'app1-e2e');
expect(e2eProject).toStrictEqual({
$schema: '../../node_modules/nx/schemas/project-schema.json',
name: 'app1-e2e',
root: 'apps/app1-e2e',
sourceRoot: 'apps/app1-e2e/src',
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 () => {
const workspace: WorkspaceConfiguration = {
version: 1,
version: 2,
generators: { '@nrwl/angular:application': { style: 'scss' } },
};
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 () => {
const workspace: WorkspaceConfiguration = {
version: 1,
version: 2,
generators: { '@nrwl/react:library': { style: 'scss' } },
};
updateWorkspaceConfiguration(tree, workspace);
@ -43,7 +43,7 @@ describe('remove-library-generator-style-default migration', () => {
describe('collection:generator', () => {
it('should remove style entry when configured', async () => {
const workspace: WorkspaceConfiguration = {
version: 1,
version: 2,
generators: { '@nrwl/angular:library': { style: 'scss' } },
};
updateWorkspaceConfiguration(tree, workspace);
@ -51,14 +51,14 @@ describe('remove-library-generator-style-default migration', () => {
await removeLibraryGeneratorStyleDefault(tree);
expect(readWorkspaceConfiguration(tree)).toStrictEqual({
version: 1,
version: 2,
generators: { '@nrwl/angular:library': {} },
});
});
it('should do nothing when style is not set', async () => {
const workspace: WorkspaceConfiguration = {
version: 1,
version: 2,
generators: { '@nrwl/angular:library': { linter: 'eslint' } },
};
updateWorkspaceConfiguration(tree, workspace);
@ -72,7 +72,7 @@ describe('remove-library-generator-style-default migration', () => {
describe('nested generator', () => {
it('should remove style entry when configured', async () => {
const workspace: WorkspaceConfiguration = {
version: 1,
version: 2,
generators: { '@nrwl/angular': { library: { style: 'scss' } } },
};
updateWorkspaceConfiguration(tree, workspace);
@ -80,14 +80,14 @@ describe('remove-library-generator-style-default migration', () => {
await removeLibraryGeneratorStyleDefault(tree);
expect(readWorkspaceConfiguration(tree)).toStrictEqual({
version: 1,
version: 2,
generators: { '@nrwl/angular': { library: {} } },
});
});
it('should do nothing when style is not set', async () => {
const workspace: WorkspaceConfiguration = {
version: 1,
version: 2,
generators: { '@nrwl/angular': { library: { linter: 'eslint' } } },
};
updateWorkspaceConfiguration(tree, workspace);
@ -101,7 +101,7 @@ describe('remove-library-generator-style-default migration', () => {
it('should format files', async () => {
jest.spyOn(devkit, 'formatFiles');
const workspace: WorkspaceConfiguration = {
version: 1,
version: 2,
generators: { '@nrwl/angular:library': { style: 'scss' } },
};
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 () => {
const project: ProjectConfiguration = {
name: 'app1',
root: 'apps/app1',
targets: { build: { executor: '@nrwl/angular:package' } },
};
@ -38,6 +39,7 @@ describe('set-build-libs-from-source migration', () => {
it('should set buildLibsFromSource to false', async () => {
addProjectConfiguration(tree, 'app1', {
name: 'app1',
root: 'apps/app1',
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 () => {
addProjectConfiguration(tree, 'app1', {
name: 'app1',
root: 'apps/app1',
targets: { 'build-base': { executor: '@nrwl/angular:webpack-browser' } },
});

View File

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

View File

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

View File

@ -37,8 +37,7 @@ describe('detox application generator', () => {
});
it('should add update `workspace.json` file', async () => {
const workspaceJson = readJson(tree, 'workspace.json');
const project = workspaceJson.projects['my-app-e2e'];
const project = readProjectConfiguration(tree, '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 () => {
const workspaceJson = readJson(tree, 'workspace.json');
const project = workspaceJson.projects['my-dir-my-app-e2e'];
const project = readProjectConfiguration(tree, '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 () => {
const workspaceJson = readJson(tree, 'workspace.json');
const project = workspaceJson.projects['my-dir-my-app-e2e'];
const project = readProjectConfiguration(tree, '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', '');
});
it('should update workspace.json', async () => {
it('should update workspace', async () => {
await expoApplicationGenerator(appTree, {
name: 'myApp',
displayName: 'myApp',
@ -27,11 +27,11 @@ describe('app', () => {
js: false,
unitTestRunner: 'none',
});
const workspaceJson = readWorkspaceConfiguration(appTree);
const workspace = readWorkspaceConfiguration(appTree);
const projects = getProjects(appTree);
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 () => {

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(
tree,
'my-lib',
@ -29,7 +29,7 @@ describe('Add Linting', () => {
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(
tree,
'my-lib',

View File

@ -65,7 +65,7 @@ describe('jestProject', () => {
expect(tree.exists('babel.config.json')).toBeTruthy();
});
it('should alter workspace.json', async () => {
it('should alter project configuration', async () => {
await jestProjectGenerator(tree, {
...defaultOptions,
project: 'lib1',
@ -160,7 +160,7 @@ describe('jestProject', () => {
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, {
...defaultOptions,
project: 'lib1',
@ -191,7 +191,7 @@ describe('jestProject', () => {
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, {
...defaultOptions,
project: 'lib1',

View File

@ -51,10 +51,6 @@ describe('lib', () => {
// unitTestRunner property is ignored.
// It only works with our executors.
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 () => {
@ -63,12 +59,8 @@ describe('lib', () => {
name: 'my-lib',
config: 'project',
});
const workspaceJsonEntry = readJson(tree, 'workspace.json').projects[
'my-lib'
];
const projectConfig = readProjectConfiguration(tree, '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 () => {
@ -77,12 +69,8 @@ describe('lib', () => {
name: 'my-lib',
config: 'workspace',
});
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);
});
});
@ -225,16 +213,15 @@ describe('lib', () => {
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, {
...defaultOptions,
name: 'myLib',
directory: 'myDir',
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'
);
});

View File

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

View File

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

View File

@ -76,6 +76,7 @@ describe('@nrwl/linter:workspace-rules-project', () => {
.toMatchInlineSnapshot(`
Object {
"$schema": "../../node_modules/nx/schemas/project-schema.json",
"name": "eslint-rules",
"root": "tools/eslint-rules",
"sourceRoot": "tools/eslint-rules",
"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 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
* 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
* 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);
this.cleanUpGeneratorsConfig(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);
for (const [projectName, { generators }] of projects.entries()) {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -3,7 +3,11 @@ import {
WholeFileChange,
workspaceFileName,
} 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';
export const getTouchedProjectsInWorkspaceJson: TouchedProjectLocator<
@ -45,7 +49,7 @@ export const getTouchedProjectsInWorkspaceJson: TouchedProjectLocator<
}
switch (change.type) {
case DiffType.Deleted: {
case JsonDiffType.Deleted: {
// We are not sure which projects used to depend on a deleted project
// so return all projects to be safe
return Object.keys(projectGraphNodes);

View File

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

View File

@ -1,5 +1,10 @@
import { calculateFileChanges, WholeFileChange } from './file-utils';
import { DiffType } from '../utils/json-diff';
import {
calculateFileChanges,
DeletedFileChange,
WholeFileChange,
} from './file-utils';
import * as fs from 'fs';
import { JsonDiffType } from '../utils/json-diff';
import { defaultFileHasher } from '../hasher/file-hasher';
import ignore from 'ignore';
@ -7,7 +12,8 @@ describe('calculateFileChanges', () => {
beforeEach(() => {
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(
['proj/index.ts'],
[],
@ -46,7 +52,7 @@ describe('calculateFileChanges', () => {
);
expect(changes[0].getChanges()).toContainEqual({
type: DiffType.Modified,
type: JsonDiffType.Modified,
path: ['dependencies', 'happy-nrwl'],
value: {
lhs: '0.0.1',
@ -54,7 +60,7 @@ describe('calculateFileChanges', () => {
},
});
expect(changes[0].getChanges()).toContainEqual({
type: DiffType.Deleted,
type: JsonDiffType.Deleted,
path: ['dependencies', 'not-awesome-nrwl'],
value: {
lhs: '0.0.1',
@ -62,7 +68,7 @@ describe('calculateFileChanges', () => {
},
});
expect(changes[0].getChanges()).toContainEqual({
type: DiffType.Added,
type: JsonDiffType.Added,
path: ['dependencies', 'awesome-nrwl'],
value: {
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', () => {
const ig = ignore();
ig.add('*.md');

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -33,7 +33,7 @@ describe('convert-to-nx-project', () => {
});
it('should throw if project && all are both specified', async () => {
const tree = createTreeWithEmptyWorkspace();
const tree = createTreeWithWorkspaceFile();
await libraryGenerator(tree, {
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 () => {
const spy = jest.spyOn(enquirer, 'prompt');
const tree = createTreeWithEmptyWorkspace();
const tree = createTreeWithWorkspaceFile();
await libraryGenerator(tree, {
name: 'lib',
@ -61,7 +61,7 @@ describe('convert-to-nx-project', () => {
it('should not prompt for a project if all is specified', async () => {
const spy = jest.spyOn(enquirer, 'prompt');
const tree = createTreeWithEmptyWorkspace();
const tree = createTreeWithWorkspaceFile();
await libraryGenerator(tree, {
name: 'lib',
@ -73,7 +73,7 @@ describe('convert-to-nx-project', () => {
});
it('should extract single project configuration to project.json', async () => {
const tree = createTreeWithEmptyWorkspace();
const tree = createTreeWithWorkspaceFile();
await libraryGenerator(tree, {
name: 'lib',
@ -93,7 +93,7 @@ describe('convert-to-nx-project', () => {
});
it('should extract all project configurations to project.json', async () => {
const tree = createTreeWithEmptyWorkspace();
const tree = createTreeWithWorkspaceFile();
await libraryGenerator(tree, {
name: 'lib',
@ -122,7 +122,7 @@ describe('convert-to-nx-project', () => {
});
it('should include tags in project.json', async () => {
const tree = createTreeWithEmptyWorkspace();
const tree = createTreeWithWorkspaceFile();
await libraryGenerator(tree, {
name: 'lib',
@ -142,7 +142,7 @@ describe('convert-to-nx-project', () => {
});
it('should set workspace.json to point to the root directory', async () => {
const tree = createTreeWithEmptyWorkspace();
const tree = createTreeWithWorkspaceFile();
await libraryGenerator(tree, {
name: 'lib',
standaloneConfig: false,
@ -171,7 +171,7 @@ describe('convert-to-nx-project', () => {
it('should format files by default', async () => {
jest.spyOn(devkit, 'formatFiles');
const tree = createTreeWithEmptyWorkspace();
const tree = createTreeWithWorkspaceFile();
await libraryGenerator(tree, {
name: 'lib',
@ -187,7 +187,7 @@ describe('convert-to-nx-project', () => {
it('should format files when passing skipFormat false', async () => {
jest.spyOn(devkit, 'formatFiles');
const tree = createTreeWithEmptyWorkspace();
const tree = createTreeWithWorkspaceFile();
await libraryGenerator(tree, {
name: 'lib',
@ -203,7 +203,7 @@ describe('convert-to-nx-project', () => {
it('should not format files when passing skipFormat true ', async () => {
jest.spyOn(devkit, 'formatFiles');
const tree = createTreeWithEmptyWorkspace();
const tree = createTreeWithWorkspaceFile();
await libraryGenerator(tree, {
name: 'lib',
@ -216,3 +216,9 @@ describe('convert-to-nx-project', () => {
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 () => {
await libraryGenerator(tree, { ...defaultOptions, name: 'my-lib' });
const workspaceJsonEntry = readJson(tree, 'workspace.json').projects[
'my-lib'
];
const projectConfig = readProjectConfiguration(tree, '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', () => {
it('should update workspace.json', async () => {
await libraryGenerator(tree, {

View File

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

View File

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

View File

@ -3,9 +3,11 @@ import {
readProjectConfiguration,
Tree,
} from '@nrwl/devkit';
import * as nxDevkit from '@nrwl/devkit';
import { createTreeWithEmptyV1Workspace } from '@nrwl/devkit/testing';
import { NormalizedSchema } from '../schema';
import { updateBuildTargets } from './update-build-targets';
import { array } from 'yargs';
describe('updateBuildTargets', () => {
let tree: Tree;
@ -82,4 +84,12 @@ describe('updateBuildTargets', () => {
'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) {
getProjects(tree).forEach((projectConfig, project) => {
let changed = false;
Object.entries(projectConfig.targets || {}).forEach(
([target, targetConfig]) => {
changed =
updateJsonValue(targetConfig, (value) => {
const [project, target, configuration] = value.split(':');
if (project === schema.projectName && target) {
@ -20,33 +22,41 @@ export function updateBuildTargets(tree: Tree, schema: NormalizedSchema) {
? `${schema.newProjectName}:${target}:${configuration}`
: `${schema.newProjectName}:${target}`;
}
});
}) || changed;
}
);
if (changed) {
updateProjectConfiguration(tree, project, projectConfig);
}
});
}
function updateJsonValue(
config: TargetConfiguration,
callback: (x: string) => void | string
) {
function recur(obj, key, value) {
): boolean {
function recur(obj, key, value): boolean {
let changed = false;
if (typeof value === 'string') {
const result = callback(value);
if (result) {
if (result && obj[key] !== result) {
obj[key] = result;
changed = true;
}
} else if (Array.isArray(value)) {
value.forEach((x, idx) => recur(value, idx, x));
} else if (typeof value === 'object') {
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]) => {
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();
});
it('should generate an empty workspace.json', async () => {
it('should not generate a workspace.json', async () => {
await newGenerator(tree, {
...defaultOptions,
name: 'my-workspace',
@ -27,7 +27,7 @@ describe('new', () => {
npmScope: 'npmScope',
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 () => {

View File

@ -6,6 +6,7 @@ import {
getWorkspacePath as devkitGetWorkspacePath,
installPackagesTask,
names,
NxJsonConfiguration,
PackageManager,
Tree,
updateJson,
@ -284,7 +285,10 @@ function setDefaultLinter(host: Tree, options: Schema) {
* This sets ESLint as the default for any schematics that default to TSLint
*/
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', 'library', 'linter', 'eslint');
setDefault(
@ -295,14 +299,18 @@ function setESLintDefault(host: Tree, options: Schema) {
'eslint'
);
return json;
});
}
);
}
/**
* This sets TSLint as the default for any schematics that default to ESLint
*/
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/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');
return json;
});
}
function getWorkspacePath(host: Tree, { directory, cli }: Schema) {
return join(directory, cli === 'angular' ? 'angular.json' : 'workspace.json');
}
);
}
function setDefault(
json: any,
json: NxJsonConfiguration,
collectionName: string,
generatorName: string,
key: string,
value: any
) {
if (!json.schematics) json.schematics = {};
if (!json.generators) json.generators = {};
if (
json.schematics[collectionName] &&
json.schematics[collectionName][generatorName]
json.generators[collectionName] &&
json.generators[collectionName][generatorName]
) {
json.schematics[collectionName][generatorName][key] = value;
} else if (json.schematics[`${collectionName}:${generatorName}`]) {
json.schematics[`${collectionName}:${generatorName}`][key] = value;
json.generators[collectionName][generatorName][key] = value;
} else if (json.generators[`${collectionName}:${generatorName}`]) {
json.generators[`${collectionName}:${generatorName}`][key] = value;
} else {
json.schematics[collectionName] = json.schematics[collectionName] || {};
json.schematics[collectionName][generatorName] = { [key]: value };
json.generators[collectionName] = json.generators[collectionName] || {};
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
getProjects(tree).forEach((project, projectName) => {
if (project.implicitDependencies) {
if (
project.implicitDependencies &&
project.implicitDependencies.some(
(projectName) => projectName === schema.projectName
)
) {
project.implicitDependencies = project.implicitDependencies.filter(
(projectName) => projectName !== schema.projectName
);
}
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',
});
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/.prettierignore')).toBe(true);
});
it('should create nx.json and workspace.json', async () => {
it('should create nx.json', async () => {
const ajv = new Ajv();
await workspaceGenerator(tree, {
@ -61,15 +61,6 @@ describe('@nrwl/workspace:workspace', () => {
});
const validateNxJson = ajv.compile(nxSchema);
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 () => {

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) {
if (options.cli === 'angular') {
updateJson(host, join(options.directory, 'package.json'), (json) => {
@ -222,7 +202,6 @@ export async function workspaceGenerator(host: Tree, options: Schema) {
createAppsAndLibsFolders(host, options);
await formatFiles(host);
formatWorkspaceJson(host, options);
}
export const workspaceSchematic = convertNxGenerator(workspaceGenerator);

View File

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

View File

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

View File

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

View File

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

View File

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