diff options
20 files changed, 412 insertions, 301 deletions
diff --git a/packages/create-astro/src/actions/context.ts b/packages/create-astro/src/actions/context.ts index a73a42e13..35e2e4688 100644 --- a/packages/create-astro/src/actions/context.ts +++ b/packages/create-astro/src/actions/context.ts @@ -1,7 +1,7 @@ -import os from 'node:os'; +import { prompt } from '@astrojs/cli-kit'; import arg from 'arg'; +import os from 'node:os'; import detectPackageManager from 'which-pm-runs'; -import { prompt } from '@astrojs/cli-kit'; import { getName, getVersion } from '../messages.js'; @@ -26,27 +26,29 @@ export interface Context { exit(code: number): never; } - export async function getContext(argv: string[]): Promise<Context> { - const flags = arg({ - '--template': String, - '--ref': String, - '--yes': Boolean, - '--no': Boolean, - '--install': Boolean, - '--no-install': Boolean, - '--git': Boolean, - '--no-git': Boolean, - '--typescript': String, - '--skip-houston': Boolean, - '--dry-run': Boolean, - '--help': Boolean, - '--fancy': Boolean, + const flags = arg( + { + '--template': String, + '--ref': String, + '--yes': Boolean, + '--no': Boolean, + '--install': Boolean, + '--no-install': Boolean, + '--git': Boolean, + '--no-git': Boolean, + '--typescript': String, + '--skip-houston': Boolean, + '--dry-run': Boolean, + '--help': Boolean, + '--fancy': Boolean, - '-y': '--yes', - '-n': '--no', - '-h': '--help', - }, { argv, permissive: true }); + '-y': '--yes', + '-n': '--no', + '-h': '--help', + }, + { argv, permissive: true } + ); const pkgManager = detectPackageManager()?.name ?? 'npm'; const [username, version] = await Promise.all([getName(), getVersion()]); @@ -75,8 +77,10 @@ export async function getContext(argv: string[]): Promise<Context> { if (typescript == undefined) typescript = 'strict'; } - skipHouston = ((os.platform() === 'win32' && !fancy) || skipHouston) ?? [yes, no, install, git, typescript].some((v) => v !== undefined); - + skipHouston = + ((os.platform() === 'win32' && !fancy) || skipHouston) ?? + [yes, no, install, git, typescript].some((v) => v !== undefined); + const context: Context = { help, prompt, @@ -95,7 +99,7 @@ export async function getContext(argv: string[]): Promise<Context> { cwd, exit(code) { process.exit(code); - } - } + }, + }; return context; } diff --git a/packages/create-astro/src/actions/dependencies.ts b/packages/create-astro/src/actions/dependencies.ts index fb935c208..5991e958f 100644 --- a/packages/create-astro/src/actions/dependencies.ts +++ b/packages/create-astro/src/actions/dependencies.ts @@ -1,9 +1,11 @@ -import type { Context } from "./context"; +import type { Context } from './context'; -import { title, info, spinner } from '../messages.js'; import { execa } from 'execa'; +import { info, spinner, title } from '../messages.js'; -export async function dependencies(ctx: Pick<Context, 'install'|'yes'|'prompt'|'pkgManager'|'cwd'|'dryRun'>) { +export async function dependencies( + ctx: Pick<Context, 'install' | 'yes' | 'prompt' | 'pkgManager' | 'cwd' | 'dryRun'> +) { let deps = ctx.install ?? ctx.yes; if (deps === undefined) { ({ deps } = await ctx.prompt({ @@ -33,11 +35,10 @@ export async function dependencies(ctx: Pick<Context, 'install'|'yes'|'prompt'|' } } -async function install({ pkgManager, cwd }: { pkgManager: string, cwd: string }) { - const installExec = execa(pkgManager, ['install'], { cwd }); - return new Promise<void>((resolve, reject) => { - installExec.on('error', (error) => reject(error)); - installExec.on('close', () => resolve()); - }); +async function install({ pkgManager, cwd }: { pkgManager: string; cwd: string }) { + const installExec = execa(pkgManager, ['install'], { cwd }); + return new Promise<void>((resolve, reject) => { + installExec.on('error', (error) => reject(error)); + installExec.on('close', () => resolve()); + }); } - diff --git a/packages/create-astro/src/actions/git.ts b/packages/create-astro/src/actions/git.ts index 6510a0f24..51a7e09c8 100644 --- a/packages/create-astro/src/actions/git.ts +++ b/packages/create-astro/src/actions/git.ts @@ -1,15 +1,15 @@ -import type { Context } from "./context"; import fs from 'node:fs'; import path from 'node:path'; +import type { Context } from './context'; import { color } from '@astrojs/cli-kit'; -import { title, info, spinner } from '../messages.js'; import { execa } from 'execa'; +import { info, spinner, title } from '../messages.js'; -export async function git(ctx: Pick<Context, 'cwd'|'git'|'yes'|'prompt'|'dryRun'>) { +export async function git(ctx: Pick<Context, 'cwd' | 'git' | 'yes' | 'prompt' | 'dryRun'>) { if (fs.existsSync(path.join(ctx.cwd, '.git'))) { await info('Nice!', `Git has already been initialized`); - return + return; } let _git = ctx.git ?? ctx.yes; if (_git === undefined) { @@ -43,6 +43,15 @@ async function init({ cwd }: { cwd: string }) { try { await execa('git', ['init'], { cwd, stdio: 'ignore' }); await execa('git', ['add', '-A'], { cwd, stdio: 'ignore' }); - await execa('git', ['commit', '-m', 'Initial commit from Astro', '--author="houston[bot] <astrobot-houston@users.noreply.github.com>"'], { cwd, stdio: 'ignore' }); + await execa( + 'git', + [ + 'commit', + '-m', + 'Initial commit from Astro', + '--author="houston[bot] <astrobot-houston@users.noreply.github.com>"', + ], + { cwd, stdio: 'ignore' } + ); } catch (e) {} } diff --git a/packages/create-astro/src/actions/intro.ts b/packages/create-astro/src/actions/intro.ts index b3ab88122..ec0c2248b 100644 --- a/packages/create-astro/src/actions/intro.ts +++ b/packages/create-astro/src/actions/intro.ts @@ -1,10 +1,10 @@ import { type Context } from './context'; -import { banner, welcome, say } from '../messages.js'; -import { label, color } from '@astrojs/cli-kit'; +import { color, label } from '@astrojs/cli-kit'; import { random } from '@astrojs/cli-kit/utils'; +import { banner, say, welcome } from '../messages.js'; -export async function intro(ctx: Pick<Context, 'skipHouston'|'version'|'username'>) { +export async function intro(ctx: Pick<Context, 'skipHouston' | 'version' | 'username'>) { if (!ctx.skipHouston) { await say([ [ diff --git a/packages/create-astro/src/actions/next-steps.ts b/packages/create-astro/src/actions/next-steps.ts index 94b0ba71b..db7387ad1 100644 --- a/packages/create-astro/src/actions/next-steps.ts +++ b/packages/create-astro/src/actions/next-steps.ts @@ -1,9 +1,9 @@ -import { Context } from "./context"; import path from 'node:path'; +import { Context } from './context'; import { nextSteps, say } from '../messages.js'; -export async function next(ctx: Pick<Context, 'cwd'|'pkgManager'|'skipHouston'>) { +export async function next(ctx: Pick<Context, 'cwd' | 'pkgManager' | 'skipHouston'>) { let projectDir = path.relative(process.cwd(), ctx.cwd); const devCmd = ctx.pkgManager === 'npm' ? 'npm run dev' : `${ctx.pkgManager} dev`; await nextSteps({ projectDir, devCmd }); diff --git a/packages/create-astro/src/actions/project-name.ts b/packages/create-astro/src/actions/project-name.ts index c849a9060..e293c86a2 100644 --- a/packages/create-astro/src/actions/project-name.ts +++ b/packages/create-astro/src/actions/project-name.ts @@ -1,12 +1,12 @@ -import type { Context } from "./context"; +import type { Context } from './context'; import { color, generateProjectName } from '@astrojs/cli-kit'; -import { title, info, log } from '../messages.js'; import path from 'node:path'; +import { info, log, title } from '../messages.js'; import { isEmpty, toValidName } from './shared.js'; -export async function projectName(ctx: Pick<Context, 'cwd'|'prompt'|'projectName'|'exit'>) { +export async function projectName(ctx: Pick<Context, 'cwd' | 'prompt' | 'projectName' | 'exit'>) { await checkCwd(ctx.cwd); if (!ctx.cwd || !isEmpty(ctx.cwd)) { diff --git a/packages/create-astro/src/actions/shared.ts b/packages/create-astro/src/actions/shared.ts index 838ee5e23..4bd852529 100644 --- a/packages/create-astro/src/actions/shared.ts +++ b/packages/create-astro/src/actions/shared.ts @@ -42,20 +42,18 @@ export function isEmpty(dirPath: string) { } export function isValidName(projectName: string) { - return /^(?:@[a-z\d\-*~][a-z\d\-*._~]*\/)?[a-z\d\-~][a-z\d\-._~]*$/.test( - projectName, - ) + return /^(?:@[a-z\d\-*~][a-z\d\-*._~]*\/)?[a-z\d\-~][a-z\d\-._~]*$/.test(projectName); } export function toValidName(projectName: string) { if (isValidName(projectName)) return projectName; - return projectName - .trim() - .toLowerCase() - .replace(/\s+/g, '-') - .replace(/^[._]/, '') - .replace(/[^a-z\d\-~]+/g, '-') + return projectName + .trim() + .toLowerCase() + .replace(/\s+/g, '-') + .replace(/^[._]/, '') + .replace(/[^a-z\d\-~]+/g, '-') .replace(/^-+/, '') - .replace(/-+$/, '') + .replace(/-+$/, ''); } diff --git a/packages/create-astro/src/actions/template.ts b/packages/create-astro/src/actions/template.ts index 805e9ddca..e809005a2 100644 --- a/packages/create-astro/src/actions/template.ts +++ b/packages/create-astro/src/actions/template.ts @@ -1,14 +1,15 @@ /* eslint no-console: 'off' */ -import type { Context } from "./context"; +import type { Context } from './context'; +import { color } from '@astrojs/cli-kit'; +import { downloadTemplate } from 'giget'; import fs from 'node:fs'; import path from 'node:path'; -import { downloadTemplate } from 'giget'; -import { error } from '../messages.js'; -import { color } from '@astrojs/cli-kit'; -import { title, info, spinner } from '../messages.js'; +import { error, info, spinner, title } from '../messages.js'; -export async function template(ctx: Pick<Context, 'template'|'prompt'|'dryRun'|'exit'|'exit'>) { +export async function template( + ctx: Pick<Context, 'template' | 'prompt' | 'dryRun' | 'exit' | 'exit'> +) { if (!ctx.template) { const { template: tmpl } = await ctx.prompt({ name: 'template', @@ -43,18 +44,27 @@ export async function template(ctx: Pick<Context, 'template'|'prompt'|'dryRun'|' // some files are only needed for online editors when using astro.new. Remove for create-astro installs. const FILES_TO_REMOVE = ['sandbox.config.json', 'CHANGELOG.md']; const FILES_TO_UPDATE = { - 'package.json': (file: string, overrides: { name: string }) => fs.promises.readFile(file, 'utf-8').then(value => ( - fs.promises.writeFile(file, JSON.stringify(Object.assign(JSON.parse(value), Object.assign(overrides, { private: undefined })), null, '\t'), 'utf-8') - )) -} + 'package.json': (file: string, overrides: { name: string }) => + fs.promises + .readFile(file, 'utf-8') + .then((value) => + fs.promises.writeFile( + file, + JSON.stringify( + Object.assign(JSON.parse(value), Object.assign(overrides, { private: undefined })), + null, + '\t' + ), + 'utf-8' + ) + ), +}; export default async function copyTemplate(tmpl: string, ctx: Context) { const ref = ctx.ref || 'latest'; const isThirdParty = tmpl.includes('/'); - const templateTarget = isThirdParty - ? tmpl - : `github:withastro/astro/examples/${tmpl}#${ref}`; + const templateTarget = isThirdParty ? tmpl : `github:withastro/astro/examples/${tmpl}#${ref}`; // Copy if (!ctx.dryRun) { @@ -64,7 +74,7 @@ export default async function copyTemplate(tmpl: string, ctx: Context) { provider: 'github', cwd: ctx.cwd, dir: '.', - }) + }); } catch (err: any) { fs.rmdirSync(ctx.cwd); if (err.message.includes('404')) { @@ -85,9 +95,9 @@ export default async function copyTemplate(tmpl: string, ctx: Context) { const updateFiles = Object.entries(FILES_TO_UPDATE).map(async ([file, update]) => { const fileLoc = path.resolve(path.join(ctx.cwd, file)); if (fs.existsSync(fileLoc)) { - return update(fileLoc, { name: ctx.projectName! }) + return update(fileLoc, { name: ctx.projectName! }); } - }) + }); await Promise.all([...removeFiles, ...updateFiles]); } diff --git a/packages/create-astro/src/actions/typescript.ts b/packages/create-astro/src/actions/typescript.ts index 0c624f889..24e9e8362 100644 --- a/packages/create-astro/src/actions/typescript.ts +++ b/packages/create-astro/src/actions/typescript.ts @@ -1,13 +1,15 @@ -import type { Context } from "./context"; +import type { Context } from './context'; -import fs from 'node:fs' -import { readFile } from 'node:fs/promises' +import { color } from '@astrojs/cli-kit'; +import fs from 'node:fs'; +import { readFile } from 'node:fs/promises'; import path from 'node:path'; import stripJsonComments from 'strip-json-comments'; -import { color } from '@astrojs/cli-kit'; -import { title, info, error, typescriptByDefault, spinner } from '../messages.js'; +import { error, info, spinner, title, typescriptByDefault } from '../messages.js'; -export async function typescript(ctx: Pick<Context, 'typescript'|'yes'|'prompt'|'dryRun'|'cwd'|'exit'>) { +export async function typescript( + ctx: Pick<Context, 'typescript' | 'yes' | 'prompt' | 'dryRun' | 'cwd' | 'exit'> +) { let ts = ctx.typescript ?? (typeof ctx.yes !== 'undefined' ? 'strict' : undefined); if (ts === undefined) { const { useTs } = await ctx.prompt({ @@ -68,7 +70,7 @@ export async function typescript(ctx: Pick<Context, 'typescript'|'yes'|'prompt'| export async function setupTypeScript(value: string, { cwd }: { cwd: string }) { const templateTSConfigPath = path.join(cwd, 'tsconfig.json'); try { - const data = await readFile(templateTSConfigPath, { encoding: 'utf-8' }) + const data = await readFile(templateTSConfigPath, { encoding: 'utf-8' }); const templateTSConfig = JSON.parse(stripJsonComments(data)); if (templateTSConfig && typeof templateTSConfig === 'object') { const result = Object.assign(templateTSConfig, { @@ -77,7 +79,9 @@ export async function setupTypeScript(value: string, { cwd }: { cwd: string }) { fs.writeFileSync(templateTSConfigPath, JSON.stringify(result, null, 2)); } else { - throw new Error("There was an error applying the requested TypeScript settings. This could be because the template's tsconfig.json is malformed") + throw new Error( + "There was an error applying the requested TypeScript settings. This could be because the template's tsconfig.json is malformed" + ); } } catch (err) { if (err && (err as any).code === 'ENOENT') { diff --git a/packages/create-astro/src/index.ts b/packages/create-astro/src/index.ts index 5ba94d53f..5090a6c1c 100644 --- a/packages/create-astro/src/index.ts +++ b/packages/create-astro/src/index.ts @@ -1,18 +1,18 @@ import { getContext } from './actions/context.js'; -import { setStdout } from './messages.js'; -import { help } from './actions/help.js'; -import { intro } from './actions/intro.js'; -import { projectName } from './actions/project-name.js'; -import { template } from './actions/template.js' import { dependencies } from './actions/dependencies.js'; import { git } from './actions/git.js'; -import { typescript, setupTypeScript } from './actions/typescript.js'; +import { help } from './actions/help.js'; +import { intro } from './actions/intro.js'; import { next } from './actions/next-steps.js'; +import { projectName } from './actions/project-name.js'; +import { template } from './actions/template.js'; +import { setupTypeScript, typescript } from './actions/typescript.js'; +import { setStdout } from './messages.js'; -const exit = () => process.exit(0) -process.on('SIGINT', exit) -process.on('SIGTERM', exit) +const exit = () => process.exit(0); +process.on('SIGINT', exit); +process.on('SIGTERM', exit); // Please also update the installation instructions in the docs at // https://github.com/withastro/docs/blob/main/src/pages/en/install/auto.md @@ -29,18 +29,10 @@ export async function main() { return; } - const steps = [ - intro, - projectName, - template, - dependencies, - git, - typescript, - next - ] + const steps = [intro, projectName, template, dependencies, git, typescript, next]; for (const step of steps) { - await step(ctx) + await step(ctx); } process.exit(0); } @@ -55,5 +47,5 @@ export { git, typescript, setupTypeScript, - next -} + next, +}; diff --git a/packages/create-astro/src/messages.ts b/packages/create-astro/src/messages.ts index c70857ada..c0adbaa4a 100644 --- a/packages/create-astro/src/messages.ts +++ b/packages/create-astro/src/messages.ts @@ -1,8 +1,8 @@ /* eslint no-console: 'off' */ +import { color, label, say as houston, spinner as load } from '@astrojs/cli-kit'; +import { align, sleep } from '@astrojs/cli-kit/utils'; import { exec } from 'node:child_process'; import { get } from 'node:https'; -import { color, label, spinner as load, say as houston } from '@astrojs/cli-kit'; -import { sleep, align } from '@astrojs/cli-kit/utils'; import stripAnsi from 'strip-ansi'; let stdout = process.stdout; @@ -11,113 +11,136 @@ export function setStdout(writable: typeof process.stdout) { stdout = writable; } -export async function say(messages: string|string[], { clear = false, hat = '' } = {}) { +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; while: (...args: any) => Promise<any>; }) { +export async function spinner(args: { + start: string; + end: string; + while: (...args: any) => Promise<any>; +}) { await load(args, { stdout }); } export const title = (text: string) => align(label(text), 'end', 7) + ' '; 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, _2) => { - if (gitName.trim()) { - return resolve(gitName.split(' ')[0].trim()); - } - exec('whoami', { encoding: 'utf-8' }, (_3, whoami, _4) => { - if (whoami.trim()) { - return resolve(whoami.split(' ')[0].trim()); - } - return resolve('astronaut'); - }); - }); -}); + `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, _2) => { + if (gitName.trim()) { + return resolve(gitName.split(' ')[0].trim()); + } + exec('whoami', { encoding: 'utf-8' }, (_3, whoami, _4) => { + if (whoami.trim()) { + return resolve(whoami.split(' ')[0].trim()); + } + return resolve('astronaut'); + }); + }); + }); let v: string; -export const getVersion = () => new Promise<string>((resolve) => { - if (v) return resolve(v); - get('https://registry.npmjs.org/astro/latest', (res) => { - let body = ''; - res.on('data', chunk => body += chunk) - res.on('end', () => { - const { version } = JSON.parse(body); - v = version; - resolve(version); - }) - }) -}) - -export const log = (message: string) => stdout.write(message + "\n"); -export const banner = async (version: string) => log(`\n${label('astro', color.bgGreen, color.black)} ${color.green(color.bold(`v${version}`))} ${color.bold('Launch sequence initiated.')}`); +export const getVersion = () => + new Promise<string>((resolve) => { + if (v) return resolve(v); + get('https://registry.npmjs.org/astro/latest', (res) => { + let body = ''; + res.on('data', (chunk) => (body += chunk)); + res.on('end', () => { + const { version } = JSON.parse(body); + v = version; + resolve(version); + }); + }); + }); + +export const log = (message: string) => stdout.write(message + '\n'); +export const banner = async (version: string) => + log( + `\n${label('astro', color.bgGreen, color.black)} ${color.green( + color.bold(`v${version}`) + )} ${color.bold('Launch sequence initiated.')}` + ); export const info = async (prefix: string, text: string) => { - await sleep(100) - if (stdout.columns < 80) { - log(`${' '.repeat(5)} ${color.cyan('◼')} ${color.cyan(prefix)}`); - log(`${' '.repeat(9)}${color.dim(text)}`); - } else { - log(`${' '.repeat(5)} ${color.cyan('◼')} ${color.cyan(prefix)} ${color.dim(text)}`); - } -} + await sleep(100); + if (stdout.columns < 80) { + log(`${' '.repeat(5)} ${color.cyan('◼')} ${color.cyan(prefix)}`); + log(`${' '.repeat(9)}${color.dim(text)}`); + } else { + log(`${' '.repeat(5)} ${color.cyan('◼')} ${color.cyan(prefix)} ${color.dim(text)}`); + } +}; export const error = async (prefix: string, text: string) => { - if (stdout.columns < 80) { - log(`${' '.repeat(5)} ${color.red('▲')} ${color.red(prefix)}`); - log(`${' '.repeat(9)}${color.dim(text)}`); - } else { - log(`${' '.repeat(5)} ${color.red('▲')} ${color.red(prefix)} ${color.dim(text)}`); - } -} + if (stdout.columns < 80) { + log(`${' '.repeat(5)} ${color.red('▲')} ${color.red(prefix)}`); + log(`${' '.repeat(9)}${color.dim(text)}`); + } else { + log(`${' '.repeat(5)} ${color.red('▲')} ${color.red(prefix)} ${color.dim(text)}`); + } +}; 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 !== '') { - 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); -} - + 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 !== '') { + 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, @@ -154,9 +177,7 @@ export function printHelp({ if (headline) { message.push( linebreak(), - `${title(commandName)} ${color.green( - `v${process.env.PACKAGE_VERSION ?? ''}` - )} ${headline}` + `${title(commandName)} ${color.green(`v${process.env.PACKAGE_VERSION ?? ''}`)} ${headline}` ); } diff --git a/packages/create-astro/test/context.test.js b/packages/create-astro/test/context.test.js index d6cb1c70f..113417417 100644 --- a/packages/create-astro/test/context.test.js +++ b/packages/create-astro/test/context.test.js @@ -10,53 +10,53 @@ describe('context', () => { expect(ctx.template).to.be.undefined; expect(ctx.skipHouston).to.eq(os.platform() === 'win32'); expect(ctx.dryRun).to.be.undefined; - }) + }); it('project name', async () => { const ctx = await getContext(['foobar']); expect(ctx.projectName).to.eq('foobar'); - }) + }); it('template', async () => { const ctx = await getContext(['--template', 'minimal']); expect(ctx.template).to.eq('minimal'); - }) + }); it('skip houston (explicit)', async () => { const ctx = await getContext(['--skip-houston']); expect(ctx.skipHouston).to.eq(true); - }) + }); it('skip houston (yes)', async () => { const ctx = await getContext(['-y']); expect(ctx.skipHouston).to.eq(true); - }) + }); it('skip houston (no)', async () => { const ctx = await getContext(['-n']); expect(ctx.skipHouston).to.eq(true); - }) + }); it('skip houston (install)', async () => { const ctx = await getContext(['--install']); expect(ctx.skipHouston).to.eq(true); - }) + }); it('dry run', async () => { const ctx = await getContext(['--dry-run']); expect(ctx.dryRun).to.eq(true); - }) + }); it('install', async () => { const ctx = await getContext(['--install']); expect(ctx.install).to.eq(true); - }) + }); it('no install', async () => { const ctx = await getContext(['--no-install']); expect(ctx.install).to.eq(false); - }) + }); it('git', async () => { const ctx = await getContext(['--git']); expect(ctx.git).to.eq(true); - }) + }); it('no git', async () => { const ctx = await getContext(['--no-git']); expect(ctx.git).to.eq(false); - }) + }); it('typescript', async () => { const ctx = await getContext(['--typescript', 'strict']); expect(ctx.typescript).to.eq('strict'); - }) -}) + }); +}); diff --git a/packages/create-astro/test/dependencies.test.js b/packages/create-astro/test/dependencies.test.js index 515c42294..88e43597b 100644 --- a/packages/create-astro/test/dependencies.test.js +++ b/packages/create-astro/test/dependencies.test.js @@ -7,36 +7,66 @@ describe('dependencies', () => { const fixture = setup(); it('--yes', async () => { - const context = { cwd: '', yes: true, pkgManager: 'npm', dryRun: true, prompt: (() => ({ deps: true }))}; + const context = { + cwd: '', + yes: true, + pkgManager: 'npm', + dryRun: true, + prompt: () => ({ deps: true }), + }; await dependencies(context); expect(fixture.hasMessage('Skipping dependency installation')).to.be.true; - }) + }); it('prompt yes', async () => { - const context = { cwd: '', pkgManager: 'npm', dryRun: true, prompt: (() => ({ deps: true })), install: undefined }; + const context = { + cwd: '', + pkgManager: 'npm', + dryRun: true, + prompt: () => ({ deps: true }), + install: undefined, + }; await dependencies(context); expect(fixture.hasMessage('Skipping dependency installation')).to.be.true; expect(context.install).to.eq(true); - }) + }); it('prompt no', async () => { - const context = { cwd: '', pkgManager: 'npm', dryRun: true, prompt: (() => ({ deps: false })), install: undefined }; + const context = { + cwd: '', + pkgManager: 'npm', + dryRun: true, + prompt: () => ({ deps: false }), + install: undefined, + }; await dependencies(context); expect(fixture.hasMessage('Skipping dependency installation')).to.be.true; expect(context.install).to.eq(false); - }) + }); it('--install', async () => { - const context = { cwd: '', install: true, pkgManager: 'npm', dryRun: true, prompt: (() => ({ deps: false })) }; + const context = { + cwd: '', + install: true, + pkgManager: 'npm', + dryRun: true, + prompt: () => ({ deps: false }), + }; await dependencies(context); expect(fixture.hasMessage('Skipping dependency installation')).to.be.true; expect(context.install).to.eq(true); - }) + }); it('--no-install', async () => { - const context = { cwd: '', install: false, pkgManager: 'npm', dryRun: true, prompt: (() => ({ deps: false })) }; + const context = { + cwd: '', + install: false, + pkgManager: 'npm', + dryRun: true, + prompt: () => ({ deps: false }), + }; await dependencies(context); expect(fixture.hasMessage('Skipping dependency installation')).to.be.true; expect(context.install).to.eq(false); - }) -}) + }); +}); diff --git a/packages/create-astro/test/git.test.js b/packages/create-astro/test/git.test.js index 4b048156a..6111b1398 100644 --- a/packages/create-astro/test/git.test.js +++ b/packages/create-astro/test/git.test.js @@ -10,14 +10,19 @@ describe('git', () => { const fixture = setup(); it('none', async () => { - const context = { cwd: '', dryRun: true, prompt: (() => ({ git: false }))}; + const context = { cwd: '', dryRun: true, prompt: () => ({ git: false }) }; await git(context); expect(fixture.hasMessage('Skipping Git initialization')).to.be.true; - }) + }); it('already initialized', async () => { - const context = { git: true, cwd: './test/fixtures/not-empty', dryRun: true, prompt: (() => ({ git: false }))}; + const context = { + git: true, + cwd: './test/fixtures/not-empty', + dryRun: true, + prompt: () => ({ git: false }), + }; await execa('git', ['init'], { cwd: './test/fixtures/not-empty' }); await git(context); @@ -25,19 +30,19 @@ describe('git', () => { // Cleanup fs.rmSync('./test/fixtures/not-empty/.git', { recursive: true, force: true }); - }) + }); it('yes (--dry-run)', async () => { - const context = { cwd: '', dryRun: true, prompt: (() => ({ git: true }))}; + const context = { cwd: '', dryRun: true, prompt: () => ({ git: true }) }; await git(context); expect(fixture.hasMessage('Skipping Git initialization')).to.be.true; - }) + }); it('no (--dry-run)', async () => { - const context = { cwd: '', dryRun: true, prompt: (() => ({ git: false }))}; + const context = { cwd: '', dryRun: true, prompt: () => ({ git: false }) }; await git(context); expect(fixture.hasMessage('Skipping Git initialization')).to.be.true; - }) -}) + }); +}); diff --git a/packages/create-astro/test/intro.test.js b/packages/create-astro/test/intro.test.js index af13954d1..9014da457 100644 --- a/packages/create-astro/test/intro.test.js +++ b/packages/create-astro/test/intro.test.js @@ -10,11 +10,11 @@ describe('intro', () => { await intro({ skipHouston: false, version: '0.0.0', username: 'user' }); expect(fixture.hasMessage('Houston:')).to.be.true; expect(fixture.hasMessage('Welcome to astro v0.0.0')).to.be.true; - }) + }); it('--skip-houston', async () => { await intro({ skipHouston: true, version: '0.0.0', username: 'user' }); expect(fixture.length()).to.eq(1); expect(fixture.hasMessage('Houston:')).to.be.false; expect(fixture.hasMessage('Launch sequence initiated')).to.be.true; - }) -}) + }); +}); diff --git a/packages/create-astro/test/next.test.js b/packages/create-astro/test/next.test.js index 46efdf67f..efc0e6728 100644 --- a/packages/create-astro/test/next.test.js +++ b/packages/create-astro/test/next.test.js @@ -11,10 +11,10 @@ describe('next steps', () => { expect(fixture.hasMessage('Liftoff confirmed.')).to.be.true; expect(fixture.hasMessage('npm run dev')).to.be.true; expect(fixture.hasMessage('Good luck out there, astronaut!')).to.be.true; - }) + }); it('--skip-houston', async () => { await next({ skipHouston: true, cwd: './it/fixtures/not-empty', pkgManager: 'npm' }); expect(fixture.hasMessage('Good luck out there, astronaut!')).to.be.false; - }) -}) + }); +}); diff --git a/packages/create-astro/test/project-name.test.js b/packages/create-astro/test/project-name.test.js index 38f1359b6..f297da076 100644 --- a/packages/create-astro/test/project-name.test.js +++ b/packages/create-astro/test/project-name.test.js @@ -7,73 +7,81 @@ describe('project name', () => { const fixture = setup(); it('pass in name', async () => { - const context = { projectName: '', cwd: './foo/bar/baz', prompt: (() => {})}; + const context = { projectName: '', cwd: './foo/bar/baz', prompt: () => {} }; await projectName(context); expect(context.cwd).to.eq('./foo/bar/baz'); expect(context.projectName).to.eq('baz'); - }) + }); it('dot', async () => { - const context = { projectName: '', cwd: '.', prompt: (() => ({ name: 'foobar' }))}; + const context = { projectName: '', cwd: '.', prompt: () => ({ name: 'foobar' }) }; await projectName(context); expect(fixture.hasMessage('"." is not empty!')).to.be.true; expect(context.projectName).to.eq('foobar'); - }) + }); it('dot slash', async () => { - const context = { projectName: '', cwd: './', prompt: (() => ({ name: 'foobar' }))}; + const context = { projectName: '', cwd: './', prompt: () => ({ name: 'foobar' }) }; await projectName(context); expect(fixture.hasMessage('"./" is not empty!')).to.be.true; expect(context.projectName).to.eq('foobar'); - }) + }); it('empty', async () => { - const context = { projectName: '', cwd: './test/fixtures/empty', prompt: (() => ({ name: 'foobar' }))}; + const context = { + projectName: '', + cwd: './test/fixtures/empty', + prompt: () => ({ name: 'foobar' }), + }; await projectName(context); expect(fixture.hasMessage('"./test/fixtures/empty" is not empty!')).to.be.false; expect(context.projectName).to.eq('empty'); - }) + }); it('not empty', async () => { - const context = { projectName: '', cwd: './test/fixtures/not-empty', prompt: (() => ({ name: 'foobar' }))}; + const context = { + projectName: '', + cwd: './test/fixtures/not-empty', + prompt: () => ({ name: 'foobar' }), + }; await projectName(context); expect(fixture.hasMessage('"./test/fixtures/not-empty" is not empty!')).to.be.true; expect(context.projectName).to.eq('foobar'); - }) + }); it('basic', async () => { - const context = { projectName: '', cwd: '', prompt: (() => ({ name: 'foobar' }))}; + const context = { projectName: '', cwd: '', prompt: () => ({ name: 'foobar' }) }; await projectName(context); expect(context.cwd).to.eq('foobar'); expect(context.projectName).to.eq('foobar'); - }) + }); it('normalize', async () => { - const context = { projectName: '', cwd: '', prompt: (() => ({ name: 'Invalid Name' }))}; + const context = { projectName: '', cwd: '', prompt: () => ({ name: 'Invalid Name' }) }; await projectName(context); expect(context.cwd).to.eq('Invalid Name'); expect(context.projectName).to.eq('invalid-name'); - }) + }); it('remove leading/trailing dashes', async () => { - const context = { projectName: '', cwd: '', prompt: (() => ({ name: '(invalid)' }))}; + const context = { projectName: '', cwd: '', prompt: () => ({ name: '(invalid)' }) }; await projectName(context); expect(context.projectName).to.eq('invalid'); - }) + }); it('handles scoped packages', async () => { - const context = { projectName: '', cwd: '', prompt: (() => ({ name: '@astro/site' }))}; + const context = { projectName: '', cwd: '', prompt: () => ({ name: '@astro/site' }) }; await projectName(context); expect(context.cwd).to.eq('@astro/site'); expect(context.projectName).to.eq('@astro/site'); - }) -}) + }); +}); diff --git a/packages/create-astro/test/template.test.js b/packages/create-astro/test/template.test.js index 53b9777ff..66c7f5446 100644 --- a/packages/create-astro/test/template.test.js +++ b/packages/create-astro/test/template.test.js @@ -7,30 +7,30 @@ describe('template', () => { const fixture = setup(); it('none', async () => { - const context = { template: '', cwd: '', dryRun: true, prompt: (() => ({ template: 'blog' })) }; + const context = { template: '', cwd: '', dryRun: true, prompt: () => ({ template: 'blog' }) }; await template(context); expect(fixture.hasMessage('Skipping template copying')).to.be.true; expect(context.template).to.eq('blog'); - }) + }); it('minimal (--dry-run)', async () => { - const context = { template: 'minimal', cwd: '', dryRun: true, prompt: (() => {})}; + const context = { template: 'minimal', cwd: '', dryRun: true, prompt: () => {} }; await template(context); expect(fixture.hasMessage('Using minimal as project template')).to.be.true; - }) + }); it('basics (--dry-run)', async () => { - const context = { template: 'basics', cwd: '', dryRun: true, prompt: (() => {})}; + const context = { template: 'basics', cwd: '', dryRun: true, prompt: () => {} }; await template(context); expect(fixture.hasMessage('Using basics as project template')).to.be.true; - }) + }); it('blog (--dry-run)', async () => { - const context = { template: 'blog', cwd: '', dryRun: true, prompt: (() => {})}; + const context = { template: 'blog', cwd: '', dryRun: true, prompt: () => {} }; await template(context); expect(fixture.hasMessage('Using blog as project template')).to.be.true; - }) -}) + }); +}); diff --git a/packages/create-astro/test/typescript.test.js b/packages/create-astro/test/typescript.test.js index 599214dff..be89a499d 100644 --- a/packages/create-astro/test/typescript.test.js +++ b/packages/create-astro/test/typescript.test.js @@ -1,7 +1,7 @@ import { expect } from 'chai'; -import fs from 'node:fs' -import { fileURLToPath } from 'node:url' +import fs from 'node:fs'; +import { fileURLToPath } from 'node:url'; import { typescript, setupTypeScript } from '../dist/index.js'; import { setup } from './utils.js'; @@ -10,70 +10,97 @@ describe('typescript', () => { const fixture = setup(); it('none', async () => { - const context = { cwd: '', dryRun: true, prompt: (() => ({ ts: 'strict', useTs: true }))}; + const context = { cwd: '', dryRun: true, prompt: () => ({ ts: 'strict', useTs: true }) }; await typescript(context); - + expect(fixture.hasMessage('Skipping TypeScript setup')).to.be.true; - }) + }); it('use false', async () => { - const context = { cwd: '', dryRun: true, prompt: (() => ({ ts: 'strict', useTs: false }))}; + const context = { cwd: '', dryRun: true, prompt: () => ({ ts: 'strict', useTs: false }) }; await typescript(context); expect(fixture.hasMessage('No worries')).to.be.true; - }) + }); it('strict', async () => { - const context = { typescript: 'strict', cwd: '', dryRun: true, prompt: (() => ({ ts: 'strict' }))}; + const context = { + typescript: 'strict', + cwd: '', + dryRun: true, + prompt: () => ({ ts: 'strict' }), + }; await typescript(context); expect(fixture.hasMessage('Using strict TypeScript configuration')).to.be.true; expect(fixture.hasMessage('Skipping TypeScript setup')).to.be.true; - }) + }); it('default', async () => { - const context = { typescript: 'default', cwd: '', dryRun: true, prompt: (() => ({ ts: 'strict' }))}; + const context = { + typescript: 'default', + cwd: '', + dryRun: true, + prompt: () => ({ ts: 'strict' }), + }; await typescript(context); expect(fixture.hasMessage('Using default TypeScript configuration')).to.be.true; expect(fixture.hasMessage('Skipping TypeScript setup')).to.be.true; - }) + }); it('relaxed', async () => { - const context = { typescript: 'relaxed', cwd: '', dryRun: true, prompt: (() => ({ ts: 'strict' }))}; + const context = { + typescript: 'relaxed', + cwd: '', + dryRun: true, + prompt: () => ({ ts: 'strict' }), + }; await typescript(context); expect(fixture.hasMessage('Using relaxed TypeScript configuration')).to.be.true; expect(fixture.hasMessage('Skipping TypeScript setup')).to.be.true; - }) + }); it('other', async () => { - const context = { typescript: 'other', cwd: '', dryRun: true, prompt: (() => ({ ts: 'strict' })), exit(code) { throw code }}; + const context = { + typescript: 'other', + cwd: '', + dryRun: true, + prompt: () => ({ ts: 'strict' }), + exit(code) { + throw code; + }, + }; let err = null; try { await typescript(context); } catch (e) { err = e; } - expect(err).to.eq(1) - }) -}) + expect(err).to.eq(1); + }); +}); describe('typescript: setup', () => { it('none', async () => { const root = new URL('./fixtures/empty/', import.meta.url); const tsconfig = new URL('./tsconfig.json', root); - await setupTypeScript('strict', { cwd: fileURLToPath(root) }) - expect(JSON.parse(fs.readFileSync(tsconfig, { encoding: 'utf-8' }))).to.deep.eq({ "extends": "astro/tsconfigs/strict" }); + await setupTypeScript('strict', { cwd: fileURLToPath(root) }); + expect(JSON.parse(fs.readFileSync(tsconfig, { encoding: 'utf-8' }))).to.deep.eq({ + extends: 'astro/tsconfigs/strict', + }); fs.rmSync(tsconfig); - }) + }); it('exists', async () => { const root = new URL('./fixtures/not-empty/', import.meta.url); const tsconfig = new URL('./tsconfig.json', root); - await setupTypeScript('strict', { cwd: fileURLToPath(root) }) - expect(JSON.parse(fs.readFileSync(tsconfig, { encoding: 'utf-8' }))).to.deep.eq({ "extends": "astro/tsconfigs/strict" }); + await setupTypeScript('strict', { cwd: fileURLToPath(root) }); + expect(JSON.parse(fs.readFileSync(tsconfig, { encoding: 'utf-8' }))).to.deep.eq({ + extends: 'astro/tsconfigs/strict', + }); fs.writeFileSync(tsconfig, `{}`); - }) -}) + }); +}); diff --git a/packages/create-astro/test/utils.js b/packages/create-astro/test/utils.js index c2cbb7245..ff5d5dd83 100644 --- a/packages/create-astro/test/utils.js +++ b/packages/create-astro/test/utils.js @@ -4,26 +4,28 @@ import stripAnsi from 'strip-ansi'; export function setup() { const ctx = { messages: [] }; before(() => { - setStdout(Object.assign({}, process.stdout, { - write(buf) { - ctx.messages.push(stripAnsi(String(buf)).trim()) - return true; - } - })) + setStdout( + Object.assign({}, process.stdout, { + write(buf) { + ctx.messages.push(stripAnsi(String(buf)).trim()); + return true; + }, + }) + ); }); beforeEach(() => { ctx.messages = []; - }) + }); return { messages() { - return ctx.messages + return ctx.messages; }, length() { - return ctx.messages.length + return ctx.messages.length; }, hasMessage(content) { - return !!ctx.messages.find(msg => msg.includes(content)) - } + return !!ctx.messages.find((msg) => msg.includes(content)); + }, }; } |