"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 node_fs_1 = require("node:fs");
const error_types_1 = require("../error-types");
const globs_1 = require("../../utils/globs");
const delayed_spinner_1 = require("../../utils/delayed-spinner");
const executor_utils_1 = require("../../command-line/run/executor-utils");
const to_project_name_1 = require("../../config/to-project-name");
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) {
    project.root = project.root === '' ? '.' : project.root;
    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);
            let matchingTargets = [];
            if ((0, globs_1.isGlobPattern)(targetName)) {
                // find all targets matching the glob pattern
                // this will map atomized targets to the glob pattern same as it does for targetDefaults
                matchingTargets = Object.keys(updatedProjectConfiguration.targets).filter((key) => (0, minimatch_1.minimatch)(key, targetName));
            }
            // If no matching targets were found, we can assume that the target name is not (meant to be) a glob pattern
            if (!matchingTargets.length) {
                matchingTargets = [targetName];
            }
            for (const matchingTargetName of matchingTargets) {
                const mergedTarget = mergeTargetConfigurations(normalizedTarget, matchingProject.targets?.[matchingTargetName], sourceMap, sourceInformation, `targets.${matchingTargetName}`);
                updatedProjectConfiguration.targets[matchingTargetName] = 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`);
        }
    }
    const createNodesPlugins = plugins.filter((plugin) => plugin.createNodes?.[0]);
    spinner = new delayed_spinner_1.DelayedSpinner(`Creating project graph nodes with ${createNodesPlugins.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 createNodesPlugins.entries()) {
        const [pattern, createNodes] = createNodesTuple;
        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, root, 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, workspaceRoot, 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(workspaceRoot, projectRootMap, nxJsonConfiguration, configurationSourceMaps);
    }
    catch (error) {
        const unknownErrors = [];
        let _errors = error instanceof AggregateError ? error.errors : [error];
        for (const e of _errors) {
            if ((0, error_types_1.isProjectsWithNoNameError)(e) ||
                (0, error_types_1.isMultipleProjectsWithSameNameError)(e) ||
                (0, error_types_1.isWorkspaceValidityError)(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 };
}
/**
 * Fast matcher for patterns without negations - uses short-circuit evaluation.
 */
function matchesSimplePatterns(file, patterns) {
    return patterns.some((pattern) => (0, minimatch_1.minimatch)(file, pattern, { dot: true }));
}
/**
 * Full matcher for patterns with negations - processes all patterns sequentially.
 * Patterns starting with '!' are negation patterns that remove files from the match set.
 * Patterns are processed in order, with later patterns overriding earlier ones.
 */
function matchesNegationPatterns(file, patterns) {
    // If first pattern is negation, start by matching everything
    let isMatch = patterns[0].startsWith('!');
    for (const pattern of patterns) {
        const isNegation = pattern.startsWith('!');
        const actualPattern = isNegation ? pattern.substring(1) : pattern;
        if ((0, minimatch_1.minimatch)(file, actualPattern, { dot: true })) {
            // Last matching pattern wins
            isMatch = !isNegation;
        }
    }
    return isMatch;
}
/**
 * Creates a matcher function for the given patterns.
 * @param patterns Array of glob patterns (can include negation patterns starting with '!')
 * @param emptyValue Value to return when patterns array is empty
 * @returns A function that checks if a file matches the patterns
 */
function createMatcher(patterns, emptyValue) {
    if (!patterns || patterns.length === 0) {
        return () => emptyValue;
    }
    const hasNegationPattern = patterns.some((p) => p.startsWith('!'));
    return hasNegationPattern
        ? (file) => matchesNegationPatterns(file, patterns)
        : (file) => matchesSimplePatterns(file, patterns);
}
function findMatchingConfigFiles(projectFiles, pattern, include, exclude) {
    const matchingConfigFiles = [];
    // Create matchers once, outside the loop
    // Empty include means include everything, empty exclude means exclude nothing
    const includes = createMatcher(include, true);
    const excludes = createMatcher(exclude, false);
    for (const file of projectFiles) {
        if ((0, minimatch_1.minimatch)(file, pattern, { dot: true })) {
            if (!includes(file)) {
                continue;
            }
            if (excludes(file)) {
                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(workspaceRoot, 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 = [];
    const validityErrors = [];
    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'];
        // We initially did this in the project.json plugin, but
        // that resulted in project.json files without names causing
        // the resulting project to change names from earlier plugins...
        if (!project.name &&
            (0, node_fs_1.existsSync)((0, path_1.join)(workspaceRoot, project.root, 'project.json'))) {
            project.name = (0, to_project_name_1.toProjectName)((0, path_1.join)(root, 'project.json'));
        }
        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;
            }
        }
    }
    for (const root in projectRootMap) {
        const project = projectRootMap[root];
        try {
            normalizeTargets(project, sourceMaps, nxJsonConfiguration, workspaceRoot, projects);
        }
        catch (e) {
            if (e instanceof error_types_1.WorkspaceValidityError) {
                validityErrors.push(e);
            }
            else {
                throw e;
            }
        }
    }
    const errors = [];
    if (conflicts.size > 0) {
        errors.push(new error_types_1.MultipleProjectsWithSameNameError(conflicts, projects));
    }
    if (projectRootsWithNoName.length > 0) {
        errors.push(new error_types_1.ProjectsWithNoNameError(projectRootsWithNoName, projects));
    }
    if (validityErrors.length > 0) {
        errors.push(...validityErrors);
    }
    if (errors.length > 0) {
        throw new AggregateError(errors);
    }
    return projectRootMap;
}
function normalizeTargets(project, sourceMaps, nxJsonConfiguration, workspaceRoot, 
/**
 * Project configurations keyed by project name
 */
projects) {
    const targetErrorMessage = [];
    for (const targetName in project.targets) {
        project.targets[targetName] = normalizeTarget(project.targets[targetName], project, workspaceRoot, projects, [project.root, targetName].join(':'));
        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, workspaceRoot, projects, ['nx.json[targetDefaults]', targetName].join(':')), projectSourceMaps);
        }
        const target = project.targets[targetName];
        if (
        // If the target has no executor or command, it doesn't do anything
        !target.executor &&
            !target.command) {
            // But it may have dependencies that do something
            if (target.dependsOn && target.dependsOn.length > 0) {
                target.executor = 'nx:noop';
            }
            else {
                // If it does nothing, and has no depenencies,
                // we can remove it.
                delete project.targets[targetName];
            }
        }
        if (target.cache && target.continuous) {
            targetErrorMessage.push(`- "${targetName}" has both "cache" and "continuous" set to true. Continuous targets cannot be cached. Please remove the "cache" property.`);
        }
    }
    if (targetErrorMessage.length > 0) {
        targetErrorMessage.unshift(`Errors detected in targets of project "${project.name}":`);
        throw new error_types_1.WorkspaceValidityError(targetErrorMessage.join('\n'));
    }
}
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 structuredClone(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, replaces tokens in options, and adds information from executor schema.
 * @param target The target to normalize
 * @param project The project that the target belongs to
 * @returns The normalized target configuration
 */
function normalizeTarget(target, project, workspaceRoot, projectsMap, errorMsgKey) {
    target = {
        ...target,
        configurations: {
            ...target.configurations,
        },
    };
    target = resolveCommandSyntacticSugar(target, project.root);
    target.options = resolveNxTokensInOptions(target.options, project, errorMsgKey);
    for (const configuration in target.configurations) {
        target.configurations[configuration] = resolveNxTokensInOptions(target.configurations[configuration], project, `${project.root}:${target}:${configuration}`);
    }
    target.parallelism ??= true;
    if (target.executor && !('continuous' in target)) {
        try {
            const [executorNodeModule, executorName] = (0, executor_utils_1.parseExecutor)(target.executor);
            const { schema } = (0, executor_utils_1.getExecutorInformation)(executorNodeModule, executorName, workspaceRoot, projectsMap);
            if (schema.continuous) {
                target.continuous ??= schema.continuous;
            }
        }
        catch (e) {
            // If the executor is not found, we assume that it is not a valid executor.
            // This means that we should not set the continuous property.
            // We could throw an error here, but it would be better to just ignore it.
        }
    }
    return target;
}
