@@ -9,29 +9,30 @@ Authored by Lee Benson <lee@leebenson.com>
99// Node
1010const os = require ( 'os' ) ;
1111const path = require ( 'path' ) ;
12- const fse = require ( 'fs-extra' ) ;
13- const spawn = require ( 'cross-spawn' ) ;
1412
1513// Third-party
1614const chalk = require ( 'chalk' ) ;
1715const yargs = require ( 'yargs' ) ;
1816const updateNotifier = require ( 'update-notifier' ) ;
1917const inquirer = require ( 'inquirer' ) ;
20- const through2 = require ( 'through2' ) ;
21- const klaw = require ( 'klaw' ) ;
22- const ejs = require ( 'ejs' ) ;
2318const exists = require ( 'command-exists' ) . sync ;
2419const spdx = require ( 'spdx' ) ;
20+ const fse = require ( 'fs-extra' ) ;
21+ const spawn = require ( 'cross-spawn' ) ;
22+ const temp = require ( 'temp' ) . track ( ) ;
23+ const yauzl = require ( 'yauzl' ) ;
24+ const request = require ( 'request' ) ;
25+ const mkdirp = require ( 'mkdirp' ) ;
2526
2627// Local
2728const banner = require ( './banner.js' ) ;
2829const usage = require ( './usage.js' ) ;
29- const package = require ( '../package.json' ) ;
30+ const pkg = require ( '../package.json' ) ;
3031
3132// ----------------------
3233
3334// Check for ReactQL updates automatically
34- updateNotifier ( { pkg : package , updateCheckInterval : 0 } ) . notify ( ) ;
35+ updateNotifier ( { pkg, updateCheckInterval : 0 } ) . notify ( ) ;
3536
3637/*
3738 Helper functions
@@ -44,12 +45,13 @@ function emoji(ifSupported, ifNot='\b') {
4445
4546// Show error message. We'll use this if yarn/npm throws back a non-zero
4647// code, to display the problem back to the console
47- function showError ( msg ) {
48+ function fatalError ( msg ) {
4849 console . error ( `
4950${ chalk . bold . bgRed ( 'ERROR' ) } ${ chalk . bgRed ( ' -- See the output below:' ) }
5051
5152${ msg }
5253 ` . trim ( ) ) ;
54+ process . exit ( ) ;
5355}
5456
5557// Finished instructions. Show the user how to use the starter kit
@@ -79,11 +81,6 @@ ${separator}
7981 ` . trim ( ) ;
8082}
8183
82- // Paths used during copying the starter kit
83- const paths = {
84- files : path . resolve ( __dirname , '../starter/files' ) ,
85- } ;
86-
8784// ASCII chars to show possible 'spinner' positions. We'll prefix this to
8885// the installation message during yarn/npm packaging
8986const spinner = [
@@ -235,116 +232,131 @@ const args = yargs
235232 // Modify path to be absolute
236233 args . path = path . resolve ( process . cwd ( ) , args . path ) ;
237234
238- // Copy the starter kit files over to the new path
239- fse . copySync ( paths . files , args . path ) ;
240-
241- // Edit `package.json` with project-specific information
242- const packageJsonFile = path . resolve ( args . path , 'package.json' ) ;
243- const packageJson = require ( packageJsonFile ) ;
244-
245- fse . writeJsonSync ( packageJsonFile , Object . assign ( packageJson , {
246- name : args . name ,
247- description : args . desc ,
248- license : args . license ,
249- } ) ) ;
250-
251- /*
252- Find template files, execute the EJS, and create a new file
253- with the rendered content.
254- */
255-
256- // We'll use `klaw` to walk through the starter kit path, and create
257- // a stream of `fs.Stats` objects representing each file
258- klaw ( args . path )
259-
260- // Create a function that will handle file objects, determine whether
261- // we have templates, and compile to EJS. We're using `through2` to
262- // take care of handling the stream
263- . pipe ( through2 . obj ( function ( item , enc , next ) {
264-
265- // We're only interested in `.reactql` template files
266- if ( / \. r e a c t q l $ / . test ( item . path ) ) {
267-
268- // Get the file content
269- const content = fse . readFileSync ( item . path , {
270- encoding : 'utf8' ,
271- } ) ;
272-
273- // Compile the template through EJS, passing `args`
274- const compiled = ejs . render ( content , { args } ) . trim ( ) ;
275-
276- // Write non-blank content out using the original file
277- // name, minus the .reactql extension
278- if ( compiled ) {
279- fse . writeFileSync (
280- item . path . replace ( / \. r e a c t q l $ / , '' ) ,
281- compiled ,
282- {
283- encoding : 'utf8' ,
235+ // Create a tmp file stream to save the file locally
236+ const file = temp . createWriteStream ( ) ;
237+
238+ // Show the separator to make it clear we've moved on to the
239+ // next step
240+ console . log ( separator ) ;
241+
242+ console . log ( 'Downloading source code from Github...' ) ;
243+
244+ // Download the .zip containing the kit's source code
245+ request
246+ . get ( 'https://github.com/reactql/kit/archive/master.zip' )
247+ . pipe (
248+ file . on ( 'finish' , ( ) => {
249+ console . log ( 'Extracting archive...' ) ;
250+ yauzl . open ( file . path , { lazyEntries : true } , ( e , zip ) => {
251+ if ( e ) fatalError ( "Couldn't read zip file" ) ;
252+
253+ // Read the zip entry
254+ zip . readEntry ( ) ;
255+
256+ // Process zip files
257+ zip . on ( 'entry' , entry => {
258+ // Remove leading folder that Github uses
259+ const fileName = entry . fileName
260+ . split ( '/' )
261+ . slice ( 1 )
262+ . join ( '/' ) ;
263+
264+ // Proceed only if we have a file name
265+ if ( fileName ) {
266+
267+ // Resolve the full file name, including the path
268+ const fullName = path . resolve ( args . path , fileName ) ;
269+
270+ // If it's a folder (based on original filename), create it
271+ if ( / \/ $ / . test ( fileName ) ) {
272+ mkdirp ( fullName , e => {
273+ if ( e ) fatalError ( `Couldn't create folder ${ fullName } ` ) ;
274+ zip . readEntry ( ) ;
275+ } ) ;
276+ } else {
277+ // Otherwise, it's a regular file -- write it
278+ zip . openReadStream ( entry , ( e , readStream ) => {
279+ if ( e ) fatalError ( `Couldn't create ZIP read stream` ) ;
280+ readStream
281+ . pipe ( fse . createWriteStream ( fullName ) )
282+ . on ( 'finish' , ( ) => zip . readEntry ( ) ) ;
283+ } ) ;
284+ }
285+ } else {
286+ // Blank filename - move on to the next one
287+ zip . readEntry ( ) ;
288+ }
289+ } )
290+ . on ( 'end' , ( ) => {
291+ // Edit `package.json` with project-specific information
292+ console . log ( 'Writing package.json...' ) ;
293+
294+ const pkgJsonFile = path . resolve ( args . path , 'package.json' ) ;
295+ const pkgJson = require ( pkgJsonFile ) ;
296+
297+ fse . writeJsonSync ( pkgJsonFile , Object . assign ( pkgJson , {
298+ name : args . name ,
299+ description : args . desc ,
300+ license : args . license ,
301+ } ) ) ;
302+
303+ // Install pakckage dependencies using yarn if we have
304+ // it, otherwise using NPM
305+ let installer ;
306+
307+ // Prefer yarn (it's faster). If it doesn't exist, fall back to
308+ // npm which every user should have. Inform the user that yarn is
309+ // the preferred option!
310+ if ( exists ( 'yarn' ) ) {
311+ installer = [ 'yarn' , [ ] ] ;
312+ console . log ( 'Installing via Yarn...\n' ) ;
313+
314+ } else {
315+ installer = [ 'npm' , [ 'i' ] ] ;
316+ console . log ( `Yarn not found; falling back to NPM. Tip: For faster future builds, install ${ chalk . underline ( 'https://yarnpkg.com' ) } \n` ) ;
284317 }
285- ) ;
286- }
287-
288- // Delete the template
289- fse . unlinkSync ( item . path ) ;
290- }
291- // Callback to say we've completed walking the tree
292- next ( ) ;
293- } ) )
294- // When finished, all of our templates have been run
295- . on ( 'finish' , ( ) => {
296- // Install the `package.json` dependencies using yarn if we have
297- // it, otherwise using NPM
298- let installer ;
299-
300- // Show the separator to make it clear we've moved on to the
301- // next step
302- console . log ( separator ) ;
303-
304- // Prefer yarn (it's faster). If it doesn't exist, fall back to
305- // npm which every user should have. Inform the user that yarn is
306- // the preferred option!
307- if ( exists ( 'yarn' ) ) {
308- installer = [ 'yarn' , [ ] ] ;
309- console . log ( 'Installing via Yarn...\n' ) ;
310-
311- } else {
312- installer = [ 'npm' , [ 'i' ] ] ;
313- console . log ( `Yarn not found; falling back to NPM. Tip: For faster future builds, install ${ chalk . underline ( 'https://yarnpkg.com' ) } \n` ) ;
314- }
315-
316- // Create a bottom bar to display the installation spinner at the bottom
317- // of the console.
318- const ui = new inquirer . ui . BottomBar ( { bottomBar : spinner [ 0 ] } ) ;
319-
320- // Temporary var to track the position of the 'spinner'
321- let i = 0 ;
322-
323- // Update the spinner every 300ms, to reflect the installation activity
324- const update = setInterval ( function ( ) {
325- ui . updateBottomBar ( `\n${ spinner [ ++ i % 4 ] } Installing modules -- Please wait...` ) ;
326- } , 300 ) ;
327-
328- // Execute yarn/npm as a child process, pipe output to stdout
329- spawn ( ...installer , { cwd : args . path , stdio : 'pipe' } )
330- . stdout . pipe ( ui . log )
331- // When finished, stop the spinner, update with usage instructons and exit
332- . on ( 'close' , function ( ) {
333- clearInterval ( update ) ;
334- ui . updateBottomBar ( '' ) ;
335- console . log ( finished ( args . path ) ) ;
336- process . exit ( ) ;
337- } ) ;
318+
319+ // Create a bottom bar to display the installation spinner at the bottom
320+ // of the console.
321+ const ui = new inquirer . ui . BottomBar ( { bottomBar : spinner [ 0 ] } ) ;
322+
323+ // Temporary var to track the position of the 'spinner'
324+ let i = 0 ;
325+
326+ // Update the spinner every 300ms, to reflect the installation activity
327+ const update = setInterval ( function ( ) {
328+ ui . updateBottomBar ( `\n${ spinner [ ++ i % 4 ] } Installing modules -- Please wait...` ) ;
329+ } , 300 ) ;
330+
331+ // Execute yarn/npm as a child process, pipe output to stdout
332+ spawn ( ...installer , { cwd : args . path , stdio : 'pipe' } )
333+ . stdout . pipe ( ui . log )
334+ . on ( 'error' , ( ) => fatalError ( "Couldn't install packages" ) )
335+ // When finished, stop the spinner, update with usage instructons and exit
336+ . on ( 'close' , function ( ) {
337+ clearInterval ( update ) ;
338+ ui . updateBottomBar ( '' ) ;
339+ console . log ( finished ( args . path ) ) ;
340+ process . exit ( ) ;
341+ } ) ;
342+ } ) ;
343+ } )
344+ } )
345+ )
346+ . on ( 'error' , ( ) => {
347+ console . error ( "Couldn't download source code from Github" ) ;
348+ process . exit ( ) ;
338349 } ) ;
339- } ) ;
350+
351+ } ) ;
340352 } ,
341353 } )
342354 . command ( {
343355 command : 'version' ,
344356 aliases : [ 'v' ] ,
345357 desc : 'Show ReactQL version' ,
346358 handler ( ) {
347- console . log ( package . version ) ;
359+ console . log ( pkg . version ) ;
348360 } ,
349361 } )
350362 . option ( 'name' , {
@@ -361,7 +373,7 @@ const args = yargs
361373 } )
362374 . option ( 'license' , {
363375 alias : 'l' ,
364- describe : 'License for package .json' ,
376+ describe : 'License for pkg .json' ,
365377 } )
366378 . help ( )
367379 . argv ;
0 commit comments