feat: update upgrade cli
This commit is contained in:
parent
eb91b3ce1d
commit
685637d6cb
4 changed files with 135 additions and 152 deletions
|
@ -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) {
|
||||
|
|
|
@ -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 }) {
|
||||
|
|
|
@ -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}`;
|
||||
}
|
||||
|
||||
|
|
|
@ -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)}`);
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue