UNPKG

@nx/js

Version:

The JS plugin for Nx contains executors and generators that provide the best experience for developing JavaScript and TypeScript projects.

1,083 lines 53.1 kB
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.createNodes = exports.createNodesV2 = exports.PLUGIN_NAME = exports.createDependencies = void 0;
const internal_1 = require("@nx/devkit/internal");
const devkit_1 = require("@nx/devkit");
const node_fs_1 = require("node:fs");
const node_path_1 = require("node:path");
const file_hasher_1 = require("nx/src/hasher/file-hasher");
const picomatch = require("picomatch");
// eslint-disable-next-line @typescript-eslint/no-restricted-imports
const lock_file_1 = require("nx/src/plugins/js/lock-file/lock-file");
const cache_directory_1 = require("nx/src/utils/cache-directory");
const installation_directory_1 = require("nx/src/utils/installation-directory");
const util_1 = require("./util");
let ts;
const resolvedTypescriptPaths = {};
function resolveTypescriptPath(projectRoot, workspaceRoot) {
    // Resolve from projectRoot first, then workspace paths, with __dirname as a
    // last resort. Required so the lookup works under layouts where @nx/js's real
    // path is outside the workspace tree (e.g. pnpm's enableGlobalVirtualStore),
    // since typescript is not a declared dep.
    resolvedTypescriptPaths[projectRoot] ??= require.resolve('typescript', {
        paths: [projectRoot, ...(0, installation_directory_1.getNxRequirePaths)(workspaceRoot), __dirname],
    });
    return resolvedTypescriptPaths[projectRoot];
}
const TSCONFIG_CACHE_VERSION = 2;
const TS_CONFIG_CACHE_PATH = (0, node_path_1.join)(cache_directory_1.workspaceDataDirectory, 'tsconfig-files.hash');
// Module-level cache store — each invocation gets a unique Symbol key
const cacheStore = new Map();
// Shared tsconfig cache — initialized once per batch, persisted to disk
let tsConfigCacheData = {};
let tsConfigCacheInitialized = false;
function readFromCache(cachePath) {
    try {
        return process.env.NX_CACHE_PROJECT_GRAPH !== 'false'
            ? (0, devkit_1.readJsonFile)(cachePath)
            : {};
    }
    catch {
        return {};
    }
}
function readTsConfigCacheData() {
    const cache = readFromCache(TS_CONFIG_CACHE_PATH);
    if (cache.version !== TSCONFIG_CACHE_VERSION) {
        return {};
    }
    return cache.data;
}
function writeToCache(cachePath, data) {
    const maxAttempts = 5;
    for (let attempt = 0; attempt < maxAttempts; attempt++) {
        const unique = (Math.random().toString(16) + '00000000').slice(2, 10);
        const tempPath = `${cachePath}.${process.pid}.${unique}.tmp`;
        try {
            (0, devkit_1.writeJsonFile)(tempPath, data, { spaces: 0 });
            (0, node_fs_1.renameSync)(tempPath, cachePath);
            return;
        }
        catch {
            try {
                (0, node_fs_1.unlinkSync)(tempPath);
            }
            catch { }
        }
    }
}
function writeTsConfigCache(data) {
    writeToCache(TS_CONFIG_CACHE_PATH, {
        version: TSCONFIG_CACHE_VERSION,
        data,
    });
}
function createConfigContext(configFilePath, workspaceRoot, projectContext) {
    const absolutePath = (0, node_path_1.join)(workspaceRoot, configFilePath);
    return {
        originalPath: configFilePath,
        absolutePath,
        relativePath: posixRelative(workspaceRoot, absolutePath),
        basename: (0, node_path_1.basename)(configFilePath),
        basenameNoExt: (0, node_path_1.basename)(configFilePath, '.json'),
        dirname: (0, node_path_1.dirname)(absolutePath),
        project: projectContext,
    };
}
function getConfigContext(configPath, workspaceRoot, cache) {
    const absolutePath = configPath.startsWith('/') || configPath.startsWith(workspaceRoot)
        ? (0, node_path_1.normalize)(configPath)
        : (0, node_path_1.join)(workspaceRoot, configPath);
    let context = cache.configContexts.get(absolutePath);
    if (context) {
        return context;
    }
    const relativePath = (0, node_path_1.relative)(workspaceRoot, absolutePath);
    const projectRoot = (0, node_path_1.dirname)(relativePath);
    if (!cache.projectContexts.has(projectRoot)) {
        cache.projectContexts.set(projectRoot, {
            root: projectRoot,
            normalized: (0, devkit_1.normalizePath)(projectRoot),
            absolute: (0, node_path_1.join)(workspaceRoot, projectRoot),
        });
    }
    const newContext = createConfigContext(relativePath, workspaceRoot, cache.projectContexts.get(projectRoot));
    cache.configContexts.set(absolutePath, newContext);
    return newContext;
}
/**
 * @deprecated The 'createDependencies' function is now a no-op. This functionality is included in 'createNodesV2'.
 */
