11#!/usr/bin/env node
2+
3+ /**
4+ * Dearest fabric maintainer 💗,
5+ * This file contains the cli logic, which governs most of the available commands fabric has to offer.
6+ *
7+ * 📢 **IMPORTANT**
8+ * CI uses these commands.
9+ * In order for CI to correctly report the result of the command, the process must receive a correct exit code
10+ * meaning that if you `spawn` a process, make sure to listen to the `exit` event and terminate the main process with the relevant code.
11+ * Failing to do so will make CI report a false positive 📉.
12+ */
13+
14+
15+
216const fs = require ( 'fs-extra' ) ;
317const os = require ( 'os' ) ;
418const _ = require ( 'lodash' ) ;
@@ -121,13 +135,13 @@ inquirer.registerPrompt('test-selection', ICheckbox);
121135
122136
123137function build ( options = { } ) {
124- const args = [ 'rollup' , '-c' , options . watch ? '--watch' : '' ] ;
138+ const cmd = [ 'rollup' , '-c' , options . watch ? '--watch' : '' ] . join ( ' ' ) ;
125139 let minDest ;
126140 if ( options . output && ! options . fast ) {
127141 const { name, base, ...rest } = path . parse ( path . resolve ( options . output ) ) ;
128142 minDest = path . format ( { name : `${ name } .min` , ...rest } ) ;
129143 }
130- return cp . spawn ( args . join ( ' ' ) , {
144+ const processOptions = {
131145 stdio : 'inherit' ,
132146 shell : true ,
133147 cwd : wd ,
@@ -138,7 +152,21 @@ function build(options = {}) {
138152 BUILD_OUTPUT : options . output ,
139153 BUILD_MIN_OUTPUT : minDest
140154 } ,
141- } ) ;
155+ }
156+ if ( options . watch ) {
157+ cp . spawn ( cmd , processOptions ) ;
158+ }
159+ else {
160+ try {
161+ cp . execSync ( cmd , processOptions ) ;
162+ } catch ( error ) {
163+ // minimal logging, no need for stack trace
164+ console . error ( error . message ) ;
165+ // inform ci
166+ process . exit ( 1 ) ;
167+ }
168+ }
169+
142170}
143171
144172function startWebsite ( ) {
@@ -244,57 +272,106 @@ function exportToWebsite(options) {
244272 } )
245273}
246274
247-
248275/**
249- *
250- * @param {'unit' | 'visual' } suite
251- * @param {string[] | null } tests file paths
252- * @param {{debug?:boolean,recreate?:boolean,verbose?:boolean,filter?:string} } [options]
276+ *
277+ * @returns {Promise<boolean | undefined> } true if some tests failed
253278 */
254- async function test ( suite , tests , options = { } ) {
255- const port = options . port || suite === 'visual' ? 8081 : 8080 ;
279+ async function runTestem ( { suite, port , launch , dev , processOptions , context } = { } ) {
280+ port = port || suite === 'visual' ? 8081 : 8080 ;
256281 try {
257282 await killPort ( port ) ;
258283 } catch ( error ) {
259284
260285 }
261286
262- const args = [
263- 'testem' ,
264- ! options . dev ? 'ci' : '' ,
287+ if ( launch ) {
288+ // open localhost
289+ const url = `http://localhost:${ port } /` ;
290+ const start = ( os . platform ( ) === 'darwin' ? 'open' : os . platform ( ) === 'win32' ? 'start' : 'xdg-open' ) ;
291+ cp . exec ( [ start , url ] . join ( ' ' ) ) ;
292+ }
293+
294+ const processCmdOptions = [
265295 '-p' , port ,
266296 '-f' , `test/testem.${ suite } .js` ,
267- '-l' , options . context . map ( _ . upperFirst ) . join ( ',' )
297+ '-l' , context . map ( _ . upperFirst ) . join ( ',' )
268298 ] ;
269299
270- cp . spawn ( args . join ( ' ' ) , {
271- cwd : wd ,
272- env : {
273- ...process . env ,
274- TEST_FILES : ( tests || [ ] ) . join ( ',' ) ,
275- NODE_CMD : [ 'qunit' , 'test/node_test_setup.js' , 'test/lib' ] . concat ( tests || `test/${ suite } ` ) . join ( ' ' ) ,
276- VERBOSE : Number ( options . verbose ) ,
277- QUNIT_DEBUG_VISUAL_TESTS : Number ( options . debug ) ,
278- QUNIT_RECREATE_VISUAL_REFS : Number ( options . recreate ) ,
279- QUNIT_FILTER : options . filter ,
280- REPORT_FILE : options . out
281- } ,
282- shell : true ,
283- stdio : 'inherit' ,
284- detached : options . dev
285- } )
286- . on ( 'exit' , ( code ) => {
287- // propagate failed exit code to the process for ci to fail
288- // don't exit if tests passed - this is for parallel local testing
289- code && process . exit ( code ) ;
300+ if ( dev ) {
301+ cp . spawn ( [ 'testem' , ...processCmdOptions ] . join ( ' ' ) , {
302+ ...processOptions ,
303+ detached : true
290304 } ) ;
305+ }
306+ else {
307+ try {
308+ cp . execSync ( [ 'testem' , 'ci' , ...processCmdOptions ] . join ( ' ' ) , processOptions ) ;
309+ } catch ( error ) {
310+ return true ;
311+ }
312+ }
313+ }
291314
292- if ( options . launch ) {
293- // open localhost
294- const url = `http://localhost:${ port } /` ;
295- const start = ( os . platform ( ) === 'darwin' ? 'open' : os . platform ( ) === 'win32' ? 'start' : 'xdg-open' ) ;
296- cp . exec ( [ start , url ] . join ( ' ' ) ) ;
315+ /**
316+ *
317+ * @param {'unit' | 'visual' } suite
318+ * @param {string[] | null } tests file paths
319+ * @param {{debug?:boolean,recreate?:boolean,verbose?:boolean,filter?:string} } [options]
320+ * @returns {Promise<boolean | undefined> } true if some tests failed
321+ */
322+ async function test ( suite , tests , options = { } ) {
323+ let failed = false ;
324+ const qunitEnv = {
325+ QUNIT_DEBUG_VISUAL_TESTS : Number ( options . debug ) ,
326+ QUNIT_RECREATE_VISUAL_REFS : Number ( options . recreate ) ,
327+ QUNIT_FILTER : options . filter ,
328+ } ;
329+ const env = {
330+ ...process . env ,
331+ TEST_FILES : ( tests || [ ] ) . join ( ',' ) ,
332+ NODE_CMD : [ 'qunit' , 'test/node_test_setup.js' , 'test/lib' ] . concat ( tests || `test/${ suite } ` ) . join ( ' ' ) ,
333+ VERBOSE : Number ( options . verbose ) ,
334+ REPORT_FILE : options . out
335+ } ;
336+ const browserContexts = options . context . filter ( c => c !== 'node' ) ;
337+
338+ // temporary revert
339+ // run node tests directly with qunit
340+ if ( options . context . includes ( 'node' ) ) {
341+ try {
342+ cp . execSync ( env . NODE_CMD , {
343+ cwd : wd ,
344+ env : {
345+ ...env ,
346+ // browser takes precendence in golden ref generation
347+ ...( browserContexts . length === 0 ? qunitEnv : { } )
348+ } ,
349+ shell : true ,
350+ stdio : 'inherit' ,
351+ } ) ;
352+ } catch ( error ) {
353+ failed = true ;
354+ }
355+ }
356+
357+ if ( browserContexts . length > 0 ) {
358+ failed = await runTestem ( {
359+ ...options ,
360+ suite,
361+ processOptions : {
362+ cwd : wd ,
363+ env : {
364+ ...env ,
365+ ...qunitEnv
366+ } ,
367+ shell : true ,
368+ stdio : 'inherit' ,
369+ } ,
370+ context : browserContexts
371+ } ) || failed ;
297372 }
373+
374+ return failed ;
298375}
299376
300377/**
@@ -410,15 +487,14 @@ async function runIntreactiveTestSuite(options) {
410487 }
411488 return acc ;
412489 } , { unit : [ ] , visual : [ ] } ) ;
413- _ . reduce ( tests , async ( queue , files , suite ) => {
414- await queue ;
490+ return Promise . all ( _ . map ( tests , ( files , suite ) => {
415491 if ( files === true ) {
416492 return test ( suite , null , options ) ;
417493 }
418494 else if ( Array . isArray ( files ) && files . length > 0 ) {
419495 return test ( suite , files , options ) ;
420496 }
421- } , Promise . resolve ( ) ) ;
497+ } ) ) ;
422498}
423499
424500program
@@ -467,31 +543,36 @@ program
467543 . option ( '-a, --all' , 'run all tests' , false )
468544 . option ( '-d, --debug' , 'debug visual tests by overriding refs (golden images) in case of visual changes' , false )
469545 . option ( '-r, --recreate' , 'recreate visual refs (golden images)' , false )
470- . option ( '-v, --verbose' , 'log passing tests' , false )
546+ . option ( '-v, --verbose' , 'log passing tests' , true )
547+ . option ( '--no-verbose' , 'disable verbose logging' )
471548 . option ( '-l, --launch' , 'launch tests in the browser' , false )
472549 . option ( '--dev' , 'runs testem in `dev` mode, without a `ci` flag' , false )
473550 . addOption ( new commander . Option ( '-c, --context <context...>' , 'context to test in' ) . choices ( [ 'node' , 'chrome' , 'firefox' ] ) . default ( [ 'node' ] ) )
474551 . option ( '-p, --port' )
475552 . option ( '-o, --out <out>' , 'path to report test results to' )
476553 . option ( '--clear-cache' , 'clear CLI test cache' , false )
477- . action ( ( options ) => {
554+ . action ( async ( options ) => {
478555 if ( options . clearCache ) {
479556 fs . removeSync ( CLI_CACHE ) ;
480557 }
481558 if ( options . all ) {
482559 options . suite = [ 'unit' , 'visual' ] ;
483560 }
561+ const results = [ ] ;
484562 if ( options . suite ) {
485- _ . reduce ( options . suite , async ( queue , suite ) => {
486- await queue ;
563+ results . push ( ...await Promise . all ( _ . map ( options . suite , ( suite ) => {
487564 return test ( suite , null , options ) ;
488- } , Promise . resolve ( ) ) ;
565+ } ) ) ) ;
489566 }
490567 else if ( options . file ) {
491- test ( options . file . startsWith ( 'visual' ) ? 'visual' : 'unit' , [ `test/${ options . file } ` ] , options ) ;
568+ results . push ( await test ( options . file . startsWith ( 'visual' ) ? 'visual' : 'unit' , [ `test/${ options . file } ` ] , options ) ) ;
492569 }
493570 else {
494- runIntreactiveTestSuite ( options ) ;
571+ results . push ( ...await runIntreactiveTestSuite ( options ) ) ;
572+ }
573+ if ( _ . some ( results ) ) {
574+ // inform ci that tests have failed
575+ process . exit ( 1 ) ;
495576 }
496577 } ) ;
497578
0 commit comments