2021-06-08 10:10:56 -05:00
import fs from 'fs' ;
import path from 'path' ;
2021-07-27 15:03:53 -05:00
import { bold , cyan , gray , green , red , yellow } from 'kleur/colors' ;
2021-07-07 14:52:44 -05:00
import fetch from 'node-fetch' ;
2021-06-08 10:10:56 -05:00
import prompts from 'prompts' ;
import degit from 'degit' ;
import yargs from 'yargs-parser' ;
2021-07-07 14:52:44 -05:00
import { FRAMEWORKS , COUNTER_COMPONENTS } from './frameworks.js' ;
2021-06-08 11:56:37 -05:00
import { TEMPLATES } from './templates.js' ;
2021-07-07 14:52:44 -05:00
import { createConfig } from './config.js' ;
2021-11-17 13:30:12 -05:00
// NOTE: In the v7.x version of npm, the default behavior of `npm init` was changed
2021-11-17 13:32:36 -05:00
// to no longer require `--` to pass args and instead pass `--` directly to us. This
// broke our arg parser, since `--` is a special kind of flag. Filtering for `--` here
2021-11-17 13:30:12 -05:00
// fixes the issue so that create-astro now works on all npm version.
2021-11-17 13:32:36 -05:00
const cleanArgv = process . argv . filter ( ( arg ) = > arg !== '--' ) ;
2021-12-06 09:19:22 -05:00
const args = yargs ( cleanArgv , { array : [ 'renderers' ] } ) ;
2021-06-08 10:10:56 -05:00
prompts . override ( args ) ;
export function mkdirp ( dir : string ) {
2021-12-22 16:11:05 -05:00
try {
fs . mkdirSync ( dir , { recursive : true } ) ;
} catch ( e : any ) {
if ( e . code === 'EEXIST' ) return ;
throw e ;
}
2021-06-08 10:10:56 -05:00
}
const { version } = JSON . parse ( fs . readFileSync ( new URL ( '../package.json' , import . meta . url ) , 'utf-8' ) ) ;
2021-07-07 14:52:44 -05:00
const POSTPROCESS_FILES = [ 'package.json' , 'astro.config.mjs' , 'CHANGELOG.md' ] ; // some files need processing after copying.
2021-06-10 11:30:48 -05:00
2021-06-08 10:10:56 -05:00
export async function main() {
2021-12-22 16:11:05 -05:00
console . log ( ` \ n ${ bold ( 'Welcome to Astro!' ) } ${ gray ( ` (create-astro v ${ version } ) ` ) } ` ) ;
console . log ( ` If you encounter a problem, visit ${ cyan ( 'https://github.com/withastro/astro/issues' ) } to search or file a new issue. \ n ` ) ;
console . log ( ` ${ green ( ` > ` ) } ${ gray ( ` Prepare for liftoff. ` ) } ` ) ;
console . log ( ` ${ green ( ` > ` ) } ${ gray ( ` Gathering mission details... ` ) } ` ) ;
const cwd = args [ '_' ] [ 2 ] || '.' ;
if ( fs . existsSync ( cwd ) ) {
if ( fs . readdirSync ( cwd ) . length > 0 ) {
const response = await prompts ( {
type : 'confirm' ,
name : 'forceOverwrite' ,
message : 'Directory not empty. Continue [force overwrite]?' ,
initial : false ,
} ) ;
if ( ! response . forceOverwrite ) {
process . exit ( 1 ) ;
}
mkdirp ( cwd ) ;
}
} else {
mkdirp ( cwd ) ;
}
const options = /** @type {import('./types/internal').Options} */ await prompts ( [
{
type : 'select' ,
name : 'template' ,
message : 'Which app template would you like to use?' ,
choices : TEMPLATES ,
} ,
] ) ;
if ( ! options . template ) {
process . exit ( 1 ) ;
}
const hash = args . commit ? ` # ${ args . commit } ` : '' ;
const templateTarget = options . template . includes ( '/' ) ? options . template : ` withastro/astro/examples/ ${ options . template } #latest ` ;
const emitter = degit ( ` ${ templateTarget } ${ hash } ` , {
cache : false ,
force : true ,
verbose : false ,
} ) ;
const selectedTemplate = TEMPLATES . find ( ( template ) = > template . value === options . template ) ;
let renderers : string [ ] = [ ] ;
if ( selectedTemplate ? . renderers === true ) {
const result = /** @type {import('./types/internal').Options} */ await prompts ( [
{
type : 'multiselect' ,
name : 'renderers' ,
message : 'Which frameworks would you like to use?' ,
choices : FRAMEWORKS ,
} ,
] ) ;
renderers = result . renderers ;
} else if ( selectedTemplate ? . renderers && Array . isArray ( selectedTemplate . renderers ) && selectedTemplate . renderers . length ) {
renderers = selectedTemplate . renderers ;
const titles = renderers . map ( ( renderer ) = > FRAMEWORKS . find ( ( item ) = > item . value === renderer ) ? . title ) . join ( ', ' ) ;
console . log ( ` ${ green ( ` ✔ ` ) } ${ bold ( ` Using template's default renderers ` ) } ${ gray ( '› ' ) } ${ titles } ` ) ;
}
// Copy
try {
// emitter.on('info', info => { console.log(info.message) });
console . log ( ` ${ green ( ` > ` ) } ${ gray ( ` Copying project files... ` ) } ` ) ;
await emitter . clone ( cwd ) ;
} catch ( err : any ) {
// degit is compiled, so the stacktrace is pretty noisy. Just report the message.
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/snowpackjs' 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 file a new issue here: https://github.com/withastro/astro/issues" ) ) ;
}
process . exit ( 1 ) ;
}
// Post-process in parallel
await Promise . all (
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 ;
}
case 'astro.config.mjs' : {
if ( selectedTemplate ? . renderers !== true ) {
break ;
}
await fs . promises . writeFile ( fileLoc , createConfig ( { renderers } ) ) ;
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 renderers
const rendererEntries = ( await Promise . all (
[ 'astro' , . . . renderers ] . map ( ( renderer : string ) = >
fetch ( ` https://registry.npmjs.org/ ${ renderer } /latest ` )
. then ( ( res : any ) = > res . json ( ) )
. then ( ( res : any ) = > [ renderer , ` ^ ${ res [ 'version' ] } ` ] )
)
) ) as any ;
packageJSON . devDependencies = { . . . ( packageJSON . devDependencies ? ? { } ) , . . . Object . fromEntries ( rendererEntries ) } ;
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 (
renderers . map ( async ( renderer ) = > {
const component = COUNTER_COMPONENTS [ renderer 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 ) ;
}
console . log ( bold ( green ( '✔' ) + ' Done!' ) ) ;
console . log ( '\nNext steps:' ) ;
let i = 1 ;
const relative = path . relative ( process . cwd ( ) , cwd ) ;
if ( relative !== '' ) {
console . log ( ` ${ i ++ } : ${ bold ( cyan ( ` cd ${ relative } ` ) ) } ` ) ;
}
console . log ( ` ${ i ++ } : ${ bold ( cyan ( 'npm install' ) ) } (or pnpm install, yarn, etc) ` ) ;
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) ` ) ;
console . log ( ` \ nTo close the dev server, hit ${ bold ( cyan ( 'Ctrl-C' ) ) } ` ) ;
console . log ( ` \ nStuck? Visit us at ${ cyan ( 'https://astro.build/chat' ) } \ n ` ) ;
2021-06-08 10:12:07 -05:00
}