const createDependencies = () => {
    return [];
};
exports.createDependencies = createDependencies;
exports.PLUGIN_NAME = '@nx/js/typescript';
const tsConfigGlob = '**/tsconfig*.json';
exports.createNodesV2 = [
    tsConfigGlob,
    async (configFilePaths, options, context) => {
        const optionsHash = (0, file_hasher_1.hashObject)(options);
        const targetsCachePath = (0, node_path_1.join)(cache_directory_1.workspaceDataDirectory, `tsc-${optionsHash}.hash`);
        const targetsCache = readFromCache(targetsCachePath);
        // Each invocation gets a unique Symbol key — guaranteed no collisions
        const cacheKey = Symbol('tsc-invocation');
        cacheStore.set(cacheKey, {
            fileHashes: {},
            rawFiles: {},
            picomatchMatchers: {},
            extendedFilesHashes: new Map(),
            configOwners: new Map(),
            projectContexts: new Map(),
            configContexts: new Map(),
        });
        const cache = cacheStore.get(cacheKey);
        initializeTsConfigCache(configFilePaths, context.workspaceRoot, cache);
        const normalizedOptions = normalizePluginOptions(options);
        const packageManager = (0, devkit_1.detectPackageManager)(context.workspaceRoot);
        const pmc = (0, devkit_1.getPackageManagerCommand)(packageManager);
        const lockFileName = (0, lock_file_1.getLockFileName)(packageManager);
        const { configFilePaths: validConfigFilePaths, hashes, projectRoots, } = await resolveValidConfigFilesAndHashes(configFilePaths, normalizedOptions, optionsHash, context, cache, lockFileName);
        try {
            return await (0, devkit_1.createNodesFromFiles)((configFilePath, options, context, idx) => {
                const projectRoot = projectRoots[idx];
                const hash = hashes[idx];
                const targetsCacheKey = `${hash}_${configFilePath}`;
                const absolutePath = (0, node_path_1.join)(context.workspaceRoot, configFilePath);
                const configContext = getConfigContext(absolutePath, context.workspaceRoot, cache);
                targetsCache[targetsCacheKey] ??= buildTscTargets(configContext, options, context, validConfigFilePaths, cache, pmc);
                const { targets } = targetsCache[targetsCacheKey];
                return {
                    projects: {
                        [projectRoot]: {
                            targets,
                        },
                    },
                };
            }, validConfigFilePaths, normalizedOptions, context);
        }
        finally {
            writeToCache(targetsCachePath, targetsCache);
            writeTsConfigCache(toRelativePaths(tsConfigCacheData, context.workspaceRoot));
            // Delete this invocation's cache — unique Symbol means no cross-invocation impact
            cacheStore.delete(cacheKey);
            // Reset shared tsconfig cache when all invocations are done
            if (cacheStore.size === 0) {
                tsConfigCacheData = {};
                tsConfigCacheInitialized = false;
            }
        }
    },
];
exports.createNodes = exports.createNodesV2;
async function resolveValidConfigFilesAndHashes(configFilePaths, options, optionsHash, context, cache, lockFileName) {
    const lockFileHash = (0, file_hasher_1.hashFile)((0, node_path_1.join)(context.workspaceRoot, lockFileName)) ?? '';
    const validConfigFilePaths = [];
    const hashes = [];
    const projectRoots = [];
    for await (const configFilePath of configFilePaths) {
        const projectRoot = (0, node_path_1.dirname)(configFilePath);
        const absolutePath = (0, node_path_1.join)(context.workspaceRoot, configFilePath);
        const configContext = getConfigContext(absolutePath, context.workspaceRoot, cache);
        // Skip configs that can't produce any targets based on plugin options
        const isTypecheckConfig = configContext.basename ===
            (options.typecheck ? options.typecheck.configName : 'tsconfig.json');
        const isBuildConfig = options.build && configContext.basename === options.build.configName;
        if (!isTypecheckConfig && !isBuildConfig) {
            continue;
        }
        if (!checkIfConfigFileShouldBeProject(configContext, cache)) {
            continue;
        }
        projectRoots.push(projectRoot);
        validConfigFilePaths.push(configFilePath);
        hashes.push(await getConfigFileHash(configFilePath, context.workspaceRoot, configContext.project, optionsHash, lockFileHash, cache));
    }
    return { configFilePaths: validConfigFilePaths, hashes, projectRoots };
}
/**
 * The cache key is composed by:
 * - hashes of the content of the relevant files that can affect what's inferred by the plugin:
 *   - current config file
 *   - config files extended by the current config file (recursively up to the root config file)
 *   - referenced config files that are internal to the owning Nx project of the current config file,
 *     or is a shallow external reference of the owning Nx project
 *   - lock file
 *   - project's package.json
 * - hash of the plugin options
 * - current config file path
 */
