UNPKG

nx

Version:

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

624 lines (623 loc) • 28.2 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.runCommand = runCommand; exports.runCommandForTasks = runCommandForTasks; exports.invokeTasksRunner = invokeTasksRunner; exports.getRunner = getRunner; exports.getRunnerOptions = getRunnerOptions; const enquirer_1 = require("enquirer"); const ora = require("ora"); const path_1 = require("path"); const nx_json_1 = require("../config/nx-json"); const client_1 = require("../daemon/client/client"); const create_task_hasher_1 = require("../hasher/create-task-hasher"); const hash_task_1 = require("../hasher/hash-task"); const native_1 = require("../native"); const project_graph_1 = require("../project-graph/project-graph"); const fileutils_1 = require("../utils/fileutils"); const is_ci_1 = require("../utils/is-ci"); const nx_cloud_utils_1 = require("../utils/nx-cloud-utils"); const output_1 = require("../utils/output"); const handle_errors_1 = require("../utils/handle-errors"); const sync_generators_1 = require("../utils/sync-generators"); const workspace_root_1 = require("../utils/workspace-root"); const create_task_graph_1 = require("./create-task-graph"); const life_cycle_1 = require("./life-cycle"); const dynamic_run_many_terminal_output_life_cycle_1 = require("./life-cycles/dynamic-run-many-terminal-output-life-cycle"); const dynamic_run_one_terminal_output_life_cycle_1 = require("./life-cycles/dynamic-run-one-terminal-output-life-cycle"); const static_run_many_terminal_output_life_cycle_1 = require("./life-cycles/static-run-many-terminal-output-life-cycle"); const static_run_one_terminal_output_life_cycle_1 = require("./life-cycles/static-run-one-terminal-output-life-cycle"); const store_run_information_life_cycle_1 = require("./life-cycles/store-run-information-life-cycle"); const task_history_life_cycle_1 = require("./life-cycles/task-history-life-cycle"); const task_history_life_cycle_old_1 = require("./life-cycles/task-history-life-cycle-old"); const task_profiling_life_cycle_1 = require("./life-cycles/task-profiling-life-cycle"); const task_timings_life_cycle_1 = require("./life-cycles/task-timings-life-cycle"); const task_results_life_cycle_1 = require("./life-cycles/task-results-life-cycle"); const task_graph_utils_1 = require("./task-graph-utils"); const utils_1 = require("./utils"); const chalk = require("chalk"); const nx_key_1 = require("../utils/nx-key"); const tasks_execution_hooks_1 = require("../project-graph/plugins/tasks-execution-hooks"); async function getTerminalOutputLifeCycle(initiatingProject, projectNames, tasks, nxArgs, nxJson, overrides) { const { runnerOptions } = getRunner(nxArgs, nxJson); const isRunOne = initiatingProject != null; const useDynamicOutput = shouldUseDynamicLifeCycle(tasks, runnerOptions, nxArgs.outputStyle); const overridesWithoutHidden = { ...overrides }; delete overridesWithoutHidden['__overrides_unparsed__']; if (isRunOne) { if (useDynamicOutput) { return await (0, dynamic_run_one_terminal_output_life_cycle_1.createRunOneDynamicOutputRenderer)({ initiatingProject, tasks, args: nxArgs, overrides: overridesWithoutHidden, }); } return { lifeCycle: new static_run_one_terminal_output_life_cycle_1.StaticRunOneTerminalOutputLifeCycle(initiatingProject, projectNames, tasks, nxArgs), renderIsDone: Promise.resolve(), }; } else { if (useDynamicOutput) { return await (0, dynamic_run_many_terminal_output_life_cycle_1.createRunManyDynamicOutputRenderer)({ projectNames, tasks, args: nxArgs, overrides: overridesWithoutHidden, }); } else { return { lifeCycle: new static_run_many_terminal_output_life_cycle_1.StaticRunManyTerminalOutputLifeCycle(projectNames, tasks, nxArgs, overridesWithoutHidden), renderIsDone: Promise.resolve(), }; } } } function createTaskGraphAndRunValidations(projectGraph, extraTargetDependencies, projectNames, nxArgs, overrides, extraOptions) { const taskGraph = (0, create_task_graph_1.createTaskGraph)(projectGraph, extraTargetDependencies, projectNames, nxArgs.targets, nxArgs.configuration, overrides, extraOptions.excludeTaskDependencies); const cycle = (0, task_graph_utils_1.findCycle)(taskGraph); if (cycle) { if (process.env.NX_IGNORE_CYCLES === 'true' || nxArgs.nxIgnoreCycles) { output_1.output.warn({ title: `The task graph has a circular dependency`, bodyLines: [`${cycle.join(' --> ')}`], }); (0, task_graph_utils_1.makeAcyclic)(taskGraph); } else { output_1.output.error({ title: `Could not execute command because the task graph has a circular dependency`, bodyLines: [`${cycle.join(' --> ')}`], }); process.exit(1); } } // validate that no atomized tasks like e2e-ci are used without Nx Cloud if (!(0, nx_cloud_utils_1.isNxCloudUsed)((0, nx_json_1.readNxJson)()) && !process.env['NX_SKIP_ATOMIZER_VALIDATION']) { (0, task_graph_utils_1.validateNoAtomizedTasks)(taskGraph, projectGraph); } return taskGraph; } async function runCommand(projectsToRun, currentProjectGraph, { nxJson }, nxArgs, overrides, initiatingProject, extraTargetDependencies, extraOptions) { const status = await (0, handle_errors_1.handleErrors)(process.env.NX_VERBOSE_LOGGING === 'true', async () => { await (0, tasks_execution_hooks_1.runPreTasksExecution)({ workspaceRoot: workspace_root_1.workspaceRoot, nxJsonConfiguration: nxJson, }); const taskResults = await runCommandForTasks(projectsToRun, currentProjectGraph, { nxJson }, { ...nxArgs, skipNxCache: nxArgs.skipNxCache || process.env.NX_SKIP_NX_CACHE === 'true' || process.env.NX_DISABLE_NX_CACHE === 'true', }, overrides, initiatingProject, extraTargetDependencies, extraOptions); const result = Object.values(taskResults).some((taskResult) => taskResult.status === 'failure' || taskResult.status === 'skipped') ? 1 : 0; await (0, tasks_execution_hooks_1.runPostTasksExecution)({ taskResults, workspaceRoot: workspace_root_1.workspaceRoot, nxJsonConfiguration: nxJson, }); return result; }); return status; } async function runCommandForTasks(projectsToRun, currentProjectGraph, { nxJson }, nxArgs, overrides, initiatingProject, extraTargetDependencies, extraOptions) { const projectNames = projectsToRun.map((t) => t.name); const { projectGraph, taskGraph } = await ensureWorkspaceIsInSyncAndGetGraphs(currentProjectGraph, nxJson, projectNames, nxArgs, overrides, extraTargetDependencies, extraOptions); const tasks = Object.values(taskGraph.tasks); const { lifeCycle, renderIsDone } = await getTerminalOutputLifeCycle(initiatingProject, projectNames, tasks, nxArgs, nxJson, overrides); const taskResults = await invokeTasksRunner({ tasks, projectGraph, taskGraph, lifeCycle, nxJson, nxArgs, loadDotEnvFiles: extraOptions.loadDotEnvFiles, initiatingProject, }); await renderIsDone; await (0, nx_key_1.printNxKey)(); return taskResults; } async function ensureWorkspaceIsInSyncAndGetGraphs(projectGraph, nxJson, projectNames, nxArgs, overrides, extraTargetDependencies, extraOptions) { let taskGraph = createTaskGraphAndRunValidations(projectGraph, extraTargetDependencies ?? {}, projectNames, nxArgs, overrides, extraOptions); if (nxArgs.skipSync) { return { projectGraph, taskGraph }; } // collect unique syncGenerators from the tasks const uniqueSyncGenerators = (0, sync_generators_1.collectEnabledTaskSyncGeneratorsFromTaskGraph)(taskGraph, projectGraph, nxJson); if (!uniqueSyncGenerators.size) { // There are no sync generators registered in the tasks to run return { projectGraph, taskGraph }; } const syncGenerators = Array.from(uniqueSyncGenerators); const results = await (0, sync_generators_1.getSyncGeneratorChanges)(syncGenerators); if (!results.length) { // There are no changes to sync, workspace is up to date return { projectGraph, taskGraph }; } const { failedGeneratorsCount, areAllResultsFailures, anySyncGeneratorsFailed, } = (0, sync_generators_1.processSyncGeneratorResultErrors)(results); const failedSyncGeneratorsFixMessageLines = (0, sync_generators_1.getFailedSyncGeneratorsFixMessageLines)(results, nxArgs.verbose); const outOfSyncTitle = 'The workspace is out of sync'; const resultBodyLines = (0, sync_generators_1.getSyncGeneratorSuccessResultsMessageLines)(results); const fixMessage = 'Make sure to run `nx sync` to apply the identified changes or set `sync.applyChanges` to `true` in your `nx.json` to apply them automatically when running tasks in interactive environments.'; const willErrorOnCiMessage = 'This will result in an error in CI.'; if ((0, is_ci_1.isCI)() || !process.stdout.isTTY) { // If the user is running in CI or is running in a non-TTY environment we // throw an error to stop the execution of the tasks. if (areAllResultsFailures) { output_1.output.error({ title: `The workspace is probably out of sync because ${failedGeneratorsCount === 1 ? 'a sync generator' : 'some sync generators'} failed to run`, bodyLines: failedSyncGeneratorsFixMessageLines, }); } else { output_1.output.error({ title: outOfSyncTitle, bodyLines: [...resultBodyLines, '', fixMessage], }); if (anySyncGeneratorsFailed) { output_1.output.error({ title: failedGeneratorsCount === 1 ? 'A sync generator failed to run' : 'Some sync generators failed to run', bodyLines: failedSyncGeneratorsFixMessageLines, }); } } process.exit(1); } if (areAllResultsFailures) { output_1.output.warn({ title: `The workspace is probably out of sync because ${failedGeneratorsCount === 1 ? 'a sync generator' : 'some sync generators'} failed to run`, bodyLines: failedSyncGeneratorsFixMessageLines, }); await confirmRunningTasksWithSyncFailures(); // if all sync generators failed to run there's nothing to sync, we just let the tasks run return { projectGraph, taskGraph }; } if (nxJson.sync?.applyChanges === false) { // If the user has set `sync.applyChanges` to `false` in their `nx.json` // we don't prompt the them and just log a warning informing them that // the workspace is out of sync and they have it set to not apply changes // automatically. output_1.output.warn({ title: outOfSyncTitle, bodyLines: [ ...resultBodyLines, '', 'Your workspace is set to not apply the identified changes automatically (`sync.applyChanges` is set to `false` in your `nx.json`).', willErrorOnCiMessage, fixMessage, ], }); if (anySyncGeneratorsFailed) { output_1.output.warn({ title: failedGeneratorsCount === 1 ? 'A sync generator failed to run' : 'Some sync generators failed to run', bodyLines: failedSyncGeneratorsFixMessageLines, }); await confirmRunningTasksWithSyncFailures(); } return { projectGraph, taskGraph }; } output_1.output.warn({ title: outOfSyncTitle, bodyLines: [ ...resultBodyLines, '', nxJson.sync?.applyChanges === true ? 'Proceeding to sync the identified changes automatically (`sync.applyChanges` is set to `true` in your `nx.json`).' : willErrorOnCiMessage, ], }); const applyChanges = nxJson.sync?.applyChanges === true || (await promptForApplyingSyncGeneratorChanges()); if (applyChanges) { const spinner = ora('Syncing the workspace...'); spinner.start(); // Flush sync generator changes to disk const flushResult = await (0, sync_generators_1.flushSyncGeneratorChanges)(results); if ('generatorFailures' in flushResult) { spinner.fail(); output_1.output.error({ title: 'Failed to sync the workspace', bodyLines: [ ...(0, sync_generators_1.getFlushFailureMessageLines)(flushResult, nxArgs.verbose), ...(flushResult.generalFailure ? [ 'If needed, you can run the tasks with the `--skip-sync` flag to disable syncing.', ] : []), ], }); await confirmRunningTasksWithSyncFailures(); } // Re-create project graph and task graph projectGraph = await (0, project_graph_1.createProjectGraphAsync)(); taskGraph = createTaskGraphAndRunValidations(projectGraph, extraTargetDependencies ?? {}, projectNames, nxArgs, overrides, extraOptions); const successTitle = anySyncGeneratorsFailed ? // the identified changes were synced successfully, but the workspace // is still not up to date, which we'll mention next 'The identified changes were synced successfully!' : // the workspace is fully up to date 'The workspace was synced successfully!'; const successSubtitle = nxJson.sync?.applyChanges === true ? 'Please make sure to commit the changes to your repository or this will error in CI.' : // The user was prompted and we already logged a message about erroring in CI // so here we just tell them to commit the changes. 'Please make sure to commit the changes to your repository.'; spinner.succeed(`${successTitle}\n\n${successSubtitle}`); if (anySyncGeneratorsFailed) { output_1.output.warn({ title: `The workspace is probably still out of sync because ${failedGeneratorsCount === 1 ? 'a sync generator' : 'some sync generators'} failed to run`, bodyLines: failedSyncGeneratorsFixMessageLines, }); await confirmRunningTasksWithSyncFailures(); } } else { if (anySyncGeneratorsFailed) { output_1.output.warn({ title: failedGeneratorsCount === 1 ? 'A sync generator failed to report the sync status' : 'Some sync generators failed to report the sync status', bodyLines: failedSyncGeneratorsFixMessageLines, }); await confirmRunningTasksWithSyncFailures(); } else { output_1.output.warn({ title: 'Syncing the workspace was skipped', bodyLines: [ 'This could lead to unexpected results or errors when running tasks.', fixMessage, ], }); } } return { projectGraph, taskGraph }; } async function promptForApplyingSyncGeneratorChanges() { try { const promptConfig = { name: 'applyChanges', type: 'autocomplete', message: 'Would you like to sync the identified changes to get your workspace up to date?', choices: [ { name: 'yes', message: 'Yes, sync the changes and run the tasks', }, { name: 'no', message: 'No, run the tasks without syncing the changes', }, ], footer: () => chalk.dim('\nYou can skip this prompt by setting the `sync.applyChanges` option to `true` in your `nx.json`.\nFor more information, refer to the docs: https://nx.dev/concepts/sync-generators.'), }; return await (0, enquirer_1.prompt)([promptConfig]).then(({ applyChanges }) => applyChanges === 'yes'); } catch { process.exit(1); } } async function confirmRunningTasksWithSyncFailures() { try { const promptConfig = { name: 'runTasks', type: 'autocomplete', message: 'Would you like to ignore the sync failures and continue running the tasks?', choices: [ { name: 'yes', message: 'Yes, ignore the failures and run the tasks', }, { name: 'no', message: `No, don't run the tasks`, }, ], footer: () => chalk.dim(`\nWhen running in CI and there are sync failures, the tasks won't run. Addressing the errors above is highly recommended to prevent failures in CI.`), }; const runTasks = await (0, enquirer_1.prompt)([ promptConfig, ]).then(({ runTasks }) => runTasks === 'yes'); if (!runTasks) { process.exit(1); } } catch { process.exit(1); } } function setEnvVarsBasedOnArgs(nxArgs, loadDotEnvFiles) { if (nxArgs.outputStyle == 'stream' || process.env.NX_BATCH_MODE === 'true' || nxArgs.batch) { process.env.NX_STREAM_OUTPUT = 'true'; process.env.NX_PREFIX_OUTPUT = 'true'; } if (nxArgs.outputStyle == 'stream-without-prefixes') { process.env.NX_STREAM_OUTPUT = 'true'; } if (loadDotEnvFiles) { process.env.NX_LOAD_DOT_ENV_FILES = 'true'; } } async function invokeTasksRunner({ tasks, projectGraph, taskGraph, lifeCycle, nxJson, nxArgs, loadDotEnvFiles, initiatingProject, }) { setEnvVarsBasedOnArgs(nxArgs, loadDotEnvFiles); // this needs to be done before we start to run the tasks const taskDetails = (0, hash_task_1.getTaskDetails)(); const { tasksRunner, runnerOptions } = getRunner(nxArgs, nxJson); let hasher = (0, create_task_hasher_1.createTaskHasher)(projectGraph, nxJson, runnerOptions); // this is used for two reasons: to fetch all remote cache hits AND // to submit everything that is known in advance to Nx Cloud to run in // a distributed fashion await (0, hash_task_1.hashTasksThatDoNotDependOnOutputsOfOtherTasks)(hasher, projectGraph, taskGraph, nxJson, taskDetails); const taskResultsLifecycle = new task_results_life_cycle_1.TaskResultsLifeCycle(); const compositedLifeCycle = new life_cycle_1.CompositeLifeCycle([ ...constructLifeCycles(lifeCycle), taskResultsLifecycle, ]); let promiseOrObservable = tasksRunner(tasks, { ...runnerOptions, lifeCycle: compositedLifeCycle, }, { initiatingProject: nxArgs.outputStyle === 'compact' ? null : initiatingProject, projectGraph, nxJson, nxArgs, taskGraph, hasher: { hashTask(task, taskGraph_, env) { if (!taskGraph_) { output_1.output.warn({ title: `TaskGraph is now required as an argument to hashTask`, bodyLines: [ `The TaskGraph object can be retrieved from the context`, 'This will result in an error in Nx 20', ], }); taskGraph_ = taskGraph; } if (!env) { output_1.output.warn({ title: `The environment variables are now required as an argument to hashTask`, bodyLines: [ `Please pass the environment variables used when running the task`, 'This will result in an error in Nx 20', ], }); env = process.env; } return hasher.hashTask(task, taskGraph_, env); }, hashTasks(task, taskGraph_, env) { if (!taskGraph_) { output_1.output.warn({ title: `TaskGraph is now required as an argument to hashTasks`, bodyLines: [ `The TaskGraph object can be retrieved from the context`, 'This will result in an error in Nx 20', ], }); taskGraph_ = taskGraph; } if (!env) { output_1.output.warn({ title: `The environment variables are now required as an argument to hashTasks`, bodyLines: [ `Please pass the environment variables used when running the tasks`, 'This will result in an error in Nx 20', ], }); env = process.env; } return hasher.hashTasks(task, taskGraph_, env); }, }, daemon: client_1.daemonClient, }); if (promiseOrObservable.subscribe) { promiseOrObservable = convertObservableToPromise(promiseOrObservable); } await promiseOrObservable; return taskResultsLifecycle.getTaskResults(); } function constructLifeCycles(lifeCycle) { const lifeCycles = []; lifeCycles.push(new store_run_information_life_cycle_1.StoreRunInformationLifeCycle()); lifeCycles.push(lifeCycle); if (process.env.NX_PERF_LOGGING === 'true') { lifeCycles.push(new task_timings_life_cycle_1.TaskTimingsLifeCycle()); } if (process.env.NX_PROFILE) { lifeCycles.push(new task_profiling_life_cycle_1.TaskProfilingLifeCycle(process.env.NX_PROFILE)); } if (!(0, nx_cloud_utils_1.isNxCloudUsed)((0, nx_json_1.readNxJson)())) { lifeCycles.push(process.env.NX_DISABLE_DB !== 'true' && !native_1.IS_WASM ? new task_history_life_cycle_1.TaskHistoryLifeCycle() : new task_history_life_cycle_old_1.LegacyTaskHistoryLifeCycle()); } return lifeCycles; } async function convertObservableToPromise(obs) { return await new Promise((res) => { let tasksResults = {}; obs.subscribe({ next: (t) => { tasksResults[t.task.id] = t.success ? 'success' : 'failure'; }, error: (error) => { output_1.output.error({ title: 'Unhandled error in task executor', }); console.error(error); res(tasksResults); }, complete: () => { res(tasksResults); }, }); }); } function shouldUseDynamicLifeCycle(tasks, options, outputStyle) { if (process.env.NX_BATCH_MODE === 'true' || process.env.NX_VERBOSE_LOGGING === 'true' || process.env.NX_TASKS_RUNNER_DYNAMIC_OUTPUT === 'false') { return false; } if (!process.stdout.isTTY) return false; if ((0, is_ci_1.isCI)()) return false; if (outputStyle === 'static' || outputStyle === 'stream') return false; return !tasks.find((t) => (0, utils_1.shouldStreamOutput)(t, null)); } function loadTasksRunner(modulePath) { try { const maybeTasksRunner = require(modulePath); // to support both babel and ts formats return 'default' in maybeTasksRunner ? maybeTasksRunner.default : maybeTasksRunner; } catch (e) { if (e.code === 'MODULE_NOT_FOUND' && (modulePath === 'nx-cloud' || modulePath === '@nrwl/nx-cloud')) { return require('../nx-cloud/nx-cloud-tasks-runner-shell') .nxCloudTasksRunnerShell; } throw e; } } function getRunner(nxArgs, nxJson) { let runner = nxArgs.runner; runner = runner ?? 'default'; if (runner !== 'default' && !nxJson.tasksRunnerOptions?.[runner]) { throw new Error(`Could not find runner configuration for ${runner}`); } const modulePath = getTasksRunnerPath(runner, nxJson); try { if (isCustomRunnerPath(modulePath)) { output_1.output.warn({ title: `Custom task runners will be replaced by a new API starting with Nx 21.`, bodyLines: [ `For more information, see https://nx.dev/deprecated/custom-tasks-runner`, ], }); } const tasksRunner = loadTasksRunner(modulePath); return { tasksRunner, runnerOptions: getRunnerOptions(runner, nxJson, nxArgs, modulePath === 'nx-cloud'), }; } catch { throw new Error(`Could not find runner configuration for ${runner}`); } } const defaultTasksRunnerPath = require.resolve('./default-tasks-runner'); function getTasksRunnerPath(runner, nxJson) { let modulePath = nxJson.tasksRunnerOptions?.[runner]?.runner; if (modulePath) { if ((0, fileutils_1.isRelativePath)(modulePath)) { return (0, path_1.join)(workspace_root_1.workspaceRoot, modulePath); } return modulePath; } const isCloudRunner = // No tasksRunnerOptions for given --runner nxJson.nxCloudAccessToken || // No runner prop in tasks runner options, check if access token is set. nxJson.tasksRunnerOptions?.[runner]?.options?.accessToken || // Cloud access token specified in env var. process.env.NX_CLOUD_ACCESS_TOKEN || // Nx Cloud ID specified in nxJson nxJson.nxCloudId; return isCloudRunner ? 'nx-cloud' : defaultTasksRunnerPath; } function getRunnerOptions(runner, nxJson, nxArgs, isCloudDefault) { const defaultCacheableOperations = []; for (const key in nxJson.targetDefaults) { if (nxJson.targetDefaults[key].cache) { defaultCacheableOperations.push(key); } } const result = { ...nxJson.tasksRunnerOptions?.[runner]?.options, ...nxArgs, }; // NOTE: we don't pull from env here because the cloud package // supports it within nx-cloud's implementation. We could // normalize it here, and that may make more sense, but // leaving it as is for now. if (nxJson.nxCloudAccessToken && isCloudDefault) { result.accessToken ??= nxJson.nxCloudAccessToken; } if (nxJson.nxCloudId && isCloudDefault) { result.nxCloudId ??= nxJson.nxCloudId; } if (nxJson.nxCloudUrl && isCloudDefault) { result.url ??= nxJson.nxCloudUrl; } if (nxJson.nxCloudEncryptionKey && isCloudDefault) { result.encryptionKey ??= nxJson.nxCloudEncryptionKey; } if (nxJson.parallel) { result.parallel ??= nxJson.parallel; } if (nxJson.cacheDirectory) { result.cacheDirectory ??= nxJson.cacheDirectory; } if (defaultCacheableOperations.length) { result.cacheableOperations ??= []; result.cacheableOperations = result.cacheableOperations.concat(defaultCacheableOperations); } if (nxJson.useDaemonProcess !== undefined) { result.useDaemonProcess ??= nxJson.useDaemonProcess; } return result; } function isCustomRunnerPath(modulePath) { return ![ 'nx-cloud', '@nrwl/nx-cloud', 'nx/tasks-runners/default', defaultTasksRunnerPath, ].includes(modulePath); }