@nx/js
Version:
186 lines (185 loc) • 8.23 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;
}
else if ((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 packageJsonPath = (0, path_1.join)(workspaceRoot, resolvedProjectPath, '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);
const projectAbsolutePath = (0, node_path_1.resolve)(workspaceRoot, resolvedProjectPath);
// Handle outFile first (has precedence over outDir)
if (tsConfig.options.outFile) {
const outFile = (0, node_path_1.resolve)(workspaceRoot, resolvedProjectPath, 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 isPathSourceFile = (path) => {
const normalizedPath = (0, node_path_1.isAbsolute)(path)
? (0, node_path_1.resolve)(workspaceRoot, path.startsWith('/') ? path.slice(1) : path)
: (0, node_path_1.resolve)(workspaceRoot, resolvedProjectPath, path);
// For outFile case, check if path points to the specific outFile
return normalizedPath === outFile;
};
// Check if any entry points match the outFile
const exports = packageJson?.exports;
if (exports) {
if (typeof exports === 'string') {
return !isPathSourceFile(exports);
}
if (typeof exports === 'object' && '.' in exports) {
const dotExport = exports['.'];
if (typeof dotExport === 'string') {
return !isPathSourceFile(dotExport);
}
else if (typeof dotExport === 'object') {
const hasMatch = Object.entries(dotExport).some(([key, value]) => {
if (key === 'types' || key === 'development')
return false;
return typeof value === 'string' && isPathSourceFile(value);
});
return !hasMatch;
}
}
}
const buildPaths = ['main', 'module'];
for (const field of buildPaths) {
if (packageJson[field] && isPathSourceFile(packageJson[field])) {
return false;
}
}
return true;
}
// Handle outDir
const outDir = tsConfig.options.outDir;
let resolvedOutDir;
if (outDir) {
const potentialOutDir = (0, node_path_1.resolve)(workspaceRoot, resolvedProjectPath, outDir);
const relativePath = (0, node_path_1.relative)(projectAbsolutePath, potentialOutDir);
// If outDir is outside project root: buildable
if (relativePath.startsWith('..')) {
return true;
}
// If outDir is inside project root, then we should check entry points
if (!relativePath.startsWith('..')) {
resolvedOutDir = potentialOutDir;
}
}
const isPathSourceFile = (path) => {
const normalizedPath = (0, node_path_1.isAbsolute)(path)
? (0, node_path_1.resolve)(workspaceRoot, path.startsWith('/') ? path.slice(1) : path)
: (0, node_path_1.resolve)(workspaceRoot, resolvedProjectPath, path);
if (resolvedOutDir) {
// If the path is within the outDir, we assume it's not a source file.
const relativePath = (0, node_path_1.relative)(resolvedOutDir, normalizedPath);
if (!relativePath.startsWith('..')) {
return false;
}
}
// If no include patterns, TypeScript includes all TS files by default
const include = tsConfig.raw?.include;
if (!include || !Array.isArray(include)) {
const ext = (0, node_path_1.extname)(path);
const tsExtensions = ['.ts', '.tsx', '.cts', '.mts'];
if (tsExtensions.includes(ext)) {
return true;
}
// If include is not defined and it's not a TS file, assume it's not a source file
return false;
}
const projectAbsolutePath = (0, node_path_1.resolve)(workspaceRoot, resolvedProjectPath);
const relativeToProject = (0, node_path_1.relative)(projectAbsolutePath, normalizedPath);
for (const pattern of include) {
if (picomatch(pattern)(relativeToProject)) {
return true;
}
}
return false;
};
const containsInvalidPath = (value) => {
if (typeof value === 'string') {
return isPathSourceFile(value);
}
else if (typeof value === 'object') {
return Object.entries(value).some(([currentKey, subValue]) => {
// Skip types and development conditions
if (currentKey === 'types' || currentKey === 'development') {
return false;
}
if (typeof subValue === 'string') {
return isPathSourceFile(subValue);
}
return false;
});
}
return false;
};
const exports = packageJson?.exports;
// Check the `.` export if `exports` is defined.
if (exports) {
if (typeof exports === 'string') {
return !isPathSourceFile(exports);
}
if (typeof exports === 'object' && '.' in exports) {
return !containsInvalidPath(exports['.']);
}
// Check other exports if `.` is not defined or valid.
for (const key in exports) {
if (key !== '.' && containsInvalidPath(exports[key])) {
return false;
}
}
return true;
}
// If `exports` is not defined, fallback to `main` and `module` fields.
const buildPaths = ['main', 'module'];
for (const field of buildPaths) {
if (packageJson[field] && isPathSourceFile(packageJson[field])) {
return false;
}
}
return true;
}
;