async function getConfigFileHash(configFilePath, workspaceRoot, project, optionsHash, lockFileHash, cache) {
    const fullConfigPath = (0, node_path_1.join)(workspaceRoot, configFilePath);
    const tsConfig = retrieveTsConfigFromCache(fullConfigPath, workspaceRoot, cache);
    const extendedConfigFiles = getExtendedConfigFiles(tsConfig, workspaceRoot, cache);
    const internalReferencedFiles = resolveInternalProjectReferences(tsConfig, workspaceRoot, project, cache);
    const externalProjectReferences = resolveShallowExternalProjectReferences(tsConfig, workspaceRoot, project, cache);
    let packageJson = null;
    try {
        packageJson = (0, devkit_1.readJsonFile)((0, node_path_1.join)(workspaceRoot, project.root, 'package.json'));
    }
    catch { }
    return (0, file_hasher_1.hashArray)([
        ...[
            fullConfigPath,
            ...extendedConfigFiles.files.sort(),
            ...Object.keys(internalReferencedFiles).sort(),
            ...Object.keys(externalProjectReferences).sort(),
        ].map((file) => getFileHash(file, workspaceRoot, cache)),
        ...extendedConfigFiles.packages.sort(),
        lockFileHash,
        optionsHash,
        ...(packageJson ? [(0, file_hasher_1.hashObject)(packageJson)] : []),
    ]);
}
function checkIfConfigFileShouldBeProject(config, cache) {
    // Do not create a project for the workspace root tsconfig files.
    if (config.project.root === '.') {
        return false;
    }
    // Do not create a project if package.json and project.json isn't there.
    const siblingFiles = (0, node_fs_1.readdirSync)(config.project.absolute);
    if (!siblingFiles.includes('package.json') &&
        !siblingFiles.includes('project.json')) {
        return false;
    }
    cache.configOwners.set(config.relativePath, config.project.normalized);
    // Do not create a project if it's not a tsconfig.json and there is no tsconfig.json in the same directory
    if (config.basename !== 'tsconfig.json' &&
        !siblingFiles.includes('tsconfig.json')) {
        return false;
    }
    // Do not create project for Next.js projects since they are not compatible with
    // project references and typecheck will fail.
    if (siblingFiles.includes('next.config.js') ||
        siblingFiles.includes('next.config.cjs') ||
        siblingFiles.includes('next.config.mjs') ||
        siblingFiles.includes('next.config.ts')) {
        return false;
    }
    return true;
}
function buildTscTargets(config, options, context, configFiles, cache, pmc) {
    const targets = {};
    const namedInputs = (0, internal_1.getNamedInputs)(config.project.root, context);
    const tsConfig = retrieveTsConfigFromCache(config.absolutePath, context.workspaceRoot, cache);
    let internalProjectReferences;
    if (options.typecheck &&
        config.basename === options.typecheck.configName &&
        tsConfig.raw?.['nx']?.addTypecheckTarget !== false) {
        internalProjectReferences = resolveInternalProjectReferences(tsConfig, context.workspaceRoot, config.project, cache);
        const externalProjectReferences = resolveShallowExternalProjectReferences(tsConfig, context.workspaceRoot, config.project, cache);
        const targetName = options.typecheck.targetName;
        const compiler = options.compiler;
        if (!targets[targetName]) {
            let command = `${compiler} --build ${options.typecheck.configName} --emitDeclarationOnly${options.verboseOutput ? ' --verbose' : ''}`;
            if (tsConfig.options.noEmit ||
                Object.values(internalProjectReferences).some((ref) => ref.options.noEmit) ||
                Object.values(externalProjectReferences).some((ref) => ref.options.noEmit)) {
                // `tsc --build` does not work with `noEmit: true`
                command = `echo "The 'typecheck' target is disabled because one or more project references set 'noEmit: true' in their tsconfig. Remove this property to resolve this issue."`;
            }
            const dependsOn = [`^${targetName}`];
            if (options.build && targets[options.build.targetName]) {
                // we already processed and have a build target
                dependsOn.unshift(options.build.targetName);
            }
            else if (options.build) {
                // check if the project will have a build target
                const buildConfigPath = (0, devkit_1.joinPathFragments)(config.project.root, options.build.configName);
                if (configFiles.some((f) => f === buildConfigPath) &&
                    (options.build.skipBuildCheck ||
                        (0, util_1.isValidPackageJsonBuildConfig)(retrieveTsConfigFromCache(buildConfigPath, context.workspaceRoot, cache), context.workspaceRoot, config.project.root))) {
                    dependsOn.unshift(options.build.targetName);
                }
            }
            targets[targetName] = {
                dependsOn,
                command,
                options: { cwd: config.project.normalized },
                cache: true,
                inputs: getInputs(namedInputs, config, tsConfig, internalProjectReferences, context.workspaceRoot, cache),
                outputs: getOutputs(config, tsConfig, internalProjectReferences, context.workspaceRoot, 
                /* emitDeclarationOnly */ true),
                syncGenerators: ['@nx/js:typescript-sync'],
                metadata: {
                    technologies: ['typescript'],
                    description: 'Runs type-checking for the project.',
                    help: {
                        command: `${pmc.exec} ${compiler} --build --help`,
                        example: {
                            args: ['--force'],
                        },
                    },
                },
            };
        }
    }
    // Build target
    if (options.build &&
        config.basename === options.build.configName &&
        (options.build.skipBuildCheck ||
            (0, util_1.isValidPackageJsonBuildConfig)(tsConfig, context.workspaceRoot, config.project.root))) {
        internalProjectReferences ??= resolveInternalProjectReferences(tsConfig, context.workspaceRoot, config.project, cache);
        const targetName = options.build.targetName;
        const compiler = options.compiler;
        targets[targetName] = {
            dependsOn: [`^${targetName}`],
            command: `${compiler} --build ${options.build.configName}${options.verboseOutput ? ' --verbose' : ''}`,
            options: { cwd: config.project.normalized },
            cache: true,
            inputs: getInputs(namedInputs, config, tsConfig, internalProjectReferences, context.workspaceRoot, cache),
            outputs: getOutputs(config, tsConfig, internalProjectReferences, context.workspaceRoot, 
            // should be false for build target, but providing it just in case is set to true
            tsConfig.options.emitDeclarationOnly),
            syncGenerators: ['@nx/js:typescript-sync'],
            metadata: {
                technologies: ['typescript'],
                description: 'Builds the project with `tsc`.',
                help: {
                    command: `${pmc.exec} ${compiler} --build --help`,
                    example: {
                        args: ['--force'],
                    },
                },
            },
        };
        (0, util_1.addBuildAndWatchDepsTargets)(context.workspaceRoot, config.project.root, targets, {
            buildDepsTargetName: options.build.buildDepsName,
            watchDepsTargetName: options.build.watchDepsName,
        }, pmc);
    }
    return { targets };
}
function getInputs(namedInputs, config, tsConfig, internalProjectReferences, workspaceRoot, cache) {
    const configFiles = new Set();
    // TODO(leo): temporary disable external dependencies until we support hashing
    // glob patterns from external dependencies
    // const externalDependencies = ['typescript'];
    const extendedConfigFiles = getExtendedConfigFiles(tsConfig, workspaceRoot, cache);
    extendedConfigFiles.files.forEach((configPath) => {
        configFiles.add(configPath);
    });
    // externalDependencies.push(...extendedConfigFiles.packages);
    const includePaths = new Set();
    const excludePaths = new Set();
    const projectTsConfigFiles = [
        [config.originalPath, tsConfig],
        ...Object.entries(internalProjectReferences),
    ];
    const absoluteProjectRoot = config.project.absolute;
    if (!ts) {
        ts = require(resolveTypescriptPath(absoluteProjectRoot, workspaceRoot));
    }
    // https://github.com/microsoft/TypeScript/blob/19b777260b26aac5707b1efd34202054164d4a9d/src/compiler/utilities.ts#L9869
    const supportedTSExtensions = [
        ts.Extension.Ts,
        ts.Extension.Tsx,
        ts.Extension.Dts,
        ts.Extension.Cts,
        ts.Extension.Dcts,
        ts.Extension.Mts,
        ts.Extension.Dmts,
    ];
    // https://github.com/microsoft/TypeScript/blob/19b777260b26aac5707b1efd34202054164d4a9d/src/compiler/utilities.ts#L9878
    const allSupportedExtensions = [
        ts.Extension.Ts,
        ts.Extension.Tsx,
        ts.Extension.Dts,
        ts.Extension.Js,
        ts.Extension.Jsx,
        ts.Extension.Cts,
        ts.Extension.Dcts,
        ts.Extension.Cjs,
        ts.Extension.Mts,
        ts.Extension.Dmts,
        ts.Extension.Mjs,
    ];
    // A spec is an implicit glob over a directory if its last component has no
    // extension and no glob characters. TypeScript normalizes the path before
    // checking its last component, so specs like "." and ".." resolve to a
    // directory name first.
    // https://github.com/microsoft/TypeScript/blob/19b777260b26aac5707b1efd34202054164d4a9d/src/compiler/utilities.ts#L9577-L9585
    const isImplicitGlobSpec = (spec) => !/[.*?]/.test((0, node_path_1.basename)((0, node_path_1.resolve)(absoluteProjectRoot, spec)));
    const normalizeInput = (input, config) => {
        const extensions = config.options.allowJs
            ? [...allSupportedExtensions]
            : [...supportedTSExtensions];
        if (config.options.resolveJsonModule) {
            extensions.push(ts.Extension.Json);
        }
        // An "includes" path "foo" is implicitly a glob "foo/**/*"
        if (isImplicitGlobSpec(input)) {
            return extensions.map((ext) => `${input}/**/*${ext}`);
        }
        return [input];
    };
    const configDirTemplate = '${configDir}';
    const substituteConfigDir = (p) => p.startsWith(configDirTemplate) ? p.replace(configDirTemplate, './') : p;
    // Helper function to get or create cached picomatch matchers
    const getOrCreateMatcher = (pattern) => {
        if (!cache.picomatchMatchers[pattern]) {
            cache.picomatchMatchers[pattern] = picomatch(pattern);
        }
        return cache.picomatchMatchers[pattern];
    };
    projectTsConfigFiles.forEach(([configPath, tsconfig]) => {
        configFiles.add(configPath);
        const offset = (0, node_path_1.relative)(absoluteProjectRoot, (0, node_path_1.dirname)(configPath));
        (tsconfig.raw?.include ?? []).forEach((p) => {
            const normalized = normalizeInput((0, node_path_1.join)(offset, substituteConfigDir(p)), tsconfig);
            normalized.forEach((input) => includePaths.add(input));
        });
        if (tsconfig.raw?.exclude) {
            /**
             * We need to filter out the exclude paths that are already included in
             * other tsconfig files. If they are not included in other tsconfig files,
             * they still correctly apply to the current file and we should keep them.
             */
            const otherFilesInclude = [];
            projectTsConfigFiles.forEach(([path, c]) => {
                if (path !== configPath) {
                    otherFilesInclude.push(...(c.raw?.include ?? []).map(substituteConfigDir));
                }
            });
            const normalize = (p) => (p.startsWith('./') ? p.slice(2) : p);
            // Static (non-glob) prefix of a spec, used for subtree coverage checks.
            const staticPrefix = (p) => {
                const segments = normalize(p).split('/');
                const firstGlobSegment = segments.findIndex((s) => /[*?]/.test(s));
                return firstGlobSegment === -1
                    ? segments.join('/')
                    : segments.slice(0, firstGlobSegment).join('/');
            };
            // A non-glob include spec (an implicit directory glob like "." or "src",
            // or a literal file path) covers an exclude spec if the exclude's static
            // prefix falls within the include's subtree.
            const includeCoversExclude = (includePath, excludePath) => {
                if (/[*?]/.test(includePath)) {
                    return false;
                }
                const rel = (0, node_path_1.relative)((0, node_path_1.resolve)(absoluteProjectRoot, normalize(includePath)), (0, node_path_1.resolve)(absoluteProjectRoot, staticPrefix(excludePath)));
                return !rel.startsWith('..') && !(0, node_path_1.isAbsolute)(rel);
            };
            tsconfig.raw.exclude.forEach((e) => {
                const excludePath = substituteConfigDir(e);
                const normalizedExclude = normalize(excludePath);
                const excludeMatcher = getOrCreateMatcher(normalizedExclude);
                if (!otherFilesInclude.some((includePath) => {
                    const normalizedInclude = normalize(includePath);
                    const includeMatcher = getOrCreateMatcher(normalizedInclude);
                    return (excludeMatcher(normalizedInclude) ||
                        includeMatcher(normalizedExclude) ||
                        includeCoversExclude(includePath, excludePath));
                })) {
                    // TS treats an implicit-glob exclude like "dist" as excluding the
                    // whole subtree, so emit it as "dist/**/*".
                    excludePaths.add(isImplicitGlobSpec(excludePath)
                        ? `${excludePath}/**/*`
                        : excludePath);
                }
            });
        }
    });
    const inputs = [];
    if (includePaths.size) {
        if ((0, node_fs_1.existsSync)((0, node_path_1.join)(workspaceRoot, config.project.root, 'package.json'))) {
            inputs.push('{projectRoot}/package.json');
        }
        inputs.push(...Array.from(configFiles).map((p) => pathToInputOrOutput(p, workspaceRoot, config.project)), ...Array.from(includePaths).map((p) => pathToInputOrOutput((0, devkit_1.joinPathFragments)(config.project.root, p), workspaceRoot, config.project)));
    }
    else {
        // If we couldn't identify any include paths, we default to the default
        // named inputs.
        inputs.push('production' in namedInputs ? 'production' : 'default');
    }
    if (excludePaths.size) {
        inputs.push(...Array.from(excludePaths).map((p) => `!${pathToInputOrOutput((0, devkit_1.joinPathFragments)(config.project.root, p), workspaceRoot, config.project)}`));
    }
    // tsc --build reads .d.ts/.d.cts/.d.mts and .tsbuildinfo files from dependent
    // tasks, not the source files of dependencies. This correctly tracks build
    // outputs from both external project references and same-project task
    // dependencies (e.g. build-native producing .d.ts files that may be excluded
    // from file watching via .nxignore). Declaration files are also read from
    // the deps projects sources.
    inputs.push({
        dependentTasksOutputFiles: '**/*.{d.ts,d.cts,d.mts,tsbuildinfo}',
        transitive: true,
    });
    inputs.push({
        fileset: '{projectRoot}/**/*.{d.ts,d.cts,d.mts}',
        dependencies: true,
    });
    const externalRefPatterns = getExternalProjectReferenceTsconfigPatterns(tsConfig, internalProjectReferences, workspaceRoot, config.project, cache);
    inputs.push(...externalRefPatterns);
    // inputs.push({ externalDependencies });
    return inputs;
}
function getOutputs(config, rootTsConfig, internalProjectReferences, workspaceRoot, emitDeclarationOnly) {
    const outputs = new Set();
    // We could have more surgical outputs based on the tsconfig options, but the
    // user could override them through the command line and that wouldn't be
    // reflected in the outputs. So, we just include everything that could be
    // produced by the tsc command.
    [
        { configPath: config.absolutePath, tsConfig: rootTsConfig },
        ...Object.entries(internalProjectReferences).map(([internalConfigPath, internalConfig]) => ({
            configPath: internalConfigPath,
            tsConfig: internalConfig,
        })),
    ].forEach(({ configPath, tsConfig }) => {
        if (tsConfig.options.outFile) {
            const outFileName = (0, node_path_1.basename)(tsConfig.options.outFile, '.js');
            const outFileDir = (0, node_path_1.dirname)(tsConfig.options.outFile);
            outputs.add(pathToInputOrOutput(tsConfig.options.outFile, workspaceRoot, config.project));
            // outFile is not be used with .cjs, .mjs, .jsx, so the list is simpler
            const outDir = (0, node_path_1.relative)(workspaceRoot, outFileDir);
            outputs.add(pathToInputOrOutput((0, devkit_1.joinPathFragments)(outDir, `${outFileName}.js.map`), workspaceRoot, config.project));
            outputs.add(pathToInputOrOutput((0, devkit_1.joinPathFragments)(outDir, `${outFileName}.d.ts`), workspaceRoot, config.project));
            outputs.add(pathToInputOrOutput((0, devkit_1.joinPathFragments)(outDir, `${outFileName}.d.ts.map`), workspaceRoot, config.project));
        }
        else if (tsConfig.options.outDir) {
            if (emitDeclarationOnly) {
                outputs.add(pathToInputOrOutput((0, devkit_1.joinPathFragments)(tsConfig.options.outDir, '**/*.{d.ts,d.cts,d.mts}'), workspaceRoot, config.project));
                if (tsConfig.options.declarationMap) {
                    outputs.add(pathToInputOrOutput((0, devkit_1.joinPathFragments)(tsConfig.options.outDir, '**/*.{d.ts,d.cts,d.mts}.map'), workspaceRoot, config.project));
                }
            }
            else {
                // List specific tsc output extensions instead of claiming the
                // entire outDir, so other tasks that write into the same
                // directory (e.g. copy-assets for .node/.wasm files) don't
                // have their outputs captured by the tsc build cache.
                const jsonExt = tsConfig.options.resolveJsonModule ? ',json' : '';
                outputs.add(pathToInputOrOutput((0, devkit_1.joinPathFragments)(tsConfig.options.outDir, `**/*.{js,cjs,mjs,jsx${jsonExt},d.ts,d.cts,d.mts}{,.map}`), workspaceRoot, config.project));
            }
        }
        else if (tsConfig.raw?.include?.length ||
            tsConfig.raw?.files?.length ||
            (!tsConfig.raw?.include && !tsConfig.raw?.files)) {
            // tsc produce files in place when no outDir or outFile is set
            outputs.add((0, devkit_1.joinPathFragments)('{projectRoot}', '**/*.js'));
            outputs.add((0, devkit_1.joinPathFragments)('{projectRoot}', '**/*.cjs'));
            outputs.add((0, devkit_1.joinPathFragments)('{projectRoot}', '**/*.mjs'));
            outputs.add((0, devkit_1.joinPathFragments)('{projectRoot}', '**/*.jsx'));
            outputs.add((0, devkit_1.joinPathFragments)('{projectRoot}', '**/*.js.map')); // should also include .cjs and .mjs data
            outputs.add((0, devkit_1.joinPathFragments)('{projectRoot}', '**/*.jsx.map'));
            outputs.add((0, devkit_1.joinPathFragments)('{projectRoot}', '**/*.d.ts'));
            outputs.add((0, devkit_1.joinPathFragments)('{projectRoot}', '**/*.d.cts'));
            outputs.add((0, devkit_1.joinPathFragments)('{projectRoot}', '**/*.d.mts'));
            outputs.add((0, devkit_1.joinPathFragments)('{projectRoot}', '**/*.d.ts.map'));
            outputs.add((0, devkit_1.joinPathFragments)('{projectRoot}', '**/*.d.cts.map'));
            outputs.add((0, devkit_1.joinPathFragments)('{projectRoot}', '**/*.d.mts.map'));
        }
        // tsc --build always produces a tsbuildinfo file.
        outputs.add(getTsBuildInfoOutputPath(tsConfig, configPath, workspaceRoot, config.project));
    });
    return Array.from(outputs);
}
/**
 * Returns the path to the tsbuildinfo file that tsc --build will produce.
 * tsc always emits this file in build mode (incremental is implicit).
 */
