@nx/js
Version:
194 lines (193 loc) • 9.69 kB
JavaScript
;
Object.defineProperty(exports, "__esModule", { value: true });
exports.addBuildAndWatchDepsTargets = addBuildAndWatchDepsTargets;
exports.isValidPackageJsonBuildConfig = isValidPackageJsonBuildConfig;
const devkit_1 = require("@nx/devkit");
const node_fs_1 = require("node:fs");
const node_path_1 = require("node:path");
const path_1 = require("path");
const picomatch = require("picomatch");
/**
* Allow uses that use incremental builds to run `nx watch-deps` to continuously build all dependencies.
*/
function addBuildAndWatchDepsTargets(workspaceRoot, projectRoot, targets, options, pmc) {
let projectName;
const projectJsonPath = (0, path_1.join)(workspaceRoot, projectRoot, 'project.json');
const packageJsonPath = (0, path_1.join)(workspaceRoot, projectRoot, 'package.json');
if ((0, node_fs_1.existsSync)(projectJsonPath)) {
const projectJson = (0, devkit_1.readJsonFile)(projectJsonPath);
projectName = projectJson.name;
}
if (!projectName && (0, node_fs_1.existsSync)(packageJsonPath)) {
const packageJson = (0, devkit_1.readJsonFile)(packageJsonPath);
projectName = packageJson.nx?.name ?? packageJson.name;
}
if (!projectName)
return;
if (projectName) {
const buildDepsTargetName = options.buildDepsTargetName ?? 'build-deps';
targets[buildDepsTargetName] = {
dependsOn: ['^build'],
};
targets[options.watchDepsTargetName ?? 'watch-deps'] = {
continuous: true,
dependsOn: [buildDepsTargetName],
command: `${pmc.exec} nx watch --projects ${projectName} --includeDependentProjects -- ${pmc.exec} nx ${buildDepsTargetName} ${projectName}`,
};
}
}
function isValidPackageJsonBuildConfig(tsConfig, workspaceRoot, projectRoot) {
const resolvedProjectPath = (0, node_path_1.isAbsolute)(projectRoot)
? (0, node_path_1.relative)(workspaceRoot, projectRoot)
: projectRoot;
const projectAbsolutePath = (0, node_path_1.resolve)(workspaceRoot, resolvedProjectPath);
const packageJsonPath = (0, path_1.join)(projectAbsolutePath, 'package.json');
if (!(0, node_fs_1.existsSync)(packageJsonPath)) {
// If the package.json file does not exist.
// Assume it's valid because it would be using `project.json` instead.
return true;
}
const packageJson = (0, devkit_1.readJsonFile)(packageJsonPath);
// Check entry points against outFile (has precedence over outDir)
if (tsConfig.options.outFile) {
return isAnyEntryPointPointingToOutFile(packageJson, tsConfig, projectAbsolutePath, workspaceRoot);
}
// Check entry points against outDir
const outDir = tsConfig.options.outDir;
if (outDir) {
return isAnyEntryPointPointingToOutDir(packageJson, outDir, workspaceRoot, projectAbsolutePath);
}
// Check entry points against include patterns
return isAnyEntryPointPointingToNonIncludedFiles(packageJson, tsConfig, workspaceRoot, projectAbsolutePath);
}
const packageJsonLegacyEntryPoints = ['main', 'module'];
function isAnyEntryPointPointingToOutFile(packageJson, tsConfig, projectAbsolutePath, workspaceRoot) {
const outFile = (0, node_path_1.resolve)(projectAbsolutePath, tsConfig.options.outFile);
const relativeToProject = (0, node_path_1.relative)(projectAbsolutePath, outFile);
// If outFile is outside project root: buildable
if (relativeToProject.startsWith('..')) {
return true;
}
// If outFile is inside project root: check if entry points point to outFile
const isPathPointingToOutputFile = (path) => {
return normalizePath(path, workspaceRoot, projectAbsolutePath) === outFile;
};
const exports = packageJson.exports;
if (!exports) {
// If any entry point points to the outFile, or if no entry points are
// defined (Node.js falls back to `./index.js`), then it's buildable
return (packageJsonLegacyEntryPoints.some((field) => packageJson[field] && isPathPointingToOutputFile(packageJson[field])) || packageJsonLegacyEntryPoints.every((field) => !packageJson[field]));
}
if (typeof exports === 'string') {
return isPathPointingToOutputFile(exports);
}
const isExportsEntryPointingToOutFile = (value) => {
if (typeof value === 'string') {
return isPathPointingToOutputFile(value);
}
if (typeof value === 'object') {
return Object.values(value).some((subValue) => typeof subValue === 'string' && isPathPointingToOutputFile(subValue));
}
return false;
};
if ('.' in exports && exports['.'] !== null) {
return isExportsEntryPointingToOutFile(exports['.']);
}
// If any export is pointing to a path inside the outDir, then it's buildable
return Object.keys(exports).some((key) => key !== '.' && isExportsEntryPointingToOutFile(exports[key]));
}
function isAnyEntryPointPointingToOutDir(packageJson, outDir, workspaceRoot, projectAbsolutePath) {
const resolvedOutDir = (0, node_path_1.resolve)(projectAbsolutePath, outDir);
const relativePath = (0, node_path_1.relative)(projectAbsolutePath, resolvedOutDir);
// If outDir is outside project root: buildable
if (relativePath.startsWith('..')) {
return true;
}
const isPathInsideOutDir = (path) => {
const normalizedPath = normalizePath(path, workspaceRoot, projectAbsolutePath);
return !(0, node_path_1.relative)(resolvedOutDir, normalizedPath).startsWith('..');
};
const exports = packageJson.exports;
if (!exports) {
// If any entry point points to a path inside the outDir, or if no entry points
// are defined (Node.js falls back to `./index.js`), then it's buildable
return (packageJsonLegacyEntryPoints.some((field) => packageJson[field] &&
packageJson[field] !== './package.json' &&
isPathInsideOutDir(packageJson[field])) || packageJsonLegacyEntryPoints.every((field) => !packageJson[field]));
}
if (typeof exports === 'string') {
return isPathInsideOutDir(exports);
}
const isExportsEntryPointingToPathInsideOutDir = (value) => {
if (typeof value === 'string') {
return isPathInsideOutDir(value);
}
if (typeof value === 'object') {
return Object.values(value).some((subValue) => typeof subValue === 'string' && isPathInsideOutDir(subValue));
}
return false;
};
if ('.' in exports && exports['.'] !== null) {
return isExportsEntryPointingToPathInsideOutDir(exports['.']);
}
// If any export is pointing to a path inside the outDir, then it's buildable
return Object.keys(exports).some((key) => key !== '.' &&
key !== './package.json' &&
isExportsEntryPointingToPathInsideOutDir(exports[key]));
}
function isAnyEntryPointPointingToNonIncludedFiles(packageJson, tsConfig, workspaceRoot, projectAbsolutePath) {
const isPathSourceFile = (path) => {
const normalizedPath = normalizePath(path, workspaceRoot, projectAbsolutePath);
const files = tsConfig.raw?.files;
const include = tsConfig.raw?.include;
if (Array.isArray(files)) {
const match = files.find((file) => normalizePath(file, workspaceRoot, projectAbsolutePath) ===
normalizedPath);
if (match) {
return true;
}
}
// If not matched by `files`, check `include` patterns
if (!Array.isArray(include)) {
// If no include patterns, TypeScript includes all TS files by default
const ext = (0, node_path_1.extname)(path);
const tsExtensions = ['.ts', '.tsx', '.cts', '.mts'];
return tsExtensions.includes(ext);
}
const relativeToProject = (0, node_path_1.relative)(projectAbsolutePath, normalizedPath);
return include.some((pattern) => picomatch(pattern)(relativeToProject));
};
// Check the `.` export if `exports` is defined.
const exports = packageJson.exports;
if (!exports) {
// If any entry point doesn't point to a source file, or if no entry points
// are defined (Node.js falls back to `./index.js`), then it's buildable
return (packageJsonLegacyEntryPoints.some((field) => packageJson[field] && !isPathSourceFile(packageJson[field])) || packageJsonLegacyEntryPoints.every((field) => !packageJson[field]));
}
if (typeof exports === 'string') {
return !isPathSourceFile(exports);
}
const isExportsEntryPointingToSource = (value) => {
if (typeof value === 'string') {
return isPathSourceFile(value);
}
if (typeof value === 'object') {
// entry point point to a source file if all conditions that are not set
// to `null` point to a source file
return Object.values(value).every((subValue) => typeof subValue !== 'string' || isPathSourceFile(subValue));
}
return false;
};
if ('.' in exports && exports['.'] !== null) {
return !isExportsEntryPointingToSource(exports['.']);
}
// If any export is not pointing to a source file, then it's buildable
return Object.keys(exports).some((key) => key !== '.' &&
key !== './package.json' &&
!isExportsEntryPointingToSource(exports[key]));
}
function normalizePath(path, workspaceRoot, projectAbsolutePath) {
return (0, node_path_1.isAbsolute)(path)
? (0, node_path_1.resolve)(workspaceRoot, path.startsWith('/') ? path.slice(1) : path)
: (0, node_path_1.resolve)(projectAbsolutePath, path);
}