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,
packageManager,
packages: [],
cwd: pathToFileURL(process.cwd()),
cwd: new URL(pathToFileURL(process.cwd()) + '/'),
dryRun,
version,
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 path from 'node:path';
import { fileURLToPath } from 'node:url';
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 { random, sleep } from '@astrojs/cli-kit/utils';
export async function install(
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) {
await info('--dry-run', `Skipping dependency installation`);
} else {
await spinner({
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.`
);
});
},
});
await runInstallCommand(ctx, dependencies, devDependencies);
}
}
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);
if (ctx.packageManager === 'yarn') await ensureYarnLock({ cwd });
const dependencies: string[] = [];
const devDependencies: string[] = [];
for (const { name, targetVersion, isDevDependency } of ctx.packages) {
const arr = isDevDependency ? devDependencies : dependencies;
arr.push(`${name}@${targetVersion}`);
}
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], { cwd, timeout: 90_000, stdio: 'ignore' });
}
await spinner({
start: `Installing dependencies with ${ctx.packageManager}...`,
end: random(done),
while: async () => {
try {
if (dependencies.length > 0) {
await shell(ctx.packageManager, ['install', ...dependencies.map(({ name, targetVersion }) => `${name}@${targetVersion}`)], { 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' })
}
} 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 }) {

View file

@ -60,6 +60,10 @@ async function verifyAstroProject(ctx: Pick<Context, 'cwd' | 'version' | 'packag
// Side-effect! Persist dependency info to the shared context
collectPackageInfo(ctx, dependencies, devDependencies);
ctx.packages.sort((a) => {
if (a.name === 'astro') return -1;
return 0;
})
return true;
}
@ -95,15 +99,28 @@ async function verifyVersions(ctx: Pick<Context, 'version' | 'packages'>, regist
}
await Promise.all(tasks);
for (const packageInfo of ctx.packages) {
// check for packageInfo.targetVersion, then fallback
// check if packageInfo.targetVersion === packageInfo.currentVersion
if (!packageInfo.targetVersion) {
return false;
}
}
return true;
}
async function resolveTargetVersion(packageInfo: PackageInfo, registry: string): Promise<void> {
const res = await fetch(`${registry}/${packageInfo.name}/tags/${packageInfo.targetVersion}`);
// MUTATE packageInfo.targetVersion to resolvedValue
const res = await fetch(`${registry}/${packageInfo.name}/${packageInfo.targetVersion}`);
const { code, version } = await res.json()
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' */
import { color, say as houston, 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 { color, label, spinner as load } from '@astrojs/cli-kit';
import detectPackageManager from 'which-pm-runs';
import { shell } from './shell.js';
@ -27,10 +23,6 @@ export function setStdout(writable: typeof process.stdout) {
stdout = writable;
}
export async function say(messages: string | string[], { clear = false, hat = '' } = {}) {
return houston(messages, { clear, hat, stdout });
}
export async function spinner(args: {
start: string;
end: string;
@ -39,77 +31,67 @@ export async function spinner(args: {
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 = [
`Let's claim your corner of the internet.`,
`I'll be your assistant today.`,
`Let's build something awesome!`,
`Let's build something great!`,
`Let's build something fast!`,
`Let's build the web we want.`,
`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 done = [
'You\'re on the latest and greatest.',
'Everything is current.',
'Integrations are all up to date.',
'All done. Thanks for using Astro!',
'Integrations up to date. Enjoy building!',
'All set, everything is up to date.',
]
export const log = (message: string) => stdout.write(message + '\n');
export const banner = async (version: string) =>
export const banner = async () =>
log(
`\n${label('astro', color.bgGreen, color.black)}${
version ? ' ' + color.green(color.bold(`v${version}`)) : ''
} ${color.bold('Launch sequence initiated.')}`
`\n${label('astro', color.bgGreen, color.black)} ${color.bold('Integration upgrade in progress.')}`
);
export const bannerAbort = () =>
log(`\n${label('astro', color.bgRed)} ${color.bold('Launch sequence aborted.')}`);
export const info = async (prefix: string, text: string) => {
await sleep(100);
if (stdout.columns < 80) {
log(`${' '.repeat(5)} ${color.cyan('◼')} ${color.cyan(prefix)}`);
export const info = async (prefix: string, text: string, version = '') => {
const length = 11 + prefix.length + text.length + version?.length;
if (length > stdout.columns) {
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)}`);
} 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) => {
if (stdout.columns < 80) {
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({
commandName,
headline,
usage,
tables,
description,
@ -191,13 +132,6 @@ export function printHelp({
let message = [];
if (headline) {
message.push(
linebreak(),
`${title(commandName)} ${color.green(`v${process.env.PACKAGE_VERSION ?? ''}`)} ${headline}`
);
}
if (usage) {
message.push(linebreak(), `${color.green(commandName)} ${color.bold(usage)}`);
}