function getTsBuildInfoOutputPath(tsConfig, configPath, workspaceRoot, project) {
    if (tsConfig.options.tsBuildInfoFile) {
        return pathToInputOrOutput(tsConfig.options.tsBuildInfoFile, workspaceRoot, project);
    }
    if (tsConfig.options.outFile) {
        const outFileName = (0, node_path_1.basename)(tsConfig.options.outFile, '.js');
        const outDir = (0, node_path_1.relative)(workspaceRoot, (0, node_path_1.dirname)(tsConfig.options.outFile));
        return pathToInputOrOutput((0, devkit_1.joinPathFragments)(outDir, `${outFileName}.tsbuildinfo`), workspaceRoot, project);
    }
    const configPathNoExt = (0, node_path_1.join)((0, node_path_1.dirname)(configPath), (0, node_path_1.basename)(configPath, '.json'));
    if (tsConfig.options.outDir) {
        // When rootDir is set, tsc resolves the config path (sans extension)
        // relative to rootDir against outDir, which can place the file outside
        // outDir (e.g. rootDir 'src' with the config one level up emits to the
        // parent of outDir). Without rootDir it just drops the file in outDir.
        // Mirror tsc's getTsBuildInfoEmitOutputFilePath so the declared output
        // matches what's emitted.
        const buildInfoPath = tsConfig.options.rootDir
            ? `${(0, node_path_1.resolve)(tsConfig.options.outDir, (0, node_path_1.relative)(tsConfig.options.rootDir, configPathNoExt))}.tsbuildinfo`
            : (0, devkit_1.joinPathFragments)(tsConfig.options.outDir, `${(0, node_path_1.basename)(configPathNoExt)}.tsbuildinfo`);
        return pathToInputOrOutput(buildInfoPath, workspaceRoot, project);
    }
    return (0, devkit_1.joinPathFragments)('{projectRoot}', `${(0, node_path_1.basename)(configPathNoExt)}.tsbuildinfo`);
}
function pathToInputOrOutput(path, workspaceRoot, project) {
    const fullProjectRoot = project.absolute;
    const fullPath = (0, node_path_1.resolve)(workspaceRoot, path);
    const pathRelativeToProjectRoot = (0, devkit_1.normalizePath)((0, node_path_1.relative)(fullProjectRoot, fullPath));
    if (pathRelativeToProjectRoot.startsWith('..')) {
        return (0, devkit_1.joinPathFragments)('{workspaceRoot}', (0, node_path_1.relative)(workspaceRoot, fullPath));
    }
    return (0, devkit_1.joinPathFragments)('{projectRoot}', pathRelativeToProjectRoot);
}
function getExtendedConfigFiles(tsConfig, workspaceRoot, cache, extendedConfigFiles = new Set(), extendedExternalPackages = new Set()) {
    for (const extendedConfigFile of tsConfig.extendedConfigFiles) {
        if (extendedConfigFile.externalPackage) {
            extendedExternalPackages.add(extendedConfigFile.externalPackage);
        }
        else if (extendedConfigFile.filePath) {
            extendedConfigFiles.add(extendedConfigFile.filePath);
            getExtendedConfigFiles(retrieveTsConfigFromCache(extendedConfigFile.filePath, workspaceRoot, cache), workspaceRoot, cache, extendedConfigFiles, extendedExternalPackages);
        }
    }
    return {
        files: Array.from(extendedConfigFiles),
        packages: Array.from(extendedExternalPackages),
    };
}
function resolveInternalProjectReferences(tsConfig, workspaceRoot, project, cache, projectReferences = {}) {
    if (!tsConfig.projectReferences?.length) {
        return {};
    }
    for (const ref of tsConfig.projectReferences) {
        let refConfigPath = ref.path;
        if (projectReferences[refConfigPath]) {
            continue;
        }
        if (!(0, node_fs_1.existsSync)(refConfigPath)) {
            continue;
        }
        if (!refConfigPath.endsWith('.json')) {
            refConfigPath = (0, node_path_1.join)(refConfigPath, 'tsconfig.json');
        }
        const refContext = getConfigContext(refConfigPath, workspaceRoot, cache);
        if (isExternalProjectReference(refContext, project, workspaceRoot, cache)) {
            continue;
        }
        projectReferences[refConfigPath] = retrieveTsConfigFromCache(refConfigPath, workspaceRoot, cache);
        resolveInternalProjectReferences(projectReferences[refConfigPath], workspaceRoot, project, cache, projectReferences);
    }
    return projectReferences;
}
function resolveShallowExternalProjectReferences(tsConfig, workspaceRoot, project, cache, projectReferences = {}) {
    if (!tsConfig.projectReferences?.length) {
        return projectReferences;
    }
    for (const ref of tsConfig.projectReferences) {
        let refConfigPath = ref.path;
        if (projectReferences[refConfigPath]) {
            continue;
        }
        if (!(0, node_fs_1.existsSync)(refConfigPath)) {
            continue;
        }
        if (!refConfigPath.endsWith('.json')) {
            refConfigPath = (0, node_path_1.join)(refConfigPath, 'tsconfig.json');
        }
        const refContext = getConfigContext(refConfigPath, workspaceRoot, cache);
        if (isExternalProjectReference(refContext, project, workspaceRoot, cache)) {
            projectReferences[refConfigPath] = retrieveTsConfigFromCache(refConfigPath, workspaceRoot, cache);
        }
    }
    return projectReferences;
}
/**
 * Collects unique tsconfig paths (relative to their project root) from the
 * project reference chain — including any `extends` chains within those refs —
 * and returns them as `^{projectRoot}/...` input patterns. We only need to
 * discover the full set of distinct relative paths.
 */
