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' ;
2022-01-20 19:00:22 -05:00
import { logger , defaultLogLevel } from './logger.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-06-08 10:12:07 -05:00
try {
fs . mkdirSync ( dir , { recursive : true } ) ;
2021-11-17 19:22:43 -05:00
} catch ( e : any ) {
2021-06-08 10:12:07 -05:00
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() {
2022-01-20 19:00:22 -05:00
logger . debug ( 'Verbose logging turned on' ) ;
2021-08-30 16:20:41 -05:00
console . log ( ` \ n ${ bold ( 'Welcome to Astro!' ) } ${ gray ( ` (create-astro v ${ version } ) ` ) } ` ) ;
2021-11-23 17:47:05 -05:00
console . log ( ` If you encounter a problem, visit ${ cyan ( 'https://github.com/withastro/astro/issues' ) } to search or file a new issue. \ n ` ) ;
2021-12-22 16:11:05 -05:00
2021-08-30 16:20:41 -05:00
console . log ( ` ${ green ( ` > ` ) } ${ gray ( ` Prepare for liftoff. ` ) } ` ) ;
console . log ( ` ${ green ( ` > ` ) } ${ gray ( ` Gathering mission details... ` ) } ` ) ;
2021-12-22 16:11:05 -05:00
2022-03-10 10:56:29 -05:00
const cwd = ( args [ '_' ] [ 2 ] as string ) || '.' ;
2021-06-08 10:12:07 -05:00
if ( fs . existsSync ( cwd ) ) {
if ( fs . readdirSync ( cwd ) . length > 0 ) {
const response = await prompts ( {
type : 'confirm' ,
name : 'forceOverwrite' ,
2021-07-30 11:44:24 -05:00
message : 'Directory not empty. Continue [force overwrite]?' ,
2021-06-08 10:12:07 -05:00
initial : false ,
} ) ;
if ( ! response . forceOverwrite ) {
process . exit ( 1 ) ;
}
2021-07-30 11:44:24 -05:00
mkdirp ( cwd ) ;
2021-06-08 10:12:07 -05:00
}
} else {
mkdirp ( cwd ) ;
}
2021-12-22 16:11:05 -05:00
2021-06-08 10:12:07 -05:00
const options = /** @type {import('./types/internal').Options} */ await prompts ( [
{
type : 'select' ,
name : 'template' ,
message : 'Which app template would you like to use?' ,
choices : TEMPLATES ,
} ,
] ) ;
2021-12-22 16:11:05 -05:00
2021-07-07 14:52:44 -05:00
if ( ! options . template ) {
process . exit ( 1 ) ;
}
2021-12-22 16:11:05 -05:00
2021-06-10 11:30:48 -05:00
const hash = args . commit ? ` # ${ args . commit } ` : '' ;
2021-12-22 16:11:05 -05:00
2021-11-23 17:47:05 -05:00
const templateTarget = options . template . includes ( '/' ) ? options . template : ` withastro/astro/examples/ ${ options . template } #latest ` ;
2021-12-22 16:11:05 -05:00
2021-07-06 14:14:22 -05:00
const emitter = degit ( ` ${ templateTarget } ${ hash } ` , {
2021-06-08 10:12:07 -05:00
cache : false ,
force : true ,
2022-01-20 19:00:22 -05:00
verbose : defaultLogLevel === 'debug' ? true : false ,
} ) ;
logger . debug ( 'Initialized degit with following config:' , ` ${ templateTarget } ${ hash } ` , {
cache : false ,
force : true ,
verbose : defaultLogLevel === 'debug' ? true : false ,
2021-06-08 10:12:07 -05:00
} ) ;
2021-12-22 16:11:05 -05:00
2021-07-07 15:10:09 -05:00
const selectedTemplate = TEMPLATES . find ( ( template ) = > template . value === options . template ) ;
2021-07-07 14:52:44 -05:00
let renderers : string [ ] = [ ] ;
2021-12-22 16:11:05 -05:00
2021-07-07 14:52:44 -05:00
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 ;
2021-08-30 16:20:41 -05:00
} else if ( selectedTemplate ? . renderers && Array . isArray ( selectedTemplate . renderers ) && selectedTemplate . renderers . length ) {
2021-07-07 14:52:44 -05:00
renderers = selectedTemplate . renderers ;
2021-07-07 15:10:09 -05:00
const titles = renderers . map ( ( renderer ) = > FRAMEWORKS . find ( ( item ) = > item . value === renderer ) ? . title ) . join ( ', ' ) ;
2021-08-30 16:20:41 -05:00
console . log ( ` ${ green ( ` ✔ ` ) } ${ bold ( ` Using template's default renderers ` ) } ${ gray ( '› ' ) } ${ titles } ` ) ;
2021-07-07 14:52:44 -05:00
}
2021-12-22 16:11:05 -05:00
2021-06-10 11:30:48 -05:00
// Copy
2021-06-08 10:12:07 -05:00
try {
2022-01-20 19:00:22 -05:00
emitter . on ( 'info' , ( info ) = > {
logger . debug ( info . message ) ;
} ) ;
2021-08-30 16:20:41 -05:00
console . log ( ` ${ green ( ` > ` ) } ${ gray ( ` Copying project files... ` ) } ` ) ;
2021-06-08 10:12:07 -05:00
await emitter . clone ( cwd ) ;
2021-11-17 19:22:43 -05:00
} catch ( err : any ) {
2022-01-20 19:00:22 -05:00
// degit is compiled, so the stacktrace is pretty noisy. Only report the stacktrace when using verbose mode.
logger . debug ( err ) ;
2021-06-08 10:12:07 -05:00
console . error ( red ( err . message ) ) ;
2021-12-22 16:11:05 -05:00
2021-07-27 15:03:53 -05:00
// Warning for issue #655
if ( err . message === 'zlib: unexpected end of file' ) {
2022-01-19 15:19:04 -05:00
console . log ( yellow ( "This seems to be a cache related problem. Remove the folder '~/.degit/github/withastro' to fix this error." ) ) ;
2021-11-23 17:47:05 -05:00
console . log ( yellow ( 'For more information check out this issue: https://github.com/withastro/astro/issues/655' ) ) ;
2021-07-27 15:03:53 -05:00
}
2021-12-22 16:11:05 -05:00
2021-11-23 09:11:56 -05:00
// 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)." ) ) ;
2022-01-20 19:00:22 -05:00
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"
)
) ;
2021-11-23 09:11:56 -05:00
}
2021-06-08 10:12:07 -05:00
process . exit ( 1 ) ;
}
2021-12-22 16:11:05 -05:00
2021-06-10 11:30:48 -05:00
// Post-process in parallel
2021-07-07 15:10:09 -05:00
await Promise . all (
POSTPROCESS_FILES . map ( async ( file ) = > {
const fileLoc = path . resolve ( path . join ( cwd , file ) ) ;
2021-12-22 16:11:05 -05:00
2021-07-07 15:10:09 -05:00
switch ( file ) {
case 'CHANGELOG.md' : {
if ( fs . existsSync ( fileLoc ) ) {
2021-10-01 12:18:40 -05:00
await fs . promises . unlink ( fileLoc ) ;
2021-07-07 15:10:09 -05:00
}
break ;
2021-07-07 14:52:44 -05:00
}
2021-07-07 15:10:09 -05:00
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 ) ) ;
2021-06-10 11:30:48 -05:00
break ;
}
2021-07-07 14:52:44 -05:00
}
2021-07-07 15:10:09 -05:00
} )
) ;
2021-12-22 16:11:05 -05:00
2021-07-07 14:52:44 -05:00
// Inject framework components into starter template
if ( selectedTemplate ? . value === 'starter' ) {
let importStatements : string [ ] = [ ] ;
let components : string [ ] = [ ] ;
2021-07-07 15:10:09 -05:00
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/ , '..' ) } '; ` ) ;
2021-07-08 13:31:05 -05:00
components . push ( ` < ${ componentName } client:visible /> ` ) ;
2021-07-07 15:10:09 -05:00
await fs . promises . writeFile ( absFileLoc , component . content ) ;
} )
) ;
2021-12-22 16:11:05 -05:00
2021-07-07 14:52:44 -05:00
const pageFileLoc = path . resolve ( path . join ( cwd , 'src' , 'pages' , 'index.astro' ) ) ;
const content = ( await fs . promises . readFile ( pageFileLoc ) ) . toString ( ) ;
2021-07-07 16:31:42 -05:00
const newContent = content
. replace ( /^(\s*)\/\* ASTRO\:COMPONENT_IMPORTS \*\//gm , ( _ , indent ) = > {
return indent + importStatements . join ( '\n' ) ;
} )
. replace ( /^(\s*)<!-- ASTRO:COMPONENT_MARKUP -->/gm , ( _ , indent ) = > {
2021-07-07 16:32:45 -05:00
return components . map ( ( ln ) = > indent + ln ) . join ( '\n' ) ;
2021-07-07 16:31:42 -05:00
} ) ;
await fs . promises . writeFile ( pageFileLoc , newContent ) ;
2021-07-07 14:52:44 -05:00
}
2021-12-22 16:11:05 -05:00
2021-06-22 09:06:07 -05:00
console . log ( bold ( green ( '✔' ) + ' Done!' ) ) ;
2021-12-22 16:11:05 -05:00
2021-06-08 10:12:07 -05:00
console . log ( '\nNext steps:' ) ;
let i = 1 ;
2021-12-22 16:11:05 -05:00
2021-06-08 10:12:07 -05:00
const relative = path . relative ( process . cwd ( ) , cwd ) ;
if ( relative !== '' ) {
console . log ( ` ${ i ++ } : ${ bold ( cyan ( ` cd ${ relative } ` ) ) } ` ) ;
}
2021-12-22 16:11:05 -05:00
2021-06-08 10:12:07 -05:00
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) ` ) ;
2021-08-19 00:07:08 -05:00
console . log ( ` ${ i ++ } : ${ bold ( cyan ( 'npm run dev' ) ) } (or pnpm, yarn, etc) ` ) ;
2021-12-22 16:11:05 -05:00
2021-06-08 10:12:07 -05:00
console . log ( ` \ nTo close the dev server, hit ${ bold ( cyan ( 'Ctrl-C' ) ) } ` ) ;
2021-08-30 16:20:41 -05:00
console . log ( ` \ nStuck? Visit us at ${ cyan ( 'https://astro.build/chat' ) } \ n ` ) ;
2021-06-08 10:12:07 -05:00
}