feat: update upgrade cli

This commit is contained in:
Nate Moore 2023-09-12 16:04:37 -05:00
parent eb91b3ce1d
commit 685637d6cb
4 changed files with 135 additions and 152 deletions

View file

@ -42,7 +42,7 @@ export async function getContext(argv: string[]): Promise<Context> {
prompt, prompt,
packageManager, packageManager,
packages: [], packages: [],
cwd: pathToFileURL(process.cwd()), cwd: new URL(pathToFileURL(process.cwd()) + '/'),
dryRun, dryRun,
version, version,
exit(code) { exit(code) {

View file

@ -1,53 +1,85 @@
import type { Context } from './context.js'; import type { Context, PackageInfo } from './context.js';
import fs from 'node:fs'; import fs from 'node:fs';
import path from 'node:path'; import path from 'node:path';
import { fileURLToPath } from 'node:url'; import { fileURLToPath } from 'node:url';
import { color } from '@astrojs/cli-kit'; import { color } from '@astrojs/cli-kit';
import { error, info, spinner } from '../messages.js'; import { celebrations, done, error, info, log, spinner, success, upgrade, banner } from '../messages.js';
import { shell } from '../shell.js'; import { shell } from '../shell.js';
import { random, sleep } from '@astrojs/cli-kit/utils';
export async function install( export async function install(
ctx: Pick<Context, 'version' | 'packages' | 'packageManager' | 'dryRun' | 'cwd'> ctx: Pick<Context, 'version' | 'packages' | 'packageManager' | 'dryRun' | 'cwd'>
) { ) {
await banner();
log('')
const { current, dependencies, devDependencies } = filterPackages(ctx);
const toInstall = [...dependencies, ...devDependencies];
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));
}
if (toInstall.length === 0 && !ctx.dryRun) {
log('')
await success(random(celebrations), random(done))
return;
}
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}`)
}
log('')
if (ctx.dryRun) { if (ctx.dryRun) {
await info('--dry-run', `Skipping dependency installation`); await info('--dry-run', `Skipping dependency installation`);
} else { } else {
await spinner({ await runInstallCommand(ctx, dependencies, devDependencies);
start: `Updating dependencies with ${ctx.packageManager}...`,
end: 'Dependencies updated',
while: () => {
return runCommand(ctx).catch((e) => {
error('error', e);
error(
'error',
`Dependencies failed to install, please run ${color.bold(
ctx.packageManager + ' install'
)} to install them manually after setup.`
);
});
},
});
} }
} }
async function runCommand(ctx: Pick<Context, 'packageManager' | 'packages' | 'cwd'>) { function filterPackages(ctx: Pick<Context, 'packages'>) {
const current: PackageInfo[] = [];
const dependencies: PackageInfo[] = [];
const devDependencies: PackageInfo[] = [];
for (const packageInfo of ctx.packages) {
const { currentVersion, targetVersion, isDevDependency } = packageInfo;
if (currentVersion === targetVersion) {
current.push(packageInfo);
} else {
const arr = isDevDependency ? devDependencies : dependencies;
arr.push(packageInfo);
}
}
return { current, dependencies, devDependencies }
}
async function runInstallCommand(ctx: Pick<Context, 'cwd' | 'packageManager'>, dependencies: PackageInfo[], devDependencies: PackageInfo[]) {
const cwd = fileURLToPath(ctx.cwd); const cwd = fileURLToPath(ctx.cwd);
if (ctx.packageManager === 'yarn') await ensureYarnLock({ cwd }); if (ctx.packageManager === 'yarn') await ensureYarnLock({ cwd });
const dependencies: string[] = []; await spinner({
const devDependencies: string[] = []; start: `Installing dependencies with ${ctx.packageManager}...`,
for (const { name, targetVersion, isDevDependency } of ctx.packages) { end: random(done),
const arr = isDevDependency ? devDependencies : dependencies; while: async () => {
arr.push(`${name}@${targetVersion}`); try {
} if (dependencies.length > 0) {
await shell(ctx.packageManager, ['install', ...dependencies.map(({ name, targetVersion }) => `${name}@${targetVersion}`)], { cwd, timeout: 90_000, stdio: 'ignore' })
if (dependencies.length > 0) { }
await shell(ctx.packageManager, ['install', ...dependencies], { 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' })
if (devDependencies.length > 0) { }
await shell(ctx.packageManager, ['install', '--save-dev', ...devDependencies], { cwd, timeout: 90_000, stdio: 'ignore' }); } catch (e) {
} const packages = [...dependencies, ...devDependencies].map(({ name, targetVersion }) => `${name}@${targetVersion}`).join(' ')
error(
'error',
`Dependencies failed to install, please run the following command manually:\n${color.bold(`${ctx.packageManager} install ${packages}`)}`
);
throw e;
}
},
});
} }
async function ensureYarnLock({ cwd }: { cwd: string }) { async function ensureYarnLock({ cwd }: { cwd: string }) {

View file

@ -60,6 +60,10 @@ async function verifyAstroProject(ctx: Pick<Context, 'cwd' | 'version' | 'packag
// Side-effect! Persist dependency info to the shared context // Side-effect! Persist dependency info to the shared context
collectPackageInfo(ctx, dependencies, devDependencies); collectPackageInfo(ctx, dependencies, devDependencies);
ctx.packages.sort((a) => {
if (a.name === 'astro') return -1;
return 0;
})
return true; return true;
} }
@ -95,15 +99,28 @@ async function verifyVersions(ctx: Pick<Context, 'version' | 'packages'>, regist
} }
await Promise.all(tasks); await Promise.all(tasks);
for (const packageInfo of ctx.packages) { for (const packageInfo of ctx.packages) {
// check for packageInfo.targetVersion, then fallback if (!packageInfo.targetVersion) {
// check if packageInfo.targetVersion === packageInfo.currentVersion return false;
}
} }
return true; return true;
} }
async function resolveTargetVersion(packageInfo: PackageInfo, registry: string): Promise<void> { async function resolveTargetVersion(packageInfo: PackageInfo, registry: string): Promise<void> {
const res = await fetch(`${registry}/${packageInfo.name}/tags/${packageInfo.targetVersion}`); const res = await fetch(`${registry}/${packageInfo.name}/${packageInfo.targetVersion}`);
const { code, version } = await res.json()
// MUTATE packageInfo.targetVersion to resolvedValue if (code === 'ResourceNotFound') {
if (packageInfo.targetVersion === 'latest') {
packageInfo.targetVersion = '';
return;
} else {
return resolveTargetVersion({ ...packageInfo, targetVersion: 'latest' }, registry);
}
}
if (packageInfo.currentVersion === version) {
return;
}
const prefix = packageInfo.targetVersion === 'latest' ? '^' : '';
packageInfo.targetVersion = `${prefix}${version}`;
} }

View file

@ -1,9 +1,5 @@
/* eslint no-console: 'off' */ /* eslint no-console: 'off' */
import { color, say as houston, label, spinner as load } from '@astrojs/cli-kit'; import { color, label, spinner as load } from '@astrojs/cli-kit';
import { align, sleep } from '@astrojs/cli-kit/utils';
import fetch from 'node-fetch-native';
import { exec } from 'node:child_process';
import stripAnsi from 'strip-ansi';
import detectPackageManager from 'which-pm-runs'; import detectPackageManager from 'which-pm-runs';
import { shell } from './shell.js'; import { shell } from './shell.js';
@ -27,10 +23,6 @@ export function setStdout(writable: typeof process.stdout) {
stdout = writable; stdout = writable;
} }
export async function say(messages: string | string[], { clear = false, hat = '' } = {}) {
return houston(messages, { clear, hat, stdout });
}
export async function spinner(args: { export async function spinner(args: {
start: string; start: string;
end: string; end: string;
@ -39,77 +31,67 @@ export async function spinner(args: {
await load(args, { stdout }); await load(args, { stdout });
} }
export const title = (text: string) => align(label(text), 'end', 7) + ' '; export const celebrations = [
'Beautiful.',
'Excellent!',
'Sweet!',
'Nice!',
'Huzzah!',
'Success.',
'Nice.',
'Wonderful.',
'Lovely!',
'Lookin\' good.',
'Awesome.'
]
export const welcome = [ export const done = [
`Let's claim your corner of the internet.`, 'You\'re on the latest and greatest.',
`I'll be your assistant today.`, 'Everything is current.',
`Let's build something awesome!`, 'Integrations are all up to date.',
`Let's build something great!`, 'All done. Thanks for using Astro!',
`Let's build something fast!`, 'Integrations up to date. Enjoy building!',
`Let's build the web we want.`, 'All set, everything is up to date.',
`Let's make the web weird!`, ]
`Let's make the web a better place!`,
`Let's create a new project!`,
`Let's create something unique!`,
`Time to build a new website.`,
`Time to build a faster website.`,
`Time to build a sweet new website.`,
`We're glad to have you on board.`,
`Keeping the internet weird since 2021.`,
`Initiating launch sequence...`,
`Initiating launch sequence... right... now!`,
`Awaiting further instructions.`,
];
export const getName = () =>
new Promise<string>((resolve) => {
exec('git config user.name', { encoding: 'utf-8' }, (_1, gitName) => {
if (gitName.trim()) {
return resolve(gitName.split(' ')[0].trim());
}
exec('whoami', { encoding: 'utf-8' }, (_3, whoami) => {
if (whoami.trim()) {
return resolve(whoami.split(' ')[0].trim());
}
return resolve('astronaut');
});
});
});
let v: string;
export const getVersion = () =>
new Promise<string>(async (resolve) => {
if (v) return resolve(v);
let registry = await getRegistry();
const { version } = await fetch(`${registry}/astro/latest`, { redirect: 'follow' }).then(
(res) => res.json(),
() => ({ version: '' })
);
v = version;
resolve(version);
});
export const log = (message: string) => stdout.write(message + '\n'); export const log = (message: string) => stdout.write(message + '\n');
export const banner = async (version: string) => export const banner = async () =>
log( log(
`\n${label('astro', color.bgGreen, color.black)}${ `\n${label('astro', color.bgGreen, color.black)} ${color.bold('Integration upgrade in progress.')}`
version ? ' ' + color.green(color.bold(`v${version}`)) : ''
} ${color.bold('Launch sequence initiated.')}`
); );
export const bannerAbort = () => export const bannerAbort = () =>
log(`\n${label('astro', color.bgRed)} ${color.bold('Launch sequence aborted.')}`); log(`\n${label('astro', color.bgRed)} ${color.bold('Launch sequence aborted.')}`);
export const info = async (prefix: string, text: string) => { export const info = async (prefix: string, text: string, version = '') => {
await sleep(100); const length = 11 + prefix.length + text.length + version?.length;
if (stdout.columns < 80) { if (length > stdout.columns) {
log(`${' '.repeat(5)} ${color.cyan('◼')} ${color.cyan(prefix)}`); log(`${' '.repeat(5)} ${color.cyan('◼')} ${prefix}`);
log(`${' '.repeat(9)}${color.dim(text)} ${color.reset(version)}`);
} else {
log(`${' '.repeat(5)} ${color.cyan('◼')} ${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;
if (length > stdout.columns) {
log(`${' '.repeat(5)} ${color.magenta('▲')} ${prefix}`);
log(`${' '.repeat(9)}${color.dim(text)} ${color.magenta(version)}`);
} else {
log(`${' '.repeat(5)} ${color.magenta('▲')} ${prefix} ${color.dim(text)} ${color.magenta(version)}`);
}
}
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(9)}${color.dim(text)}`); log(`${' '.repeat(9)}${color.dim(text)}`);
} else { } else {
log(`${' '.repeat(5)} ${color.cyan('◼')} ${color.cyan(prefix)} ${color.dim(text)}`); log(`${' '.repeat(5)} ${color.green("✔")} ${prefix} ${color.dim(text)}`);
} }
}; };
export const error = async (prefix: string, text: string) => { export const error = async (prefix: string, text: string) => {
if (stdout.columns < 80) { if (stdout.columns < 80) {
log(`${' '.repeat(5)} ${color.red('▲')} ${color.red(prefix)}`); log(`${' '.repeat(5)} ${color.red('▲')} ${color.red(prefix)}`);
@ -119,49 +101,8 @@ export const error = async (prefix: string, text: string) => {
} }
}; };
export const typescriptByDefault = async () => {
await info(`No worries!`, 'TypeScript is supported in Astro by default,');
log(`${' '.repeat(9)}${color.dim('but you are free to continue writing JavaScript instead.')}`);
await sleep(1000);
};
export const nextSteps = async ({ projectDir, devCmd }: { projectDir: string; devCmd: string }) => {
const max = stdout.columns;
const prefix = max < 80 ? ' ' : ' '.repeat(9);
await sleep(200);
log(
`\n ${color.bgCyan(` ${color.black('next')} `)} ${color.bold(
'Liftoff confirmed. Explore your project!'
)}`
);
await sleep(100);
if (projectDir !== '') {
projectDir = projectDir.includes(' ') ? `"./${projectDir}"` : `./${projectDir}`;
const enter = [
`\n${prefix}Enter your project directory using`,
color.cyan(`cd ${projectDir}`, ''),
];
const len = enter[0].length + stripAnsi(enter[1]).length;
log(enter.join(len > max ? '\n' + prefix : ' '));
}
log(
`${prefix}Run ${color.cyan(devCmd)} to start the dev server. ${color.cyan('CTRL+C')} to stop.`
);
await sleep(100);
log(
`${prefix}Add frameworks like ${color.cyan(`react`)} or ${color.cyan(
'tailwind'
)} using ${color.cyan('astro add')}.`
);
await sleep(100);
log(`\n${prefix}Stuck? Join us at ${color.cyan(`https://astro.build/chat`)}`);
await sleep(200);
};
export function printHelp({ export function printHelp({
commandName, commandName,
headline,
usage, usage,
tables, tables,
description, description,
@ -191,13 +132,6 @@ export function printHelp({
let message = []; let message = [];
if (headline) {
message.push(
linebreak(),
`${title(commandName)} ${color.green(`v${process.env.PACKAGE_VERSION ?? ''}`)} ${headline}`
);
}
if (usage) { if (usage) {
message.push(linebreak(), `${color.green(commandName)} ${color.bold(usage)}`); message.push(linebreak(), `${color.green(commandName)} ${color.bold(usage)}`);
} }