function getExternalProjectReferenceTsconfigPatterns(tsConfig, internalProjectReferences, workspaceRoot, project, cache) {
    const externalRefs = {};
    // Collect direct external references from the root tsconfig
    resolveShallowExternalProjectReferences(tsConfig, workspaceRoot, project, cache, externalRefs);
    // Collect external references from internal project references
    for (const refTsConfig of Object.values(internalProjectReferences)) {
        resolveShallowExternalProjectReferences(refTsConfig, workspaceRoot, project, cache, externalRefs);
    }
    // Collect unique tsconfig paths (relative to project root) from the
    // reference chain, seeded with the external refs.
    const uniqueRelPaths = new Set();
    const visited = new Set();
    const worklist = [];
    for (const refConfigPath of Object.keys(externalRefs)) {
        const refContext = getConfigContext(refConfigPath, workspaceRoot, cache);
        uniqueRelPaths.add(posixRelative(refContext.project.absolute, refConfigPath));
        worklist.push({
            configPath: refConfigPath,
            ownerProject: refContext.project,
        });
    }
    for (let i = 0; i < worklist.length; i++) {
        const { configPath, ownerProject } = worklist[i];
        if (visited.has(configPath)) {
            continue;
        }
        visited.add(configPath);
        const wsRelPath = posixRelative(workspaceRoot, configPath);
        const tsConfigData = tsConfigCacheData[wsRelPath]?.data;
        if (!tsConfigData) {
            continue;
        }
        // Walk `extends` chains for the visited tsconfig. tsc reads extended
        // configs while resolving project references, so the rel paths must be
        // emitted as inputs. Workspace-root files (no owning project) are skipped
        // — those are covered by the local project's own extends walk.
        if (tsConfigData.extendedConfigFiles?.length) {
            for (const extended of tsConfigData.extendedConfigFiles) {
                if (!extended.filePath || visited.has(extended.filePath)) {
                    continue;
                }
                const extendedWsRelPath = posixRelative(workspaceRoot, extended.filePath);
                if (!tsConfigCacheData[extendedWsRelPath]) {
                    continue;
                }
                const extendedContext = getConfigContext(extended.filePath, workspaceRoot, cache);
                if (extendedContext.project.root &&
                    extendedContext.project.root !== '.') {
                    uniqueRelPaths.add(posixRelative(extendedContext.project.absolute, extended.filePath));
                    worklist.push({
                        configPath: extended.filePath,
                        ownerProject: extendedContext.project,
                    });
                }
            }
        }
        if (!tsConfigData.projectReferences?.length) {
            continue;
        }
        for (const ref of tsConfigData.projectReferences) {
            let refPath = ref.path;
            if (!refPath.endsWith('.json')) {
                refPath = (0, node_path_1.join)(refPath, 'tsconfig.json');
            }
            // Skip references not found in the tsconfig cache
            const refWsRelPath = posixRelative(workspaceRoot, refPath);
            if (!tsConfigCacheData[refWsRelPath]) {
                continue;
            }
            const refContext = getConfigContext(refPath, workspaceRoot, cache);
            // Collect paths for references within the same project
            if (!isExternalProjectReference(refContext, ownerProject, workspaceRoot, cache)) {
                uniqueRelPaths.add(posixRelative(ownerProject.absolute, refPath));
            }
            if (!visited.has(refPath)) {
                worklist.push({
                    configPath: refPath,
                    ownerProject: refContext.project,
                });
            }
        }
    }
    return Array.from(uniqueRelPaths).map((relPath) => `^{projectRoot}/${relPath}`);
}
function isExternalProjectReference(refConfig, project, workspaceRoot, cache) {
    const owner = cache.configOwners.get(refConfig.relativePath);
    if (owner !== undefined) {
        return owner !== project.normalized;
    }
    let currentPath = refConfig.dirname;
    if ((0, node_path_1.relative)(project.absolute, currentPath).startsWith('..')) {
        return true;
    }
    while (currentPath !== project.absolute) {
        if ((0, node_fs_1.existsSync)((0, node_path_1.join)(currentPath, 'package.json')) ||
            (0, node_fs_1.existsSync)((0, node_path_1.join)(currentPath, 'project.json'))) {
            const owner = posixRelative(workspaceRoot, currentPath);
            cache.configOwners.set(refConfig.relativePath, owner);
            return owner !== project.normalized;
        }
        currentPath = (0, node_path_1.dirname)(currentPath);
    }
    return false;
}
function retrieveTsConfigFromCache(tsConfigPath, workspaceRoot, cache) {
    const relativePath = posixRelative(workspaceRoot, tsConfigPath);
    // we don't need to check the hash if it's in the cache, because we've already
    // checked it when we initially populated the cache
    return tsConfigCacheData[relativePath]
        ? tsConfigCacheData[relativePath].data
        : readTsConfigAndCache(tsConfigPath, workspaceRoot, cache);
}
function initializeTsConfigCache(configFilePaths, workspaceRoot, cache) {
    if (!tsConfigCacheInitialized) {
        tsConfigCacheData = toAbsolutePaths(readTsConfigCacheData(), workspaceRoot);
        tsConfigCacheInitialized = true;
    }
    // ensure hashes are checked and the cache is invalidated and populated as needed
    for (const configFilePath of configFilePaths) {
        const fullConfigPath = (0, node_path_1.join)(workspaceRoot, configFilePath);
        readTsConfigAndCache(fullConfigPath, workspaceRoot, cache);
    }
}
function readTsConfigAndCache(tsConfigPath, workspaceRoot, cache) {
    const relativePath = posixRelative(workspaceRoot, tsConfigPath);
    const hash = getFileHash(tsConfigPath, workspaceRoot, cache);
    let extendedFilesHash;
    if (tsConfigCacheData[relativePath] &&
        tsConfigCacheData[relativePath].hash === hash) {
        extendedFilesHash = getExtendedFilesHash(tsConfigCacheData[relativePath].data.extendedConfigFiles, workspaceRoot, cache);
        if (tsConfigCacheData[relativePath].extendedFilesHash === extendedFilesHash) {
            return tsConfigCacheData[relativePath].data;
        }
    }
    const tsConfig = readTsConfig(tsConfigPath, workspaceRoot, cache);
    const extendedConfigFiles = [];
    if (tsConfig.raw?.extends) {
        const extendsArray = typeof tsConfig.raw.extends === 'string'
            ? [tsConfig.raw.extends]
            : tsConfig.raw.extends;
        for (const extendsPath of extendsArray) {
            const extendedConfigFile = resolveExtendedTsConfigPath(extendsPath, (0, node_path_1.dirname)(tsConfigPath));
            if (extendedConfigFile) {
                extendedConfigFiles.push(extendedConfigFile);
            }
        }
    }
    extendedFilesHash ??= getExtendedFilesHash(extendedConfigFiles, workspaceRoot, cache);
    tsConfigCacheData[relativePath] = {
        data: {
            options: tsConfig.options,
            projectReferences: tsConfig.projectReferences,
            raw: tsConfig.raw,
            extendedConfigFiles,
        },
        hash,
        extendedFilesHash,
    };
    return tsConfigCacheData[relativePath].data;
}
function getExtendedFilesHash(extendedConfigFiles, workspaceRoot, cache) {
    if (!extendedConfigFiles.length) {
        return '';
    }
    // Create a stable cache key from sorted file paths
    const cacheKey = extendedConfigFiles
        .map((f) => f.filePath || f.externalPackage)
        .filter(Boolean)
        .sort()
        .join('|');
    if (cache.extendedFilesHashes.has(cacheKey)) {
        return cache.extendedFilesHashes.get(cacheKey);
    }
    const hashes = [];
    for (const extendedConfigFile of extendedConfigFiles) {
        if (extendedConfigFile.externalPackage) {
            hashes.push(extendedConfigFile.externalPackage);
        }
        else if (extendedConfigFile.filePath) {
            hashes.push(getFileHash(extendedConfigFile.filePath, workspaceRoot, cache));
            hashes.push(getExtendedFilesHash(readTsConfigAndCache(extendedConfigFile.filePath, workspaceRoot, cache).extendedConfigFiles, workspaceRoot, cache));
        }
    }
    const hash = hashes.join('|');
    cache.extendedFilesHashes.set(cacheKey, hash);
    return hash;
}
function readTsConfig(tsConfigPath, workspaceRoot, cache) {
    if (!ts) {
        ts = require(resolveTypescriptPath(workspaceRoot, workspaceRoot));
    }
    // Normalize to forward slashes for TypeScript compatibility on Windows.
    // TypeScript's parser normalizes paths inconsistently — diagnostics use
    // normalized paths but the source file retains the original, causing
    // assertion failures when backslashes are present.
    tsConfigPath = tsConfigPath.replaceAll('\\', '/');
    const tsSys = {
        ...ts.sys,
        readFile: (path) => readFile(path, workspaceRoot, cache),
        readDirectory: () => [],
    };
    const readResult = ts.readConfigFile(tsConfigPath, tsSys.readFile);
    // read with a custom host that won't read directories which is only used
    // to identify the filenames included in the program, which we won't use
    return ts.parseJsonConfigFileContent(readResult.config, tsSys, (0, node_path_1.dirname)(tsConfigPath));
}
function normalizePluginOptions(pluginOptions = {}) {
    const defaultCompiler = 'tsc';
    const compiler = pluginOptions.compiler ?? defaultCompiler;
    const defaultTypecheckTargetName = 'typecheck';
    const defaultTypecheckConfigName = 'tsconfig.json';
    let typecheck = {
        configName: defaultTypecheckConfigName,
        targetName: defaultTypecheckTargetName,
    };
    if (pluginOptions.typecheck === false) {
        typecheck = false;
    }
    else if (pluginOptions.typecheck &&
        typeof pluginOptions.typecheck !== 'boolean') {
        typecheck = {
            configName: pluginOptions.typecheck.configName ?? defaultTypecheckConfigName,
            targetName: pluginOptions.typecheck.targetName ?? defaultTypecheckTargetName,
        };
    }
    const defaultBuildTargetName = 'build';
    const defaultBuildConfigName = 'tsconfig.lib.json';
    let build = {
        targetName: defaultBuildTargetName,
        configName: defaultBuildConfigName,
        buildDepsName: 'build-deps',
        watchDepsName: 'watch-deps',
        skipBuildCheck: false,
    };
    // Build target is not enabled by default
    if (!pluginOptions.build) {
        build = false;
    }
    else if (pluginOptions.build && typeof pluginOptions.build !== 'boolean') {
        build = {
            targetName: pluginOptions.build.targetName ?? defaultBuildTargetName,
            configName: pluginOptions.build.configName ?? defaultBuildConfigName,
            buildDepsName: pluginOptions.build.buildDepsName ?? 'build-deps',
            watchDepsName: pluginOptions.build.watchDepsName ?? 'watch-deps',
            skipBuildCheck: pluginOptions.build.skipBuildCheck ?? false,
        };
    }
    return {
        compiler,
        typecheck,
        build,
        verboseOutput: pluginOptions.verboseOutput ?? false,
    };
}
function resolveExtendedTsConfigPath(tsConfigPath, directory) {
    try {
        const resolvedPath = require.resolve(tsConfigPath, {
            paths: directory ? [directory] : undefined,
        });
        if (tsConfigPath.startsWith('.') ||
            !resolvedPath.includes('/node_modules/')) {
            return { filePath: resolvedPath };
        }
        // parse the package from the tsconfig path
        const packageName = tsConfigPath.startsWith('@')
            ? tsConfigPath.split('/').slice(0, 2).join('/')
            : tsConfigPath.split('/')[0];
        return { filePath: resolvedPath, externalPackage: packageName };
    }
    catch {
        return null;
    }
}
function getFileHash(filePath, workspaceRoot, cache) {
    const relativePath = posixRelative(workspaceRoot, filePath);
    if (!cache.fileHashes[relativePath]) {
        const content = readFile(filePath, workspaceRoot, cache);
        cache.fileHashes[relativePath] = (0, file_hasher_1.hashArray)([content]);
    }
    return cache.fileHashes[relativePath];
}
function readFile(filePath, workspaceRoot, cache) {
    const relativePath = posixRelative(workspaceRoot, filePath);
    if (!cache.rawFiles[relativePath]) {
        const content = (0, node_fs_1.readFileSync)(filePath, 'utf8');
        cache.rawFiles[relativePath] = content;
    }
    return cache.rawFiles[relativePath];
}
function toAbsolutePaths(cache, workspaceRoot) {
    const updatedCache = {};
    for (const [key, { data, extendedFilesHash, hash }] of Object.entries(cache)) {
        const raw = {
            nx: { addTypecheckTarget: data.raw?.['nx']?.addTypecheckTarget },
        };
        if (data.raw?.include) {
            raw.include = data.raw.include;
        }
        if (data.raw?.exclude) {
            raw.exclude = data.raw.exclude;
        }
        if (data.raw?.files) {
            raw.files = data.raw.files;
        }
        updatedCache[key] = {
            data: {
                options: {
                    noEmit: data.options.noEmit,
                    allowJs: data.options.allowJs,
                    resolveJsonModule: data.options.resolveJsonModule,
                    emitDeclarationOnly: data.options.emitDeclarationOnly,
                    declarationMap: data.options.declarationMap,
                },
                raw,
                extendedConfigFiles: data.extendedConfigFiles,
            },
            extendedFilesHash,
            hash,
        };
        if (data.options.rootDir) {
            updatedCache[key].data.options.rootDir = (0, node_path_1.join)(workspaceRoot, data.options.rootDir);
        }
        if (data.options.outDir) {
            updatedCache[key].data.options.outDir = (0, node_path_1.join)(workspaceRoot, data.options.outDir);
        }
        if (data.options.outFile) {
            updatedCache[key].data.options.outFile = (0, node_path_1.join)(workspaceRoot, data.options.outFile);
        }
        if (data.options.tsBuildInfoFile) {
            updatedCache[key].data.options.tsBuildInfoFile = (0, node_path_1.join)(workspaceRoot, data.options.tsBuildInfoFile);
        }
        if (data.extendedConfigFiles.length) {
            updatedCache[key].data.extendedConfigFiles.forEach((file) => {
                file.filePath = (0, node_path_1.join)(workspaceRoot, file.filePath);
            });
        }
        if (data.projectReferences) {
            updatedCache[key].data.projectReferences = data.projectReferences.map((ref) => ({ ...ref, path: (0, node_path_1.join)(workspaceRoot, ref.path) }));
        }
    }
    return updatedCache;
}
function toRelativePaths(cache, workspaceRoot) {
    const updatedCache = {};
    for (const [key, { data, extendedFilesHash, hash }] of Object.entries(cache)) {
        const raw = {
            nx: { addTypecheckTarget: data.raw?.['nx']?.addTypecheckTarget },
        };
        if (data.raw?.include) {
            raw.include = data.raw.include;
        }
        if (data.raw?.exclude) {
            raw.exclude = data.raw.exclude;
        }
        if (data.raw?.files) {
            raw.files = data.raw.files;
        }
        updatedCache[key] = {
            data: {
                options: {
                    noEmit: data.options.noEmit,
                    allowJs: data.options.allowJs,
                    resolveJsonModule: data.options.resolveJsonModule,
                    emitDeclarationOnly: data.options.emitDeclarationOnly,
                    declarationMap: data.options.declarationMap,
                },
                raw,
                extendedConfigFiles: data.extendedConfigFiles,
            },
            extendedFilesHash,
            hash,
        };
        if (data.options.rootDir) {
            updatedCache[key].data.options.rootDir = posixRelative(workspaceRoot, data.options.rootDir);
        }
        if (data.options.outDir) {
            updatedCache[key].data.options.outDir = posixRelative(workspaceRoot, data.options.outDir);
        }
        if (data.options.outFile) {
            updatedCache[key].data.options.outFile = posixRelative(workspaceRoot, data.options.outFile);
        }
        if (data.options.tsBuildInfoFile) {
            updatedCache[key].data.options.tsBuildInfoFile = posixRelative(workspaceRoot, data.options.tsBuildInfoFile);
        }
        if (data.extendedConfigFiles.length) {
            updatedCache[key].data.extendedConfigFiles.forEach((file) => {
                file.filePath = posixRelative(workspaceRoot, file.filePath);
            });
        }
        if (data.projectReferences) {
            updatedCache[key].data.projectReferences = data.projectReferences.map((ref) => ({
                ...ref,
                path: posixRelative(workspaceRoot, ref.path),
            }));
        }
    }
    return updatedCache;
}
function posixRelative(workspaceRoot, path) {
    return (0, devkit_1.normalizePath)((0, node_path_1.relative)(workspaceRoot, path));
}