77
88const Generator = require ( 'yeoman-generator' ) ;
99const chalk = require ( 'chalk' ) ;
10- const debug = require ( './debug' ) ( 'artifact-generator' ) ;
11- const utils = require ( './utils' ) ;
12- const StatusConflicter = utils . StatusConflicter ;
10+ const { StatusConflicter, readTextFromStdin} = require ( './utils' ) ;
11+ const path = require ( 'path' ) ;
12+ const fs = require ( 'fs' ) ;
13+ const readline = require ( 'readline' ) ;
14+ const debug = require ( './debug' ) ( 'base-generator' ) ;
15+
1316/**
1417 * Base Generator for LoopBack 4
1518 */
@@ -28,11 +31,206 @@ module.exports = class BaseGenerator extends Generator {
2831 * Subclasses can extend _setupGenerator() to set up the generator
2932 */
3033 _setupGenerator ( ) {
34+ this . option ( 'config' , {
35+ type : String ,
36+ alias : 'c' ,
37+ description : 'JSON file name or value to configure options' ,
38+ } ) ;
39+
40+ this . option ( 'yes' , {
41+ type : Boolean ,
42+ alias : 'y' ,
43+ description :
44+ 'Skip all confirmation prompts with default or provided value' ,
45+ } ) ;
46+
3147 this . artifactInfo = this . artifactInfo || {
3248 rootDir : 'src' ,
3349 } ;
3450 }
3551
52+ /**
53+ * Read a json document from stdin
54+ */
55+ async _readJSONFromStdin ( ) {
56+ if ( process . stdin . isTTY ) {
57+ this . log (
58+ chalk . green (
59+ 'Please type in a json object line by line ' +
60+ '(Press <ctrl>-D or type EOF to end):' ,
61+ ) ,
62+ ) ;
63+ }
64+
65+ try {
66+ const jsonStr = await readTextFromStdin ( ) ;
67+ return JSON . parse ( jsonStr ) ;
68+ } catch ( e ) {
69+ if ( ! process . stdin . isTTY ) {
70+ debug ( e , jsonStr ) ;
71+ }
72+ throw e ;
73+ }
74+ }
75+
76+ async setOptions ( ) {
77+ let opts = { } ;
78+ const jsonFileOrValue = this . options . config ;
79+ try {
80+ if ( jsonFileOrValue === 'stdin' || ! process . stdin . isTTY ) {
81+ this . options [ 'yes' ] = true ;
82+ opts = await this . _readJSONFromStdin ( ) ;
83+ } else if ( typeof jsonFileOrValue === 'string' ) {
84+ const jsonFile = path . resolve ( process . cwd ( ) , jsonFileOrValue ) ;
85+ if ( fs . existsSync ( jsonFile ) ) {
86+ opts = this . fs . readJSON ( jsonFile ) ;
87+ } else {
88+ // Try parse the config as stringified json
89+ opts = JSON . parse ( jsonFileOrValue ) ;
90+ }
91+ }
92+ } catch ( e ) {
93+ this . exit ( e ) ;
94+ return ;
95+ }
96+ if ( typeof opts !== 'object' ) {
97+ this . exit ( 'Invalid config file or value: ' + jsonFileOrValue ) ;
98+ return ;
99+ }
100+ for ( const o in opts ) {
101+ if ( this . options [ o ] == null ) {
102+ this . options [ o ] = opts [ o ] ;
103+ }
104+ }
105+ }
106+
107+ /**
108+ * Check if a question can be skipped in `express` mode
109+ * @param {object } question A yeoman prompt
110+ */
111+ _isQuestionOptional ( question ) {
112+ return (
113+ question . default != null || // Having a default value
114+ this . options [ question . name ] != null || // Configured in options
115+ question . type === 'list' || // A list
116+ question . type === 'rawList' || // A raw list
117+ question . type === 'checkbox' || // A checkbox
118+ question . type === 'confirm'
119+ ) ; // A confirmation
120+ }
121+
122+ /**
123+ * Get the default answer for a question
124+ * @param {* } question
125+ */
126+ async _getDefaultAnswer ( question , answers ) {
127+ let def = question . default ;
128+ if ( typeof question . default === 'function' ) {
129+ def = await question . default ( answers ) ;
130+ }
131+ let defaultVal = def ;
132+
133+ if ( def == null ) {
134+ // No `default` is set for the question, check existing answers
135+ defaultVal = answers [ question . name ] ;
136+ if ( defaultVal != null ) return defaultVal ;
137+ }
138+
139+ if ( question . type === 'confirm' ) {
140+ return defaultVal != null ? defaultVal : true ;
141+ }
142+ if ( question . type === 'list' || question . type === 'rawList' ) {
143+ // Default to 1st item
144+ if ( def == null ) def = 0 ;
145+ if ( typeof def === 'number' ) {
146+ // The `default` is an index
147+ const choice = question . choices [ def ] ;
148+ if ( choice ) {
149+ defaultVal = choice . value || choice . name ;
150+ }
151+ } else {
152+ // The default is a value
153+ if ( question . choices . map ( c => c . value || c . name ) . includes ( def ) ) {
154+ defaultVal = def ;
155+ }
156+ }
157+ } else if ( question . type === 'checkbox' ) {
158+ if ( def == null ) {
159+ defaultVal = question . choices
160+ . filter ( c => c . checked && ! c . disabled )
161+ . map ( c => c . value || c . name ) ;
162+ } else {
163+ defaultVal = def
164+ . map ( d => {
165+ if ( typeof d === 'number' ) {
166+ const choice = question . choices [ d ] ;
167+ if ( choice && ! choice . disabled ) {
168+ return choice . value || choice . name ;
169+ }
170+ } else {
171+ if (
172+ question . choices . find (
173+ c => ! c . disabled && d === ( c . value || c . name ) ,
174+ )
175+ ) {
176+ return d ;
177+ }
178+ }
179+ return undefined ;
180+ } )
181+ . filter ( v => v != null ) ;
182+ }
183+ }
184+ return defaultVal ;
185+ }
186+
187+ /**
188+ * Override the base prompt to skip prompts with default answers
189+ * @param questions One or more questions
190+ */
191+ async prompt ( questions ) {
192+ // Normalize the questions to be an array
193+ if ( ! Array . isArray ( questions ) ) {
194+ questions = [ questions ] ;
195+ }
196+ if ( ! this . options [ 'yes' ] ) {
197+ if ( ! process . stdin . isTTY ) {
198+ const msg = 'The stdin is not a terminal. No prompt is allowed.' ;
199+ this . log ( chalk . red ( msg ) ) ;
200+ this . exit ( new Error ( msg ) ) ;
201+ return ;
202+ }
203+ // Non-express mode, continue to prompt
204+ return await super . prompt ( questions ) ;
205+ }
206+
207+ const answers = Object . assign ( { } , this . options ) ;
208+
209+ for ( const q of questions ) {
210+ let when = q . when ;
211+ if ( typeof when === 'function' ) {
212+ when = await q . when ( answers ) ;
213+ }
214+ if ( when === false ) continue ;
215+ if ( this . _isQuestionOptional ( q ) ) {
216+ const answer = await this . _getDefaultAnswer ( q , answers ) ;
217+ debug ( '%s: %j' , q . name , answer ) ;
218+ answers [ q . name ] = answer ;
219+ } else {
220+ if ( ! process . stdin . isTTY ) {
221+ const msg = 'The stdin is not a terminal. No prompt is allowed.' ;
222+ this . log ( chalk . red ( msg ) ) ;
223+ this . exit ( new Error ( msg ) ) ;
224+ return ;
225+ }
226+ // Only prompt for non-skipped questions
227+ const props = await super . prompt ( [ q ] ) ;
228+ Object . assign ( answers , props ) ;
229+ }
230+ }
231+ return answers ;
232+ }
233+
36234 /**
37235 * Override the usage text by replacing `yo loopback4:` with `lb4 `.
38236 */
@@ -96,7 +294,10 @@ module.exports = class BaseGenerator extends Generator {
96294 */
97295 end ( ) {
98296 if ( this . shouldExit ( ) ) {
297+ debug ( this . exitGeneration ) ;
99298 this . log ( chalk . red ( 'Generation is aborted:' , this . exitGeneration ) ) ;
299+ // Fail the process
300+ process . exitCode = 1 ;
100301 return false ;
101302 }
102303 return true ;
0 commit comments