UNPKG

@angular/cli

Version:
918 lines • 38.7 kB
"use strict"; /** * @license * Copyright Google LLC All Rights Reserved. * * Use of this source code is governed by an MIT-style license that can be * found in the LICENSE file at https://angular.dev/license */ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { Object.defineProperty(o, "default", { enumerable: true, value: v }); }) : function(o, v) { o["default"] = v; }); var __importStar = (this && this.__importStar) || (function () { var ownKeys = function(o) { ownKeys = Object.getOwnPropertyNames || function (o) { var ar = []; for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k; return ar; }; return ownKeys(o); }; return function (mod) { if (mod && mod.__esModule) return mod; var result = {}; if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]); __setModuleDefault(result, mod); return result; }; })(); var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); exports.RegistryClient = void 0; exports.getSatisfyingVersion = getSatisfyingVersion; exports.angularMajorCompatGuarantee = angularMajorCompatGuarantee; exports.isPnpActive = isPnpActive; exports.findPackageJson = findPackageJson; exports.resolveUserUpdatePlan = resolveUserUpdatePlan; exports.printUpdateUsageMessage = printUpdateUsageMessage; exports.applyUpdatePlan = applyUpdatePlan; const core_1 = require("@angular-devkit/core"); const node_fs_1 = require("node:fs"); const node_module_1 = require("node:module"); const path = __importStar(require("node:path")); const npm_package_arg_1 = __importDefault(require("npm-package-arg")); const semver = __importStar(require("semver")); class RegistryClient { packageManager; logger; minReleaseAge; getRegistryName; metadataCache = new Map(); manifestCache = new Map(); constructor(packageManager, logger, minReleaseAge = 0, getRegistryName) { this.packageManager = packageManager; this.logger = logger; this.minReleaseAge = minReleaseAge; this.getRegistryName = getRegistryName; } async getMetadata(packageName) { const registryName = this.getRegistryName ? this.getRegistryName(packageName) : packageName; let promise = this.metadataCache.get(registryName); if (!promise) { promise = this.packageManager.getRegistryMetadata(registryName).catch((e) => { this.metadataCache.delete(registryName); throw e; }); this.metadataCache.set(registryName, promise); } const metadata = await promise; if (metadata && registryName !== packageName) { return { ...metadata, name: packageName }; } return metadata; } async getManifest(packageName, version) { const registryName = this.getRegistryName ? this.getRegistryName(packageName) : packageName; const key = `${registryName}@${version}`; let promise = this.manifestCache.get(key); if (!promise) { promise = this.packageManager.getRegistryManifest(registryName, version).catch((e) => { this.manifestCache.delete(key); throw e; }); this.manifestCache.set(key, promise); } const manifest = await promise; if (manifest && registryName !== packageName) { return { ...manifest, name: packageName }; } return manifest; } } exports.RegistryClient = RegistryClient; function isReleaseAgeSatisfied(registryClient, metadata, version) { const minReleaseAge = registryClient.minReleaseAge; if (!minReleaseAge || !metadata.time) { return true; } const publishTimeStr = metadata.time[version]; if (!publishTimeStr) { return true; } const publishTime = Date.parse(publishTimeStr); if (isNaN(publishTime)) { return true; } return Date.now() - publishTime >= minReleaseAge; } async function getSatisfyingVersion(registryClient, metadata, range, next) { const options = { includePrerelease: next || undefined }; let candidates = metadata.versions.filter((v) => semver.satisfies(v, range, options)); candidates = candidates.filter((version) => isReleaseAgeSatisfied(registryClient, metadata, version)); const sorted = semver.rsort(candidates); for (const version of sorted) { const manifest = await registryClient.getManifest(metadata.name, version); if (manifest && !manifest.deprecated) { return version; } } // Fallback to deprecated versions if no non-deprecated version satisfies for (const version of sorted) { const manifest = await registryClient.getManifest(metadata.name, version); if (manifest) { return version; } } return null; } function angularMajorCompatGuarantee(range) { let newRange = semver.validRange(range); if (!newRange) { return range; } let major = 1; while (!semver.gtr(major + '.0.0', newRange)) { major++; if (major >= 99) { return newRange; } } newRange = range; for (let minor = 0; minor < 20; minor++) { newRange += ` || ^${major}.${minor}.0-alpha.0 `; } return semver.validRange(newRange) || range; } const knownPeerCompatibleList = { '@angular/core': angularMajorCompatGuarantee, }; function _updatePeerVersion(infoMap, name, range) { const maybePackageInfo = infoMap.get(name); if (!maybePackageInfo) { return range; } if (maybePackageInfo.target) { name = maybePackageInfo.target.updateMetadata.packageGroupName || name; } else { name = maybePackageInfo.installed.updateMetadata.packageGroupName || name; } const maybeTransform = knownPeerCompatibleList[name]; if (maybeTransform) { if (typeof maybeTransform == 'function') { return maybeTransform(range); } else { return maybeTransform; } } return range; } function _validateForwardPeerDependencies(name, infoMap, logger) { let error = false; const info = infoMap.get(name); if (!info || !info.target) { return error; } const peerDependencies = info.target.packageJson.peerDependencies || {}; const peerDependenciesMeta = info.target.packageJson.peerDependenciesMeta || {}; for (const [peer, range] of Object.entries(peerDependencies)) { const peerInfo = infoMap.get(peer); if (!peerInfo) { continue; } const isOptional = !!peerDependenciesMeta[peer]?.optional; const resolvedRange = _updatePeerVersion(infoMap, peer, range); const resolvedVersion = peerInfo.target ? peerInfo.target.version : peerInfo.installed.version; if (!semver.satisfies(resolvedVersion, resolvedRange, { includePrerelease: true })) { logger.error(`Package ${JSON.stringify(name)} has an incompatible peer dependency to ` + `${JSON.stringify(peer)} (requires ${JSON.stringify(range)}, ` + `would install ${JSON.stringify(resolvedVersion)}).`); error = error || !isOptional; } } return error; } function _validateReversePeerDependencies(name, version, infoMap, logger, next) { let error = false; for (const [installed, installedInfo] of infoMap.entries()) { const installedLogger = logger.createChild(installed); installedLogger.debug(`${installed}...`); const peers = (installedInfo.target || installedInfo.installed).packageJson.peerDependencies; const peersMeta = (installedInfo.target || installedInfo.installed).packageJson .peerDependenciesMeta; for (const [peer, range] of Object.entries(peers || {})) { if (peer !== name) { continue; } const isOptional = !!peersMeta?.[peer]?.optional; const resolvedRange = _updatePeerVersion(infoMap, name, range); if (!semver.satisfies(version, resolvedRange, { includePrerelease: next || undefined })) { logger.error(`Package ${JSON.stringify(installed)} has an incompatible peer dependency to ` + `${JSON.stringify(name)} (requires ${JSON.stringify(range)}, ` + `would install ${JSON.stringify(version)}).`); error = error || !isOptional; } } } return error; } function _validateUpdatePackages(infoMap, force, next, logger) { logger.debug('Validating peer dependencies...'); let error = false; for (const name of infoMap.keys()) { const info = infoMap.get(name); if (!info || !info.target) { continue; } logger.debug(`Checking ${name}...`); error = _validateForwardPeerDependencies(name, infoMap, logger) || error; error = _validateReversePeerDependencies(name, info.target.version, infoMap, logger, next) || error; } if (error && !force) { throw new Error('Incompatible peer dependencies found. See above for details. ' + 'You can bypass this check using the --force option.'); } } function _getUpdateMetadata(packageJson, logger) { const metadata = packageJson['ng-update']; const result = { packageGroup: {}, requirements: {}, }; if (!metadata || typeof metadata != 'object' || Array.isArray(metadata)) { return result; } if (metadata['packageGroup']) { const packageGroup = metadata['packageGroup']; if (Array.isArray(packageGroup) && packageGroup.every((x) => typeof x == 'string')) { result.packageGroup = packageGroup.reduce((group, name) => { group[name] = packageJson.version; return group; }, {}); } else if (typeof packageGroup == 'object' && packageGroup !== null) { result.packageGroup = Object.entries(packageGroup).reduce((group, [name, version]) => { if (typeof version == 'string') { group[name] = version; } return group; }, {}); } else { logger.warn(`PackageGroup metadata for ${packageJson.name} is malformed. Ignoring.`); } } if (typeof metadata['packageGroupName'] == 'string') { result.packageGroupName = metadata['packageGroupName']; } if (typeof metadata['migrations'] == 'string') { result.migrations = metadata['migrations']; } return result; } function isPnpActive(workspaceRoot) { return (process.versions.pnp !== undefined || (0, node_fs_1.existsSync)(path.join(workspaceRoot, '.pnp.cjs')) || (0, node_fs_1.existsSync)(path.join(workspaceRoot, '.pnp.js'))); } function findPackageJson(workspaceDir, packageName) { if (isPnpActive(workspaceDir)) { try { const workspaceRequire = (0, node_module_1.createRequire)(path.join(workspaceDir, 'package.json')); return workspaceRequire.resolve(`${packageName}/package.json`); } catch { return undefined; } } let currentDir = workspaceDir; while (true) { const candidatePath = path.join(currentDir, 'node_modules', packageName, 'package.json'); if ((0, node_fs_1.existsSync)(candidatePath)) { return (0, node_fs_1.realpathSync)(candidatePath); } const parentDir = path.dirname(currentDir); if (parentDir === currentDir) { break; } currentDir = parentDir; } return undefined; } function getInstalledPackageJson(packageName, workspaceRoot) { try { const manifestPath = findPackageJson(workspaceRoot, packageName); if (manifestPath) { const content = (0, node_fs_1.readFileSync)(manifestPath, 'utf8'); return JSON.parse(content); } } catch { } return null; } function getInstalledVersion(packageName, workspaceRoot) { const pkgJson = getInstalledPackageJson(packageName, workspaceRoot); return pkgJson?.version ?? null; } function _buildLocalPackageInfo(name, allDependencies, workspaceRoot) { const packageJsonRange = allDependencies.get(name); if (!packageJsonRange) { throw new Error(`Package ${JSON.stringify(name)} was not found in package.json.`); } const localPkgJson = getInstalledPackageJson(name, workspaceRoot); if (!localPkgJson) { throw new Error(`Package ${name} is not installed.`); } const installedVersion = localPkgJson.version; const npmPackageJson = { name, versions: [installedVersion], 'dist-tags': {}, }; const logger = new core_1.logging.NullLogger(); return { name, npmPackageJson, installed: { version: installedVersion, packageJson: localPkgJson, updateMetadata: _getUpdateMetadata(localPkgJson, logger), }, packageJsonRange, }; } async function _buildPackageInfo(packages, allDependencies, npmPackageJson, workspaceRoot, registryClient, logger) { const name = npmPackageJson.name; const packageJsonRange = allDependencies.get(name); if (!packageJsonRange) { throw new Error(`Package ${JSON.stringify(name)} was not found in package.json.`); } const localPkgJson = getInstalledPackageJson(name, workspaceRoot); let installedVersion = localPkgJson?.version; if (!installedVersion) { installedVersion = (await getSatisfyingVersion(registryClient, npmPackageJson, packageJsonRange)); } if (!installedVersion) { throw new Error(`An unexpected error happened; could not determine version for package ${name}.`); } const installedPackageJson = localPkgJson || (await registryClient.getManifest(name, installedVersion)); if (!installedPackageJson) { throw new Error(`An unexpected error happened; package ${name} has no version ${installedVersion}.`); } let targetVersion = packages.get(name); if (targetVersion) { const distTags = npmPackageJson['dist-tags'] ?? {}; let resolvedVersion = distTags[targetVersion] ?? (targetVersion === 'next' ? distTags['latest'] : undefined); if (resolvedVersion && !isReleaseAgeSatisfied(registryClient, npmPackageJson, resolvedVersion)) { resolvedVersion = undefined; } if (resolvedVersion) { targetVersion = resolvedVersion; } else { targetVersion = (await getSatisfyingVersion(registryClient, npmPackageJson, distTags[targetVersion] || targetVersion === 'next' ? '*' : targetVersion)); } } if (targetVersion && semver.lte(targetVersion, installedVersion)) { logger.debug(`Package ${name} already satisfied by package.json (${packageJsonRange}).`); targetVersion = undefined; } let target; if (targetVersion) { const targetPackageJson = await registryClient.getManifest(name, targetVersion); if (targetPackageJson) { target = { version: targetVersion, packageJson: targetPackageJson, updateMetadata: _getUpdateMetadata(targetPackageJson, logger), }; } } return { name, npmPackageJson, installed: { version: installedVersion, packageJson: installedPackageJson, updateMetadata: _getUpdateMetadata(installedPackageJson, logger), }, target, packageJsonRange, }; } function splitPackageName(pkg) { let name = pkg; let version; if (pkg.startsWith('@')) { const parts = pkg.split('@'); name = '@' + parts[1]; version = parts[2]; } else if (pkg.includes('@')) { const parts = pkg.split('@'); name = parts[0]; version = parts[1]; } return { name, version }; } function _buildPackageList(options, allDependencies, logger) { const packages = new Map(); const inputPackages = options.packages ?? []; if (inputPackages.length === 0) { return packages; } for (const pkg of inputPackages) { const { name: pkgName, version: pkgVersion } = splitPackageName(pkg); if (!allDependencies.has(pkgName)) { throw new Error(`Package ${JSON.stringify(pkgName)} is not in package.json.`); } let targetVersion = pkgVersion; if (options.migrateOnly && !targetVersion && options.from) { targetVersion = options.from; } packages.set(pkgName, (targetVersion || (options.next ? 'next' : 'latest'))); } return packages; } async function resolvePackageVersion(registryClient, metadata, range, next = false) { const distTags = metadata['dist-tags'] ?? {}; let resolvedVersion = distTags[range] ?? (range === 'next' ? distTags['latest'] : undefined); if (resolvedVersion && !isReleaseAgeSatisfied(registryClient, metadata, resolvedVersion)) { resolvedVersion = undefined; } if (resolvedVersion) { return resolvedVersion; } return getSatisfyingVersion(registryClient, metadata, distTags[range] || range === 'next' ? '*' : range, next); } async function _addPackageGroup(packages, allDependencies, metadata, registryClient, logger) { const maybePackage = packages.get(metadata.name); if (!maybePackage) { return; } const distTags = metadata['dist-tags'] ?? {}; let version = maybePackage; let resolvedVersion = distTags[version] ?? (version === 'next' ? distTags['latest'] : undefined); if (resolvedVersion && !isReleaseAgeSatisfied(registryClient, metadata, resolvedVersion)) { resolvedVersion = undefined; } if (resolvedVersion) { version = resolvedVersion; } else { version = (await getSatisfyingVersion(registryClient, metadata, distTags[version] || version === 'next' ? '*' : version)) ?? version; } const packageJson = await registryClient.getManifest(metadata.name, version); if (!packageJson) { return; } const ngUpdateMetadata = packageJson['ng-update']; if (!ngUpdateMetadata) { return; } const packageGroup = ngUpdateMetadata['packageGroup']; if (!packageGroup) { return; } let packageGroupNormalized; if (Array.isArray(packageGroup) && !packageGroup.some((x) => typeof x != 'string')) { packageGroupNormalized = packageGroup.reduce((acc, curr) => { acc[curr] = version; return acc; }, {}); } else if (typeof packageGroup === 'object' && packageGroup !== null) { packageGroupNormalized = Object.entries(packageGroup).reduce((acc, [name, v]) => { if (typeof v === 'string') { acc[name] = v; } return acc; }, {}); } else { logger.warn(`PackageGroup metadata for ${metadata.name} is malformed. Ignoring.`); return; } for (const [member, memberVersion] of Object.entries(packageGroupNormalized)) { if (packages.has(member)) { continue; } if (allDependencies.has(member)) { packages.set(member, memberVersion); } } } async function _addPeerDependencies(packages, allDependencies, npmPackageJson, workspaceRoot, registryClient, logger) { const maybePackage = packages.get(npmPackageJson.name); if (!maybePackage) { return; } const distTags = npmPackageJson['dist-tags'] ?? {}; const version = distTags[maybePackage] || maybePackage; const packageJson = await registryClient.getManifest(npmPackageJson.name, version); if (!packageJson) { return; } for (const [peer, range] of Object.entries(packageJson.peerDependencies || {})) { if (packages.has(peer)) { continue; } const installedVersion = getInstalledVersion(peer, workspaceRoot); if (installedVersion) { if (semver.satisfies(installedVersion, range)) { continue; } } else { const packageJsonRange = allDependencies.get(peer); if (packageJsonRange) { const peerMetadata = await registryClient.getMetadata(peer); if (peerMetadata) { const resolvedInstalledVersion = await getSatisfyingVersion(registryClient, peerMetadata, packageJsonRange); if (resolvedInstalledVersion && semver.satisfies(resolvedInstalledVersion, range)) { continue; } } } } packages.set(peer, range); } } function _formatVersion(v) { if (v === undefined) { return v; } if (semver.valid(v)) { return v; } const coerced = semver.coerce(v); return coerced ? coerced.toString() : undefined; } function getRegistryNameAndRange(name, specifier) { try { const result = npm_package_arg_1.default.resolve(name, specifier); if (result.type === 'alias' && result.subSpec) { return { name: result.subSpec.name ?? name, range: result.subSpec.fetchSpec ?? specifier, }; } } catch { } return { name, range: specifier }; } function isPkgFromRegistry(name, specifier) { const result = npm_package_arg_1.default.resolve(name, specifier); return !!result.registry; } async function checkCatalogUpdates(normalizedPackages, packageJsonContent, registryClient, workspaceRoot, options) { const catalogUpdates = []; for (const requestedPkg of normalizedPackages) { const { name: pkgName } = splitPackageName(requestedPkg); const specifier = packageJsonContent.dependencies?.[pkgName] || packageJsonContent.devDependencies?.[pkgName] || packageJsonContent.peerDependencies?.[pkgName]; if (specifier?.startsWith('catalog:')) { const current = getInstalledVersion(pkgName, workspaceRoot) ?? 'unknown'; let target = 'latest'; try { const metadata = await registryClient.getMetadata(pkgName); if (metadata) { const resolved = await resolvePackageVersion(registryClient, metadata, options.next ? 'next' : 'latest', !!options.next); target = resolved ?? 'latest'; } } catch { // Fallback to 'latest' tag } catalogUpdates.push({ name: pkgName, current, target, specifier }); } } if (catalogUpdates.length > 0) { const packageManagerName = options.packageManager ?? 'your package manager'; const installCmd = packageManagerName === 'yarn' ? 'yarn install' : 'pnpm install'; const updatesList = catalogUpdates .map((pkg) => ` - ${pkg.name} (${pkg.specifier}) -> Target version: ${pkg.target}`) .join('\n'); const migrationCommands = catalogUpdates .map((pkg) => { const fromVer = pkg.current === 'unknown' ? '<current-version>' : pkg.current; return ` ng update ${pkg.name} --migrate-only --from ${fromVer}`; }) .join('\n'); throw new Error(`The following packages to update are configured to use \`catalog:\`:\n` + `${updatesList}\n\n` + `Because catalogs are shared across the monorepo, 'ng update' cannot modify them directly.\n` + `Please perform the following steps to update:\n` + ` 1. Manually update the versions for these packages in your catalog configuration file ` + `(e.g., pnpm-workspace.yaml or .yarnrc.yml).\n` + ` 2. Run '${installCmd}' to install the updated versions.\n` + ` 3. Run the following command(s) from the workspace root to execute the migration schematics:\n` + `${migrationCommands}`); } } async function resolveUserUpdatePlan(options, packageManager, logger) { const workspaceRoot = options.workspaceRoot ?? process.cwd(); const packageJsonPath = path.join(workspaceRoot, 'package.json'); if (!(0, node_fs_1.existsSync)(packageJsonPath)) { throw new Error('Could not find a package.json. Are you in a Node project?'); } const rawJson = (0, node_fs_1.readFileSync)(packageJsonPath, 'utf8'); const packageJsonContent = JSON.parse(rawJson); const getDependencies = (deps) => Object.entries(deps ?? {}).map(([name, range]) => [name, range]); const allRawDeps = [ ...getDependencies(packageJsonContent.dependencies), ...getDependencies(packageJsonContent.devDependencies), ...getDependencies(packageJsonContent.peerDependencies), ]; const npmDeps = new Map(allRawDeps.filter(([name, specifier]) => { try { return isPkgFromRegistry(name, specifier); } catch { logger.warn(`Package ${name} was not found on the registry. Skipping.`); return false; } })); const packagesOption = options.packages ?? []; const normalizedPackages = packagesOption.reduce((acc, curr) => { return acc.concat(curr.split(',')); }, []); options.packages = normalizedPackages; if (options.migrateOnly && options.from) { if (options.packages.length !== 1) { throw new Error('--from requires that only a single package be passed.'); } } options.from = _formatVersion(options.from); options.to = _formatVersion(options.to); const usingYarn = options.packageManager === 'yarn'; const minReleaseAge = await packageManager.getMinimumReleaseAge(); const getRegistryName = (name) => { const specifier = npmDeps.get(name); if (specifier) { return getRegistryNameAndRange(name, specifier).name; } return name; }; const registryClient = new RegistryClient(packageManager, logger, minReleaseAge, getRegistryName); await checkCatalogUpdates(normalizedPackages, packageJsonContent, registryClient, workspaceRoot, options); const packages = _buildPackageList(options, npmDeps, logger); const getOrFetchPackageMetadata = async (packageName) => { return registryClient.getMetadata(packageName); }; if (packages.size === 0) { await Promise.all(Array.from(npmDeps.keys(), (depName) => getOrFetchPackageMetadata(depName))); } else { let lastPackagesSize; do { lastPackagesSize = packages.size; let lastGroupSize; do { lastGroupSize = packages.size; for (const name of Array.from(packages.keys())) { const metadata = await getOrFetchPackageMetadata(name); const spec = packages.get(name); if (metadata && spec) { const resolvedVersion = await resolvePackageVersion(registryClient, metadata, spec, !!options.next); if (resolvedVersion) { packages.set(name, resolvedVersion); } await _addPackageGroup(packages, npmDeps, metadata, registryClient, logger); } } } while (packages.size > lastGroupSize); for (const name of Array.from(packages.keys())) { const metadata = await getOrFetchPackageMetadata(name); const spec = packages.get(name); if (metadata && spec) { const resolvedVersion = await resolvePackageVersion(registryClient, metadata, spec, !!options.next); if (resolvedVersion) { packages.set(name, resolvedVersion); } await _addPeerDependencies(packages, npmDeps, metadata, workspaceRoot, registryClient, logger); } } } while (packages.size > lastPackagesSize); } const isListingUpdates = packages.size === 0; const packageInfoEntries = await Promise.all(Array.from(npmDeps.keys(), async (depName) => { const isUpdating = packages.has(depName); const localPkgJson = getInstalledPackageJson(depName, workspaceRoot); if (isListingUpdates || isUpdating || !localPkgJson) { const metadata = await getOrFetchPackageMetadata(depName); if (metadata) { const info = await _buildPackageInfo(packages, npmDeps, metadata, workspaceRoot, registryClient, logger); return [depName, info]; } } return [depName, _buildLocalPackageInfo(depName, npmDeps, workspaceRoot)]; })); const packageInfoMap = new Map(packageInfoEntries); const packagesToUpdate = new Map(); const migrationsToRun = []; if (packages.size > 0) { if (!(options.migrateOnly && options.from && options.packages)) { const sublog = new core_1.logging.LevelCapLogger('validation', logger.createChild(''), 'warn'); _validateUpdatePackages(packageInfoMap, !!options.force, !!options.next, sublog); for (const [name, info] of packageInfoMap.entries()) { if (!info.target || !info.installed) { continue; } packagesToUpdate.set(name, info.target.version); if (info.target.updateMetadata.migrations) { migrationsToRun.push({ package: name, collection: info.target.updateMetadata.migrations, from: info.installed.version, to: info.target.version, }); } } } } return { packagesToUpdate, migrationsToRun, packageInfoMap, registryClient, }; } async function printUpdateUsageMessage(infoMap, registryClient, logger, next = false) { const packageGroups = new Map(); const mappedPackages = await Promise.all(Array.from(infoMap.entries(), async ([name, info]) => { const distTags = info.npmPackageJson['dist-tags'] ?? {}; let tag = next ? (distTags['next'] ? 'next' : 'latest') : 'latest'; let version = distTags[tag] ?? info.installed.version; const versions = info.npmPackageJson.versions ?? []; const versionDiff = semver.diff(info.installed.version, version); if (versionDiff !== 'patch' && versionDiff !== 'minor' && /^@(?:angular|nguniversal)\//.test(name)) { const installedMajorVersion = semver.parse(info.installed.version)?.major; const toInstallMajorVersion = semver.parse(version)?.major; if (installedMajorVersion !== undefined && toInstallMajorVersion !== undefined && installedMajorVersion < toInstallMajorVersion - 1) { const nextMajorVersion = `${installedMajorVersion + 1}.`; const nextMajorVersions = versions .filter((v) => v.startsWith(nextMajorVersion)) .sort((a, b) => (a > b ? -1 : 1)); if (nextMajorVersions.length) { version = nextMajorVersions[0]; tag = ''; } } } const target = info.target?.packageJson || (await registryClient.getManifest(name, version)); return { name, info, version, tag, target, }; })); const packagesToUpdate = mappedPackages .filter(({ info, version, target }) => target?.['ng-update'] && semver.compare(info.installed.version, version) < 0) .map(({ name, info, version, tag, target }) => { // Look for packageGroup. const ngUpdate = target?.['ng-update']; const packageGroup = ngUpdate?.['packageGroup']; if (packageGroup) { const packageGroupNames = Array.isArray(packageGroup) ? packageGroup : Object.keys(packageGroup); const packageGroupName = ngUpdate?.['packageGroupName'] || packageGroupNames.find((n) => infoMap.has(n)); if (packageGroupName) { if (packageGroups.has(name)) { return null; } for (const groupName of packageGroupNames) { packageGroups.set(groupName, packageGroupName); } packageGroups.set(packageGroupName, packageGroupName); name = packageGroupName; } } let command = `ng update ${name}`; if (!tag) { command += `@${semver.parse(version)?.major || version}`; } else if (tag == 'next') { command += ' --next'; } return [name, `${info.installed.version} -> ${version} `, command]; }) .filter((x) => x !== null) .sort((a, b) => a[0].localeCompare(b[0])); if (packagesToUpdate.length == 0) { logger.info('We analyzed your package.json and everything seems to be in order. Good work!'); return; } logger.info('We analyzed your package.json, there are some packages to update:\n'); // Find the largest name to know the padding needed. let namePad = Math.max(...[...infoMap.keys()].map((x) => x.length)) + 2; if (!Number.isFinite(namePad)) { namePad = 30; } const pads = [namePad, 25, 0]; logger.info(' ' + ['Name', 'Version', 'Command to update'].map((x, i) => x.padEnd(pads[i])).join('')); const totalWidth = pads.reduce((sum, width) => sum + width, 20); logger.info(` ${'-'.repeat(totalWidth)}`); packagesToUpdate.forEach((fields) => { if (!fields) { return; } logger.info(' ' + fields.map((x, i) => x.padEnd(pads[i])).join('')); }); logger.info(`\nThere might be additional packages which don't provide 'ng update' capabilities that are outdated.\n` + `You can update the additional packages by running the update command of your package manager.`); } async function applyUpdatePlan(workspaceRoot, plan, logger) { const packageJsonPath = path.join(workspaceRoot, 'package.json'); const packageJsonContent = await node_fs_1.promises.readFile(packageJsonPath, 'utf8'); const packageJson = JSON.parse(packageJsonContent); const updateDependency = (deps, name, newVersion) => { const oldVersion = deps[name]; const aliasPrefix = 'npm:'; // If the dependency uses an npm package alias (e.g., "npm:registry-name@version-range"), // parse and reconstruct the alias with the new target version while preserving // the original alias registry name and any version prefix character (like ^ or ~). if (oldVersion.startsWith(aliasPrefix)) { const specifier = oldVersion.slice(aliasPrefix.length); const lastAtIndex = specifier.lastIndexOf('@'); if (lastAtIndex > 0) { const registryName = specifier.slice(0, lastAtIndex); const versionRange = specifier.slice(lastAtIndex + 1); // Retain any semantic versioning operator prefix (e.g., ^ or ~) from the target version range. const execResult = /^[\^~]/.exec(versionRange); deps[name] = `${aliasPrefix}${registryName}@${execResult ? execResult[0] : ''}${newVersion}`; } else { // If there's no `@` character defining a version specifier in the alias (e.g. "npm:packageName"), // leave it as-is without attempting to inject a version suffix. deps[name] = oldVersion; } } else { // Standard dependency formatting, keeping any semantic versioning operator prefix (e.g., ^ or ~). const execResult = /^[\^~]/.exec(oldVersion); deps[name] = `${execResult ? execResult[0] : ''}${newVersion}`; } }; for (const [name, targetVersion] of plan.packagesToUpdate.entries()) { logger.info(`Updating package.json with dependency ${name} to version ${targetVersion}...`); if (packageJson.dependencies && packageJson.dependencies[name]) { updateDependency(packageJson.dependencies, name, targetVersion); if (packageJson.devDependencies) { delete packageJson.devDependencies[name]; } if (packageJson.peerDependencies) { delete packageJson.peerDependencies[name]; } } else if (packageJson.devDependencies && packageJson.devDependencies[name]) { updateDependency(packageJson.devDependencies, name, targetVersion); if (packageJson.peerDependencies) { delete packageJson.peerDependencies[name]; } } else if (packageJson.peerDependencies && packageJson.peerDependencies[name]) { updateDependency(packageJson.peerDependencies, name, targetVersion); } else { if (!packageJson.dependencies) { packageJson.dependencies = {}; } packageJson.dependencies[name] = `^${targetVersion}`; } } const eofMatches = packageJsonContent.match(/\r?\n$/); const eof = eofMatches?.[0] ?? ''; const newContent = JSON.stringify(packageJson, null, 2) + eof; await node_fs_1.promises.writeFile(packageJsonPath, newContent, 'utf8'); } //# sourceMappingURL=update-resolver.js.map