Skip to content

Commit

Permalink
feat(core): add an ability to extend nx.json config
Browse files Browse the repository at this point in the history
  • Loading branch information
Nrwl CI Machine authored and vsavkin committed Aug 17, 2021
1 parent 149a357 commit 5f02a10
Show file tree
Hide file tree
Showing 10 changed files with 131 additions and 45 deletions.
5 changes: 1 addition & 4 deletions e2e/workspace/src/custom-layout.test.ts
Expand Up @@ -21,10 +21,7 @@ describe('custom workspace layout', () => {
packageInstall('@nrwl/react @nrwl/angular @nrwl/express');

const nxJson = readJson('nx.json');
expect(nxJson.workspaceLayout).toEqual({
libsDir: 'packages',
appsDir: 'packages',
});
expect(nxJson.extends).toEqual('@nrwl/workspace/presets/npm.json');

const reactApp = uniq('reactapp');
const reactLib = uniq('reactlib');
Expand Down
9 changes: 9 additions & 0 deletions packages/devkit/src/generators/project-configuration.spec.ts
Expand Up @@ -171,6 +171,15 @@ describe('project configuration', () => {
expect(readJson(tree, 'workspace.json').$schema).not.toBeDefined();
expect(readJson(tree, 'nx.json').$schema).not.toBeDefined();
});

it('should skip properties that are identical to the extends property', () => {
workspaceConfiguration['$schema'] = 'schema';

updateWorkspaceConfiguration(tree, workspaceConfiguration);

expect(readJson(tree, 'workspace.json').$schema).not.toBeDefined();
expect(readJson(tree, 'nx.json').$schema).not.toBeDefined();
});
});

