diff options
Diffstat (limited to 'packages/create-astro/src')
-rw-r--r-- | packages/create-astro/src/index.ts | 286 |
1 files changed, 166 insertions, 120 deletions
diff --git a/packages/create-astro/src/index.ts b/packages/create-astro/src/index.ts index 9c102c37e..80b3b4fd1 100644 --- a/packages/create-astro/src/index.ts +++ b/packages/create-astro/src/index.ts @@ -10,6 +10,7 @@ import { FRAMEWORKS, COUNTER_COMPONENTS, Integration } from './frameworks.js'; import { TEMPLATES } from './templates.js'; import { createConfig } from './config.js'; import { logger, defaultLogLevel } from './logger.js'; +import { execa } from 'execa'; // 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 @@ -40,6 +41,8 @@ const FILES_TO_REMOVE = ['.stackblitzrc', 'sandbox.config.json']; // some files const POSTPROCESS_FILES = ['package.json', 'astro.config.mjs', 'CHANGELOG.md']; // some files need processing after copying. export async function main() { + const pkgManager = pkgManagerFromUserAgent(process.env.npm_config_user_agent); + logger.debug('Verbose logging turned on'); console.log(`\n${bold('Welcome to Astro!')} ${gray(`(create-astro v${version})`)}`); console.log( @@ -138,140 +141,169 @@ export async function main() { spinner = ora({ color: 'green', text: 'Copying project files...' }).start(); // Copy - try { - emitter.on('info', (info) => { - logger.debug(info.message); - }); - await emitter.clone(cwd); - } catch (err: any) { - // degit is compiled, so the stacktrace is pretty noisy. Only report the stacktrace when using verbose mode. - logger.debug(err); - console.error(red(err.message)); - - // Warning for issue #655 - if (err.message === 'zlib: unexpected end of file') { - console.log( - yellow( - "This seems to be a cache related problem. Remove the folder '~/.degit/github/withastro' to fix this error." - ) - ); - console.log( - yellow( - 'For more information check out this issue: https://github.com/withastro/astro/issues/655' - ) - ); - } + if (!args.dryrun) { + try { + emitter.on('info', (info) => { + logger.debug(info.message); + }); + await emitter.clone(cwd); + } catch (err: any) { + // degit is compiled, so the stacktrace is pretty noisy. Only report the stacktrace when using verbose mode. + logger.debug(err); + console.error(red(err.message)); + + // Warning for issue #655 + if (err.message === 'zlib: unexpected end of file') { + console.log( + yellow( + "This seems to be a cache related problem. Remove the folder '~/.degit/github/withastro' to fix this error." + ) + ); + console.log( + yellow( + 'For more information check out this issue: https://github.com/withastro/astro/issues/655' + ) + ); + } - // Helpful message when encountering the "could not find commit hash for ..." error - if (err.code === 'MISSING_REF') { - console.log( - yellow( - "This seems to be an issue with degit. Please check if you have 'git' installed on your system, and install it if you don't have (https://git-scm.com)." - ) - ); - console.log( - yellow( - "If you do have 'git' installed, please run this command with the --verbose flag and file a new issue with the command output here: https://github.com/withastro/astro/issues" - ) - ); + // Helpful message when encountering the "could not find commit hash for ..." error + if (err.code === 'MISSING_REF') { + console.log( + yellow( + "This seems to be an issue with degit. Please check if you have 'git' installed on your system, and install it if you don't have (https://git-scm.com)." + ) + ); + console.log( + yellow( + "If you do have 'git' installed, please run this command with the --verbose flag and file a new issue with the command output here: https://github.com/withastro/astro/issues" + ) + ); + } + spinner.fail(); + process.exit(1); } - spinner.fail(); - process.exit(1); - } - // Post-process in parallel - await Promise.all([ - ...FILES_TO_REMOVE.map(async (file) => { - const fileLoc = path.resolve(path.join(cwd, file)); - return fs.promises.rm(fileLoc); - }), - ...POSTPROCESS_FILES.map(async (file) => { - const fileLoc = path.resolve(path.join(cwd, file)); - - switch (file) { - case 'CHANGELOG.md': { - if (fs.existsSync(fileLoc)) { - await fs.promises.unlink(fileLoc); + // Post-process in parallel + await Promise.all([ + ...FILES_TO_REMOVE.map(async (file) => { + const fileLoc = path.resolve(path.join(cwd, file)); + return fs.promises.rm(fileLoc); + }), + ...POSTPROCESS_FILES.map(async (file) => { + const fileLoc = path.resolve(path.join(cwd, file)); + + switch (file) { + case 'CHANGELOG.md': { + if (fs.existsSync(fileLoc)) { + await fs.promises.unlink(fileLoc); + } + break; } - break; - } - case 'astro.config.mjs': { - if (selectedTemplate?.integrations !== true) { + case 'astro.config.mjs': { + if (selectedTemplate?.integrations !== true) { + break; + } + await fs.promises.writeFile(fileLoc, createConfig({ integrations })); break; } - await fs.promises.writeFile(fileLoc, createConfig({ integrations })); - break; - } - case 'package.json': { - const packageJSON = JSON.parse(await fs.promises.readFile(fileLoc, 'utf8')); - delete packageJSON.snowpack; // delete snowpack config only needed in monorepo (can mess up projects) - // Fetch latest versions of selected integrations - const integrationEntries = ( - await Promise.all( - integrations.map((integration) => - fetch(`https://registry.npmjs.org/${integration.packageName}/latest`) - .then((res) => res.json()) - .then((res: any) => { - let dependencies: [string, string][] = [[res['name'], `^${res['version']}`]]; - - if (res['peerDependencies']) { - for (const peer in res['peerDependencies']) { - dependencies.push([peer, res['peerDependencies'][peer]]); + case 'package.json': { + const packageJSON = JSON.parse(await fs.promises.readFile(fileLoc, 'utf8')); + delete packageJSON.snowpack; // delete snowpack config only needed in monorepo (can mess up projects) + // Fetch latest versions of selected integrations + const integrationEntries = ( + await Promise.all( + integrations.map((integration) => + fetch(`https://registry.npmjs.org/${integration.packageName}/latest`) + .then((res) => res.json()) + .then((res: any) => { + let dependencies: [string, string][] = [[res['name'], `^${res['version']}`]]; + + if (res['peerDependencies']) { + for (const peer in res['peerDependencies']) { + dependencies.push([peer, res['peerDependencies'][peer]]); + } } - } - return dependencies; - }) + return dependencies; + }) + ) ) - ) - ).flat(1); - // merge and sort dependencies - packageJSON.devDependencies = { - ...(packageJSON.devDependencies ?? {}), - ...Object.fromEntries(integrationEntries), - }; - packageJSON.devDependencies = Object.fromEntries( - Object.entries(packageJSON.devDependencies).sort((a, b) => a[0].localeCompare(b[0])) - ); - await fs.promises.writeFile(fileLoc, JSON.stringify(packageJSON, undefined, 2)); - break; + ).flat(1); + // merge and sort dependencies + packageJSON.devDependencies = { + ...(packageJSON.devDependencies ?? {}), + ...Object.fromEntries(integrationEntries), + }; + packageJSON.devDependencies = Object.fromEntries( + Object.entries(packageJSON.devDependencies).sort((a, b) => a[0].localeCompare(b[0])) + ); + await fs.promises.writeFile(fileLoc, JSON.stringify(packageJSON, undefined, 2)); + break; + } } - } - }), - ]); + }), + ]); - // Inject framework components into starter template - if (selectedTemplate?.value === 'starter') { - let importStatements: string[] = []; - let components: string[] = []; - await Promise.all( - integrations.map(async (integration) => { - const component = COUNTER_COMPONENTS[integration.id as keyof typeof COUNTER_COMPONENTS]; - const componentName = path.basename(component.filename, path.extname(component.filename)); - const absFileLoc = path.resolve(cwd, component.filename); - importStatements.push( - `import ${componentName} from '${component.filename.replace(/^src/, '..')}';` - ); - components.push(`<${componentName} client:visible />`); - await fs.promises.writeFile(absFileLoc, component.content); - }) - ); - - const pageFileLoc = path.resolve(path.join(cwd, 'src', 'pages', 'index.astro')); - const content = (await fs.promises.readFile(pageFileLoc)).toString(); - const newContent = content - .replace(/^(\s*)\/\* ASTRO\:COMPONENT_IMPORTS \*\//gm, (_, indent) => { - return indent + importStatements.join('\n'); - }) - .replace(/^(\s*)<!-- ASTRO:COMPONENT_MARKUP -->/gm, (_, indent) => { - return components.map((ln) => indent + ln).join('\n'); - }); - await fs.promises.writeFile(pageFileLoc, newContent); + // Inject framework components into starter template + if (selectedTemplate?.value === 'starter') { + let importStatements: string[] = []; + let components: string[] = []; + await Promise.all( + integrations.map(async (integration) => { + const component = COUNTER_COMPONENTS[integration.id as keyof typeof COUNTER_COMPONENTS]; + const componentName = path.basename(component.filename, path.extname(component.filename)); + const absFileLoc = path.resolve(cwd, component.filename); + importStatements.push( + `import ${componentName} from '${component.filename.replace(/^src/, '..')}';` + ); + components.push(`<${componentName} client:visible />`); + await fs.promises.writeFile(absFileLoc, component.content); + }) + ); + + const pageFileLoc = path.resolve(path.join(cwd, 'src', 'pages', 'index.astro')); + const content = (await fs.promises.readFile(pageFileLoc)).toString(); + const newContent = content + .replace(/^(\s*)\/\* ASTRO\:COMPONENT_IMPORTS \*\//gm, (_, indent) => { + return indent + importStatements.join('\n'); + }) + .replace(/^(\s*)<!-- ASTRO:COMPONENT_MARKUP -->/gm, (_, indent) => { + return components.map((ln) => indent + ln).join('\n'); + }); + await fs.promises.writeFile(pageFileLoc, newContent); + } } spinner.succeed(); console.log(bold(green('✔') + ' Done!')); + const installResponse = await prompts({ + type: 'confirm', + name: 'install', + message: `Would you like us to run "${pkgManager} install?"`, + initial: true, + }); + + if (!installResponse) { + process.exit(0); + } + + if (installResponse.install) { + const installExec = execa(pkgManager, ['install'], { cwd }); + const installingPackagesMsg = `Installing packages${emojiWithFallback(' 📦', '...')}`; + spinner = ora({ color: 'green', text: installingPackagesMsg }).start(); + if (!args.dryrun) { + await new Promise<void>((resolve, reject) => { + installExec.stdout?.on('data', function (data) { + spinner.text = `${installingPackagesMsg}\n${bold(`[${pkgManager}]`)} ${data}`; + }); + installExec.on('error', (error) => reject(error)); + installExec.on('close', () => resolve()); + }); + } + spinner.succeed(); + } + console.log('\nNext steps:'); let i = 1; const relative = path.relative(process.cwd(), cwd); @@ -279,14 +311,28 @@ export async function main() { console.log(` ${i++}: ${bold(cyan(`cd ${relative}`))}`); } - console.log(` ${i++}: ${bold(cyan('npm install'))} (or pnpm install, yarn, etc)`); + if (!installResponse.install) { + console.log(` ${i++}: ${bold(cyan(`${pkgManager} install`))}`); + } console.log( ` ${i++}: ${bold( cyan('git init && git add -A && git commit -m "Initial commit"') )} (optional step)` ); - console.log(` ${i++}: ${bold(cyan('npm run dev'))} (or pnpm, yarn, etc)`); + const runCommand = pkgManager === 'npm' ? 'npm run dev' : `${pkgManager} dev`; + console.log(` ${i++}: ${bold(cyan(runCommand))}`); console.log(`\nTo close the dev server, hit ${bold(cyan('Ctrl-C'))}`); console.log(`\nStuck? Visit us at ${cyan('https://astro.build/chat')}\n`); } + +function emojiWithFallback(char: string, fallback: string) { + return process.platform !== 'win32' ? char : fallback; +} + +function pkgManagerFromUserAgent(userAgent?: string) { + if (!userAgent) return 'npm'; + const pkgSpec = userAgent.split(' ')[0]; + const pkgSpecArr = pkgSpec.split('/'); + return pkgSpecArr[0]; +} |