import axios from 'axios'; import { coerce, gt, maxSatisfying, SemVer } from 'semver'; type PackageSpec = string | { main: string; children: string[] }; const packagesToUpdate: PackageSpec[] = [ { main: '@angular/cli', children: [ '@angular-devkit/build-angular', '@angular-devkit/core', '@angular-devkit/schematics', '@angular/build', '@angular/pwa', '@angular/ssr', '@schematics/angular', ], }, { main: '@angular-devkit/architect', children: ['@angular-devkit/build-webpack'], }, { main: '@angular/core', children: [ '@angular/common', '@angular/compiler', '@angular/compiler-cli', '@angular/localize', '@angular/platform-browser', '@angular/platform-server', '@angular/router', ], }, { main: '@angular/material', children: ['@angular/cdk', '@angular/google-maps'], }, 'ng-packagr', ]; // Resolved from `@angular/core`'s peerDependencies (not its own dist-tags) so it // stays aligned with whatever range Angular actually accepts. Only zone.js: rxjs // is intentionally excluded - Angular accepts a wide rxjs range (6 || 7), so we // don't pin/bump it from here (which would otherwise force it into the migration). const angularCorePeerResolvedPackages = ['zone.js'] as const; export async function fetchVersionsFromRegistry( targetVersion: 'latest' | 'next' ) { console.log('⏳ - Fetching versions from registry...'); const packageVersionMap = new Map(); await Promise.all( packagesToUpdate.map((pkgSpec) => fetch(packageVersionMap, pkgSpec, targetVersion) ) ); await resolveAngularCorePeerDependencies(packageVersionMap); console.log('✅ - Finished fetching versions from registry'); return packageVersionMap; } async function resolveAngularCorePeerDependencies( packageVersionMap: Map ) { const angularCoreVersion = packageVersionMap.get('@angular/core')!; const { data } = await axios.get( `https://registry.npmjs.org/@angular/core/${angularCoreVersion}` ); const peerDependencies: Record = data.peerDependencies ?? {}; await Promise.all( angularCorePeerResolvedPackages.map(async (pkgName) => { const peerRange = peerDependencies[pkgName]; if (!peerRange) { console.warn( ` ⚠️ @angular/core@${angularCoreVersion} declares no peerDependency for "${pkgName}", skipping` ); return; } const { data } = await axios.get(`https://registry.npmjs.org/${pkgName}`); const resolved = maxSatisfying( Object.keys(data.versions ?? {}), peerRange, { includePrerelease: false } ); if (!resolved) { console.warn( ` ⚠️ Could not resolve a published version of "${pkgName}" matching peer range "${peerRange}"` ); return; } packageVersionMap.set(pkgName, resolved); console.log(` ${pkgName}: ${resolved}`); }) ); } async function fetch( packageVersionMap: Map, pkgSpec: PackageSpec, targetVersion: 'latest' | 'next' ) { function setPackageVersions(pkgSpec: PackageSpec, version: string) { if (typeof pkgSpec === 'string') { packageVersionMap.set(pkgSpec, version); return; } packageVersionMap.set(pkgSpec.main, version); for (const child of pkgSpec.children) { packageVersionMap.set(child, version); } } const pkgName = typeof pkgSpec === 'string' ? pkgSpec : pkgSpec.main; // set it to empty initially to keep the order of the specified packages setPackageVersions(pkgSpec, ''); const response = await axios.get(`https://registry.npmjs.org/${pkgName}`); const distTags = response.data['dist-tags']; const latestVersion = distTags['latest']; if (targetVersion === 'latest') { setPackageVersions(pkgSpec, latestVersion); console.log(` ${pkgName}: ${latestVersion}`); } else { const nextVersion = distTags['next'] ?? latestVersion; const coercedNextVersion = coerce(nextVersion) as SemVer; // check which is the greater version const versionToUse = gt(coercedNextVersion, latestVersion) ? nextVersion : latestVersion; setPackageVersions(pkgSpec, versionToUse); console.log(` ${pkgName}: ${versionToUse}`); } }