UNPKG

nx

Version:

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

800 lines (799 loc) • 35.7 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.mergeProjectConfigurationIntoRootMap = mergeProjectConfigurationIntoRootMap; exports.mergeMetadata = mergeMetadata; exports.createProjectConfigurationsWithPlugins = createProjectConfigurationsWithPlugins; exports.findMatchingConfigFiles = findMatchingConfigFiles; exports.readProjectConfigurationsFromRootMap = readProjectConfigurationsFromRootMap; exports.validateProject = validateProject; exports.mergeTargetDefaultWithTargetDefinition = mergeTargetDefaultWithTargetDefinition; exports.mergeTargetConfigurations = mergeTargetConfigurations; exports.isCompatibleTarget = isCompatibleTarget; exports.resolveNxTokensInOptions = resolveNxTokensInOptions; exports.readTargetDefaultsForTarget = readTargetDefaultsForTarget; exports.normalizeTarget = normalizeTarget; const logger_1 = require("../../utils/logger"); const fileutils_1 = require("../../utils/fileutils"); const workspace_root_1 = require("../../utils/workspace-root"); const minimatch_1 = require("minimatch"); const path_1 = require("path"); const perf_hooks_1 = require("perf_hooks"); const error_types_1 = require("../error-types"); const globs_1 = require("../../utils/globs"); const delayed_spinner_1 = require("../../utils/delayed-spinner"); function mergeProjectConfigurationIntoRootMap(projectRootMap, project, configurationSourceMaps, sourceInformation, // This function is used when reading project configuration // in generators, where we don't want to do this. skipTargetNormalization) { if (configurationSourceMaps && !configurationSourceMaps[project.root]) { configurationSourceMaps[project.root] = {}; } const sourceMap = configurationSourceMaps?.[project.root]; let matchingProject = projectRootMap[project.root]; if (!matchingProject) { projectRootMap[project.root] = { root: project.root, }; matchingProject = projectRootMap[project.root]; if (sourceMap) { sourceMap[`root`] = sourceInformation; } } // This handles top level properties that are overwritten. // e.g. `srcRoot`, `projectType`, or other fields that shouldn't be extended // Note: `name` is set specifically here to keep it from changing. The name is // always determined by the first inference plugin to ID a project, unless it has // a project.json in which case it was already updated above. const updatedProjectConfiguration = { ...matchingProject, }; for (const k in project) { if (![ 'tags', 'implicitDependencies', 'generators', 'targets', 'metadata', 'namedInputs', ].includes(k)) { updatedProjectConfiguration[k] = project[k]; if (sourceMap) { sourceMap[`${k}`] = sourceInformation; } } } // The next blocks handle properties that should be themselves merged (e.g. targets, tags, and implicit dependencies) if (project.tags) { updatedProjectConfiguration.tags = Array.from(new Set((matchingProject.tags ?? []).concat(project.tags))); if (sourceMap) { sourceMap['tags'] ??= sourceInformation; project.tags.forEach((tag) => { sourceMap[`tags.${tag}`] = sourceInformation; }); } } if (project.implicitDependencies) { updatedProjectConfiguration.implicitDependencies = (matchingProject.implicitDependencies ?? []).concat(project.implicitDependencies); if (sourceMap) { sourceMap['implicitDependencies'] ??= sourceInformation; project.implicitDependencies.forEach((implicitDependency) => { sourceMap[`implicitDependencies.${implicitDependency}`] = sourceInformation; }); } } if (project.generators) { // Start with generators config in new project. updatedProjectConfiguration.generators = { ...project.generators }; if (sourceMap) { sourceMap['generators'] ??= sourceInformation; for (const generator in project.generators) { sourceMap[`generators.${generator}`] = sourceInformation; for (const property in project.generators[generator]) { sourceMap[`generators.${generator}.${property}`] = sourceInformation; } } } if (matchingProject.generators) { // For each generator that was already defined, shallow merge the options. // Project contains the new info, so it has higher priority. for (const generator in matchingProject.generators) { updatedProjectConfiguration.generators[generator] = { ...matchingProject.generators[generator], ...project.generators[generator], }; } } } if (project.namedInputs) { updatedProjectConfiguration.namedInputs = { ...matchingProject.namedInputs, ...project.namedInputs, }; if (sourceMap) { sourceMap['namedInputs'] ??= sourceInformation; for (const namedInput in project.namedInputs) { sourceMap[`namedInputs.${namedInput}`] = sourceInformation; } } } if (project.metadata) { updatedProjectConfiguration.metadata = mergeMetadata(sourceMap, sourceInformation, 'metadata', project.metadata, matchingProject.metadata); } if (project.targets) { // We merge the targets with special handling, so clear this back to the // targets as defined originally before merging. updatedProjectConfiguration.targets = matchingProject?.targets ?? {}; if (sourceMap) { sourceMap['targets'] ??= sourceInformation; } // For each target defined in the new config for (const targetName in project.targets) { // Always set source map info for the target, but don't overwrite info already there // if augmenting an existing target. const target = project.targets?.[targetName]; if (sourceMap) { sourceMap[`targets.${targetName}`] = sourceInformation; } const normalizedTarget = skipTargetNormalization ? target : resolveCommandSyntacticSugar(target, project.root); const mergedTarget = mergeTargetConfigurations(normalizedTarget, matchingProject.targets?.[targetName], sourceMap, sourceInformation, `targets.${targetName}`); updatedProjectConfiguration.targets[targetName] = mergedTarget; } } projectRootMap[updatedProjectConfiguration.root] = updatedProjectConfiguration; } function mergeMetadata(sourceMap, sourceInformation, baseSourceMapPath, metadata, matchingMetadata) { const result = { ...(matchingMetadata ?? {}), }; for (const [metadataKey, value] of Object.entries(metadata)) { const existingValue = matchingMetadata?.[metadataKey]; if (Array.isArray(value) && Array.isArray(existingValue)) { for (const item of [...value]) { const newLength = result[metadataKey].push(item); if (sourceMap) { sourceMap[`${baseSourceMapPath}.${metadataKey}.${newLength - 1}`] = sourceInformation; } } } else if (Array.isArray(value) && existingValue === undefined) { result[metadataKey] ??= value; if (sourceMap) { sourceMap[`${baseSourceMapPath}.${metadataKey}`] = sourceInformation; } for (let i = 0; i < value.length; i++) { if (sourceMap) { sourceMap[`${baseSourceMapPath}.${metadataKey}.${i}`] = sourceInformation; } } } else if (typeof value === 'object' && typeof existingValue === 'object') { for (const key in value) { const existingValue = matchingMetadata?.[metadataKey]?.[key]; if (Array.isArray(value[key]) && Array.isArray(existingValue)) { for (const item of value[key]) { const i = result[metadataKey][key].push(item); if (sourceMap) { sourceMap[`${baseSourceMapPath}.${metadataKey}.${key}.${i - 1}`] = sourceInformation; } } } else { result[metadataKey][key] = value[key]; if (sourceMap) { sourceMap[`${baseSourceMapPath}.${metadataKey}`] = sourceInformation; } } } } else { result[metadataKey] = value; if (sourceMap) { sourceMap[`${baseSourceMapPath}.${metadataKey}`] = sourceInformation; if (typeof value === 'object') { for (const k in value) { sourceMap[`${baseSourceMapPath}.${metadataKey}.${k}`] = sourceInformation; if (Array.isArray(value[k])) { for (let i = 0; i < value[k].length; i++) { sourceMap[`${baseSourceMapPath}.${metadataKey}.${k}.${i}`] = sourceInformation; } } } } } } } return result; } /** * Transforms a list of project paths into a map of project configurations. * * @param root The workspace root * @param nxJson The NxJson configuration * @param workspaceFiles A list of non-ignored workspace files * @param plugins The plugins that should be used to infer project configuration */ async function createProjectConfigurationsWithPlugins(root = workspace_root_1.workspaceRoot, nxJson, projectFiles, // making this parameter allows devkit to pick up newly created projects plugins) { perf_hooks_1.performance.mark('build-project-configs:start'); let spinner; const inProgressPlugins = new Set(); function updateSpinner() { if (!spinner || inProgressPlugins.size === 0) { return; } if (inProgressPlugins.size === 1) { spinner.setMessage(`Creating project graph nodes with ${inProgressPlugins.values().next().value}`); } else if (process.env.NX_VERBOSE_LOGGING === 'true') { spinner.setMessage([ `Creating project graph nodes with ${inProgressPlugins.size} plugins`, ...Array.from(inProgressPlugins).map((p) => ` - ${p}`), ].join('\n')); } else { spinner.setMessage(`Creating project graph nodes with ${inProgressPlugins.size} plugins`); } } spinner = new delayed_spinner_1.DelayedSpinner(`Creating project graph nodes with ${plugins.length} plugins`); const results = []; const errors = []; // We iterate over plugins first - this ensures that plugins specified first take precedence. for (const [index, { index: pluginIndex, createNodes: createNodesTuple, include, exclude, name: pluginName, },] of plugins.entries()) { const [pattern, createNodes] = createNodesTuple ?? []; if (!pattern) { continue; } const matchingConfigFiles = findMatchingConfigFiles(projectFiles[index], pattern, include, exclude); inProgressPlugins.add(pluginName); let r = createNodes(matchingConfigFiles, { nxJsonConfiguration: nxJson, workspaceRoot: root, }) .catch((e) => { const error = (0, error_types_1.isAggregateCreateNodesError)(e) ? // This is an expected error if something goes wrong while processing files. e : // This represents a single plugin erroring out with a hard error. new error_types_1.AggregateCreateNodesError([[null, e]], []); if (pluginIndex !== undefined) { error.pluginIndex = pluginIndex; } (0, error_types_1.formatAggregateCreateNodesError)(error, pluginName); // This represents a single plugin erroring out with a hard error. errors.push(error); // The plugin didn't return partial results, so we return an empty array. return error.partialResults.map((r) => [pluginName, r[0], r[1], index]); }) .finally(() => { inProgressPlugins.delete(pluginName); updateSpinner(); }); results.push(r); } return Promise.all(results).then((results) => { spinner?.cleanup(); const { projectRootMap, externalNodes, rootMap, configurationSourceMaps } = mergeCreateNodesResults(results, nxJson, errors); perf_hooks_1.performance.mark('build-project-configs:end'); perf_hooks_1.performance.measure('build-project-configs', 'build-project-configs:start', 'build-project-configs:end'); if (errors.length === 0) { return { projects: projectRootMap, externalNodes, projectRootMap: rootMap, sourceMaps: configurationSourceMaps, matchingProjectFiles: projectFiles.flat(), }; } else { throw new error_types_1.ProjectConfigurationsError(errors, { projects: projectRootMap, externalNodes, projectRootMap: rootMap, sourceMaps: configurationSourceMaps, matchingProjectFiles: projectFiles.flat(), }); } }); } function mergeCreateNodesResults(results, nxJsonConfiguration, errors) { perf_hooks_1.performance.mark('createNodes:merge - start'); const projectRootMap = {}; const externalNodes = {}; const configurationSourceMaps = {}; for (const result of results.flat()) { const [pluginName, file, nodes, pluginIndex] = result; const { projects: projectNodes, externalNodes: pluginExternalNodes } = nodes; const sourceInfo = [file, pluginName]; for (const node in projectNodes) { // Handles `{projects: {'libs/foo': undefined}}`. if (!projectNodes[node]) { continue; } const project = { root: node, ...projectNodes[node], }; try { mergeProjectConfigurationIntoRootMap(projectRootMap, project, configurationSourceMaps, sourceInfo); } catch (error) { errors.push(new error_types_1.MergeNodesError({ file, pluginName, error, pluginIndex, })); } } Object.assign(externalNodes, pluginExternalNodes); } try { validateAndNormalizeProjectRootMap(projectRootMap, nxJsonConfiguration, configurationSourceMaps); } catch (e) { if ((0, error_types_1.isProjectsWithNoNameError)(e) || (0, error_types_1.isMultipleProjectsWithSameNameError)(e)) { errors.push(e); } else { throw e; } } const rootMap = createRootMap(projectRootMap); perf_hooks_1.performance.mark('createNodes:merge - end'); perf_hooks_1.performance.measure('createNodes:merge', 'createNodes:merge - start', 'createNodes:merge - end'); return { projectRootMap, externalNodes, rootMap, configurationSourceMaps }; } function findMatchingConfigFiles(projectFiles, pattern, include, exclude) { const matchingConfigFiles = []; for (const file of projectFiles) { if ((0, minimatch_1.minimatch)(file, pattern, { dot: true })) { if (include) { const included = include.some((includedPattern) => (0, minimatch_1.minimatch)(file, includedPattern, { dot: true })); if (!included) { continue; } } if (exclude) { const excluded = exclude.some((excludedPattern) => (0, minimatch_1.minimatch)(file, excludedPattern, { dot: true })); if (excluded) { continue; } } matchingConfigFiles.push(file); } } return matchingConfigFiles; } function readProjectConfigurationsFromRootMap(projectRootMap) { const projects = {}; // If there are projects that have the same name, that is an error. // This object tracks name -> (all roots of projects with that name) // to provide better error messaging. const conflicts = new Map(); const projectRootsWithNoName = []; for (const root in projectRootMap) { const project = projectRootMap[root]; // We're setting `// targets` as a comment `targets` is empty due to Project Crystal. // Strip it before returning configuration for usage. if (project['// targets']) delete project['// targets']; try { validateProject(project, projects); projects[project.name] = project; } catch (e) { if ((0, error_types_1.isProjectWithNoNameError)(e)) { projectRootsWithNoName.push(e.projectRoot); } else if ((0, error_types_1.isProjectWithExistingNameError)(e)) { const rootErrors = conflicts.get(e.projectName) ?? [ projects[e.projectName].root, ]; rootErrors.push(e.projectRoot); conflicts.set(e.projectName, rootErrors); } else { throw e; } } } if (conflicts.size > 0) { throw new error_types_1.MultipleProjectsWithSameNameError(conflicts, projects); } if (projectRootsWithNoName.length > 0) { throw new error_types_1.ProjectsWithNoNameError(projectRootsWithNoName, projects); } return projects; } function validateAndNormalizeProjectRootMap(projectRootMap, nxJsonConfiguration, sourceMaps = {}) { // Name -> Project, used to validate that all projects have unique names const projects = {}; // If there are projects that have the same name, that is an error. // This object tracks name -> (all roots of projects with that name) // to provide better error messaging. const conflicts = new Map(); const projectRootsWithNoName = []; for (const root in projectRootMap) { const project = projectRootMap[root]; // We're setting `// targets` as a comment `targets` is empty due to Project Crystal. // Strip it before returning configuration for usage. if (project['// targets']) delete project['// targets']; try { validateProject(project, projects); projects[project.name] = project; } catch (e) { if ((0, error_types_1.isProjectWithNoNameError)(e)) { projectRootsWithNoName.push(e.projectRoot); } else if ((0, error_types_1.isProjectWithExistingNameError)(e)) { const rootErrors = conflicts.get(e.projectName) ?? [ projects[e.projectName].root, ]; rootErrors.push(e.projectRoot); conflicts.set(e.projectName, rootErrors); } else { throw e; } } normalizeTargets(project, sourceMaps, nxJsonConfiguration); } if (conflicts.size > 0) { throw new error_types_1.MultipleProjectsWithSameNameError(conflicts, projects); } if (projectRootsWithNoName.length > 0) { throw new error_types_1.ProjectsWithNoNameError(projectRootsWithNoName, projects); } return projectRootMap; } function normalizeTargets(project, sourceMaps, nxJsonConfiguration) { for (const targetName in project.targets) { project.targets[targetName] = normalizeTarget(project.targets[targetName], project); const projectSourceMaps = sourceMaps[project.root]; const targetConfig = project.targets[targetName]; const targetDefaults = deepClone(readTargetDefaultsForTarget(targetName, nxJsonConfiguration.targetDefaults, targetConfig.executor)); // We only apply defaults if they exist if (targetDefaults && isCompatibleTarget(targetConfig, targetDefaults)) { project.targets[targetName] = mergeTargetDefaultWithTargetDefinition(targetName, project, normalizeTarget(targetDefaults, project), projectSourceMaps); } if ( // If the target has no executor or command, it doesn't do anything !project.targets[targetName].executor && !project.targets[targetName].command) { // But it may have dependencies that do something if (project.targets[targetName].dependsOn && project.targets[targetName].dependsOn.length > 0) { project.targets[targetName].executor = 'nx:noop'; } else { // If it does nothing, and has no depenencies, // we can remove it. delete project.targets[targetName]; } } } } function validateProject(project, // name -> project knownProjects) { if (!project.name) { try { const { name } = (0, fileutils_1.readJsonFile)((0, path_1.join)(project.root, 'package.json')); if (!name) { throw new Error(`Project at ${project.root} has no name provided.`); } project.name = name; } catch { throw new error_types_1.ProjectWithNoNameError(project.root); } } else if (knownProjects[project.name] && knownProjects[project.name].root !== project.root) { throw new error_types_1.ProjectWithExistingNameError(project.name, project.root); } } function targetDefaultShouldBeApplied(key, sourceMap) { const sourceInfo = sourceMap[key]; if (!sourceInfo) { return true; } // The defined value of the target is from a plugin that // isn't part of Nx's core plugins, so target defaults are // applied on top of it. const [, plugin] = sourceInfo; return !plugin?.startsWith('nx/'); } function deepClone(obj) { return JSON.parse(JSON.stringify(obj)); } function mergeTargetDefaultWithTargetDefinition(targetName, project, targetDefault, sourceMap) { const targetDefinition = project.targets[targetName] ?? {}; const result = deepClone(targetDefinition); for (const key in targetDefault) { switch (key) { case 'options': { const normalizedDefaults = resolveNxTokensInOptions(targetDefault.options, project, targetName); for (const optionKey in normalizedDefaults) { const sourceMapKey = `targets.${targetName}.options.${optionKey}`; if (targetDefinition.options[optionKey] === undefined || targetDefaultShouldBeApplied(sourceMapKey, sourceMap)) { result.options[optionKey] = targetDefault.options[optionKey]; sourceMap[sourceMapKey] = ['nx.json', 'nx/target-defaults']; } } break; } case 'configurations': { if (!result.configurations) { result.configurations = {}; sourceMap[`targets.${targetName}.configurations`] = [ 'nx.json', 'nx/target-defaults', ]; } for (const configuration in targetDefault.configurations) { if (!result.configurations[configuration]) { result.configurations[configuration] = {}; sourceMap[`targets.${targetName}.configurations.${configuration}`] = ['nx.json', 'nx/target-defaults']; } const normalizedConfigurationDefaults = resolveNxTokensInOptions(targetDefault.configurations[configuration], project, targetName); for (const configurationKey in normalizedConfigurationDefaults) { const sourceMapKey = `targets.${targetName}.configurations.${configuration}.${configurationKey}`; if (targetDefinition.configurations?.[configuration]?.[configurationKey] === undefined || targetDefaultShouldBeApplied(sourceMapKey, sourceMap)) { result.configurations[configuration][configurationKey] = targetDefault.configurations[configuration][configurationKey]; sourceMap[sourceMapKey] = ['nx.json', 'nx/target-defaults']; } } } break; } default: { const sourceMapKey = `targets.${targetName}.${key}`; if (targetDefinition[key] === undefined || targetDefaultShouldBeApplied(sourceMapKey, sourceMap)) { result[key] = targetDefault[key]; sourceMap[sourceMapKey] = ['nx.json', 'nx/target-defaults']; } break; } } } return result; } /** * Merges two targets. * * Most properties from `target` will overwrite any properties from `baseTarget`. * Options and configurations are treated differently - they are merged together if the executor definition is compatible. * * @param target The target definition with higher priority * @param baseTarget The target definition that should be overwritten. Can be undefined, in which case the target is returned as-is. * @param projectConfigSourceMap The source map to be filled with metadata about where each property came from * @param sourceInformation The metadata about where the new target was defined * @param targetIdentifier The identifier for the target to merge, used for source map * @returns A merged target configuration */ function mergeTargetConfigurations(target, baseTarget, projectConfigSourceMap, sourceInformation, targetIdentifier) { const { configurations: defaultConfigurations, options: defaultOptions, ...baseTargetProperties } = baseTarget ?? {}; // Target is "compatible", e.g. executor is defined only once or is the same // in both places. This means that it is likely safe to merge const isCompatible = isCompatibleTarget(baseTarget ?? {}, target); if (!isCompatible && projectConfigSourceMap) { // if the target is not compatible, we will simply override the options // we have to delete old entries from the source map for (const key in projectConfigSourceMap) { if (key.startsWith(`${targetIdentifier}`)) { delete projectConfigSourceMap[key]; } } } // merge top level properties if they're compatible const result = { ...(isCompatible ? baseTargetProperties : {}), ...target, }; // record top level properties in source map if (projectConfigSourceMap) { projectConfigSourceMap[targetIdentifier] = sourceInformation; // record root level target properties to source map for (const targetProperty in target) { const targetPropertyId = `${targetIdentifier}.${targetProperty}`; projectConfigSourceMap[targetPropertyId] = sourceInformation; } } // merge options if there are any // if the targets aren't compatible, we simply discard the old options during the merge if (target.options || defaultOptions) { result.options = mergeOptions(target.options, isCompatible ? defaultOptions : undefined, projectConfigSourceMap, sourceInformation, targetIdentifier); } // merge configurations if there are any // if the targets aren't compatible, we simply discard the old configurations during the merge if (target.configurations || defaultConfigurations) { result.configurations = mergeConfigurations(target.configurations, isCompatible ? defaultConfigurations : undefined, projectConfigSourceMap, sourceInformation, targetIdentifier); } if (target.metadata) { result.metadata = mergeMetadata(projectConfigSourceMap, sourceInformation, `${targetIdentifier}.metadata`, target.metadata, baseTarget?.metadata); } return result; } /** * Checks if targets options are compatible - used when merging configurations * to avoid merging options for @nx/js:tsc into something like @nx/webpack:webpack. * * If the executors are both specified and don't match, the options aren't considered * "compatible" and shouldn't be merged. */ function isCompatibleTarget(a, b) { const oneHasNoExecutor = !a.executor || !b.executor; const bothHaveSameExecutor = a.executor === b.executor; if (oneHasNoExecutor) return true; if (!bothHaveSameExecutor) return false; const isRunCommands = a.executor === 'nx:run-commands'; if (isRunCommands) { const aCommand = a.options?.command ?? a.options?.commands?.join(' && '); const bCommand = b.options?.command ?? b.options?.commands?.join(' && '); const oneHasNoCommand = !aCommand || !bCommand; const hasSameCommand = aCommand === bCommand; return oneHasNoCommand || hasSameCommand; } const isRunScript = a.executor === 'nx:run-script'; if (isRunScript) { const aScript = a.options?.script; const bScript = b.options?.script; const oneHasNoScript = !aScript || !bScript; const hasSameScript = aScript === bScript; return oneHasNoScript || hasSameScript; } return true; } function mergeConfigurations(newConfigurations, baseConfigurations, projectConfigSourceMap, sourceInformation, targetIdentifier) { const mergedConfigurations = {}; const configurations = new Set([ ...Object.keys(baseConfigurations ?? {}), ...Object.keys(newConfigurations ?? {}), ]); for (const configuration of configurations) { mergedConfigurations[configuration] = { ...(baseConfigurations?.[configuration] ?? {}), ...(newConfigurations?.[configuration] ?? {}), }; } // record new configurations & configuration properties in source map if (projectConfigSourceMap) { for (const newConfiguration in newConfigurations) { projectConfigSourceMap[`${targetIdentifier}.configurations.${newConfiguration}`] = sourceInformation; for (const configurationProperty in newConfigurations[newConfiguration]) { projectConfigSourceMap[`${targetIdentifier}.configurations.${newConfiguration}.${configurationProperty}`] = sourceInformation; } } } return mergedConfigurations; } function mergeOptions(newOptions, baseOptions, projectConfigSourceMap, sourceInformation, targetIdentifier) { const mergedOptions = { ...(baseOptions ?? {}), ...(newOptions ?? {}), }; // record new options & option properties in source map if (projectConfigSourceMap) { for (const newOption in newOptions) { projectConfigSourceMap[`${targetIdentifier}.options.${newOption}`] = sourceInformation; } } return mergedOptions; } function resolveNxTokensInOptions(object, project, key) { const result = Array.isArray(object) ? [...object] : { ...object }; for (let [opt, value] of Object.entries(object ?? {})) { if (typeof value === 'string') { const workspaceRootMatch = /^(\{workspaceRoot\}\/?)/.exec(value); if (workspaceRootMatch?.length) { value = value.replace(workspaceRootMatch[0], ''); } if (value.includes('{workspaceRoot}')) { throw new Error(`${logger_1.NX_PREFIX} The {workspaceRoot} token is only valid at the beginning of an option. (${key})`); } value = value.replace(/\{projectRoot\}/g, project.root); result[opt] = value.replace(/\{projectName\}/g, project.name); } else if (typeof value === 'object' && value) { result[opt] = resolveNxTokensInOptions(value, project, [key, opt].join('.')); } } return result; } function readTargetDefaultsForTarget(targetName, targetDefaults, executor) { if (executor && targetDefaults?.[executor]) { // If an executor is defined in project.json, defaults should be read // from the most specific key that matches that executor. // e.g. If executor === run-commands, and the target is named build: // Use, use nx:run-commands if it is present // If not, use build if it is present. return targetDefaults?.[executor]; } else if (targetDefaults?.[targetName]) { // If the executor is not defined, the only key we have is the target name. return targetDefaults?.[targetName]; } let matchingTargetDefaultKey = null; for (const key in targetDefaults ?? {}) { if ((0, globs_1.isGlobPattern)(key) && (0, minimatch_1.minimatch)(targetName, key)) { if (!matchingTargetDefaultKey || matchingTargetDefaultKey.length < key.length) { matchingTargetDefaultKey = key; } } } if (matchingTargetDefaultKey) { return targetDefaults[matchingTargetDefaultKey]; } return null; } function createRootMap(projectRootMap) { const map = {}; for (const projectRoot in projectRootMap) { const projectName = projectRootMap[projectRoot].name; map[projectRoot] = projectName; } return map; } function resolveCommandSyntacticSugar(target, key) { const { command, ...config } = target ?? {}; if (!command) { return target; } if (config.executor) { throw new Error(`${logger_1.NX_PREFIX} Project at ${key} should not have executor and command both configured.`); } else { return { ...config, executor: 'nx:run-commands', options: { ...config.options, command: command, }, }; } } /** * Expand's `command` syntactic sugar and replaces tokens in options. * @param target The target to normalize * @param project The project that the target belongs to * @returns The normalized target configuration */ function normalizeTarget(target, project) { target = { ...target, configurations: { ...target.configurations, }, }; target = resolveCommandSyntacticSugar(target, project.root); target.options = resolveNxTokensInOptions(target.options, project, `${project.root}:${target}`); for (const configuration in target.configurations) { target.configurations[configuration] = resolveNxTokensInOptions(target.configurations[configuration], project, `${project.root}:${target}:${configuration}`); } target.parallelism ??= true; return target; }