UNPKG

nx

Version:

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

219 lines (218 loc) 10.5 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.MULTI_MAJOR_MODE_FLAG = void 0; exports.isNxTarget = isNxTarget; exports.maybePromptOrWarnMultiMajorMigration = maybePromptOrWarnMultiMajorMigration; const safe_prompt_1 = require("./safe-prompt"); const semver_1 = require("semver"); const installed_nx_version_1 = require("../../utils/installed-nx-version"); const output_1 = require("../../utils/output"); const resolve_package_version_1 = require("./resolve-package-version"); const migrate_analytics_1 = require("./migrate-analytics"); const version_utils_1 = require("./version-utils"); const INCREMENTAL_UPDATE_GUIDE_URL = 'https://nx.dev/docs/guides/tips-n-tricks/advanced-update#one-major-version-at-a-time-small-steps'; exports.MULTI_MAJOR_MODE_FLAG = '--multi-major-mode'; const MULTI_MAJOR_MODE_ENV = 'NX_MULTI_MAJOR_MODE'; // Caret-major (`^X.0.0`) excludes prereleases per semver, so the registry // resolution returns the highest stable in major X. async function resolveLatestStableInMajor(packageName, majorVersion) { try { // Probe only: this resolves prompt choices the user may never pick, so it // must not prompt or write pnpm excludes. The chosen version is resolved // again (with side effects) when migrations actually run. const resolved = await (0, resolve_package_version_1.resolvePackageVersionRespectingMinReleaseAge)(packageName, `^${majorVersion}.0.0`, { applySideEffects: false }); return (0, semver_1.valid)(resolved) ? resolved : null; } catch { return null; } } const multiMajorHeader = (pkg, installed, target) => `Migrating across multiple major versions: ${pkg}@${installed}${pkg}@${target}.`; const multiMajorBodyLines = [ `The recommended process is to update one major version at a time, in small steps.`, `See ${INCREMENTAL_UPDATE_GUIDE_URL}`, ]; function warnMultiMajorMigration(targetPackage, installed, target) { output_1.output.warn({ title: multiMajorHeader(targetPackage, installed, target), bodyLines: [ ...multiMajorBodyLines, `Pass ${exports.MULTI_MAJOR_MODE_FLAG}=direct (or =gradual) or set ${MULTI_MAJOR_MODE_ENV} to silence this warning.`, ], }); } function logGradualStep(targetPackage, step, target) { // Status-only announcement. The follow-up instruction ("re-run `nx migrate` // to continue toward the original target") lives in the Next Steps block at // the end of the run, where it's adjacent to the other re-run guidance and // not scrolled out of view by the migration output. output_1.output.log({ title: `Migrating to ${targetPackage}@${step} (one step toward ${targetPackage}@${target}).`, }); } function warnGradualUnavailable(targetPackage, target, reason) { output_1.output.warn({ title: `Could not look up incremental migration options for ${exports.MULTI_MAJOR_MODE_FLAG}=gradual. Proceeding directly to ${targetPackage}@${target}.`, bodyLines: [reason], }); } // Returns the chosen target version. Caller replaces `targetVersion` with it. // At least one of `latestInCurrent`/`latestInNext` must be present. async function promptMultiMajorMigration(args) { const choices = []; let recommendedMarked = false; if (args.latestInCurrent) { choices.push({ name: args.latestInCurrent, message: `Migrate to ${args.targetPackage}@${args.latestInCurrent} (latest in current major) [recommended]`, }); recommendedMarked = true; } if (args.latestInNext) { choices.push({ name: args.latestInNext, message: `Migrate to ${args.targetPackage}@${args.latestInNext} (next major)${recommendedMarked ? '' : ' [recommended]'}`, }); } choices.push({ name: args.target, message: `Migrate directly to ${args.targetPackage}@${args.target}`, }); output_1.output.log({ title: multiMajorHeader(args.targetPackage, args.installed, args.target), bodyLines: multiMajorBodyLines, }); const { chosen } = await (0, safe_prompt_1.migratePrompt)({ type: 'select', name: 'chosen', message: 'How would you like to proceed?', choices, }); return chosen; } // Flag wins over env var; only the two literal values are honoured. function resolveMultiMajorMode(options) { if (options.multiMajorMode === 'direct' || options.multiMajorMode === 'gradual') { return options.multiMajorMode; } const env = process.env[MULTI_MAJOR_MODE_ENV]; if (env === 'direct' || env === 'gradual') return env; return undefined; } // Whether the target is the canonical Nx package for its era (`@nrwl/workspace` // for legacy `< 14`, `nx`/`@nx/workspace` otherwise). function isNxTarget(targetPackage, targetVersion) { if ((0, version_utils_1.isLegacyEra)(targetVersion)) { return targetPackage === '@nrwl/workspace'; } return targetPackage === 'nx' || targetPackage === '@nx/workspace'; } async function maybePromptOrWarnMultiMajorMigration(args) { const { include, options, targetPackage } = args; let { targetVersion } = args; if (include === 'optional') return { chosen: targetVersion }; const multiMajorMode = resolveMultiMajorMode(options); if (multiMajorMode === 'direct') { return { chosen: targetVersion, decision: 'direct' }; } // The multi-major catch-up only applies to Nx's own versioned cascade, so it // keys off the canonical Nx package identity, not `--include` eligibility. if (!isNxTarget(targetPackage, targetVersion)) { return { chosen: targetVersion }; } // Bare-package-name positionals (e.g. `nx migrate nx`, `nx migrate // @nx/workspace`) leave `targetVersion` as the literal `'latest'` because // `parseTargetPackageAndVersion` only resolves dist-tags via the registry // when they appear standalone or after `@`. Resolve here so the remaining // semver gates (and the subsequent walk) see a concrete version. if (version_utils_1.DIST_TAGS.includes(targetVersion)) { try { targetVersion = await (0, version_utils_1.normalizeVersionWithTagCheck)(targetPackage, targetVersion); } catch { if (multiMajorMode === 'gradual') { warnGradualUnavailable(targetPackage, targetVersion, `Failed to resolve the '${targetVersion}' dist-tag against the registry.`); } return { chosen: targetVersion }; } } if (!(0, semver_1.valid)(targetVersion) || (0, version_utils_1.isLegacyEra)(targetVersion)) { return { chosen: targetVersion }; } const installed = (0, installed_nx_version_1.getInstalledNxVersion)(); if (!installed || !(0, semver_1.valid)(installed)) return { chosen: targetVersion }; // Legacy-era installs are out of scope for the multi-major check. if ((0, version_utils_1.isLegacyEra)(installed)) return { chosen: targetVersion }; const installedMajor = (0, semver_1.major)(installed); if ((0, semver_1.major)(targetVersion) - installedMajor < 2) { return { chosen: targetVersion }; } const interactive = (0, safe_prompt_1.canPrompt)(options.interactive); // Non-interactive (non-TTY, CI, or --no-interactive) without gradual opt-in // stays on the warn-only path; avoid the registry round-trip used to look // up incremental migration options. if (!interactive && multiMajorMode !== 'gradual') { warnMultiMajorMigration(targetPackage, installed, targetVersion); return { chosen: targetVersion, decision: 'direct' }; } const [latestInCurrent, latestInNext] = await Promise.all([ resolveLatestStableInMajor(targetPackage, installedMajor), resolveLatestStableInMajor(targetPackage, installedMajor + 1), ]); // Only suggest the current-major latest when there's at least a minor // delta — a same-minor patch bump isn't a meaningful incremental step. Skip // major 22 (the last release before the v23 era): a within-22 step doesn't // advance toward a v23+ target, so suggest the next major instead. const showCurrent = installedMajor !== 22 && latestInCurrent && (0, semver_1.gt)(latestInCurrent, installed) && (0, semver_1.minor)(latestInCurrent) > (0, semver_1.minor)(installed) ? latestInCurrent : null; if (multiMajorMode === 'gradual') { const step = showCurrent ?? latestInNext; if (step) { logGradualStep(targetPackage, step, targetVersion); return { chosen: step, originalTarget: targetVersion, gradual: true, decision: 'gradual', }; } // Registry returned no eligible incremental version (or the lookup // failed); without a step to land on, gradual silently degrades to direct. // Surface that explicitly so the safety rail the user opted into isn't // invisibly disabled. warnGradualUnavailable(targetPackage, targetVersion, `Could not find an eligible version in major ${installedMajor} or ${installedMajor + 1} (registry lookup returned no result or failed).`); return { chosen: targetVersion, decision: 'direct' }; } if (interactive && (showCurrent || latestInNext)) { const chosen = await promptMultiMajorMigration({ targetPackage, installed, target: targetVersion, latestInCurrent: showCurrent, latestInNext, }); // The prompt returns a concrete version; map it back to the option the // user picked for the prompt event's choice, then collapse to // gradual/direct for the returned decision (any incremental step counts // as gradual). (0, migrate_analytics_1.reportMigratePrompt)('multi_major', chosen === targetVersion ? 'direct' : chosen === showCurrent ? 'latest-in-current' : 'latest-in-next'); return chosen !== targetVersion ? { chosen, originalTarget: targetVersion, decision: 'gradual' } : { chosen, decision: 'direct' }; } warnMultiMajorMigration(targetPackage, installed, targetVersion); return { chosen: targetVersion, decision: 'direct' }; }