UNPKG

nx

Version:

The core Nx plugin contains the core functionality of Nx like the project graph, nx commands and task orchestration.

290 lines (289 loc) • 13.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.updateNxJson = exports.readNxJson = void 0; exports.addProjectConfiguration = addProjectConfiguration; exports.updateProjectConfiguration = updateProjectConfiguration; exports.removeProjectConfiguration = removeProjectConfiguration; exports.readProjectConfiguration = readProjectConfiguration; exports.getProjects = getProjects; exports.getRelativeProjectJsonSchemaPath = getRelativeProjectJsonSchemaPath; const minimatch_1 = require("minimatch"); const path_1 = require("path"); const package_json_1 = require("../../plugins/package-json"); const project_json_1 = require("../../plugins/project-json/build-nodes/project-json"); const angular_json_1 = require("../../adapter/angular-json"); const project_configuration_utils_1 = require("../../project-graph/utils/project-configuration-utils"); const workspace_context_1 = require("../../utils/workspace-context"); const output_1 = require("../../utils/output"); const path_2 = require("../../utils/path"); const json_1 = require("./json"); const to_project_name_1 = require("../../config/to-project-name"); var nx_json_1 = require("./nx-json"); Object.defineProperty(exports, "readNxJson", { enumerable: true, get: function () { return nx_json_1.readNxJson; } }); Object.defineProperty(exports, "updateNxJson", { enumerable: true, get: function () { return nx_json_1.updateNxJson; } }); /** * Adds project configuration to the Nx workspace. * * @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 - whether the project is configured in workspace.json or not */ function addProjectConfiguration(tree, projectName, projectConfiguration, standalone = true) { const projectConfigFile = (0, path_2.joinPathFragments)(projectConfiguration.root, 'project.json'); if (!standalone) { output_1.output.warn({ title: 'Nx only supports standalone projects. Setting standalone to false is ignored.', }); } if (tree.exists(projectConfigFile)) { throw new Error(`Cannot create a new project ${projectName} at ${projectConfiguration.root}. A project already exists in this directory.`); } delete projectConfiguration.$schema; handleEmptyTargets(projectName, projectConfiguration); (0, json_1.writeJson)(tree, projectConfigFile, { name: projectName, $schema: getRelativeProjectJsonSchemaPath(tree, projectConfiguration), ...projectConfiguration, root: undefined, }); } /** * Updates the configuration of an existing project. * * @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 */ function updateProjectConfiguration(tree, projectName, projectConfiguration) { if (tree.exists((0, path_2.joinPathFragments)(projectConfiguration.root, 'project.json'))) { updateProjectConfigurationInProjectJson(tree, projectName, projectConfiguration); } else if (tree.exists((0, path_2.joinPathFragments)(projectConfiguration.root, 'package.json'))) { updateProjectConfigurationInPackageJson(tree, projectName, projectConfiguration); } else { throw new Error(`Cannot update Project ${projectName} at ${projectConfiguration.root}. It either doesn't exist yet, or may not use project.json for configuration. Use \`addProjectConfiguration()\` instead if you want to create a new project.`); } } function updateProjectConfigurationInPackageJson(tree, projectName, projectConfiguration) { const packageJsonFile = (0, path_2.joinPathFragments)(projectConfiguration.root, 'package.json'); const packageJson = (0, json_1.readJson)(tree, packageJsonFile); projectConfiguration.name = projectName; if (packageJson.name === projectConfiguration.name) { delete projectConfiguration.name; } if (projectConfiguration.targets && !Object.keys(projectConfiguration.targets).length) { delete projectConfiguration.targets; } packageJson.nx = { ...packageJson.nx, ...projectConfiguration, }; // We don't want to ever this since it is inferred delete packageJson.nx.root; // Only set `nx` property in `package.json` if it is a root project (necessary to mark it as Nx project), // or if there are properties to be set. If it is empty, then avoid it so we don't add unnecessary boilerplate. if (projectConfiguration.root === '.' || Object.keys(packageJson.nx).length > 0) { (0, json_1.writeJson)(tree, packageJsonFile, packageJson); } } function updateProjectConfigurationInProjectJson(tree, projectName, projectConfiguration) { const projectConfigFile = (0, path_2.joinPathFragments)(projectConfiguration.root, 'project.json'); handleEmptyTargets(projectName, projectConfiguration); (0, json_1.writeJson)(tree, projectConfigFile, { name: projectConfiguration.name ?? projectName, $schema: getRelativeProjectJsonSchemaPath(tree, projectConfiguration), ...projectConfiguration, root: undefined, }); } /** * Removes the configuration of an existing project. * * @param tree - the file system tree * @param projectName - unique name. Often directories are part of the name (e.g., mydir-mylib) */ function removeProjectConfiguration(tree, projectName) { const projectConfiguration = readProjectConfiguration(tree, projectName); if (!projectConfiguration) { throw new Error(`Cannot delete Project ${projectName}`); } const projectConfigFile = (0, path_2.joinPathFragments)(projectConfiguration.root, 'project.json'); if (tree.exists(projectConfigFile)) { tree.delete(projectConfigFile); } } /** * Reads a project configuration. * * @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 */ function readProjectConfiguration(tree, projectName) { const allProjects = readAndCombineAllProjectConfigurations(tree); if (!allProjects[projectName]) { // temporary polyfill to make sure our generators work for existing angularcli workspaces if (tree.exists('angular.json')) { const angularJson = toNewFormat((0, json_1.readJson)(tree, 'angular.json')); if (angularJson.projects[projectName]) return angularJson.projects[projectName]; } throw new Error(`Cannot find configuration for '${projectName}'`); } return allProjects[projectName]; } /** * Get a map of all projects in a workspace. * * Use {@link readProjectConfiguration} if only one project is needed. */ function getProjects(tree) { let allProjects = readAndCombineAllProjectConfigurations(tree); // temporary polyfill to make sure our generators work for existing angularcli workspaces if (tree.exists('angular.json')) { const angularJson = toNewFormat((0, json_1.readJson)(tree, 'angular.json')); allProjects = { ...allProjects, ...angularJson.projects }; } return new Map(Object.keys(allProjects || {}).map((projectName) => { return [projectName, allProjects[projectName]]; })); } function getRelativeProjectJsonSchemaPath(tree, project) { return (0, path_2.normalizePath)((0, path_1.relative)((0, path_1.join)(tree.root, project.root), (0, path_1.join)(tree.root, 'node_modules/nx/schemas/project-schema.json'))); } function readAndCombineAllProjectConfigurations(tree) { /** * We can't update projects that come from plugins anyways, so we are going * to ignore them for now. Plugins should add their own add/create/update methods * if they would like to use devkit to update inferred projects. */ const patterns = [ '**/project.json', 'project.json', ...(0, package_json_1.getGlobPatternsFromPackageManagerWorkspaces)(tree.root, (p) => (0, json_1.readJson)(tree, p, { expectComments: true }), (p) => { const content = tree.read(p, 'utf-8'); const { load } = require('@zkochan/js-yaml'); return load(content, { filename: p }); }, (p) => tree.exists(p)), ]; const globbedFiles = (0, workspace_context_1.globWithWorkspaceContextSync)(tree.root, patterns); const createdFiles = findCreatedProjectFiles(tree, patterns); const deletedFiles = findDeletedProjectFiles(tree, patterns); const projectFiles = [...globbedFiles, ...createdFiles].filter((r) => deletedFiles.indexOf(r) === -1); const rootMap = {}; for (const projectFile of projectFiles) { if ((0, path_1.basename)(projectFile) === 'project.json') { const json = (0, json_1.readJson)(tree, projectFile); const config = (0, project_json_1.buildProjectFromProjectJson)(json, projectFile); (0, project_configuration_utils_1.mergeProjectConfigurationIntoRootMap)(rootMap, config, undefined, undefined, true); } else if ((0, path_1.basename)(projectFile) === 'package.json') { const packageJson = (0, json_1.readJson)(tree, projectFile); // We don't want to have all of the extra inferred stuff in here, as // when generators update the project they shouldn't inline that stuff. // so rather than using `buildProjectFromPackageJson` and stripping it out // we are going to build the config manually. const config = { root: (0, path_1.dirname)(projectFile), name: packageJson.name ?? (0, to_project_name_1.toProjectName)(projectFile), ...packageJson.nx, }; if (!rootMap[config.root]) { (0, project_configuration_utils_1.mergeProjectConfigurationIntoRootMap)(rootMap, // Inferred targets, tags, etc don't show up when running generators // This is to help avoid running into issues when trying to update the workspace config, undefined, undefined, true); } } } return (0, project_configuration_utils_1.readProjectConfigurationsFromRootMap)(rootMap); } /** * Used to ensure that projects created during * the same devkit generator run show up when * there is no project.json file, as `glob` * cannot find them. * * We exclude the root `package.json` from this list unless * considered a project during workspace generation */ function findCreatedProjectFiles(tree, globPatterns) { const createdProjectFiles = []; for (const change of tree.listChanges()) { if (change.type === 'CREATE') { const fileName = (0, path_1.basename)(change.path); if (globPatterns.some((pattern) => (0, minimatch_1.minimatch)(change.path, pattern, { dot: true }))) { createdProjectFiles.push(change.path); } else if (fileName === 'package.json') { try { const contents = JSON.parse(change.content.toString()); if (contents.nx) { createdProjectFiles.push(change.path); } } catch { } } } } return createdProjectFiles.map(path_2.normalizePath); } /** * Used to ensure that projects created during * the same devkit generator run show up when * there is no project.json file, as `glob` * cannot find them. */ function findDeletedProjectFiles(tree, globPatterns) { return tree .listChanges() .filter((f) => { return (f.type === 'DELETE' && globPatterns.some((pattern) => (0, minimatch_1.minimatch)(f.path, pattern))); }) .map((r) => r.path); } function toNewFormat(w) { const projects = {}; Object.keys(w.projects || {}).forEach((name) => { if (typeof w.projects[name] === 'string') return; const projectConfig = w.projects[name]; if (projectConfig.architect) { (0, angular_json_1.renamePropertyWithStableKeys)(projectConfig, 'architect', 'targets'); } if (projectConfig.schematics) { (0, angular_json_1.renamePropertyWithStableKeys)(projectConfig, 'schematics', 'generators'); } Object.values(projectConfig.targets || {}).forEach((target) => { if (target.builder !== undefined) { (0, angular_json_1.renamePropertyWithStableKeys)(target, 'builder', 'executor'); } }); projects[name] = projectConfig; }); w.projects = projects; if (w.schematics) { (0, angular_json_1.renamePropertyWithStableKeys)(w, 'schematics', 'generators'); } if (w.version !== 2) { w.version = 2; } return w; } function handleEmptyTargets(projectName, projectConfiguration) { if (projectConfiguration.targets && !Object.keys(projectConfiguration.targets).length) { // Re-order `targets` to appear after the `// target` comment. delete projectConfiguration.targets; projectConfiguration['// targets'] = `to see all targets run: nx show project ${projectName} --web`; projectConfiguration.targets = {}; } else { delete projectConfiguration['// targets']; } }