diff options
-rw-r--r-- | packages/upgrade/src/actions/context.ts | 4 | ||||
-rw-r--r-- | packages/upgrade/src/actions/help.ts | 2 | ||||
-rw-r--r-- | packages/upgrade/src/actions/install.ts | 97 | ||||
-rw-r--r-- | packages/upgrade/src/actions/verify.ts | 55 | ||||
-rw-r--r-- | packages/upgrade/src/index.ts | 14 | ||||
-rw-r--r-- | packages/upgrade/src/messages.ts | 43 | ||||
-rw-r--r-- | packages/upgrade/src/shell.ts | 2 | ||||
-rw-r--r-- | packages/upgrade/test/install.test.js | 56 | ||||
-rw-r--r-- | packages/upgrade/test/utils.js | 5 | ||||
-rw-r--r-- | packages/upgrade/tsconfig.json | 5 |
10 files changed, 171 insertions, 112 deletions
diff --git a/packages/upgrade/src/actions/context.ts b/packages/upgrade/src/actions/context.ts index d5a5778d4..f821864b5 100644 --- a/packages/upgrade/src/actions/context.ts +++ b/packages/upgrade/src/actions/context.ts @@ -36,7 +36,7 @@ export async function getContext(argv: string[]): Promise<Context> { '-h': '--help', }, { argv, permissive: true } - ) + ); const packageManager = detectPackageManager()?.name ?? 'npm'; const { _: [version = 'latest'] = [], '--help': help = false, '--dry-run': dryRun } = flags; @@ -52,5 +52,5 @@ export async function getContext(argv: string[]): Promise<Context> { exit(code) { process.exit(code); }, - } satisfies Context + } satisfies Context; } diff --git a/packages/upgrade/src/actions/help.ts b/packages/upgrade/src/actions/help.ts index d61abc71e..2e25b7e84 100644 --- a/packages/upgrade/src/actions/help.ts +++ b/packages/upgrade/src/actions/help.ts @@ -8,7 +8,7 @@ export function help() { tables: { Flags: [ ['--help (-h)', 'See all available flags.'], - ['--dry-run', 'Walk through steps without executing.'] + ['--dry-run', 'Walk through steps without executing.'], ], }, }); diff --git a/packages/upgrade/src/actions/install.ts b/packages/upgrade/src/actions/install.ts index 4696d5eb5..3f343463f 100644 --- a/packages/upgrade/src/actions/install.ts +++ b/packages/upgrade/src/actions/install.ts @@ -1,37 +1,56 @@ import type { Context, PackageInfo } from './context.js'; +import { color, say } from '@astrojs/cli-kit'; +import { random, sleep } from '@astrojs/cli-kit/utils'; import fs from 'node:fs'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; -import { color, say } from '@astrojs/cli-kit'; -import { pluralize, celebrations, done, error, info, log, spinner, success, upgrade, banner, title, changelog, warn, bye, newline } from '../messages.js'; +import { + banner, + bye, + celebrations, + changelog, + done, + error, + info, + newline, + pluralize, + spinner, + success, + title, + upgrade, + warn, +} from '../messages.js'; import { shell } from '../shell.js'; -import { random, sleep } from '@astrojs/cli-kit/utils'; -import { satisfies } from 'semver'; export async function install( - ctx: Pick<Context, 'version' | 'packages' | 'packageManager' | 'prompt' | 'dryRun' | 'exit' | 'cwd'> + ctx: Pick< + Context, + 'version' | 'packages' | 'packageManager' | 'prompt' | 'dryRun' | 'exit' | 'cwd' + > ) { await banner(); newline(); const { current, dependencies, devDependencies } = filterPackages(ctx); const toInstall = [...dependencies, ...devDependencies].sort(sortPackages); for (const packageInfo of current.sort(sortPackages)) { - const tag = /^\d/.test(packageInfo.targetVersion) ? packageInfo.targetVersion : packageInfo.targetVersion.slice(1) - await info(`${packageInfo.name}`, `is up to date on`, `v${tag}`) + const tag = /^\d/.test(packageInfo.targetVersion) + ? packageInfo.targetVersion + : packageInfo.targetVersion.slice(1); + await info(`${packageInfo.name}`, `is up to date on`, `v${tag}`); await sleep(random(50, 150)); } if (toInstall.length === 0 && !ctx.dryRun) { - newline() + newline(); await success(random(celebrations), random(done)); return; } - const majors: PackageInfo[] = [] + const majors: PackageInfo[] = []; for (const packageInfo of toInstall) { const word = ctx.dryRun ? 'can' : 'will'; - await upgrade(packageInfo, `${word} be updated to`) + await upgrade(packageInfo, `${word} be updated to`); if (packageInfo.isMajor) { - majors.push(packageInfo) + majors.push(packageInfo); } } if (majors.length > 0) { @@ -39,22 +58,25 @@ export async function install( name: 'proceed', type: 'confirm', label: title('wait'), - message: `${pluralize(['One package has', 'Some packages have'], majors.length)} breaking changes. Continue?`, + message: `${pluralize( + ['One package has', 'Some packages have'], + majors.length + )} breaking changes. Continue?`, initial: true, }); if (!proceed) { return ctx.exit(0); } - + newline(); - + await warn('check', `Be sure to follow the ${pluralize('CHANGELOG', majors.length)}.`); for (const pkg of majors.sort(sortPackages)) { await changelog(pkg.name, pkg.changelogTitle!, pkg.changelogURL!); } } - newline() + newline(); if (ctx.dryRun) { await info('--dry-run', `Skipping dependency installation`); } else { @@ -76,13 +98,13 @@ function filterPackages(ctx: Pick<Context, 'packages'>) { arr.push(packageInfo); } } - return { current, dependencies, devDependencies } + return { current, dependencies, devDependencies }; } /** - * An `Array#sort` comparator function to normalize how packages are displayed. - * This only changes how the packages are displayed in the CLI, it is not persisted to `package.json`. - */ + * An `Array#sort` comparator function to normalize how packages are displayed. + * This only changes how the packages are displayed in the CLI, it is not persisted to `package.json`. + */ function sortPackages(a: PackageInfo, b: PackageInfo): number { if (a.isMajor && !b.isMajor) return 1; if (b.isMajor && !a.isMajor) return -1; @@ -93,7 +115,11 @@ function sortPackages(a: PackageInfo, b: PackageInfo): number { return a.name.localeCompare(b.name); } -async function runInstallCommand(ctx: Pick<Context, 'cwd' | 'packageManager' | 'exit'>, dependencies: PackageInfo[], devDependencies: PackageInfo[]) { +async function runInstallCommand( + ctx: Pick<Context, 'cwd' | 'packageManager' | 'exit'>, + dependencies: PackageInfo[], + devDependencies: PackageInfo[] +) { const cwd = fileURLToPath(ctx.cwd); if (ctx.packageManager === 'yarn') await ensureYarnLock({ cwd }); @@ -103,17 +129,40 @@ async function runInstallCommand(ctx: Pick<Context, 'cwd' | 'packageManager' | ' while: async () => { try { if (dependencies.length > 0) { - await shell(ctx.packageManager, ['install', ...dependencies.map(({ name, targetVersion }) => `${name}@${(targetVersion).replace(/^\^/, '')}`)], { cwd, timeout: 90_000, stdio: 'ignore' }) + await shell( + ctx.packageManager, + [ + 'install', + ...dependencies.map( + ({ name, targetVersion }) => `${name}@${targetVersion.replace(/^\^/, '')}` + ), + ], + { cwd, timeout: 90_000, stdio: 'ignore' } + ); } if (devDependencies.length > 0) { - await shell(ctx.packageManager, ['install', '--save-dev', ...devDependencies.map(({ name, targetVersion }) => `${name}@${(targetVersion).replace(/^\^/, '')}`)], { cwd, timeout: 90_000, stdio: 'ignore' }) + await shell( + ctx.packageManager, + [ + 'install', + '--save-dev', + ...devDependencies.map( + ({ name, targetVersion }) => `${name}@${targetVersion.replace(/^\^/, '')}` + ), + ], + { cwd, timeout: 90_000, stdio: 'ignore' } + ); } } catch { - const packages = [...dependencies, ...devDependencies].map(({ name, targetVersion }) => `${name}@${targetVersion}`).join(' ') + const packages = [...dependencies, ...devDependencies] + .map(({ name, targetVersion }) => `${name}@${targetVersion}`) + .join(' '); newline(); error( 'error', - `Dependencies failed to install, please run the following command manually:\n${color.bold(`${ctx.packageManager} install ${packages}`)}` + `Dependencies failed to install, please run the following command manually:\n${color.bold( + `${ctx.packageManager} install ${packages}` + )}` ); return ctx.exit(1); } diff --git a/packages/upgrade/src/actions/verify.ts b/packages/upgrade/src/actions/verify.ts index be86c5344..82daa9a06 100644 --- a/packages/upgrade/src/actions/verify.ts +++ b/packages/upgrade/src/actions/verify.ts @@ -1,14 +1,13 @@ import type { Context, PackageInfo } from './context.js'; +import { color } from '@astrojs/cli-kit'; import dns from 'node:dns/promises'; import { existsSync } from 'node:fs'; import { readFile } from 'node:fs/promises'; -import { color } from '@astrojs/cli-kit'; -import { bannerAbort, error, getRegistry, info, log, newline } from '../messages.js'; -import semverDiff from 'semver/functions/diff.js'; import semverCoerce from 'semver/functions/coerce.js'; +import semverDiff from 'semver/functions/diff.js'; import semverParse from 'semver/functions/parse.js'; - +import { bannerAbort, error, getRegistry, info, newline } from '../messages.js'; export async function verify( ctx: Pick<Context, 'version' | 'packages' | 'cwd' | 'dryRun' | 'exit'> @@ -49,21 +48,21 @@ function safeJSONParse(value: string) { try { return JSON.parse(value); } catch {} - return {} + return {}; } async function verifyAstroProject(ctx: Pick<Context, 'cwd' | 'version' | 'packages'>) { const packageJson = new URL('./package.json', ctx.cwd); - if (!existsSync(packageJson)) return false; + if (!existsSync(packageJson)) return false; const contents = await readFile(packageJson, { encoding: 'utf-8' }); if (!contents.includes('astro')) return false; - const { dependencies = {}, devDependencies = {} } = safeJSONParse(contents) + const { dependencies = {}, devDependencies = {} } = safeJSONParse(contents); if (dependencies['astro'] === undefined && devDependencies['astro'] === undefined) return false; - + // Side-effect! Persist dependency info to the shared context collectPackageInfo(ctx, dependencies, devDependencies); - + return true; } @@ -71,14 +70,18 @@ function isAstroPackage(name: string) { return name === 'astro' || name.startsWith('@astrojs/'); } -function collectPackageInfo(ctx: Pick<Context, 'version' | 'packages'>, dependencies: Record<string, string>, devDependencies: Record<string, string>) { +function collectPackageInfo( + ctx: Pick<Context, 'version' | 'packages'>, + dependencies: Record<string, string>, + devDependencies: Record<string, string> +) { for (const [name, currentVersion] of Object.entries(dependencies)) { if (!isAstroPackage(name)) continue; ctx.packages.push({ name, currentVersion, targetVersion: ctx.version, - }) + }); } for (const [name, currentVersion] of Object.entries(devDependencies)) { if (!isAstroPackage(name)) continue; @@ -86,12 +89,15 @@ function collectPackageInfo(ctx: Pick<Context, 'version' | 'packages'>, dependen name, currentVersion, targetVersion: ctx.version, - isDevDependency: true - }) + isDevDependency: true, + }); } } -async function verifyVersions(ctx: Pick<Context, 'version' | 'packages' | 'exit'>, registry: string) { +async function verifyVersions( + ctx: Pick<Context, 'version' | 'packages' | 'exit'>, + registry: string +) { const tasks: Promise<void>[] = []; for (const packageInfo of ctx.packages) { tasks.push(resolveTargetVersion(packageInfo, registry)); @@ -110,11 +116,13 @@ async function verifyVersions(ctx: Pick<Context, 'version' | 'packages' | 'exit' } async function resolveTargetVersion(packageInfo: PackageInfo, registry: string): Promise<void> { - const packageMetadata = await fetch(`${registry}/${packageInfo.name}`, { headers: { accept: 'application/vnd.npm.install-v1+json' }}); + const packageMetadata = await fetch(`${registry}/${packageInfo.name}`, { + headers: { accept: 'application/vnd.npm.install-v1+json' }, + }); if (packageMetadata.status >= 400) { throw new Error(`Unable to resolve "${packageInfo.name}"`); } - const { "dist-tags": distTags } = await packageMetadata.json(); + const { 'dist-tags': distTags } = await packageMetadata.json(); let version = distTags[packageInfo.targetVersion]; if (version) { packageInfo.tag = packageInfo.targetVersion; @@ -159,7 +167,16 @@ async function resolveTargetVersion(packageInfo: PackageInfo, registry: string): } } -function extractChangelogURLFromRepository(repository: Record<string, string>, version: string, branch = 'main') { - return repository.url.replace('git+', '').replace('.git', '') + `/blob/${branch}/` + repository.directory + '/CHANGELOG.md#' + version.replace(/\./g, '') +function extractChangelogURLFromRepository( + repository: Record<string, string>, + version: string, + branch = 'main' +) { + return ( + repository.url.replace('git+', '').replace('.git', '') + + `/blob/${branch}/` + + repository.directory + + '/CHANGELOG.md#' + + version.replace(/\./g, '') + ); } - diff --git a/packages/upgrade/src/index.ts b/packages/upgrade/src/index.ts index ab216ad56..8131e6eb3 100644 --- a/packages/upgrade/src/index.ts +++ b/packages/upgrade/src/index.ts @@ -1,7 +1,7 @@ import { getContext } from './actions/context.js'; -import { install } from './actions/install.js'; import { help } from './actions/help.js'; +import { install } from './actions/install.js'; import { verify } from './actions/verify.js'; import { setStdout } from './messages.js'; @@ -21,10 +21,7 @@ export async function main() { return; } - const steps = [ - verify, - install, - ]; + const steps = [verify, install]; for (const step of steps) { await step(ctx); @@ -32,9 +29,4 @@ export async function main() { process.exit(0); } -export { - install, - getContext, - setStdout, - verify, -}; +export { getContext, install, setStdout, verify }; diff --git a/packages/upgrade/src/messages.ts b/packages/upgrade/src/messages.ts index d043db310..e159a6f06 100644 --- a/packages/upgrade/src/messages.ts +++ b/packages/upgrade/src/messages.ts @@ -1,11 +1,11 @@ /* eslint no-console: 'off' */ -import type { PackageInfo } from './actions/context.js'; import { color, label, spinner as load } from '@astrojs/cli-kit'; import { align } from '@astrojs/cli-kit/utils'; -import detectPackageManager from 'which-pm-runs'; -import { shell } from './shell.js'; import semverParse from 'semver/functions/parse.js'; import terminalLink from 'terminal-link'; +import detectPackageManager from 'which-pm-runs'; +import type { PackageInfo } from './actions/context.js'; +import { shell } from './shell.js'; // Users might lack access to the global npm registry, this function // checks the user's project type and will return the proper npm registry @@ -51,28 +51,28 @@ export const celebrations = [ 'Nice.', 'Wonderful.', 'Lovely!', - 'Lookin\' good.', - 'Awesome.' -] + "Lookin' good.", + 'Awesome.', +]; export const done = [ - 'You\'re on the latest and greatest.', + "You're on the latest and greatest.", 'Your integrations are up-to-date.', 'Everything is current.', 'Everything is up to date.', 'Integrations are all up to date.', 'Everything is on the latest and greatest.', 'Integrations are up to date.', -] +]; export const bye = [ 'Thanks for using Astro!', 'Have fun building!', 'Take it easy, astronaut!', - 'Can\'t wait to see what you build.', + "Can't wait to see what you build.", 'Good luck out there.', 'See you around, astronaut.', -] +]; export const log = (message: string) => stdout.write(message + '\n'); @@ -80,7 +80,9 @@ export const newline = () => stdout.write('\n'); export const banner = async () => log( - `\n${label('astro', color.bgGreen, color.black)} ${color.bold('Integration upgrade in progress.')}` + `\n${label('astro', color.bgGreen, color.black)} ${color.bold( + 'Integration upgrade in progress.' + )}` ); export const bannerAbort = () => @@ -88,7 +90,7 @@ export const bannerAbort = () => export const warn = async (prefix: string, text: string) => { log(`${label(prefix, color.bgCyan, color.black)} ${text}`); -} +}; export const info = async (prefix: string, text: string, version = '') => { const length = 11 + prefix.length + text.length + version?.length; @@ -97,12 +99,14 @@ export const info = async (prefix: string, text: string, version = '') => { log(`${' '.repeat(5)} ${color.cyan(symbol)} ${prefix}`); log(`${' '.repeat(9)}${color.dim(text)} ${color.reset(version)}`); } else { - log(`${' '.repeat(5)} ${color.cyan(symbol)} ${prefix} ${color.dim(text)} ${color.reset(version)}`); + log( + `${' '.repeat(5)} ${color.cyan(symbol)} ${prefix} ${color.dim(text)} ${color.reset(version)}` + ); } -} +}; export const upgrade = async (packageInfo: PackageInfo, text: string) => { const { name, isMajor = false, targetVersion } = packageInfo; - + const bg = isMajor ? (v: string) => color.bgYellow(color.black(` ${v} `)) : color.green; const style = isMajor ? color.yellow : color.green; const symbol = isMajor ? '▲' : '●'; @@ -116,17 +120,18 @@ export const upgrade = async (packageInfo: PackageInfo, text: string) => { } else { log(`${' '.repeat(5)} ${style(symbol)} ${name} ${color.dim(text)} ${bg(version)}`); } -} +}; -export const title = (text: string) => align(label(text, color.bgYellow, color.black), 'end', 7) + ' '; +export const title = (text: string) => + align(label(text, color.bgYellow, color.black), 'end', 7) + ' '; export const success = async (prefix: string, text: string) => { const length = 10 + prefix.length + text.length; if (length > stdout.columns) { - log(`${' '.repeat(5)} ${color.green("✔")} ${prefix}`); + log(`${' '.repeat(5)} ${color.green('✔')} ${prefix}`); log(`${' '.repeat(9)}${color.dim(text)}`); } else { - log(`${' '.repeat(5)} ${color.green("✔")} ${prefix} ${color.dim(text)}`); + log(`${' '.repeat(5)} ${color.green('✔')} ${prefix} ${color.dim(text)}`); } }; diff --git a/packages/upgrade/src/shell.ts b/packages/upgrade/src/shell.ts index 47eb4857a..65a83327d 100644 --- a/packages/upgrade/src/shell.ts +++ b/packages/upgrade/src/shell.ts @@ -41,7 +41,7 @@ export async function shell( shell: true, stdio: opts.stdio, timeout: opts.timeout, - signal + signal, }); const done = new Promise((resolve) => child.on('close', resolve)); [stdout, stderr] = await Promise.all([text(child.stdout), text(child.stderr)]); diff --git a/packages/upgrade/test/install.test.js b/packages/upgrade/test/install.test.js index 082f1c252..05c46cdce 100644 --- a/packages/upgrade/test/install.test.js +++ b/packages/upgrade/test/install.test.js @@ -9,7 +9,7 @@ describe('install', () => { version: 'latest', packageManager: 'npm', dryRun: true, - } + }; it('up to date', async () => { const context = { @@ -19,8 +19,8 @@ describe('install', () => { name: 'astro', currentVersion: '1.0.0', targetVersion: '1.0.0', - } - ] + }, + ], }; await install(context); expect(fixture.hasMessage('◼ astro is up to date on v1.0.0')).to.be.true; @@ -34,8 +34,8 @@ describe('install', () => { name: 'astro', currentVersion: '1.0.0', targetVersion: '1.0.1', - } - ] + }, + ], }; await install(context); expect(fixture.hasMessage('● astro can be updated to v1.0.1')).to.be.true; @@ -49,8 +49,8 @@ describe('install', () => { name: 'astro', currentVersion: '1.0.0', targetVersion: '1.2.0', - } - ] + }, + ], }; await install(context); expect(fixture.hasMessage('● astro can be updated to v1.2.0')).to.be.true; @@ -61,9 +61,9 @@ describe('install', () => { let exitCode; const context = { ...ctx, - prompt: () => { + prompt: () => { prompted = true; - return { proceed: false } + return { proceed: false }; }, exit: (code) => { exitCode = code; @@ -75,9 +75,9 @@ describe('install', () => { targetVersion: '2.0.0', isMajor: true, changelogTitle: 'CHANGELOG', - changelogURL: 'https://example.com' - } - ] + changelogURL: 'https://example.com', + }, + ], }; await install(context); expect(fixture.hasMessage('▲ astro can be updated to v2.0.0')).to.be.true; @@ -91,9 +91,9 @@ describe('install', () => { let exitCode; const context = { ...ctx, - prompt: () => { + prompt: () => { prompted = true; - return { proceed: true } + return { proceed: true }; }, exit: (code) => { exitCode = code; @@ -105,9 +105,9 @@ describe('install', () => { targetVersion: '2.0.0', isMajor: true, changelogTitle: 'CHANGELOG', - changelogURL: 'https://example.com' - } - ] + changelogURL: 'https://example.com', + }, + ], }; await install(context); expect(fixture.hasMessage('▲ astro can be updated to v2.0.0')).to.be.true; @@ -121,9 +121,9 @@ describe('install', () => { let exitCode; const context = { ...ctx, - prompt: () => { + prompt: () => { prompted = true; - return { proceed: true } + return { proceed: true }; }, exit: (code) => { exitCode = code; @@ -135,7 +135,7 @@ describe('install', () => { targetVersion: '2.0.0', isMajor: true, changelogTitle: 'CHANGELOG', - changelogURL: 'https://example.com' + changelogURL: 'https://example.com', }, { name: 'b', @@ -143,9 +143,9 @@ describe('install', () => { targetVersion: '7.0.0', isMajor: true, changelogTitle: 'CHANGELOG', - changelogURL: 'https://example.com' - } - ] + changelogURL: 'https://example.com', + }, + ], }; await install(context); expect(fixture.hasMessage('▲ a can be updated to v2.0.0')).to.be.true; @@ -163,9 +163,9 @@ describe('install', () => { let exitCode; const context = { ...ctx, - prompt: () => { + prompt: () => { prompted = true; - return { proceed: true } + return { proceed: true }; }, exit: (code) => { exitCode = code; @@ -192,9 +192,9 @@ describe('install', () => { targetVersion: '3.0.0', isMajor: true, changelogTitle: 'CHANGELOG', - changelogURL: 'https://example.com' - } - ] + changelogURL: 'https://example.com', + }, + ], }; await install(context); expect(fixture.hasMessage('◼ current is up to date on v1.0.0')).to.be.true; diff --git a/packages/upgrade/test/utils.js b/packages/upgrade/test/utils.js index 5f649d7e2..d57ceacd3 100644 --- a/packages/upgrade/test/utils.js +++ b/packages/upgrade/test/utils.js @@ -38,7 +38,7 @@ const resetBasicFixture = async () => { ); const overriddenPackageJson = Object.assign(packageJsonData, { dependencies: { - astro: '1.0.0' + astro: '1.0.0', }, }); @@ -49,5 +49,4 @@ const resetBasicFixture = async () => { ]); }; -export const resetFixtures = () => - Promise.allSettled([resetBasicFixture()]); +export const resetFixtures = () => Promise.allSettled([resetBasicFixture()]); diff --git a/packages/upgrade/tsconfig.json b/packages/upgrade/tsconfig.json index 34c6b1f2b..d15ade00f 100644 --- a/packages/upgrade/tsconfig.json +++ b/packages/upgrade/tsconfig.json @@ -1,9 +1,6 @@ { "extends": "../../tsconfig.base.json", - "include": [ - "src", - "index.d.ts" - ], + "include": ["src", "index.d.ts"], "compilerOptions": { "allowJs": true, "emitDeclarationOnly": false, |