@nx/js
Version:
857 lines (856 loc) • 41.4 kB
JavaScript
Object.defineProperty(exports, "__esModule", { value: true });
exports.createNodes = exports.createNodesV2 = exports.PLUGIN_NAME = exports.createDependencies = void 0;
const devkit_1 = require("@nx/devkit");
const get_named_inputs_1 = require("@nx/devkit/src/utils/get-named-inputs");
const node_fs_1 = require("node:fs");
const node_path_1 = require("node:path");
const posix = require("node:path/posix");
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 util_1 = require("./util");
let ts;
const pmc = (0, devkit_1.getPackageManagerCommand)();
const TSCONFIG_CACHE_VERSION = 1;
const TS_CONFIG_CACHE_PATH = (0, node_path_1.join)(cache_directory_1.workspaceDataDirectory, 'tsconfig-files.hash');
let tsConfigCacheData;
let cache;
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,
});
}
/**
* @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);
cache = { fileHashes: {}, rawFiles: {}, isExternalProjectReference: {} };
initializeTsConfigCache(configFilePaths, context.workspaceRoot);
const normalizedOptions = normalizePluginOptions(options);
const { configFilePaths: validConfigFilePaths, hashes, projectRoots, } = await resolveValidConfigFilesAndHashes(configFilePaths, optionsHash, context);
try {
return await (0, devkit_1.createNodesFromFiles)((configFilePath, options, context, idx) => {
const projectRoot = projectRoots[idx];
const hash = hashes[idx];
const cacheKey = `${hash}_${configFilePath}`;
targetsCache[cacheKey] ??= buildTscTargets((0, node_path_1.join)(context.workspaceRoot, configFilePath), projectRoot, options, context);
const { targets } = targetsCache[cacheKey];
return {
projects: {
[projectRoot]: {
projectType: 'library',
targets,
},
},
};
}, validConfigFilePaths, normalizedOptions, context);
}
finally {
writeToCache(targetsCachePath, targetsCache);
writeTsConfigCache(toRelativePaths(tsConfigCacheData, context.workspaceRoot));
}
},
];
exports.createNodes = [
tsConfigGlob,
async (configFilePath, options, context) => {
devkit_1.logger.warn('`createNodes` is deprecated. Update your plugin to utilize createNodesV2 instead. In Nx 20, this will change to the createNodesV2 API.');
const projectRoot = (0, node_path_1.dirname)(configFilePath);
if (!checkIfConfigFileShouldBeProject(configFilePath, projectRoot, context)) {
return {};
}
const normalizedOptions = normalizePluginOptions(options);
cache = { fileHashes: {}, rawFiles: {}, isExternalProjectReference: {} };
initializeTsConfigCache([configFilePath], context.workspaceRoot);
const { targets } = buildTscTargets((0, node_path_1.join)(context.workspaceRoot, configFilePath), projectRoot, normalizedOptions, context);
writeTsConfigCache(toRelativePaths(tsConfigCacheData, context.workspaceRoot));
return {
projects: {
[projectRoot]: {
projectType: 'library',
targets,
},
},
};
},
];
async function resolveValidConfigFilesAndHashes(configFilePaths, optionsHash, context) {
const lockFileHash = (0, file_hasher_1.hashFile)((0, node_path_1.join)(context.workspaceRoot, (0, lock_file_1.getLockFileName)((0, devkit_1.detectPackageManager)(context.workspaceRoot)))) ?? '';
const validConfigFilePaths = [];
const hashes = [];
const projectRoots = [];
for await (const configFilePath of configFilePaths) {
const projectRoot = (0, node_path_1.dirname)(configFilePath);
if (!checkIfConfigFileShouldBeProject(configFilePath, projectRoot, context)) {
continue;
}
projectRoots.push(projectRoot);
validConfigFilePaths.push(configFilePath);
hashes.push(await getConfigFileHash(configFilePath, context.workspaceRoot, projectRoot, optionsHash, lockFileHash));
}
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, projectRoot, optionsHash, lockFileHash) {
const fullConfigPath = (0, node_path_1.join)(workspaceRoot, configFilePath);
const tsConfig = retrieveTsConfigFromCache(fullConfigPath, workspaceRoot);
const extendedConfigFiles = getExtendedConfigFiles(tsConfig, workspaceRoot);
const internalReferencedFiles = resolveInternalProjectReferences(tsConfig, workspaceRoot, projectRoot);
const externalProjectReferences = resolveShallowExternalProjectReferences(tsConfig, workspaceRoot, projectRoot);
let packageJson = null;
try {
packageJson = (0, devkit_1.readJsonFile)((0, node_path_1.join)(workspaceRoot, projectRoot, '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)),
...extendedConfigFiles.packages.sort(),
lockFileHash,
optionsHash,
...(packageJson ? [(0, file_hasher_1.hashObject)(packageJson)] : []),
// change this to bust the cache when making changes that would yield
// different results for the same hash
(0, file_hasher_1.hashObject)({ bust: 3 }),
]);
}
function checkIfConfigFileShouldBeProject(configFilePath, projectRoot, context) {
// Do not create a project for the workspace root tsconfig files.
if (projectRoot === '.') {
return false;
}
// Do not create a project if package.json and project.json isn't there.
const siblingFiles = (0, node_fs_1.readdirSync)((0, node_path_1.join)(context.workspaceRoot, projectRoot));
if (!siblingFiles.includes('package.json') &&
!siblingFiles.includes('project.json')) {
return false;
}
// Do not create a project if it's not a tsconfig.json and there is no tsconfig.json in the same directory
if ((0, node_path_1.basename)(configFilePath) !== '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(configFilePath, projectRoot, options, context) {
const targets = {};
const namedInputs = (0, get_named_inputs_1.getNamedInputs)(projectRoot, context);
const tsConfig = retrieveTsConfigFromCache(configFilePath, context.workspaceRoot);
let internalProjectReferences;
// Typecheck target
if ((0, node_path_1.basename)(configFilePath) === 'tsconfig.json' &&
options.typecheck &&
tsConfig.raw?.['nx']?.addTypecheckTarget !== false) {
internalProjectReferences = resolveInternalProjectReferences(tsConfig, context.workspaceRoot, projectRoot);
const externalProjectReferences = resolveShallowExternalProjectReferences(tsConfig, context.workspaceRoot, projectRoot);
const targetName = options.typecheck.targetName;
if (!targets[targetName]) {
let command = `tsc --build --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)(projectRoot, options.build.configName);
if (context.configFiles.some((f) => f === buildConfigPath) &&
(0, util_1.isValidPackageJsonBuildConfig)(retrieveTsConfigFromCache(buildConfigPath, context.workspaceRoot), context.workspaceRoot, projectRoot)) {
dependsOn.unshift(options.build.targetName);
}
}
targets[targetName] = {
dependsOn,
command,
options: { cwd: projectRoot },
cache: true,
inputs: getInputs(namedInputs, configFilePath, tsConfig, internalProjectReferences, context.workspaceRoot, projectRoot),
outputs: getOutputs(configFilePath, tsConfig, internalProjectReferences, context.workspaceRoot, projectRoot,
/* emitDeclarationOnly */ true),
syncGenerators: ['@nx/js:typescript-sync'],
metadata: {
technologies: ['typescript'],
description: 'Runs type-checking for the project.',
help: {
command: `${pmc.exec} tsc --build --help`,
example: {
args: ['--force'],
},
},
},
};
}
}
// Build target
if (options.build &&
(0, node_path_1.basename)(configFilePath) === options.build.configName &&
(0, util_1.isValidPackageJsonBuildConfig)(tsConfig, context.workspaceRoot, projectRoot)) {
internalProjectReferences ??= resolveInternalProjectReferences(tsConfig, context.workspaceRoot, projectRoot);
const targetName = options.build.targetName;
targets[targetName] = {
dependsOn: [`^${targetName}`],
command: `tsc --build ${options.build.configName}${options.verboseOutput ? ' --verbose' : ''}`,
options: { cwd: projectRoot },
cache: true,
inputs: getInputs(namedInputs, configFilePath, tsConfig, internalProjectReferences, context.workspaceRoot, projectRoot),
outputs: getOutputs(configFilePath, tsConfig, internalProjectReferences, context.workspaceRoot, projectRoot,
// 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} tsc --build --help`,
example: {
args: ['--force'],
},
},
},
};
(0, util_1.addBuildAndWatchDepsTargets)(context.workspaceRoot, projectRoot, targets, {
buildDepsTargetName: options.build.buildDepsName,
watchDepsTargetName: options.build.watchDepsName,
}, pmc);
}
return { targets };
}
function getInputs(namedInputs, configFilePath, tsConfig, internalProjectReferences, workspaceRoot, projectRoot) {
const configFiles = new Set();
const externalDependencies = ['typescript'];
const extendedConfigFiles = getExtendedConfigFiles(tsConfig, workspaceRoot);
extendedConfigFiles.files.forEach((configPath) => {
configFiles.add(configPath);
});
externalDependencies.push(...extendedConfigFiles.packages);
const includePaths = new Set();
const excludePaths = new Set();
const projectTsConfigFiles = [
[configFilePath, tsConfig],
...Object.entries(internalProjectReferences),
];
const absoluteProjectRoot = (0, node_path_1.join)(workspaceRoot, projectRoot);
if (!ts) {
ts = require('typescript');
}
// 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,
];
const normalizeInput = (input, config) => {
const extensions = config.options.allowJs
? [...allSupportedExtensions]
: [...supportedTSExtensions];
if (config.options.resolveJsonModule) {
extensions.push(ts.Extension.Json);
}
const segments = input.split('/');
// An "includes" path "foo" is implicitly a glob "foo/**/*" if its last
// segment has no extension, and does not contain any glob characters
// itself.
// https://github.com/microsoft/TypeScript/blob/19b777260b26aac5707b1efd34202054164d4a9d/src/compiler/utilities.ts#L9577-L9585
if (!/[.*?]/.test(segments.at(-1))) {
return extensions.map((ext) => `${segments.join('/')}/**/*${ext}`);
}
return [input];
};
const configDirTemplate = '${configDir}';
const substituteConfigDir = (p) => p.startsWith(configDirTemplate) ? p.replace(configDirTemplate, './') : p;
projectTsConfigFiles.forEach(([configPath, config]) => {
configFiles.add(configPath);
const offset = (0, node_path_1.relative)(absoluteProjectRoot, (0, node_path_1.dirname)(configPath));
(config.raw?.include ?? []).forEach((p) => {
const normalized = normalizeInput((0, node_path_1.join)(offset, substituteConfigDir(p)), config);
normalized.forEach((input) => includePaths.add(input));
});
if (config.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);
config.raw.exclude.forEach((e) => {
const excludePath = substituteConfigDir(e);
if (!otherFilesInclude.some((includePath) => picomatch(normalize(excludePath))(normalize(includePath)) ||
picomatch(normalize(includePath))(normalize(excludePath)))) {
excludePaths.add(excludePath);
}
});
}
});
const inputs = [];
if (includePaths.size) {
if ((0, node_fs_1.existsSync)((0, node_path_1.join)(workspaceRoot, projectRoot, 'package.json'))) {
inputs.push('{projectRoot}/package.json');
}
inputs.push(...Array.from(configFiles).map((p) => pathToInputOrOutput(p, workspaceRoot, projectRoot)), ...Array.from(includePaths).map((p) => pathToInputOrOutput((0, devkit_1.joinPathFragments)(projectRoot, p), workspaceRoot, projectRoot)));
}
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)(projectRoot, p), workspaceRoot, projectRoot)}`));
}
if (hasExternalProjectReferences(configFilePath, tsConfig, workspaceRoot, projectRoot)) {
// Importing modules from a referenced project will load its output declaration files (d.ts)
// https://www.typescriptlang.org/docs/handbook/project-references.html#what-is-a-project-reference
inputs.push({ dependentTasksOutputFiles: '**/*.d.ts' });
}
else {
inputs.push('production' in namedInputs ? '^production' : '^default');
}
inputs.push({ externalDependencies });
return inputs;
}
function getOutputs(configFilePath, tsConfig, internalProjectReferences, workspaceRoot, projectRoot, 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.
[tsConfig, ...Object.values(internalProjectReferences)].forEach((config) => {
if (config.options.outFile) {
const outFileName = (0, node_path_1.basename)(config.options.outFile, '.js');
const outFileDir = (0, node_path_1.dirname)(config.options.outFile);
outputs.add(pathToInputOrOutput(config.options.outFile, workspaceRoot, projectRoot));
// 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, projectRoot));
outputs.add(pathToInputOrOutput((0, devkit_1.joinPathFragments)(outDir, `${outFileName}.d.ts`), workspaceRoot, projectRoot));
outputs.add(pathToInputOrOutput((0, devkit_1.joinPathFragments)(outDir, `${outFileName}.d.ts.map`), workspaceRoot, projectRoot));
// https://www.typescriptlang.org/tsconfig#tsBuildInfoFile
outputs.add(tsConfig.options.tsBuildInfoFile
? pathToInputOrOutput(tsConfig.options.tsBuildInfoFile, workspaceRoot, projectRoot)
: pathToInputOrOutput((0, devkit_1.joinPathFragments)(outDir, `${outFileName}.tsbuildinfo`), workspaceRoot, projectRoot));
}
else if (config.options.outDir) {
if (emitDeclarationOnly) {
outputs.add(pathToInputOrOutput((0, devkit_1.joinPathFragments)(config.options.outDir, '**/*.d.ts'), workspaceRoot, projectRoot));
if (tsConfig.options.declarationMap) {
outputs.add(pathToInputOrOutput((0, devkit_1.joinPathFragments)(config.options.outDir, '**/*.d.ts.map'), workspaceRoot, projectRoot));
}
}
else {
outputs.add(pathToInputOrOutput(config.options.outDir, workspaceRoot, projectRoot));
}
if (config.options.tsBuildInfoFile) {
if (emitDeclarationOnly ||
!(0, node_path_1.normalize)(config.options.tsBuildInfoFile).startsWith(`${(0, node_path_1.normalize)(config.options.outDir)}${node_path_1.sep}`)) {
// https://www.typescriptlang.org/tsconfig#tsBuildInfoFile
outputs.add(pathToInputOrOutput(config.options.tsBuildInfoFile, workspaceRoot, projectRoot));
}
}
else if (config.options.rootDir && config.options.rootDir !== '.') {
// If rootDir is set, then the tsbuildinfo file will be outside the outDir so we need to add it.
const relativeRootDir = (0, node_path_1.relative)(config.options.rootDir, (0, node_path_1.join)(workspaceRoot, projectRoot));
outputs.add(pathToInputOrOutput((0, devkit_1.joinPathFragments)(config.options.outDir, relativeRootDir, `*.tsbuildinfo`), workspaceRoot, projectRoot));
}
else if (emitDeclarationOnly) {
// https://www.typescriptlang.org/tsconfig#tsBuildInfoFile
const name = (0, node_path_1.basename)(configFilePath, '.json');
outputs.add(pathToInputOrOutput((0, devkit_1.joinPathFragments)(config.options.outDir, `${name}.tsbuildinfo`), workspaceRoot, projectRoot));
}
}
else if (config.raw?.include?.length ||
config.raw?.files?.length ||
(!config.raw?.include && !config.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'));
// https://www.typescriptlang.org/tsconfig#tsBuildInfoFile
const name = (0, node_path_1.basename)(configFilePath, '.json');
outputs.add(tsConfig.options.tsBuildInfoFile
? pathToInputOrOutput(tsConfig.options.tsBuildInfoFile, workspaceRoot, projectRoot)
: (0, devkit_1.joinPathFragments)('{projectRoot}', `${name}.tsbuildinfo`));
}
});
return Array.from(outputs);
}
function pathToInputOrOutput(path, workspaceRoot, projectRoot) {
const fullProjectRoot = (0, node_path_1.resolve)(workspaceRoot, projectRoot);
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, 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), workspaceRoot, extendedConfigFiles, extendedExternalPackages);
}
}
return {
files: Array.from(extendedConfigFiles),
packages: Array.from(extendedExternalPackages),
};
}
function resolveInternalProjectReferences(tsConfig, workspaceRoot, projectRoot, projectReferences = {}) {
if (!tsConfig.projectReferences?.length) {
return {};
}
for (const ref of tsConfig.projectReferences) {
let refConfigPath = ref.path;
if (projectReferences[refConfigPath]) {
// Already resolved
continue;
}
if (!(0, node_fs_1.existsSync)(refConfigPath)) {
// the referenced tsconfig doesn't exist, ignore it
continue;
}
if (isExternalProjectReference(refConfigPath, workspaceRoot, projectRoot)) {
continue;
}
if (!refConfigPath.endsWith('.json')) {
refConfigPath = (0, node_path_1.join)(refConfigPath, 'tsconfig.json');
}
projectReferences[refConfigPath] = retrieveTsConfigFromCache(refConfigPath, workspaceRoot);
resolveInternalProjectReferences(projectReferences[refConfigPath], workspaceRoot, projectRoot, projectReferences);
}
return projectReferences;
}
function resolveShallowExternalProjectReferences(tsConfig, workspaceRoot, projectRoot, projectReferences = {}) {
if (!tsConfig.projectReferences?.length) {
return projectReferences;
}
for (const ref of tsConfig.projectReferences) {
let refConfigPath = ref.path;
if (projectReferences[refConfigPath]) {
// Already resolved
continue;
}
if (!(0, node_fs_1.existsSync)(refConfigPath)) {
// the referenced tsconfig doesn't exist, ignore it
continue;
}
if (isExternalProjectReference(refConfigPath, workspaceRoot, projectRoot)) {
if (!refConfigPath.endsWith('.json')) {
refConfigPath = (0, node_path_1.join)(refConfigPath, 'tsconfig.json');
}
projectReferences[refConfigPath] = retrieveTsConfigFromCache(refConfigPath, workspaceRoot);
}
}
return projectReferences;
}
function hasExternalProjectReferences(tsConfigPath, tsConfig, workspaceRoot, projectRoot, seen = new Set()) {
if (!tsConfig.projectReferences?.length) {
return false;
}
seen.add(tsConfigPath);
for (const ref of tsConfig.projectReferences) {
let refConfigPath = ref.path;
if (seen.has(refConfigPath)) {
// Already seen
continue;
}
if (!(0, node_fs_1.existsSync)(refConfigPath)) {
// the referenced tsconfig doesn't exist, ignore it
continue;
}
if (isExternalProjectReference(refConfigPath, workspaceRoot, projectRoot)) {
return true;
}
if (!refConfigPath.endsWith('.json')) {
refConfigPath = (0, node_path_1.join)(refConfigPath, 'tsconfig.json');
}
const refTsConfig = retrieveTsConfigFromCache(refConfigPath, workspaceRoot);
const result = hasExternalProjectReferences(refConfigPath, refTsConfig, workspaceRoot, projectRoot, seen);
if (result) {
return true;
}
}
return false;
}
function isExternalProjectReference(refTsConfigPath, workspaceRoot, projectRoot) {
const relativePath = posixRelative(workspaceRoot, refTsConfigPath);
if (cache.isExternalProjectReference[relativePath] !== undefined) {
return cache.isExternalProjectReference[relativePath];
}
const absoluteProjectRoot = (0, node_path_1.join)(workspaceRoot, projectRoot);
let currentPath = getTsConfigDirName(refTsConfigPath);
if ((0, node_path_1.relative)(absoluteProjectRoot, currentPath).startsWith('..')) {
// it's outside of the project root, so it's an external project reference
cache.isExternalProjectReference[relativePath] = true;
return true;
}
while (currentPath !== absoluteProjectRoot) {
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'))) {
// it's inside a nested project root, so it's and external project reference
cache.isExternalProjectReference[relativePath] = true;
return true;
}
currentPath = (0, node_path_1.dirname)(currentPath);
}
// it's inside the project root, so it's an internal project reference
cache.isExternalProjectReference[relativePath] = false;
return false;
}
function getTsConfigDirName(tsConfigPath) {
return (0, node_fs_1.statSync)(tsConfigPath).isFile()
? (0, node_path_1.dirname)(tsConfigPath)
: (0, node_path_1.normalize)(tsConfigPath);
}
function retrieveTsConfigFromCache(tsConfigPath, workspaceRoot) {
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);
}
function initializeTsConfigCache(configFilePaths, workspaceRoot) {
tsConfigCacheData = toAbsolutePaths(readTsConfigCacheData(), workspaceRoot);
// 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);
}
}
function readTsConfigAndCache(tsConfigPath, workspaceRoot) {
const relativePath = posixRelative(workspaceRoot, tsConfigPath);
const hash = getFileHash(tsConfigPath, workspaceRoot);
let extendedFilesHash;
if (tsConfigCacheData[relativePath] &&
tsConfigCacheData[relativePath].hash === hash) {
extendedFilesHash = getExtendedFilesHash(tsConfigCacheData[relativePath].data.extendedConfigFiles, workspaceRoot);
if (tsConfigCacheData[relativePath].extendedFilesHash === extendedFilesHash) {
return tsConfigCacheData[relativePath].data;
}
}
const tsConfig = readTsConfig(tsConfigPath, workspaceRoot);
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);
tsConfigCacheData[relativePath] = {
data: {
options: tsConfig.options,
projectReferences: tsConfig.projectReferences,
raw: tsConfig.raw,
extendedConfigFiles,
},
hash,
extendedFilesHash,
};
return tsConfigCacheData[relativePath].data;
}
function getExtendedFilesHash(extendedConfigFiles, workspaceRoot) {
const hashes = [];
if (!extendedConfigFiles.length) {
return '';
}
for (const extendedConfigFile of extendedConfigFiles) {
if (extendedConfigFile.externalPackage) {
hashes.push(extendedConfigFile.externalPackage);
}
else if (extendedConfigFile.filePath) {
hashes.push(getFileHash(extendedConfigFile.filePath, workspaceRoot));
hashes.push(getExtendedFilesHash(readTsConfigAndCache(extendedConfigFile.filePath, workspaceRoot)
.extendedConfigFiles, workspaceRoot));
}
}
return hashes.join('|');
}
function readTsConfig(tsConfigPath, workspaceRoot) {
if (!ts) {
ts = require('typescript');
}
const tsSys = {
...ts.sys,
readFile: (path) => readFile(path, workspaceRoot),
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 defaultTypecheckTargetName = 'typecheck';
let typecheck = {
targetName: defaultTypecheckTargetName,
};
if (pluginOptions.typecheck === false) {
typecheck = false;
}
else if (pluginOptions.typecheck &&
typeof pluginOptions.typecheck !== 'boolean') {
typecheck = {
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',
};
// 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',
};
}
return {
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) {
const relativePath = posixRelative(workspaceRoot, filePath);
if (!cache.fileHashes[relativePath]) {
const content = readFile(filePath, workspaceRoot);
cache.fileHashes[relativePath] = (0, file_hasher_1.hashArray)([content]);
}
return cache.fileHashes[relativePath];
}
function readFile(filePath, workspaceRoot) {
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)) {
updatedCache[key] = {
data: {
options: { noEmit: data.options.noEmit },
raw: {
nx: { addTypecheckTarget: data.raw?.['nx']?.addTypecheckTarget },
},
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)) {
updatedCache[key] = {
data: {
options: { noEmit: data.options.noEmit },
raw: {
nx: { addTypecheckTarget: data.raw?.['nx']?.addTypecheckTarget },
},
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 posix.normalize((0, node_path_1.relative)(workspaceRoot, path));
}
;