Skip to content

Commit

Permalink
feat(devkit): make nx.json optional (#6398)
Browse files Browse the repository at this point in the history
* feat(devkit): make nx.json optional (#5678)

Allow project configuration functions to work without nx.json configuration file, this is particulary handy for regular Angular CLI projects.

* docs(devkit): make `NxJsonConfiguration` partial

Co-authored-by: Jason Jean <jasonjean1993@gmail.com>
  • Loading branch information
edbzn and FrozenPandaz committed Jul 29, 2021
1 parent 9f1e530 commit 5a93037
Show file tree
Hide file tree
Showing 6 changed files with 211 additions and 62 deletions.
2 changes: 1 addition & 1 deletion docs/angular/api-nx-devkit/index.md
Expand Up @@ -482,7 +482,7 @@ Implementation of a target of a project that handles multiple projects to be bat

### WorkspaceConfiguration

Ƭ **WorkspaceConfiguration**: `Omit`<[`WorkspaceJsonConfiguration`](../../angular/nx-devkit/index#workspacejsonconfiguration), `"projects"`\> & `Omit`<[`NxJsonConfiguration`](../../angular/nx-devkit/index#nxjsonconfiguration), `"projects"`\>
Ƭ **WorkspaceConfiguration**: `Omit`<[`WorkspaceJsonConfiguration`](../../angular/nx-devkit/index#workspacejsonconfiguration), `"projects"`\> & `Partial`<`Omit`<[`NxJsonConfiguration`](../../angular/nx-devkit/index#nxjsonconfiguration), `"projects"`\>\>

## Variables

Expand Down
2 changes: 1 addition & 1 deletion docs/node/api-nx-devkit/index.md
Expand Up @@ -482,7 +482,7 @@ Implementation of a target of a project that handles multiple projects to be bat

### WorkspaceConfiguration

Ƭ **WorkspaceConfiguration**: `Omit`<[`WorkspaceJsonConfiguration`](../../node/nx-devkit/index#workspacejsonconfiguration), `"projects"`\> & `Omit`<[`NxJsonConfiguration`](../../node/nx-devkit/index#nxjsonconfiguration), `"projects"`\>
Ƭ **WorkspaceConfiguration**: `Omit`<[`WorkspaceJsonConfiguration`](../../node/nx-devkit/index#workspacejsonconfiguration), `"projects"`\> & `Partial`<`Omit`<[`NxJsonConfiguration`](../../node/nx-devkit/index#nxjsonconfiguration), `"projects"`\>\>

## Variables

Expand Down
2 changes: 1 addition & 1 deletion docs/react/api-nx-devkit/index.md
Expand Up @@ -482,7 +482,7 @@ Implementation of a target of a project that handles multiple projects to be bat

### WorkspaceConfiguration

Ƭ **WorkspaceConfiguration**: `Omit`<[`WorkspaceJsonConfiguration`](../../react/nx-devkit/index#workspacejsonconfiguration), `"projects"`\> & `Omit`<[`NxJsonConfiguration`](../../react/nx-devkit/index#nxjsonconfiguration), `"projects"`\>
Ƭ **WorkspaceConfiguration**: `Omit`<[`WorkspaceJsonConfiguration`](../../react/nx-devkit/index#workspacejsonconfiguration), `"projects"`\> & `Partial`<`Omit`<[`NxJsonConfiguration`](../../react/nx-devkit/index#nxjsonconfiguration), `"projects"`\>\>

## Variables

Expand Down
151 changes: 144 additions & 7 deletions packages/devkit/src/generators/project-configuration.spec.ts
@@ -1,17 +1,18 @@
import { Tree } from '@nrwl/tao/src/shared/tree';
import { ProjectConfiguration } from '@nrwl/tao/src/shared/workspace';

import { createTreeWithEmptyWorkspace } from '../tests/create-tree-with-empty-workspace';
import { readJson } from '../utils/json';
import {
readProjectConfiguration,
addProjectConfiguration,
updateProjectConfiguration,
removeProjectConfiguration,
getProjects,
readProjectConfiguration,
readWorkspaceConfiguration,
WorkspaceConfiguration,
removeProjectConfiguration,
updateProjectConfiguration,
updateWorkspaceConfiguration,
WorkspaceConfiguration,
} from './project-configuration';
import { createTreeWithEmptyWorkspace } from '../tests/create-tree-with-empty-workspace';
import { ProjectConfiguration } from '@nrwl/tao/src/shared/workspace';
import { readJson } from '../utils/json';

const baseTestProjectConfig: ProjectConfiguration = {
root: 'libs/test',
Expand Down Expand Up @@ -171,4 +172,140 @@ describe('project configuration', () => {
expect(readJson(tree, 'nx.json').$schema).not.toBeDefined();
});
});

describe('without nx.json', () => {
beforeEach(() => tree.delete('nx.json'));

afterEach(() => expect(tree.exists('nx.json')).toEqual(false));

it('should create project.json file when adding a project if standalone is true', () => {
addProjectConfiguration(tree, 'test', baseTestProjectConfig, true);

expect(tree.exists('libs/test/project.json')).toBeTruthy();
});

it('should create project.json file if all other apps in the workspace use project.json', () => {
addProjectConfiguration(
tree,
'project-a',
{
root: 'apps/project-a',
targets: {},
},
true
);
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", () => {
addProjectConfiguration(
tree,
'project-a',
{
root: 'apps/project-a',
targets: {},
},
false
);
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', () => {
addProjectConfiguration(tree, 'test', baseTestProjectConfig, false);

expect(tree.exists('libs/test/project.json')).toBeFalsy();
});

it('should be able to read from standalone projects', () => {
tree.write(
'workspace.json',
JSON.stringify(
{
projects: {
test: {
root: '/libs/test',
},
},
},
null,
2
)
);

const projectConfig = readProjectConfiguration(tree, 'test');

expect(projectConfig).toEqual({
root: '/libs/test',
});
});

it('should update project.json file when updating a project', () => {
addProjectConfiguration(tree, 'test', baseTestProjectConfig, true);
const expectedProjectConfig = {
...baseTestProjectConfig,
targets: { build: { executor: '' } },
};
updateProjectConfiguration(tree, 'test', expectedProjectConfig);

expect(readJson(tree, 'libs/test/project.json')).toEqual(
expectedProjectConfig
);
});

it('should update workspace.json file when updating an inline project', () => {
addProjectConfiguration(tree, 'test', baseTestProjectConfig, false);
const expectedProjectConfig = {
...baseTestProjectConfig,
targets: { build: { executor: '' } },
};
updateProjectConfiguration(tree, 'test', expectedProjectConfig);

expect(readJson(tree, 'workspace.json').projects.test).toEqual(
expectedProjectConfig
);
});

it('should remove project.json file when removing project configuration', () => {
addProjectConfiguration(tree, 'test', baseTestProjectConfig, true);
removeProjectConfiguration(tree, 'test');

expect(tree.exists('test/project.json')).toBeFalsy();
});

it('should remove project configuration', () => {
addProjectConfiguration(tree, 'test', baseTestProjectConfig, false);
removeProjectConfiguration(tree, 'test');

expect(readJson(tree, 'workspace.json').projects.test).toBeUndefined();
});

it('should support workspaces with standalone and inline projects', () => {
addProjectConfiguration(tree, 'test', baseTestProjectConfig, true);
addProjectConfiguration(tree, 'test2', baseTestProjectConfig, false);

const configurations = getProjects(tree);

expect(configurations.get('test')).toEqual(baseTestProjectConfig);
expect(configurations.get('test2')).toEqual(baseTestProjectConfig);
});
});
});
97 changes: 55 additions & 42 deletions packages/devkit/src/generators/project-configuration.ts
@@ -1,26 +1,28 @@
import type { Tree } from '@nrwl/tao/src/shared/tree';
import {
ProjectConfiguration,
RawWorkspaceJsonConfiguration,
toNewFormat,
WorkspaceJsonConfiguration,
} from '@nrwl/tao/src/shared/workspace';
import { readJson, updateJson, writeJson } from '../utils/json';
import type {
NxJsonConfiguration,
NxJsonProjectConfiguration,
} from '@nrwl/tao/src/shared/nx';

import {
getWorkspaceLayout,
getWorkspacePath,
} from '../utils/get-workspace-layout';
import { readJson, updateJson, writeJson } from '../utils/json';
import { joinPathFragments } from '../utils/path';

import type { Tree } from '@nrwl/tao/src/shared/tree';
import type {
NxJsonConfiguration,
NxJsonProjectConfiguration,
} from '@nrwl/tao/src/shared/nx';

export type WorkspaceConfiguration = Omit<
WorkspaceJsonConfiguration,
'projects'
> &
Omit<NxJsonConfiguration, 'projects'>;
Partial<Omit<NxJsonConfiguration, 'projects'>>;

/**
* Adds project configuration to the Nx workspace.
Expand Down Expand Up @@ -89,13 +91,13 @@ export function getProjects(
tree: Tree
): Map<string, ProjectConfiguration & NxJsonProjectConfiguration> {
const workspace = readWorkspace(tree);
const nxJson = readJson<NxJsonConfiguration>(tree, 'nx.json');
const nxJson = readNxJson(tree);

return new Map(
Object.keys(workspace.projects || {}).map((projectName) => {
return [
projectName,
getProjectConfiguration(tree, projectName, workspace, nxJson),
getProjectConfiguration(projectName, workspace, nxJson),
];
})
);
Expand All @@ -109,11 +111,15 @@ export function getProjects(
export function readWorkspaceConfiguration(tree: Tree): WorkspaceConfiguration {
const workspace = readWorkspace(tree);
delete workspace.projects;
const nxJson = readJson<NxJsonConfiguration>(tree, 'nx.json');
delete nxJson.projects;

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

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

Expand Down Expand Up @@ -165,9 +171,12 @@ export function updateWorkspaceConfiguration(
return { ...json, ...workspace };
}
);
updateJson<NxJsonConfiguration>(tree, 'nx.json', (json) => {
return { ...json, ...nxJson };
});

if (tree.exists('nx.json')) {
updateJson<NxJsonConfiguration>(tree, 'nx.json', (json) => {
return { ...json, ...nxJson };
});
}
}

/**
Expand All @@ -193,16 +202,16 @@ export function readProjectConfiguration(
);
}

const nxJson = readJson<NxJsonConfiguration>(tree, 'nx.json');
const nxJson = readNxJson(tree);

// TODO: Remove after confirming that nx.json should be optional.
// if (!nxJson.projects[projectName]) {
// throw new Error(
// `Cannot find configuration for '${projectName}' in nx.json`
// );
// }
return getProjectConfiguration(projectName, workspace, nxJson);
}

return getProjectConfiguration(tree, projectName, workspace, nxJson);
export function readNxJson(tree: Tree): NxJsonConfiguration | null {
if (!tree.exists('nx.json')) {
return null;
}
return readJson<NxJsonConfiguration>(tree, 'nx.json');
}

/**
Expand All @@ -222,24 +231,21 @@ export function isStandaloneProject(tree: Tree, project: string): boolean {
}

function getProjectConfiguration(
tree: Tree,
projectName: string,
workspace: WorkspaceJsonConfiguration,
nxJson: NxJsonConfiguration
nxJson: NxJsonConfiguration | null
): ProjectConfiguration & NxJsonProjectConfiguration {
return {
...readWorkspaceSection(tree, workspace, projectName),
...readNxJsonSection(nxJson, projectName),
...readWorkspaceSection(workspace, projectName),
...(nxJson === null ? {} : readNxJsonSection(nxJson, projectName)),
};
}

function readWorkspaceSection(
tree: Tree,
workspace: WorkspaceJsonConfiguration,
projectName: string
) {
const config = workspace.projects[projectName];
return config;
return workspace.projects[projectName];
}

function readNxJsonSection(nxJson: NxJsonConfiguration, projectName: string) {
Expand All @@ -252,9 +258,13 @@ function setProjectConfiguration(
projectConfiguration: ProjectConfiguration & NxJsonProjectConfiguration,
mode: 'create' | 'update' | 'delete',
standalone: boolean = false
) {
): void {
const hasNxJson = tree.exists('nx.json');

if (mode === 'delete') {
addProjectToNxJson(tree, projectName, undefined, mode);
if (hasNxJson) {
addProjectToNxJson(tree, projectName, undefined, mode);
}
addProjectToWorkspaceJson(tree, projectName, undefined, mode);
return;
}
Expand All @@ -265,23 +275,26 @@ function setProjectConfiguration(
);
}

const { tags, implicitDependencies } = projectConfiguration;
addProjectToWorkspaceJson(
tree,
projectName,
projectConfiguration,
mode,
standalone
);
addProjectToNxJson(
tree,
projectName,
{
tags,
implicitDependencies,
},
mode
);

if (hasNxJson) {
const { tags, implicitDependencies } = projectConfiguration;
addProjectToNxJson(
tree,
projectName,
{
tags,
implicitDependencies,
},
mode
);
}
}

function addProjectToWorkspaceJson(
Expand Down

1 comment on commit 5a93037

@vercel
Copy link

@vercel vercel bot commented on 5a93037 Jul 29, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please sign in to comment.