From bca381144ee07983dda47147428999781f56a301 Mon Sep 17 00:00:00 2001 From: Nate Moore Date: Mon, 18 Sep 2023 11:05:07 -0500 Subject: [PATCH] wip: add confirmation before updating major --- packages/upgrade/package.json | 2 ++ packages/upgrade/src/actions/context.ts | 1 + packages/upgrade/src/actions/install.ts | 34 +++++++++++++++++++------ packages/upgrade/src/actions/verify.ts | 10 +++++--- packages/upgrade/src/messages.ts | 30 ++++++++++++++++------ pnpm-lock.yaml | 10 ++++++++ 6 files changed, 67 insertions(+), 20 deletions(-) diff --git a/packages/upgrade/package.json b/packages/upgrade/package.json index 1689fd8b8..e442d8edc 100644 --- a/packages/upgrade/package.json +++ b/packages/upgrade/package.json @@ -32,9 +32,11 @@ "//b": "DEPENDENCIES IS FOR UNBUNDLED PACKAGES", "dependencies": { "@astrojs/cli-kit": "^0.2.3", + "semver": "^7.5.4", "which-pm-runs": "^1.1.0" }, "devDependencies": { + "@types/semver": "^7.5.2", "@types/which-pm-runs": "^1.0.0", "arg": "^5.0.2", "astro-scripts": "workspace:*", diff --git a/packages/upgrade/src/actions/context.ts b/packages/upgrade/src/actions/context.ts index c4ffebf74..b68e96cc4 100644 --- a/packages/upgrade/src/actions/context.ts +++ b/packages/upgrade/src/actions/context.ts @@ -21,6 +21,7 @@ export interface PackageInfo { currentVersion: string; targetVersion: string; isDevDependency?: boolean; + isMajor?: boolean; } export async function getContext(argv: string[]): Promise { diff --git a/packages/upgrade/src/actions/install.ts b/packages/upgrade/src/actions/install.ts index e44f9fe99..16e1f022c 100644 --- a/packages/upgrade/src/actions/install.ts +++ b/packages/upgrade/src/actions/install.ts @@ -4,12 +4,17 @@ import fs from 'node:fs'; import path from 'node:path'; import { fileURLToPath } from 'node:url'; import { color } from '@astrojs/cli-kit'; -import { celebrations, done, error, info, log, spinner, success, upgrade, banner } from '../messages.js'; +import { celebrations, done, error, info, log, spinner, success, upgrade, banner, title } from '../messages.js'; import { shell } from '../shell.js'; import { random, sleep } from '@astrojs/cli-kit/utils'; +const pluralize = (n: number) => { + if (n === 1) return `One package has`; + return `Some packages have`; +} + export async function install( - ctx: Pick + ctx: Pick ) { await banner(); log('') @@ -18,17 +23,30 @@ export async function install( for (const packageInfo of current) { 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(0, 100)); + await sleep(random(50, 150)); } if (toInstall.length === 0 && !ctx.dryRun) { log('') await success(random(celebrations), random(done)) return; } + const majors: PackageInfo[] = [] for (const packageInfo of [...dependencies, ...devDependencies]) { - const tag = /^\d/.test(packageInfo.targetVersion) ? packageInfo.targetVersion : packageInfo.targetVersion.slice(1) - const word = ctx.dryRun ? 'can' : 'will' - await upgrade(`${packageInfo.name}`, `${word} be updated to`, `v${tag}`) + const word = ctx.dryRun ? 'can' : 'will'; + await upgrade(packageInfo, `${word} be updated to`) + if (packageInfo.isMajor) { + majors.push(packageInfo) + } + } + if (majors.length > 0) { + const { proceed } = await ctx.prompt({ + name: 'proceed', + type: 'confirm', + label: title('WARN!'), + message: `Continue? ${pluralize(majors.length)} breaking changes!`, + initial: true, + }); + if (!proceed) ctx.exit(0); } log('') @@ -65,10 +83,10 @@ async function runInstallCommand(ctx: Pick, d while: async () => { try { if (dependencies.length > 0) { - await shell(ctx.packageManager, ['install', ...dependencies.map(({ name, targetVersion }) => `${name}@${targetVersion}`)], { 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}`)], { 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 (e) { const packages = [...dependencies, ...devDependencies].map(({ name, targetVersion }) => `${name}@${targetVersion}`).join(' ') diff --git a/packages/upgrade/src/actions/verify.ts b/packages/upgrade/src/actions/verify.ts index 5529649db..fe90b221c 100644 --- a/packages/upgrade/src/actions/verify.ts +++ b/packages/upgrade/src/actions/verify.ts @@ -5,6 +5,8 @@ import { existsSync } from 'node:fs'; import { readFile } from 'node:fs/promises'; import { color } from '@astrojs/cli-kit'; import { bannerAbort, error, getRegistry, info, log } from '../messages.js'; +import semverDiff from 'semver/functions/diff.js'; +import semverCoerce from 'semver/functions/coerce.js' export async function verify( @@ -60,10 +62,6 @@ async function verifyAstroProject(ctx: Pick { - if (a.name === 'astro') return -1; - return 0; - }) return true; } @@ -122,5 +120,9 @@ async function resolveTargetVersion(packageInfo: PackageInfo, registry: string): } const prefix = packageInfo.targetVersion === 'latest' ? '^' : ''; packageInfo.targetVersion = `${prefix}${version}`; + const fromVersion = semverCoerce(packageInfo.currentVersion)!; + const toVersion = semverCoerce(packageInfo.targetVersion)!; + const bump = semverDiff(fromVersion, toVersion); + packageInfo.isMajor = bump === 'major'; } diff --git a/packages/upgrade/src/messages.ts b/packages/upgrade/src/messages.ts index fe3e5aff3..cb741b221 100644 --- a/packages/upgrade/src/messages.ts +++ b/packages/upgrade/src/messages.ts @@ -1,7 +1,10 @@ /* 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 semverCoerce from 'semver/functions/coerce.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 @@ -61,27 +64,38 @@ export const banner = async () => ); export const bannerAbort = () => - log(`\n${label('astro', color.bgRed)} ${color.bold('Launch sequence aborted.')}`); + log(`\n${label('astro', color.bgRed)} ${color.bold('Integration upgrade aborted.')}`); export const info = async (prefix: string, text: string, version = '') => { const length = 11 + prefix.length + text.length + version?.length; + const symbol = '◼'; if (length > stdout.columns) { - log(`${' '.repeat(5)} ${color.cyan('◼')} ${prefix}`); + log(`${' '.repeat(5)} ${color.cyan(symbol)} ${prefix}`); log(`${' '.repeat(9)}${color.dim(text)} ${color.reset(version)}`); } else { - log(`${' '.repeat(5)} ${color.cyan('◼')} ${prefix} ${color.dim(text)} ${color.reset(version)}`); + log(`${' '.repeat(5)} ${color.cyan(symbol)} ${prefix} ${color.dim(text)} ${color.reset(version)}`); } } -export const upgrade = async (prefix: string, text: string, version = '') => { - const length = 11 + prefix.length + text.length + version.length; +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 ? '▲' : '●'; + const toVersion = semverCoerce(targetVersion)!; + const version = `v${toVersion.version}`; + + const length = 12 + name.length + text.length + version.length; if (length > stdout.columns) { - log(`${' '.repeat(5)} ${color.magenta('▲')} ${prefix}`); - log(`${' '.repeat(9)}${color.dim(text)} ${color.magenta(version)}`); + log(`${' '.repeat(5)} ${style(symbol)} ${name}`); + log(`${' '.repeat(9)}${color.dim(text)} ${bg(version)}`); } else { - log(`${' '.repeat(5)} ${color.magenta('▲')} ${prefix} ${color.dim(text)} ${color.magenta(version)}`); + 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 success = async (prefix: string, text: string) => { const length = 10 + prefix.length + text.length; if (length > stdout.columns) { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index f78c1b8ca..54af1df87 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -5088,10 +5088,16 @@ importers: '@astrojs/cli-kit': specifier: ^0.2.3 version: 0.2.3 + semver: + specifier: ^7.5.4 + version: 7.5.4 which-pm-runs: specifier: ^1.1.0 version: 1.1.0 devDependencies: + '@types/semver': + specifier: ^7.5.2 + version: 7.5.2 '@types/which-pm-runs': specifier: ^1.0.0 version: 1.0.0 @@ -9058,6 +9064,10 @@ packages: resolution: {integrity: sha512-G8hZ6XJiHnuhQKR7ZmysCeJWE08o8T0AXtk5darsCaTVsYZhhgUrq53jizaR2FvsoeCwJhlmwTjkXBY5Pn/ZHw==} dev: true + /@types/semver@7.5.2: + resolution: {integrity: sha512-7aqorHYgdNO4DM36stTiGO3DvKoex9TQRwsJU6vMaFGyqpBA1MNZkz+PG3gaNUPpTAOYhT1WR7M1JyA3fbS9Cw==} + dev: true + /@types/send@0.17.1: resolution: {integrity: sha512-Cwo8LE/0rnvX7kIIa3QHCkcuF21c05Ayb0ZfxPiv0W8VRiZiNW/WuRupHKpqqGVGf7SUA44QSOUKaEd9lIrd/Q==} dependencies: