nx
Version:
219 lines (218 loc) • 10.5 kB
JavaScript
;
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' };
}