UNPKG

nx

Version:

The core Nx plugin contains the core functionality of Nx like the project graph, nx commands and task orchestration.

266 lines (265 loc) 11 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.resolveTarget = resolveTarget; exports.hasCustomHasher = hasCustomHasher; exports.normalizePath = normalizePath; exports.deduplicateFolderEntries = deduplicateFolderEntries; exports.pc = pc; exports.printList = printList; exports.printJson = printJson; exports.pathsUnder = pathsUnder; exports.renderCheckResults = renderCheckResults; exports.setCheckExitCode = setCheckExitCode; const path_1 = require("path"); const calculate_default_project_name_1 = require("../../../config/calculate-default-project-name"); const configuration_1 = require("../../../config/configuration"); const project_graph_1 = require("../../../project-graph/project-graph"); const find_matching_projects_1 = require("../../../utils/find-matching-projects"); const output_1 = require("../../../utils/output"); const split_target_1 = require("../../../utils/split-target"); const workspace_root_1 = require("../../../utils/workspace-root"); async function resolveTarget(args, opts) { performance.mark('code-loading:end'); performance.measure('code-loading', 'init-local', 'code-loading:end'); let graph; let sourceMaps; if (opts?.withSourceMaps) { const result = await (0, project_graph_1.createProjectGraphAndSourceMapsAsync)(); graph = result.projectGraph; sourceMaps = result.sourceMaps; } else { graph = await (0, project_graph_1.createProjectGraphAsync)(); } const nxJson = (0, configuration_1.readNxJson)(); const { projectName, targetName, configurationName } = resolveTargetIdentifier(args, graph, nxJson); // `resolveProjectNode` accepts a pattern specifier (`my-*`), so the concrete // name it resolved to is the one everything downstream has to use — it is a // lookup key for the task id, not just a label. const node = resolveProjectNode(projectName, graph); const resolvedProjectName = node.name; if (!node.data.targets?.[targetName]) { reportTargetNotFound(resolvedProjectName, targetName, node); } const configuration = configurationName ?? args.configuration; if (configuration) { validateConfiguration(resolvedProjectName, targetName, configuration, node.data.targets[targetName]); } return { graph, nxJson, projectName: resolvedProjectName, targetName, configuration, node, sourceMaps, }; } // ── Target identifier & project resolution ────────────────────────── function resolveTargetIdentifier(args, graph, nxJson) { if (!args.target) { output_1.output.error({ title: 'No target specified.', bodyLines: [ `Please specify a target using:`, ` nx show target <project:target>`, ` nx show target <target> (infers project from cwd)`, ], }); process.exit(1); } const defaultProjectName = (0, calculate_default_project_name_1.calculateDefaultProjectName)(process.cwd(), workspace_root_1.workspaceRoot, (0, project_graph_1.readProjectsConfigurationFromProjectGraph)(graph), nxJson); const [project, target, config] = (0, split_target_1.splitTarget)(args.target, graph, { currentProject: defaultProjectName, }); if (project && target) { return { projectName: project, targetName: target, configurationName: config, }; } const targetName = project; // splitTarget returns the string as the first element const projectName = defaultProjectName; if (!projectName) { output_1.output.error({ title: `Could not infer project from the current working directory.`, bodyLines: [ `Please specify the project explicitly:`, ` nx show target <project>:${targetName}`, ``, `Or run this command from within a project directory.`, ], }); process.exit(1); } return { projectName, targetName }; } function resolveProjectNode(projectName, graph) { let node = graph.nodes[projectName]; if (!node) { const projects = (0, find_matching_projects_1.findMatchingProjects)([projectName], graph.nodes); if (projects.length === 1) { node = graph.nodes[projects[0]]; } else if (projects.length > 1) { output_1.output.error({ title: `Multiple projects matched "${projectName}":`, bodyLines: projects.length > 100 ? [...projects.slice(0, 100), '...'] : projects, }); process.exit(1); } else { output_1.output.error({ title: `Could not find project "${projectName}".` }); process.exit(1); } } return node; } function reportTargetNotFound(projectName, targetName, node) { const availableTargets = Object.keys(node.data.targets ?? {}); output_1.output.error({ title: `Target "${targetName}" not found for project "${projectName}".`, bodyLines: availableTargets.length ? [`Available targets:`, ...availableTargets.map((t) => ` - ${t}`)] : [`This project has no targets configured.`], }); process.exit(1); } function validateConfiguration(projectName, targetName, configuration, targetConfig) { const availableConfigs = Object.keys(targetConfig.configurations ?? {}); if (!availableConfigs.includes(configuration)) { output_1.output.error({ title: `Configuration "${configuration}" not found for target "${projectName}:${targetName}".`, bodyLines: availableConfigs.length ? [ `Available configurations:`, ...availableConfigs.map((c) => ` - ${c}`), ] : [`This target has no configurations.`], }); process.exit(1); } } // ── Custom hasher detection ────────────────────────────────────────── /** * Checks whether a target's executor defines a custom hasher. * Returns true if the executor has a hasherFactory — meaning the * standard input-based hashing is bypassed for this target. */ function hasCustomHasher(projectName, targetName, graph) { // Lazy require avoids a circular dependency with tasks-runner/utils. const { getExecutorForTask } = require('../../../tasks-runner/utils'); try { const { projects } = (0, project_graph_1.readProjectsConfigurationFromProjectGraph)(graph); const task = { id: `${projectName}:${targetName}`, target: { project: projectName, target: targetName }, overrides: {}, }; return !!getExecutorForTask(task, projects).hasherFactory; } catch { return false; } } // ── Small helpers shared across slices ─────────────────────────────── function normalizePath(p) { const absolute = (0, path_1.resolve)(process.cwd(), p); return (0, path_1.relative)(workspace_root_1.workspaceRoot, absolute).replace(/\\/g, '/'); } function deduplicateFolderEntries(items) { const normalized = items.map((item) => ({ original: item, path: normalizePath(item), })); return normalized .filter(({ path }) => { const dirPrefix = path.endsWith('/') ? path : path + '/'; return !normalized.some((other) => other.path !== path && other.path.startsWith(dirPrefix)); }) .map(({ original }) => original); } let _pc; function pc() { return (_pc ??= require('picocolors')); } function printList(header, items, prefix = '\n') { if (items.length === 0) return; console.log(`${prefix}${pc().bold(header)}:`); for (const item of items) console.log(` ${item}`); } function printJson(data) { const result = {}; for (const [key, value] of Object.entries(data)) { if (Array.isArray(value) && value.length === 0) continue; result[key] = value; } console.log(JSON.stringify(result, null, 2)); } /** The paths that live under `dir`. An empty `dir` is the workspace root. */ function pathsUnder(dir, paths) { if (dir === '') return [...paths]; const prefix = dir.endsWith('/') ? dir : dir + '/'; return paths.filter((p) => p.startsWith(prefix)); } const NOUNS = { input: { preposition: 'for', contained: 'input file' }, output: { preposition: 'of', contained: 'output path' }, }; function renderCheckResults(results, project, target, noun) { if (results.length >= 2) { renderBatchCheckResults(results, project, target, noun); return; } for (const result of results) { renderCheckResult(result, project, target, noun); } } function setCheckExitCode(results) { for (const result of results) { process.exitCode ||= result.matched || result.contained.length ? 0 : 1; } } function renderCheckResult(result, project, target, noun) { const c = pc(); const { preposition, contained } = NOUNS[noun]; const label = `${c.cyan(project)}:${c.green(target)}`; if (result.matched) { const category = result.category ? ` (${result.category})` : ''; console.log(`${c.green('✓')} ${c.bold(result.value)} is an ${noun} ${preposition} ${label}${category}`); } else if (result.contained.length > 0) { console.log(`${c.yellow('~')} ${c.bold(result.file)} is a directory containing ${c.bold(String(result.contained.length))} ${contained}(s) ${preposition} ${label}`); for (const item of [...result.contained].sort()) console.log(` ${item}`); } else { console.log(`${c.red('✗')} ${c.bold(result.value)} is ${c.red('not')} an ${noun} ${preposition} ${label}`); } } function renderBatchCheckResults(results, project, target, noun) { const c = pc(); const { preposition, contained } = NOUNS[noun]; const label = `${c.cyan(project)}:${c.green(target)}`; const matched = results.filter((r) => r.matched); const directories = results.filter((r) => !r.matched && r.contained.length); const unmatched = results.filter((r) => !r.matched && !r.contained.length); if (matched.length > 0 || directories.length > 0) { console.log(`\n${c.green('✓')} These arguments were ${noun}s ${preposition} ${label}:`); for (const r of matched) console.log(` ${r.value}`); for (const r of directories) { console.log(` ${r.file} (directory containing ${r.contained.length} ${contained}s)`); } } if (unmatched.length > 0) { console.log(`\n${c.red('✗')} These arguments were ${c.red('not')} ${noun}s ${preposition} ${label}:`); for (const r of unmatched) console.log(` ${r.value}`); } }