3939'use strict' ;
4040
4141var chalk = require ( 'chalk' ) ;
42+ var validateProjectName = require ( "validate-npm-package-name" ) ;
4243
4344var currentNodeVersion = process . versions . node ;
4445if ( currentNodeVersion . split ( '.' ) [ 0 ] < 4 ) {
@@ -58,6 +59,7 @@ var path = require('path');
5859var execSync = require ( 'child_process' ) . execSync ;
5960var spawn = require ( 'cross-spawn' ) ;
6061var semver = require ( 'semver' ) ;
62+ var dns = require ( 'dns' ) ;
6163
6264var projectName ;
6365
@@ -97,6 +99,14 @@ if (typeof projectName === 'undefined') {
9799 process . exit ( 1 ) ;
98100}
99101
102+ function printValidationResults ( results ) {
103+ if ( typeof results !== 'undefined' ) {
104+ results . forEach ( function ( error ) {
105+ console . error ( chalk . red ( ' * ' + error ) ) ;
106+ } ) ;
107+ }
108+ }
109+
100110var hiddenProgram = new commander . Command ( )
101111 . option ( '--internal-testing-template <path-to-template>' , '(internal usage only, DO NOT RELY ON THIS) ' +
102112 'use a non-standard application template' )
@@ -145,25 +155,47 @@ function shouldUseYarn() {
145155 }
146156}
147157
148- function install ( dependencies , verbose , callback ) {
149- var command ;
150- var args ;
151- if ( shouldUseYarn ( ) ) {
152- command = 'yarnpkg' ;
153- args = [ 'add' , '--exact' ] . concat ( dependencies ) ;
154- } else {
155- checkNpmVersion ( ) ;
156- command = 'npm' ;
157- args = [ 'install' , '--save' , '--save-exact' ] . concat ( dependencies ) ;
158- }
158+ function install ( useYarn , dependencies , verbose , isOnline ) {
159+ return new Promise ( function ( resolve , reject ) {
160+ var command ;
161+ var args ;
162+ if ( useYarn ) {
163+ command = 'yarnpkg' ;
164+ args = [
165+ 'add' ,
166+ '--exact' ,
167+ ] ;
168+ if ( ! isOnline ) {
169+ args . push ( '--offline' ) ;
170+ }
171+ [ ] . push . apply ( args , dependencies ) ;
172+
173+ if ( ! isOnline ) {
174+ console . log ( chalk . yellow ( 'You appear to be offline.' ) ) ;
175+ console . log ( chalk . yellow ( 'Falling back to the local Yarn cache.' ) ) ;
176+ console . log ( ) ;
177+ }
178+
179+ } else {
180+ checkNpmVersion ( ) ;
181+ command = 'npm' ;
182+ args = [ 'install' , '--save' , '--save-exact' ] . concat ( dependencies ) ;
183+ }
159184
160- if ( verbose ) {
161- args . push ( '--verbose' ) ;
162- }
185+ if ( verbose ) {
186+ args . push ( '--verbose' ) ;
187+ }
163188
164- var child = spawn ( command , args , { stdio : 'inherit' } ) ;
165- child . on ( 'close' , function ( code ) {
166- callback ( code , command , args ) ;
189+ var child = spawn ( command , args , { stdio : 'inherit' } ) ;
190+ child . on ( 'close' , function ( code ) {
191+ if ( code !== 0 ) {
192+ reject ( {
193+ command : command + ' ' + args . join ( ' ' )
194+ } ) ;
195+ return ;
196+ }
197+ resolve ( ) ;
198+ } ) ;
167199 } ) ;
168200}
169201
@@ -179,29 +211,63 @@ function run(root, appName, version, verbose, originalDirectory, template) {
179211 ', and ' + chalk . cyan ( packageName ) + '...'
180212 ) ;
181213 console . log ( ) ;
182-
183- install ( allDependencies , verbose , function ( code , command , args ) {
184- if ( code !== 0 ) {
185- console . error ( chalk . cyan ( command + ' ' + args . join ( ' ' ) ) + ' failed' ) ;
214+
215+ var useYarn = shouldUseYarn ( ) ;
216+ checkIfOnline ( useYarn )
217+ . then ( function ( isOnline ) {
218+ return install ( useYarn , allDependencies , verbose , isOnline ) ;
219+ } )
220+ . then ( function ( ) {
221+ checkNodeVersion ( packageName ) ;
222+
223+ // Since react-scripts has been installed with --save
224+ // we need to move it into devDependencies and rewrite package.json
225+ // also ensure react dependencies have caret version range
226+ fixDependencies ( packageName ) ;
227+
228+ var scriptsPath = path . resolve (
229+ process . cwd ( ) ,
230+ 'node_modules' ,
231+ packageName ,
232+ 'scripts' ,
233+ 'init.js'
234+ ) ;
235+ var init = require ( scriptsPath ) ;
236+ init ( root , appName , verbose , originalDirectory , template ) ;
237+ } )
238+ . catch ( function ( reason ) {
239+ console . log ( ) ;
240+ console . log ( 'Aborting installation.' ) ;
241+ if ( reason . command ) {
242+ console . log ( ' ' + chalk . cyan ( reason . command ) , 'has failed.' )
243+ }
244+ console . log ( ) ;
245+
246+ // On 'exit' we will delete these files from target directory.
247+ var knownGeneratedFiles = [
248+ 'package.json' , 'npm-debug.log' , 'yarn-error.log' , 'yarn-debug.log' , 'node_modules'
249+ ] ;
250+ var currentFiles = fs . readdirSync ( path . join ( root ) ) ;
251+ currentFiles . forEach ( function ( file ) {
252+ knownGeneratedFiles . forEach ( function ( fileToMatch ) {
253+ // This will catch `(npm-debug|yarn-error|yarn-debug).log*` files
254+ // and the rest of knownGeneratedFiles.
255+ if ( ( fileToMatch . match ( / .l o g / g) && file . indexOf ( fileToMatch ) === 0 ) || file === fileToMatch ) {
256+ console . log ( 'Deleting generated file...' , chalk . cyan ( file ) ) ;
257+ fs . removeSync ( path . join ( root , file ) ) ;
258+ }
259+ } ) ;
260+ } ) ;
261+ var remainingFiles = fs . readdirSync ( path . join ( root ) ) ;
262+ if ( ! remainingFiles . length ) {
263+ // Delete target folder if empty
264+ console . log ( 'Deleting' , chalk . cyan ( appName + '/' ) , 'from' , chalk . cyan ( path . resolve ( root , '..' ) ) ) ;
265+ process . chdir ( path . resolve ( root , '..' ) ) ;
266+ fs . removeSync ( path . join ( root ) ) ;
267+ }
268+ console . log ( 'Done.' ) ;
186269 process . exit ( 1 ) ;
187- }
188-
189- checkNodeVersion ( packageName ) ;
190-
191- // Since react-scripts has been installed with --save
192- // We need to move it into devDependencies and rewrite package.json
193- moveReactScriptsToDev ( packageName ) ;
194-
195- var scriptsPath = path . resolve (
196- process . cwd ( ) ,
197- 'node_modules' ,
198- packageName ,
199- 'scripts' ,
200- 'init.js'
201- ) ;
202- var init = require ( scriptsPath ) ;
203- init ( root , appName , verbose , originalDirectory , template ) ;
204- } ) ;
270+ } ) ;
205271}
206272
207273function getInstallPackage ( version ) {
@@ -280,11 +346,18 @@ function checkNodeVersion(packageName) {
280346}
281347
282348function checkAppName ( appName ) {
349+ var validationResult = validateProjectName ( appName ) ;
350+ if ( ! validationResult . validForNewPackages ) {
351+ console . error ( 'Could not create a project called ' + chalk . red ( '"' + appName + '"' ) + ' because of npm naming restrictions:' ) ;
352+ printValidationResults ( validationResult . errors ) ;
353+ printValidationResults ( validationResult . warnings ) ;
354+ process . exit ( 1 ) ;
355+ }
356+
283357 // TODO: there should be a single place that holds the dependencies
284358 var dependencies = [ 'react' , 'react-dom' ] ;
285359 var devDependencies = [ 'react-scripts' ] ;
286360 var allDependencies = dependencies . concat ( devDependencies ) . sort ( ) ;
287-
288361 if ( allDependencies . indexOf ( appName ) >= 0 ) {
289362 console . error (
290363 chalk . red (
@@ -302,7 +375,29 @@ function checkAppName(appName) {
302375 }
303376}
304377
305- function moveReactScriptsToDev ( packageName ) {
378+ function makeCaretRange ( dependencies , name ) {
379+ var version = dependencies [ name ] ;
380+
381+ if ( typeof version === 'undefined' ) {
382+ console . error (
383+ chalk . red ( 'Missing ' + name + ' dependency in package.json' )
384+ ) ;
385+ process . exit ( 1 ) ;
386+ }
387+
388+ var patchedVersion = '^' + version ;
389+
390+ if ( ! semver . validRange ( patchedVersion ) ) {
391+ console . error (
392+ 'Unable to patch ' + name + ' dependency version because version ' + chalk . red ( version ) + ' will become invalid ' + chalk . red ( patchedVersion )
393+ ) ;
394+ patchedVersion = version ;
395+ }
396+
397+ dependencies [ name ] = patchedVersion ;
398+ }
399+
400+ function fixDependencies ( packageName ) {
306401 var packagePath = path . join ( process . cwd ( ) , 'package.json' ) ;
307402 var packageJson = require ( packagePath ) ;
308403
@@ -326,6 +421,9 @@ function moveReactScriptsToDev(packageName) {
326421 packageJson . devDependencies [ packageName ] = packageVersion ;
327422 delete packageJson . dependencies [ packageName ] ;
328423
424+ makeCaretRange ( packageJson . dependencies , 'react' ) ;
425+ makeCaretRange ( packageJson . dependencies , 'react-dom' ) ;
426+
329427 fs . writeFileSync ( packagePath , JSON . stringify ( packageJson , null , 2 ) ) ;
330428}
331429
@@ -341,3 +439,17 @@ function isSafeToCreateProjectIn(root) {
341439 return validFiles . indexOf ( file ) >= 0 ;
342440 } ) ;
343441}
442+
443+ function checkIfOnline ( useYarn ) {
444+ if ( ! useYarn ) {
445+ // Don't ping the Yarn registry.
446+ // We'll just assume the best case.
447+ return Promise . resolve ( true ) ;
448+ }
449+
450+ return new Promise ( function ( resolve ) {
451+ dns . resolve ( 'registry.yarnpkg.com' , function ( err ) {
452+ resolve ( err === null ) ;
453+ } ) ;
454+ } ) ;
455+ }
0 commit comments