468 lines
13 KiB
TypeScript
468 lines
13 KiB
TypeScript
import {
|
|
ProjectConfiguration,
|
|
RawWorkspaceJsonConfiguration,
|
|
toNewFormat,
|
|
WorkspaceJsonConfiguration,
|
|
} from '@nrwl/tao/src/shared/workspace';
|
|
|
|
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'
|
|
> &
|
|
Partial<Omit<NxJsonConfiguration, 'projects'>>;
|
|
|
|
/**
|
|
* Adds project configuration to the Nx workspace.
|
|
*
|
|
* The project configuration is stored in workspace.json and nx.json. The utility will update
|
|
* both files.
|
|
*
|
|
* @param tree - the file system tree
|
|
* @param projectName - unique name. Often directories are part of the name (e.g., mydir-mylib)
|
|
* @param projectConfiguration - project configuration
|
|
* @param standalone - should the project use package.json? If false, the project config is inside workspace.json
|
|
*/
|
|
export function addProjectConfiguration(
|
|
tree: Tree,
|
|
projectName: string,
|
|
projectConfiguration: ProjectConfiguration & NxJsonProjectConfiguration,
|
|
standalone: boolean = false
|
|
): void {
|
|
standalone = standalone || getWorkspaceLayout(tree).standaloneAsDefault;
|
|
setProjectConfiguration(
|
|
tree,
|
|
projectName,
|
|
projectConfiguration,
|
|
'create',
|
|
standalone
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Updates the configuration of an existing project.
|
|
*
|
|
* The project configuration is stored in workspace.json and nx.json. The utility will update
|
|
* both files.
|
|
*
|
|
* @param tree - the file system tree
|
|
* @param projectName - unique name. Often directories are part of the name (e.g., mydir-mylib)
|
|
* @param projectConfiguration - project configuration
|
|
*/
|
|
export function updateProjectConfiguration(
|
|
tree: Tree,
|
|
projectName: string,
|
|
projectConfiguration: ProjectConfiguration & NxJsonProjectConfiguration
|
|
): void {
|
|
setProjectConfiguration(tree, projectName, projectConfiguration, 'update');
|
|
}
|
|
|
|
/**
|
|
* Removes the configuration of an existing project.
|
|
*
|
|
* The project configuration is stored in workspace.json and nx.json.
|
|
* The utility will update both files.
|
|
*/
|
|
export function removeProjectConfiguration(
|
|
tree: Tree,
|
|
projectName: string
|
|
): void {
|
|
setProjectConfiguration(tree, projectName, undefined, 'delete');
|
|
}
|
|
|
|
/**
|
|
* Get a map of all projects in a workspace.
|
|
*
|
|
* Use {@link readProjectConfiguration} if only one project is needed.
|
|
*/
|
|
export function getProjects(
|
|
tree: Tree
|
|
): Map<string, ProjectConfiguration & NxJsonProjectConfiguration> {
|
|
const workspace = readWorkspace(tree);
|
|
const nxJson = readNxJson(tree);
|
|
|
|
return new Map(
|
|
Object.keys(workspace.projects || {}).map((projectName) => {
|
|
return [
|
|
projectName,
|
|
getProjectConfiguration(projectName, workspace, nxJson),
|
|
];
|
|
})
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Read general workspace configuration such as the default project or cli settings
|
|
*
|
|
* This does _not_ provide projects configuration, use {@link readProjectConfiguration} instead.
|
|
*/
|
|
export function readWorkspaceConfiguration(tree: Tree): WorkspaceConfiguration {
|
|
const workspace = readWorkspace(tree);
|
|
delete workspace.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,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* Update general workspace configuration such as the default project or cli settings.
|
|
*
|
|
* This does _not_ update projects configuration, use {@link updateProjectConfiguration} or {@link addProjectConfiguration} instead.
|
|
*/
|
|
export function updateWorkspaceConfiguration(
|
|
tree: Tree,
|
|
workspaceConfig: WorkspaceConfiguration
|
|
): void {
|
|
const {
|
|
// Angular Json Properties
|
|
version,
|
|
cli,
|
|
defaultProject,
|
|
generators,
|
|
|
|
// Nx Json Properties
|
|
implicitDependencies,
|
|
plugins,
|
|
npmScope,
|
|
targetDependencies,
|
|
workspaceLayout,
|
|
tasksRunnerOptions,
|
|
affected,
|
|
} = workspaceConfig;
|
|
const workspace: Omit<Required<WorkspaceJsonConfiguration>, 'projects'> = {
|
|
version,
|
|
cli,
|
|
defaultProject,
|
|
generators,
|
|
};
|
|
const nxJson: Omit<Required<NxJsonConfiguration>, 'projects'> = {
|
|
implicitDependencies,
|
|
plugins,
|
|
npmScope,
|
|
targetDependencies,
|
|
workspaceLayout,
|
|
tasksRunnerOptions,
|
|
affected,
|
|
};
|
|
|
|
updateJson<WorkspaceJsonConfiguration>(
|
|
tree,
|
|
getWorkspacePath(tree),
|
|
(json) => {
|
|
return { ...json, ...workspace };
|
|
}
|
|
);
|
|
|
|
if (tree.exists('nx.json')) {
|
|
updateJson<NxJsonConfiguration>(tree, 'nx.json', (json) => {
|
|
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.
|
|
*
|
|
* The project configuration is stored in workspace.json and nx.json. The utility will read
|
|
* both files.
|
|
*
|
|
* @param tree - the file system tree
|
|
* @param projectName - unique name. Often directories are part of the name (e.g., mydir-mylib)
|
|
* @throws If supplied projectName cannot be found
|
|
*/
|
|
export function readProjectConfiguration(
|
|
tree: Tree,
|
|
projectName: string
|
|
): ProjectConfiguration & NxJsonProjectConfiguration {
|
|
const workspace = readWorkspace(tree);
|
|
if (!workspace.projects[projectName]) {
|
|
throw new Error(
|
|
`Cannot find configuration for '${projectName}' in ${getWorkspacePath(
|
|
tree
|
|
)}.`
|
|
);
|
|
}
|
|
|
|
const nxJson = readNxJson(tree);
|
|
|
|
return getProjectConfiguration(projectName, workspace, nxJson);
|
|
}
|
|
|
|
export function readNxJson(tree: Tree): NxJsonConfiguration | null {
|
|
if (!tree.exists('nx.json')) {
|
|
return null;
|
|
}
|
|
let nxJson = readJson<NxJsonConfiguration>(tree, 'nx.json');
|
|
const nxJsonExtends = readNxJsonExtends(tree, nxJson as any);
|
|
if (nxJsonExtends) {
|
|
nxJson = { ...nxJsonExtends, ...nxJson };
|
|
}
|
|
return nxJson;
|
|
}
|
|
|
|
/**
|
|
* Returns if a project has a standalone configuration (project.json).
|
|
*
|
|
* @param tree - the file system tree
|
|
* @param project - the project name
|
|
*/
|
|
export function isStandaloneProject(tree: Tree, project: string): boolean {
|
|
const rawWorkspace = readJson<RawWorkspaceJsonConfiguration>(
|
|
tree,
|
|
getWorkspacePath(tree)
|
|
);
|
|
const projectConfig = rawWorkspace.projects?.[project];
|
|
|
|
return typeof projectConfig === 'string';
|
|
}
|
|
|
|
function getProjectConfiguration(
|
|
projectName: string,
|
|
workspace: WorkspaceJsonConfiguration,
|
|
nxJson: NxJsonConfiguration | null
|
|
): ProjectConfiguration & NxJsonProjectConfiguration {
|
|
return {
|
|
...readWorkspaceSection(workspace, projectName),
|
|
...(nxJson === null ? {} : readNxJsonSection(nxJson, projectName)),
|
|
};
|
|
}
|
|
|
|
function readWorkspaceSection(
|
|
workspace: WorkspaceJsonConfiguration,
|
|
projectName: string
|
|
) {
|
|
return workspace.projects[projectName];
|
|
}
|
|
|
|
function readNxJsonSection(nxJson: NxJsonConfiguration, projectName: string) {
|
|
return nxJson.projects?.[projectName];
|
|
}
|
|
|
|
function setProjectConfiguration(
|
|
tree: Tree,
|
|
projectName: string,
|
|
projectConfiguration: ProjectConfiguration & NxJsonProjectConfiguration,
|
|
mode: 'create' | 'update' | 'delete',
|
|
standalone: boolean = false
|
|
): void {
|
|
const hasNxJson = tree.exists('nx.json');
|
|
|
|
if (mode === 'delete') {
|
|
if (hasNxJson) {
|
|
addProjectToNxJson(tree, projectName, undefined, mode);
|
|
}
|
|
addProjectToWorkspaceJson(tree, projectName, undefined, mode);
|
|
return;
|
|
}
|
|
|
|
if (!projectConfiguration) {
|
|
throw new Error(
|
|
`Cannot ${mode} "${projectName}" with value ${projectConfiguration}`
|
|
);
|
|
}
|
|
|
|
addProjectToWorkspaceJson(
|
|
tree,
|
|
projectName,
|
|
projectConfiguration,
|
|
mode,
|
|
standalone
|
|
);
|
|
|
|
if (hasNxJson) {
|
|
const { tags, implicitDependencies } = projectConfiguration;
|
|
addProjectToNxJson(
|
|
tree,
|
|
projectName,
|
|
{
|
|
tags,
|
|
implicitDependencies,
|
|
},
|
|
mode
|
|
);
|
|
}
|
|
}
|
|
|
|
function addProjectToWorkspaceJson(
|
|
tree: Tree,
|
|
projectName: string,
|
|
project: ProjectConfiguration & NxJsonProjectConfiguration,
|
|
mode: 'create' | 'update' | 'delete',
|
|
standalone: boolean = false
|
|
) {
|
|
const path = getWorkspacePath(tree);
|
|
const workspaceJson = readJson<RawWorkspaceJsonConfiguration>(tree, path);
|
|
|
|
validateWorkspaceJsonOperations(mode, workspaceJson, projectName);
|
|
|
|
const configFile =
|
|
mode === 'create' && standalone
|
|
? joinPathFragments(project.root, 'project.json')
|
|
: getProjectFileLocation(tree, projectName);
|
|
|
|
if (configFile) {
|
|
if (mode === 'delete') {
|
|
tree.delete(configFile);
|
|
delete workspaceJson.projects[projectName];
|
|
} else {
|
|
writeJson(tree, configFile, project);
|
|
}
|
|
if (mode === 'create') {
|
|
workspaceJson.projects[projectName] = project.root;
|
|
}
|
|
} else {
|
|
let workspaceConfiguration: ProjectConfiguration;
|
|
if (project) {
|
|
const { tags, implicitDependencies, ...c } = project;
|
|
workspaceConfiguration = c;
|
|
}
|
|
workspaceJson.projects[projectName] = workspaceConfiguration;
|
|
}
|
|
writeJson(tree, path, workspaceJson);
|
|
}
|
|
|
|
function addProjectToNxJson(
|
|
tree: Tree,
|
|
projectName: string,
|
|
config: NxJsonProjectConfiguration,
|
|
mode: 'create' | 'update' | 'delete'
|
|
) {
|
|
// distributed project files do not use nx.json,
|
|
// so only proceed if the project does not use them.
|
|
if (!getProjectFileLocation(tree, projectName)) {
|
|
const nxJson = readJson<NxJsonConfiguration>(tree, 'nx.json');
|
|
if (mode === 'delete') {
|
|
delete nxJson.projects[projectName];
|
|
} else {
|
|
nxJson.projects[projectName] = {
|
|
...{
|
|
tags: [],
|
|
},
|
|
...(config || {}),
|
|
};
|
|
}
|
|
writeJson(tree, 'nx.json', nxJson);
|
|
}
|
|
}
|
|
|
|
function readWorkspace(tree: Tree): WorkspaceJsonConfiguration {
|
|
const workspaceJson = inlineProjectConfigurationsWithTree(tree);
|
|
const originalVersion = workspaceJson.version;
|
|
return {
|
|
...toNewFormat(workspaceJson),
|
|
version: originalVersion,
|
|
};
|
|
}
|
|
|
|
/**
|
|
* This has to be separate from the inline functionality inside tao,
|
|
* as the functionality in tao does not use a Tree. Changes made during
|
|
* a generator would not be present during runtime execution.
|
|
* @returns
|
|
*/
|
|
function inlineProjectConfigurationsWithTree(
|
|
tree: Tree
|
|
): WorkspaceJsonConfiguration {
|
|
const path = getWorkspacePath(tree);
|
|
const workspaceJson = readJson<RawWorkspaceJsonConfiguration>(tree, path);
|
|
Object.entries(workspaceJson.projects || {}).forEach(([project, config]) => {
|
|
if (typeof config === 'string') {
|
|
const configFileLocation = joinPathFragments(config, 'project.json');
|
|
workspaceJson.projects[project] = readJson<
|
|
ProjectConfiguration & NxJsonProjectConfiguration
|
|
>(tree, configFileLocation);
|
|
}
|
|
});
|
|
return workspaceJson as WorkspaceJsonConfiguration;
|
|
}
|
|
|
|
/**
|
|
* @description Determine where a project's configuration is located.
|
|
* @returns file path if separate from root config, null otherwise.
|
|
*/
|
|
function getProjectFileLocation(tree: Tree, project: string): string | null {
|
|
const rawWorkspace = readJson<RawWorkspaceJsonConfiguration>(
|
|
tree,
|
|
getWorkspacePath(tree)
|
|
);
|
|
const projectConfig = rawWorkspace.projects?.[project];
|
|
return typeof projectConfig === 'string'
|
|
? joinPathFragments(projectConfig, 'project.json')
|
|
: null;
|
|
}
|
|
|
|
function validateWorkspaceJsonOperations(
|
|
mode: 'create' | 'update' | 'delete',
|
|
workspaceJson: RawWorkspaceJsonConfiguration | WorkspaceJsonConfiguration,
|
|
projectName: string
|
|
) {
|
|
if (mode == 'create' && workspaceJson.projects[projectName]) {
|
|
throw new Error(
|
|
`Cannot create Project '${projectName}'. It already exists.`
|
|
);
|
|
}
|
|
if (mode == 'update' && !workspaceJson.projects[projectName]) {
|
|
throw new Error(
|
|
`Cannot update Project '${projectName}'. It does not exist.`
|
|
);
|
|
}
|
|
if (mode == 'delete' && !workspaceJson.projects[projectName]) {
|
|
throw new Error(
|
|
`Cannot delete Project '${projectName}'. It does not exist.`
|
|
);
|
|
}
|
|
}
|