describe('without nx.json', () => {
Expand Down
51 changes: 45 additions & 6 deletions packages/devkit/src/generators/project-configuration.ts
Expand Up @@ -112,14 +112,19 @@ export function readWorkspaceConfiguration(tree: Tree): WorkspaceConfiguration {
const workspace = readWorkspace(tree);
delete workspace.projects;

const nxJson = readNxJson(tree);
if (nxJson !== null) {
delete nxJson.projects;
let nxJson = readNxJson(tree);
if (nxJson === null) {
return workspace;
}

const nxJsonExtends = readNxJsonExtends(tree, nxJson as any);
if (nxJsonExtends) {
nxJson = { ...nxJsonExtends, ...nxJson };
}

return {
...workspace,
...(nxJson === null ? {} : nxJson),
...nxJson,
};
}

Expand Down Expand Up @@ -174,11 +179,40 @@ export function updateWorkspaceConfiguration(

if (tree.exists('nx.json')) {
updateJson<NxJsonConfiguration>(tree, 'nx.json', (json) => {
return { ...json, ...nxJson };
const nxJsonExtends = readNxJsonExtends(tree, nxJson as any);
if (nxJsonExtends) {
const changedPropsOfNxJson = {};
Object.keys(nxJson).forEach((prop) => {
if (
JSON.stringify([prop], null, 2) !=
JSON.stringify(nxJsonExtends[prop], null, 2)
) {
changedPropsOfNxJson[prop] = nxJson[prop];
}
});
return { ...json, ...changedPropsOfNxJson };
} else {
return { ...json, ...nxJson };
}
});
}
}

function readNxJsonExtends(tree: Tree, nxJson: { extends?: string }) {
if (nxJson.extends) {
const extendsPath = nxJson.extends;
try {
return JSON.parse(
tree.read(joinPathFragments('node_modules', extendsPath), 'utf-8')
);
} catch (e) {
throw new Error(`Unable to resolve nx.json extends. Error: ${e.message}`);
}
} else {
return null;
}
}

/**
* Reads a project configuration.
*
Expand Down Expand Up @@ -211,7 +245,12 @@ export function readNxJson(tree: Tree): NxJsonConfiguration | null {
if (!tree.exists('nx.json')) {
return null;
}
return readJson<NxJsonConfiguration>(tree, 'nx.json');
let nxJson = readJson<NxJsonConfiguration>(tree, 'nx.json');
const nxJsonExtends = readNxJsonExtends(tree, nxJson as any);
if (nxJsonExtends) {
nxJson = { ...nxJsonExtends, ...nxJson };
}
return nxJson;
}

/**
Expand Down
21 changes: 21 additions & 0 deletions packages/workspace/presets/npm.json
@@ -0,0 +1,21 @@
{
"implicitDependencies": {
"package.json": {
"dependencies": "*",
"devDependencies": "*"
},
".eslintrc.json": "*"
},
"targetDependencies": {
"build": [
{
"target": "build",
"projects": "dependencies"
}
]
},
"workspaceLayout": {
"appsDir": "packages",
"libsDir": "packages"
}
}
20 changes: 19 additions & 1 deletion packages/workspace/src/core/file-utils.ts
Expand Up @@ -229,7 +229,25 @@ export function readNxJson(): NxJsonConfiguration {
}
);

return config;
const nxJsonExtends = readNxJsonExtends(config as any);
if (nxJsonExtends) {
return { ...nxJsonExtends, ...config };
} else {
return config;
}
}

function readNxJsonExtends(nxJson: { extends?: string }) {
if (nxJson.extends) {
const extendsPath = nxJson.extends;
try {
return readJsonFile(require.resolve(extendsPath));
} catch (e) {
throw new Error(`Unable to resolve nx.json extends. Error: ${e.message}`);
}
} else {
return null;
}
}

export function workspaceLayout(): { appsDir: string; libsDir: string } {
Expand Down
10 changes: 1 addition & 9 deletions packages/workspace/src/generators/new/new.ts
Expand Up @@ -182,15 +182,7 @@ export async function newGenerator(host: Tree, options: Schema) {
);
}

const layout: 'packages' | 'apps-and-libs' =
options.preset === 'oss' ? 'packages' : 'apps-and-libs';
const workspaceOpts = {
...options,
layout,
preset: undefined,
nxCloud: undefined,
};
await workspaceGenerator(host, workspaceOpts);
await workspaceGenerator(host, { ...options, nxCloud: undefined } as any);

if (options.cli === 'angular') {
setDefaultPackageManager(host, options);
Expand Down
2 changes: 1 addition & 1 deletion packages/workspace/src/generators/workspace/schema.d.ts
Expand Up @@ -7,6 +7,6 @@ export interface Schema {
style?: string;
commit?: { name: string; email: string; message?: string };
cli: 'nx' | 'angular';
layout: 'apps-and-libs' | 'packages';
preset: string;
defaultBase: string;
}
6 changes: 2 additions & 4 deletions packages/workspace/src/generators/workspace/schema.json
Expand Up @@ -44,11 +44,9 @@
"description": "The directory name to create the workspace in.",
"default": ""
},
"layout": {
"preset": {
"type": "string",
"description": "Layout of the workspace",
"default": "apps-and-libs",
"enum": ["apps-and-libs", "packages"]
"description": "Preset of the workspace"
},
"npmScope": {
"type": "string",
Expand Down
21 changes: 9 additions & 12 deletions packages/workspace/src/generators/workspace/workspace.spec.ts
Expand Up @@ -15,7 +15,7 @@ describe('@nrwl/workspace:workspace', () => {
name: 'proj',
directory: 'proj',
cli: 'nx',
layout: 'apps-and-libs',
preset: 'empty',
defaultBase: 'main',
});
expect(tree.exists('/proj/nx.json')).toBe(true);
Expand All @@ -29,7 +29,7 @@ describe('@nrwl/workspace:workspace', () => {
name: 'proj',
directory: 'proj',
cli: 'nx',
layout: 'apps-and-libs',
preset: 'empty',
defaultBase: 'master',
});
const nxJson = readJson<NxJsonConfiguration>(tree, '/proj/nx.json');
Expand Down Expand Up @@ -70,7 +70,7 @@ describe('@nrwl/workspace:workspace', () => {
name: 'proj',
directory: 'proj',
cli: 'nx',
layout: 'apps-and-libs',
preset: 'empty',
defaultBase: 'main',
});
expect(tree.read('proj/.prettierrc', 'utf-8')).toMatchSnapshot();
Expand All @@ -81,7 +81,7 @@ describe('@nrwl/workspace:workspace', () => {
name: 'proj',
directory: 'proj',
cli: 'nx',
layout: 'apps-and-libs',
preset: 'empty',
defaultBase: 'main',
});
const recommendations = readJson<{ recommendations: string[] }>(
Expand All @@ -97,7 +97,7 @@ describe('@nrwl/workspace:workspace', () => {
name: 'proj',
directory: 'proj',
cli: 'angular',
layout: 'apps-and-libs',
preset: 'empty',
defaultBase: 'main',
});
const recommendations = readJson<{ recommendations: string[] }>(
Expand All @@ -113,7 +113,7 @@ describe('@nrwl/workspace:workspace', () => {
name: 'proj',
directory: 'proj',
cli: 'angular',
layout: 'apps-and-libs',
preset: 'empty',
defaultBase: 'main',
});
expect(tree.exists('/proj/decorate-angular-cli.js')).toBe(true);
Expand All @@ -128,7 +128,7 @@ describe('@nrwl/workspace:workspace', () => {
name: 'proj',
directory: 'proj',
cli: 'nx',
layout: 'apps-and-libs',
preset: 'empty',
defaultBase: 'main',
});
expect(tree.exists('/proj/decorate-angular-cli.js')).toBe(false);
Expand All @@ -141,16 +141,13 @@ describe('@nrwl/workspace:workspace', () => {
name: 'proj',
directory: 'proj',
cli: 'nx',
layout: 'packages',
preset: 'oss',
defaultBase: 'main',
});
expect(tree.exists('/proj/packages/.gitkeep')).toBe(true);
expect(tree.exists('/proj/apps/.gitkeep')).toBe(false);
expect(tree.exists('/proj/libs/.gitkeep')).toBe(false);
const nx = readJson(tree, '/proj/nx.json');
expect(nx.workspaceLayout).toEqual({
appsDir: 'packages',
libsDir: 'packages',
});
expect(nx.extends).toEqual('@nrwl/workspace/presets/npm.json');
});
});
31 changes: 23 additions & 8 deletions packages/workspace/src/generators/workspace/workspace.ts
Expand Up @@ -29,20 +29,24 @@ function decorateAngularClI(host: Tree, options: Schema) {
host.write(join(options.directory, 'decorate-angular-cli.js'), decorateCli);
}

function setWorkspaceLayoutProperties(tree: Tree, options: Schema) {
function setPresetProperty(tree: Tree, options: Schema) {
updateJson(tree, join(options.directory, 'nx.json'), (json) => {
if (options.layout === 'packages') {
json.workspaceLayout = {
appsDir: 'packages',
libsDir: 'packages',
};
if (options.preset === 'oss') {
addPropertyWithStableKeys(
json,
'extends',
'@nrwl/workspace/presets/npm.json'
);
delete json.implicitDependencies;
delete json.targetDependencies;
delete json.workspaceLayout;
}
return json;
});
}

function createAppsAndLibsFolders(host: Tree, options: Schema) {
if (options.layout === 'packages') {
if (options.preset === 'oss') {
host.write(join(options.directory, 'packages/.gitkeep'), '');
} else {
host.write(join(options.directory, 'apps/.gitkeep'), '');
Expand Down Expand Up @@ -107,11 +111,22 @@ export async function workspaceGenerator(host: Tree, options: Schema) {
if (options.cli === 'angular') {
decorateAngularClI(host, options);
}
setWorkspaceLayoutProperties(host, options);
setPresetProperty(host, options);
createAppsAndLibsFolders(host, options);

await formatFiles(host);
formatWorkspaceJson(host, options);
}

export const workspaceSchematic = convertNxGenerator(workspaceGenerator);

function addPropertyWithStableKeys(obj: any, key: string, value: string) {
const copy = { ...obj };
Object.keys(obj).forEach((k) => {
delete obj[k];
});
obj[key] = value;
Object.keys(copy).forEach((k) => {
obj[k] = copy[k];
});
}

0 comments on commit 5f02a10

Please sign in to comment.