diff --git a/.changeset/strong-students-tie.md b/.changeset/strong-students-tie.md new file mode 100644 index 000000000000..9ac0be8f761b --- /dev/null +++ b/.changeset/strong-students-tie.md @@ -0,0 +1,5 @@ +--- +'create-astro': patch +--- + +Update "next steps" with more informative text on each CLI command. Oh, and gradients. A lot more gradients. diff --git a/packages/create-astro/package.json b/packages/create-astro/package.json index 5a1e2af99c53..de3c1a35b45c 100644 --- a/packages/create-astro/package.json +++ b/packages/create-astro/package.json @@ -30,6 +30,7 @@ "dependencies": { "@types/degit": "^2.8.3", "@types/prompts": "^2.0.14", + "chalk": "^5.0.1", "degit": "^2.8.4", "execa": "^6.1.0", "kleur": "^4.1.4", diff --git a/packages/create-astro/src/gradient.ts b/packages/create-astro/src/gradient.ts new file mode 100644 index 000000000000..2d4c48d81858 --- /dev/null +++ b/packages/create-astro/src/gradient.ts @@ -0,0 +1,91 @@ +import chalk from 'chalk'; +import ora from 'ora'; +import type { Ora } from 'ora'; + +const gradientColors = [ + `#ff5e00`, + `#ff4c29`, + `#ff383f`, + `#ff2453`, + `#ff0565`, + `#ff007b`, + `#f5008b`, + `#e6149c`, + `#d629ae`, + `#c238bd`, +]; + +export const rocketAscii = '■■▶'; + +// get a reference to scroll through while loading +// visual representation of what this generates: +// gradientColors: "..xxXX" +// referenceGradient: "..xxXXXXxx....xxXX" +const referenceGradient = [ + ...gradientColors, + // draw the reverse of the gradient without + // accidentally mutating the gradient (ugh, reverse()) + ...[...gradientColors].reverse(), + ...gradientColors, +]; + +// async-friendly setTimeout +const sleep = (time: number) => + new Promise((resolve) => { + setTimeout(resolve, time); + }); + +function getGradientAnimFrames() { + const frames = []; + for (let start = 0; start < gradientColors.length * 2; start++) { + const end = start + gradientColors.length - 1; + frames.push( + referenceGradient + .slice(start, end) + .map((g) => chalk.bgHex(g)(' ')) + .join('') + ); + } + return frames; +} + +function getIntroAnimFrames() { + const frames = []; + for (let end = 1; end <= gradientColors.length; end++) { + const leadingSpacesArr = Array.from( + new Array(Math.abs(gradientColors.length - end - 1)), + () => ' ' + ); + const gradientArr = gradientColors.slice(0, end).map((g) => chalk.bgHex(g)(' ')); + frames.push([...leadingSpacesArr, ...gradientArr].join('')); + } + return frames; +} + +/** + * Generate loading spinner with rocket flames! + * @param text display text next to rocket + * @returns Ora spinner for running .stop() + */ +export async function loadWithRocketGradient(text: string): Promise { + const frames = getIntroAnimFrames(); + const intro = ora({ + spinner: { + interval: 30, + frames, + }, + text: `${rocketAscii} ${text}`, + }); + intro.start(); + await sleep((frames.length - 1) * intro.interval); + intro.stop(); + const spinner = ora({ + spinner: { + interval: 80, + frames: getGradientAnimFrames(), + }, + text: `${rocketAscii} ${text}`, + }).start(); + + return spinner; +} diff --git a/packages/create-astro/src/index.ts b/packages/create-astro/src/index.ts index dab8e7a3b9a4..0546bd7512e9 100644 --- a/packages/create-astro/src/index.ts +++ b/packages/create-astro/src/index.ts @@ -8,6 +8,7 @@ import ora from 'ora'; import { TEMPLATES } from './templates.js'; import { logger, defaultLogLevel } from './logger.js'; import { execa, execaCommand } from 'execa'; +import { loadWithRocketGradient, rocketAscii } from './gradient.js'; // NOTE: In the v7.x version of npm, the default behavior of `npm init` was changed // to no longer require `--` to pass args and instead pass `--` directly to us. This @@ -42,10 +43,6 @@ export async function main() { logger.debug('Verbose logging turned on'); console.log(`\n${bold('Welcome to Astro!')} ${gray(`(create-astro v${version})`)}`); - let spinner = ora({ color: 'green', text: 'Prepare for liftoff.' }); - - spinner.succeed(); - let cwd = args['_'][2] as string; if (cwd && isEmpty(cwd)) { @@ -95,6 +92,8 @@ export async function main() { process.exit(1); } + const templateSpinner = await loadWithRocketGradient('Copying project files...'); + const hash = args.commit ? `#${args.commit}` : ''; const templateTarget = `withastro/astro/examples/${options.template}#latest`; @@ -111,8 +110,6 @@ export async function main() { verbose: defaultLogLevel === 'debug' ? true : false, }); - spinner = ora({ color: 'green', text: 'Copying project files...' }).start(); - // Copy if (!args.dryrun) { try { @@ -152,7 +149,7 @@ export async function main() { ) ); } - spinner.fail(); + templateSpinner.fail(); process.exit(1); } @@ -167,8 +164,8 @@ export async function main() { ); } - spinner.succeed(); - console.log(bold(green('✔') + ' Done!')); + templateSpinner.text = green('Template copied!'); + templateSpinner.succeed(); const installResponse = await prompts({ type: 'confirm', @@ -184,15 +181,18 @@ export async function main() { if (installResponse.install && !args.dryrun) { const installExec = execa(pkgManager, ['install'], { cwd }); const installingPackagesMsg = `Installing packages${emojiWithFallback(' 📦', '...')}`; - spinner = ora({ color: 'green', text: installingPackagesMsg }).start(); + const installSpinner = await loadWithRocketGradient(installingPackagesMsg); await new Promise((resolve, reject) => { installExec.stdout?.on('data', function (data) { - spinner.text = `${installingPackagesMsg}\n${bold(`[${pkgManager}]`)} ${data}`; + installSpinner.text = `${rocketAscii} ${installingPackagesMsg}\n${bold( + `[${pkgManager}]` + )} ${data}`; }); installExec.on('error', (error) => reject(error)); installExec.on('close', () => resolve()); }); - spinner.succeed(); + installSpinner.text = green('Packages installed!'); + installSpinner.succeed(); } const astroAddCommand = installResponse.install @@ -240,21 +240,22 @@ export async function main() { await execaCommand('git init', { cwd }); } + ora({ text: green('Done. Ready for liftoff!') }).succeed(); console.log(`\n${bgCyan(black(' Next steps '))}\n`); - const relative = path.relative(process.cwd(), cwd); - const startCommand = []; - if (relative !== '') { - startCommand.push(bold(cyan(`cd ${relative}`))); - } + const projectDir = path.relative(process.cwd(), cwd); + const devCmd = pkgManager === 'npm' ? 'npm run dev' : `${pkgManager} dev`; + + console.log( + `You can now ${bold(cyan('cd'))} into the ${bold(cyan(projectDir))} project directory.` + ); + console.log( + `Run ${bold(cyan(devCmd))} to start the Astro dev server. ${bold(cyan('CTRL-C'))} to close.` + ); if (!installResponse.install) { - startCommand.push(bold(cyan(`${pkgManager} install`))); + console.log(yellow(`Remember to install dependencies first!`)); } - startCommand.push(bold(cyan(pkgManager === 'npm' ? 'npm run dev' : `${pkgManager} dev`))); - console.log(startCommand.join(' && ')); - - console.log(`\nTo close the dev server, hit ${bold(cyan('Ctrl-C'))}`); - console.log(`Stuck? Visit us at ${cyan('https://astro.build/chat')}\n`); + console.log(`\nStuck? Come join us at ${bold(cyan('https://astro.build/chat'))}`); } function emojiWithFallback(char: string, fallback: string) { diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 29b9251f04b6..be8f0175ef84 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -1256,6 +1256,7 @@ importers: '@types/yargs-parser': ^21.0.0 astro-scripts: workspace:* chai: ^4.3.6 + chalk: ^5.0.1 degit: ^2.8.4 execa: ^6.1.0 kleur: ^4.1.4 @@ -1267,6 +1268,7 @@ importers: dependencies: '@types/degit': 2.8.3 '@types/prompts': 2.0.14 + chalk: 5.0.1 degit: 2.8.4 execa: 6.1.0 kleur: 4.1.4