create-gasket-app 7.0.2-cli.0 → 7.2.0-canary.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (34) hide show
  1. package/README.md +222 -2
  2. package/lib/commands/create.js +148 -0
  3. package/lib/config/default-plugins.js +7 -0
  4. package/lib/index.d.ts +264 -0
  5. package/lib/index.js +24 -9
  6. package/lib/scaffold/action-wrapper.js +37 -0
  7. package/lib/scaffold/actions/create-hooks.js +25 -0
  8. package/lib/scaffold/actions/generate-files.js +185 -0
  9. package/lib/scaffold/actions/global-prompts.js +158 -0
  10. package/lib/scaffold/actions/index.js +33 -0
  11. package/lib/scaffold/actions/install-modules.js +15 -0
  12. package/lib/scaffold/actions/link-modules.js +19 -0
  13. package/lib/scaffold/actions/load-preset.js +60 -0
  14. package/lib/scaffold/actions/mkdir.js +30 -0
  15. package/lib/scaffold/actions/post-create-hooks.js +31 -0
  16. package/lib/scaffold/actions/preset-config-hooks.js +13 -0
  17. package/lib/scaffold/actions/preset-prompt-hooks.js +17 -0
  18. package/lib/scaffold/actions/print-report.js +89 -0
  19. package/lib/scaffold/actions/prompt-hooks.js +43 -0
  20. package/lib/scaffold/actions/setup-pkg.js +34 -0
  21. package/lib/scaffold/actions/write-gasket-config.js +117 -0
  22. package/lib/scaffold/actions/write-pkg.js +19 -0
  23. package/lib/scaffold/config-builder.js +523 -0
  24. package/lib/scaffold/create-context.js +187 -0
  25. package/lib/scaffold/dump-error-context.js +28 -0
  26. package/lib/scaffold/files.js +30 -0
  27. package/lib/scaffold/utils.js +22 -0
  28. package/lib/utils/create-option.js +18 -0
  29. package/lib/utils/index.js +5 -0
  30. package/lib/utils/logo.js +12 -0
  31. package/lib/utils/process-args.js +37 -0
  32. package/lib/utils/process-command.js +50 -0
  33. package/lib/utils/process-options.js +44 -0
  34. package/package.json +29 -7
@@ -0,0 +1,523 @@
1
+ /* eslint-disable complexity, max-statements */
2
+ import deepmerge from 'deepmerge';
3
+ import { default as semver } from 'semver';
4
+ import { default as diagnostics } from 'diagnostics';
5
+ const debug = diagnostics('gasket:cli:package');
6
+
7
+ /**
8
+ * Simple object check without bringing in a large
9
+ * utility library.
10
+ *
11
+ * @param {*} value - What to test if an object
12
+ * @returns {Boolean} results
13
+ */
14
+ function isObject(value) {
15
+ return value && typeof value === 'object';
16
+ }
17
+
18
+ /*
19
+ * Known semver prefix values.
20
+ * Adapted from @vue/cli under MIT
21
+ * https://github.com/vuejs/vue-cli/blob/f09722c/packages/%40vue/cli/lib/util/mergeDeps.js#L53
22
+ */
23
+ const semverPrefixes = /^(~|\^|>=?)/;
24
+
25
+ /*
26
+ * Known package.json dependency values
27
+ * Adapted from @vue/cli under MIT
28
+ * https://github.com/vuejs/vue-cli/blob/f09722c/packages/%40vue/cli/lib/util/mergeDeps.js#L10-L11
29
+ */
30
+ const versionTypes = {
31
+ uri: /^(?:file|git|git\+ssh|git\+http|git\+https|git\+file|https?):/,
32
+ github: /^[^/]+\/[^/]+/
33
+ };
34
+
35
+ /**
36
+ * Validates if the version `v` is valid for `package.json`
37
+ * dependencies, devDependencies, etc.
38
+ *
39
+ * @param {string} v Version in package.json field.
40
+ * @returns {boolean} Value indicating if npm accepts the value
41
+ */
42
+ function isValidVersion(v) {
43
+ //
44
+ // Remark: this explicitly forbids using npm dist-tags
45
+ // as valid versions. To support this every call must hit an npm
46
+ // registry to see what dist-tags are available. This is not feasible.
47
+ //
48
+ return v === 'latest'
49
+ || v.match(versionTypes.uri) != null // eslint-disable-line eqeqeq
50
+ || v.match(versionTypes.github) != null // eslint-disable-line eqeqeq
51
+ || semver.validRange(v);
52
+ }
53
+
54
+ /**
55
+ * ConfigBuilder is an extensible data structure for **specifically**
56
+ * managing `package.json` data.
57
+ *
58
+ * @type {ConfigBuilder}
59
+ */
60
+ export class ConfigBuilder {
61
+ /**
62
+ * ConfigBuilder
63
+ *
64
+ * @param {Object} [fields] - Initial fields
65
+ * @param {Object} [options] - Additional setup options
66
+ * @param {String[]} [options.orderBy] - Preferred order to sort top-level keys
67
+ * @param {String[]} [options.orderedFields] - Fields that should be sorted
68
+ * @param {String[]} [options.objectFields] - Fields that are required to be object type
69
+ * @param {String[]} [options.semverFields] - Fields that are aware of semantic versioning
70
+ */
71
+ constructor(fields = {}, options = {}) {
72
+ this.fields = Object.assign({}, fields);
73
+ this.original = fields;
74
+
75
+ this.blame = new Map();
76
+ this.force = new Set();
77
+
78
+ this.orderBy = options.orderBy;
79
+ this.orderedFields = options.orderedFields;
80
+ this.objectFields = options.objectFields;
81
+ this.semverFields = options.semverFields;
82
+ this.warnings = options.warnings;
83
+
84
+ // Any semverFields are also object fields and ordered fields.
85
+ if (Array.isArray(this.semverFields)) {
86
+ this.orderedFields = (this.orderedFields || []).concat(this.semverFields);
87
+ this.objectFields = (this.objectFields || []).concat(this.semverFields);
88
+ }
89
+ }
90
+
91
+ /**
92
+ * Creator method to get a new instance
93
+ *
94
+ * @param {Object} [fields] - Initial fields
95
+ * @param {Object} [options] - Additional setup options
96
+ * @returns {ConfigBuilder} instance
97
+ */
98
+ static create(fields = {}, options = {}) {
99
+ return new ConfigBuilder(fields, options);
100
+ }
101
+
102
+ /**
103
+ * Create an instance configured with options for package.json files
104
+ *
105
+ * @param {Object} [fields] - Initial fields
106
+ * @param {Object} [options] - Additional setup options
107
+ * @returns {ConfigBuilder} instance
108
+ */
109
+ static createPackageJson(fields = {}, options = {}) {
110
+ return new ConfigBuilder(fields, {
111
+ ...options,
112
+ orderBy: [
113
+ 'name',
114
+ 'version',
115
+ 'description',
116
+ 'license',
117
+ 'repository',
118
+ 'scripts',
119
+ 'dependencies',
120
+ 'devDependencies',
121
+ 'peerDependencies',
122
+ 'optionalDependencies'
123
+ ],
124
+ semverFields: [
125
+ 'dependencies',
126
+ 'devDependencies',
127
+ 'peerDependencies',
128
+ 'optionalDependencies'
129
+ ],
130
+ objectFields: [
131
+ 'scripts'
132
+ ]
133
+ });
134
+ }
135
+
136
+ warn(message) {
137
+ if (this.warnings) {
138
+ this.warnings.push(message);
139
+ } else {
140
+ console.warn(message);
141
+ }
142
+ }
143
+
144
+ /**
145
+ * Adds all `[key, value]` pairs in the `fields` provided.
146
+ * @param {object|function(current)} fields - Object to merge.
147
+ * Can be a function that accepts the current fields and object to merge.
148
+ * @param {Object} source Plugin to blame if conflicts arise from this operation.
149
+ *
150
+ * Adapted from @vue/cli under MIT License:
151
+ * https://github.com/vuejs/vue-cli/blob/f09722c/packages/%40vue/cli/lib/GeneratorAPI.js#L117-L150
152
+ */
153
+ extend(fields, source) {
154
+ const current = this.fields;
155
+ const toMerge = typeof fields === 'function'
156
+ ? fields(current)
157
+ : fields;
158
+
159
+ // Silently ignore any falsey values
160
+ if (!toMerge || typeof toMerge !== 'object') {
161
+ return;
162
+ }
163
+
164
+ Object.entries(toMerge).forEach(([k, v]) => {
165
+ this.add(k, v, source || this.source);
166
+ });
167
+ }
168
+
169
+ /**
170
+ * Performs an intelligent, domain-aware merge of the `value` for
171
+ * the given `key` into the package.json fields associated with this instance.
172
+ * @param {string} key - Field in package.json to add or extend.
173
+ * @param {*} value - Target value to set for key provided.
174
+ * @param {Object} source - Plugin to blame if conflicts arise from this operation.
175
+ * @param {object} [options] - Optional arguments for add behavior
176
+ * @param {boolean} [options.force] - Should the semver version override other attempts
177
+ *
178
+ * Adapted from @vue/cli under MIT License:
179
+ * https://github.com/vuejs/vue-cli/blob/f09722c/packages/%40vue/cli/lib/GeneratorAPI.js#L117-L150
180
+ */
181
+ add(key, value, source, options = {}) {
182
+ if (typeof value === 'undefined') return;
183
+ const existing = this.fields[key];
184
+ const { name = 'Unknown plugin' } = (source || this.source || {});
185
+
186
+ debug('add', { [key]: value, existing, from: name });
187
+ if (Array.isArray(this.objectFields) && this.objectFields.includes(key)) {
188
+ if (!isObject(value)) {
189
+ throw new Error(`${key} must be an object. Received ${value}`);
190
+ }
191
+
192
+ if (Array.isArray(this.semverFields) && this.semverFields.includes(key)) {
193
+ if (!existing) this.fields[key] = {};
194
+
195
+ this.semanticMerge({
196
+ key,
197
+ value,
198
+ existing: this.fields[key],
199
+ name,
200
+ ...options
201
+ });
202
+
203
+ return;
204
+ }
205
+
206
+ if (!existing) {
207
+ this.fields[key] = Object.assign({}, value);
208
+ return;
209
+ }
210
+
211
+ // TODO: ensure that conflicts are properly surfaced to users
212
+ this.fields[key] = Object.assign({},
213
+ existing || {},
214
+ value
215
+ );
216
+ } else if (!(key in this.fields)) {
217
+ this.fields[key] = value;
218
+ } else if (Array.isArray(value) && Array.isArray(existing)) {
219
+ this.fields[key] = this.mergeArrayDeduped(existing, value);
220
+ } else if (isObject(value) && isObject(existing)) {
221
+ this.fields[key] = deepmerge(existing, value, {
222
+ arrayMerge: this.mergeArrayDeduped
223
+ });
224
+ } else {
225
+ this.fields[key] = value;
226
+ }
227
+ }
228
+
229
+ /**
230
+ * addPlugin - Add plugin import to the gasket file and use the value in the plugins array
231
+ * @param {string} pluginImport - name of the import used as a value - `import pluginImport...`
232
+ * @param {string} pluginName - name of the plugin import/package - `from 'pluginName'`
233
+ * @example
234
+ * addPlugin('pluginA', '@gasket/plugin-a')
235
+ *
236
+ * // gasket.js
237
+ * import pluginA from '@gasket/plugin-a';
238
+ *
239
+ * export default makeGasket({
240
+ * plugins: [
241
+ * pluginA
242
+ * ]
243
+ * });
244
+ */
245
+ addPlugin(pluginImport, pluginName) {
246
+ this.add('plugins', [`${pluginImport}`]);
247
+ this.add('pluginImports', { [pluginImport]: pluginName });
248
+ }
249
+
250
+ /**
251
+ * addImport - Add a non-plugin import to the gasket file
252
+ * @param {string} importName - name of the import used as a value - `import fs...`
253
+ * @param {string} importPath - path of the import - `from 'fs'`
254
+ * @returns {ConfigBuilder} - instance for chaining
255
+ * @example
256
+ * Can be default or named import
257
+ * addImport('{ readFileSync }', 'fs')
258
+ * addImport('fs', 'fs')
259
+ *
260
+ * // gasket.js
261
+ * import { readFileSync } from 'fs';
262
+ * import fs from 'fs';
263
+ */
264
+ addImport(importName, importPath) {
265
+ this.add('imports', { [importName]: importPath });
266
+ return this;
267
+ }
268
+
269
+ /**
270
+ * addExpression - add programmatic expression to the gasket file
271
+ * @param {string} expression - expression to add after imports
272
+ * @returns {ConfigBuilder} - instance for chaining
273
+ * @example
274
+ *
275
+ * .addImport('fs', 'fs')
276
+ * .addExpression('const file = fs.readFileSync(\'./file.txt\')')
277
+ *
278
+ * // gasket.js
279
+ * import fs from 'fs';
280
+ * const file = fs.readFileSync('./file.txt');
281
+ */
282
+ addExpression(expression) {
283
+ this.add('expressions', [expression.trim()]);
284
+ return this;
285
+ }
286
+
287
+ /**
288
+ * injectValue - Inject a value into the gasket config object
289
+ * @param {string} configKey - collapsed object path to inject value into - `express.config.routes`
290
+ * @param {string} injectedValue - string used as a value
291
+ * @example
292
+ * .addImport('{ routes }', './routes')
293
+ * .injectValue('express.routes', 'routes');
294
+ *
295
+ * // gasket.js
296
+ * export default makeGasket({
297
+ * express: {
298
+ * routes: routes
299
+ * }
300
+ * });
301
+ */
302
+ injectValue(configKey, injectedValue) {
303
+ this.add('injectionAssignments', { [configKey]: injectedValue });
304
+ }
305
+
306
+ /**
307
+ * Checks if a dependency has been already added
308
+ * @param {String} key Dependency bucket
309
+ * @param {String} value Dependency to search
310
+ * @returns {Bool} True if the dependency exists on the bucket
311
+ */
312
+ has(key, value) {
313
+ const existing = this.fields[key];
314
+
315
+ if (!existing) {
316
+ return false;
317
+ }
318
+
319
+ if (Array.isArray(existing)) {
320
+ return existing.includes(value);
321
+ } else if (isObject(existing)) {
322
+ return (value in existing);
323
+ }
324
+
325
+ return value === existing;
326
+ }
327
+
328
+ /**
329
+ * Returns the existing and target array merged without duplicates
330
+ * @param {Array} existing Partial lattice to merge.
331
+ * @param {Array} target Partial lattice to merge.
332
+ * @returns {Array} existing ∪ (i.e. union) target
333
+ *
334
+ * Adapted from @vue/cli under MIT License:
335
+ * https://github.com/vuejs/vue-cli/blob/f09722c/packages/%40vue/cli/lib/GeneratorAPI.js#L15
336
+ */
337
+ mergeArrayDeduped(existing, target) {
338
+ return Array.from(new Set([...existing, ...target]));
339
+ }
340
+
341
+ /**
342
+ * Attempts to merge all entries within the `value` provided by
343
+ * the plugin specified by `name` into the `existing` semver-aware
344
+ * Object `key` (e.g. dependencies, etc.) for this instance.
345
+ *
346
+ * Merge algorithm:
347
+ *
348
+ * - ∀ [dep, ver] := Object.entries(value)
349
+ * and [prev] := any existing version for dep
350
+ *
351
+ * - If ver is not valid semver ––> ■
352
+ * - If ¬∃ prev ––> set and blame [dep, ver]
353
+ * - If ver > prev ––> set and blame [dep, ver]
354
+ * - If ¬(ver ∩ prev) ––> Conflict. Print.
355
+ *
356
+ * @param {string} options.key {devD,peerD,optionalD,d}ependencies
357
+ * @param {Object} options.value Updates for { name: version } pairs
358
+ * @param {Object} options.existing Existing { name: version } pairs
359
+ * @param {string} options.name Plugin name providing merge `value``
360
+ * @param {boolean} [options.force] Should the semver version override other attempts
361
+ *
362
+ * Adapted from @vue/cli under MIT License:
363
+ * https://github.com/vuejs/vue-cli/blob/f09722c/packages/%40vue/cli/lib/util/mergeDeps.js
364
+ */
365
+ semanticMerge({ key, value, existing, name, force = false }) {
366
+
367
+ const setBlame = blameId => {
368
+ this.blame.set(blameId, [name]);
369
+ if (force) this.force.add(blameId);
370
+ };
371
+
372
+ Object.entries(value).forEach(([dep, ver]) => {
373
+ const prev = existing[dep];
374
+ if (!isValidVersion(ver)) {
375
+ this.warn(`Invalid "${key}" provided by ${name}: ${dep}@${ver}.`);
376
+ return;
377
+ }
378
+
379
+ const blameId = `${key}.${dep}`;
380
+ if (!prev) {
381
+ existing[dep] = ver;
382
+ setBlame(blameId);
383
+ return;
384
+ }
385
+
386
+ const blamed = this.blame.get(blameId);
387
+ const forced = this.force.has(blameId);
388
+
389
+ if (prev === ver) {
390
+ if (forced) return;
391
+ if (force) {
392
+ setBlame(blameId);
393
+ } else {
394
+ blamed.push(name);
395
+ }
396
+ return;
397
+ }
398
+
399
+ const prevName = blamed.join(', ');
400
+ if (!forced) {
401
+ const newer = force ? ver : this.tryGetNewerRange(prev, ver);
402
+ const overridden = newer === ver;
403
+ if (overridden) {
404
+ existing[dep] = ver;
405
+ setBlame(blameId);
406
+ }
407
+ }
408
+
409
+ if (!semver.validRange(prev) || !semver.validRange(ver) || !semver.intersects(prev, ver)) {
410
+ let forceMsg = force ? '(forced)' : '';
411
+ forceMsg = forced && force ? '(cannot be forced)' : forceMsg;
412
+ this.warn(`
413
+ Conflicting versions for ${dep} in "${key}":
414
+ - ${prev} provided by ${prevName} ${forced && '(forced)' || ''}
415
+ - ${ver} provided by ${name} ${forceMsg}
416
+ Using ${existing[dep]}, but this may cause unexpected behavior.
417
+ `);
418
+ }
419
+ });
420
+ }
421
+
422
+ /**
423
+ * Normalizes a potential semver range into a semver string
424
+ * and returns the newest version
425
+ * @param {String} r1 Semver string (potentially invalid).
426
+ * @param {String} r2 Semver string (potentially invalid).
427
+ * @returns {String|undefined} Newest semver version.
428
+ *
429
+ * Adapted from @vue/cli under MIT License:
430
+ * https://github.com/vuejs/vue-cli/blob/f09722c/packages/%40vue/cli/lib/util/mergeDeps.js#L58-L64
431
+ */
432
+ tryGetNewerRange(r1, r2) {
433
+ if (r1 === 'latest' || r2 === 'latest') {
434
+ return r1 === 'latest' ? r1 : r2;
435
+ }
436
+
437
+ const v1 = this.rangeToVersion(r1);
438
+ const v2 = this.rangeToVersion(r2);
439
+ if (semver.valid(v1) && semver.valid(v2)) {
440
+ return semver.gt(v1, v2) ? r1 : r2;
441
+ }
442
+ }
443
+
444
+ /**
445
+ * Performs a naive attempt to take a transform a semver range
446
+ * into a concrete version that may be used for "newness"
447
+ * comparison.
448
+ *
449
+ * @param {string} range Valid "basic" semver: ^X.Y.Z, ~A.B.C, >=2.3.x, 1.x.x
450
+ * @returns {string} Concrete as possible version: X.Y.Z, A.B.C, 2.3.0, 1.0.0
451
+ */
452
+ rangeToVersion(range) {
453
+ return range
454
+ .replace(semverPrefixes, '')
455
+ .replace(/x/g, '0')
456
+ .trim();
457
+ }
458
+
459
+ /**
460
+ * Orders top-level keys by `orderBy` options with any fields specified in
461
+ * the `orderFields` options having their keys sorted.
462
+ * @returns {Object} Ready to be serialized JavaScript object.
463
+ */
464
+ toJSON() {
465
+ if (Array.isArray(this.orderedFields)) {
466
+ this.orderedFields.forEach(k => {
467
+ if (this.fields[k]) {
468
+ this.fields[k] = this.toOrderedKeys(this.fields[k]);
469
+ }
470
+ });
471
+ }
472
+
473
+ return this.toOrderedKeys(this.fields, this.orderBy);
474
+ }
475
+
476
+ /**
477
+ * Orders the given object, `obj`, applying any (optional)
478
+ * key order specified via `orderBy`. If no `orderBy` is provided
479
+ * keys are ordered lexographically.
480
+ * @param {Object} obj Object to transform to ordered keys
481
+ * @param {string[]} [orderBy] Explicit key order to use.
482
+ * @returns {Object} Shallow clone of `obj` with ordered keys
483
+ *
484
+ * Adapted from @vue/cli under MIT License:
485
+ * https://github.com/vuejs/vue-cli/blob/f09722c/packages/%40vue/cli/lib/util/sortObject.js
486
+ */
487
+ toOrderedKeys(obj, orderBy) {
488
+ if (!obj) return;
489
+
490
+ // Create an optional order if necessary
491
+ let order;
492
+ if (Array.isArray(orderBy)) {
493
+ order = orderBy.reduce((acc, key, i) => {
494
+ acc[key] = i;
495
+ return acc;
496
+ }, {});
497
+ }
498
+
499
+ /*
500
+ * Sorts based on the `order` defined above.
501
+ */
502
+ function sortByOrder(a, b) {
503
+ const indexA = typeof order[a] === 'undefined'
504
+ ? Infinity
505
+ : order[a];
506
+
507
+ const indexB = typeof order[b] === 'undefined'
508
+ ? Infinity
509
+ : order[b];
510
+
511
+ return indexA - indexB;
512
+ }
513
+
514
+ const keys = order
515
+ ? Object.keys(obj).sort(sortByOrder)
516
+ : Object.keys(obj).sort();
517
+
518
+ return keys.reduce((acc, key) => {
519
+ acc[key] = obj[key];
520
+ return acc;
521
+ }, {});
522
+ }
523
+ }
@@ -0,0 +1,187 @@
1
+ /* eslint-disable max-len, max-statements */
2
+ import fs from 'fs';
3
+ import path from 'path';
4
+ import { readConfig } from '../scaffold/utils.js';
5
+
6
+ /**
7
+ * The CreateRuntime represents a shallow proxy to a CreateContext
8
+ * that automatically adds transactional information for providing
9
+ * CLI users with blame information in the event of conflicts.
10
+ *
11
+ * @param {CreateContext} context - Create context.
12
+ * @param {Plugin} source - Gasket plugin the create context is within.
13
+ * @returns {Proxy} Shallow proxy to the `context` provided that will
14
+ * pass `source` to `context.{files,pkg}.*` methods.
15
+ */
16
+ function makeCreateRuntime(context, source) {
17
+ //
18
+ // Create a set of overrides for our Proxy
19
+ //
20
+ const overrides = {
21
+ source,
22
+ files: {
23
+ add(...globs) {
24
+ context.files.add({ globs, source });
25
+ }
26
+ },
27
+ pkg: {
28
+ extend(fields) {
29
+ context.pkg.extend(fields, source);
30
+ },
31
+
32
+ add(key, value, options) {
33
+ context.pkg.add(key, value, source, options);
34
+ },
35
+
36
+ has(key, value) {
37
+ return context.pkg.has(key, value);
38
+ }
39
+ }
40
+ };
41
+
42
+ return new Proxy(context, {
43
+ get(obj, key) {
44
+ if (overrides[key]) return overrides[key];
45
+ return obj[key];
46
+ },
47
+ set(obj, key, value) {
48
+ if (key !== 'pkg' && key !== 'files' && key !== 'source') {
49
+ obj[key] = value;
50
+ }
51
+ }
52
+ });
53
+ }
54
+
55
+ function flatten(acc, values) {
56
+ return acc.concat(values);
57
+ }
58
+
59
+ /**
60
+ * Create Context
61
+ *
62
+ * @type {CreateContext}
63
+ *
64
+ * -- Added by command args and options
65
+ *
66
+ * @property {String} appName - Short name of the app
67
+ * @property {String} cwd - Current work directory
68
+ * @property {String} dest - Path to the target app (Default: cwd/appName)
69
+ * @property {String} relDest - Relative path to the target app
70
+ * @property {Boolean} extant - Whether or not target directory already exists
71
+ * @property {[String]} localPresets - paths to the local presets packages
72
+ * @property {PresetDesc[]} rawPresets - Raw preset desc from args. Can include version constraint. Added by load-preset if using localPresets.
73
+ * @property {String[]} pkgLinks - Local packages that should be linked
74
+ * @property {String[]} messages - non-error/warning messages to report
75
+ * @property {String[]} warnings - warnings messages to report
76
+ * @property {String[]} errors - error messages to report but do not exit process
77
+ * @property {String[]} nextSteps - any next steps to report for user
78
+ * @property {Set<String>} generatedFiles - any generated files to show in report
79
+ * @property {any[]} presets - Default empty array, populated by load-preset with actual imports
80
+ * @property {Object} presetConfig - Default to object w/empty plugins array to be populated by `presetConfig` hook
81
+ *
82
+ * -- Added by `global-prompts`
83
+ *
84
+ * @property {String} appDescription - Description of app
85
+ * @property {Boolean} gitInit - Should a git repo be initialized and first commit
86
+ * @property {String} testPlugins - Array of testing plugins
87
+ * @property {String} packageManager - Which package manager to use (Default: 'npm')
88
+ * @property {String} installCmd - Derived install command (Default: 'npm install')
89
+ * @property {String} localCmd - Derived local run command (Default: 'npx gasket local')
90
+ * @property {Boolean} destOverride - Whether or not the user wants to override an extant directory
91
+ *
92
+ * -- Added by `load-preset`
93
+ *
94
+ * @property {PresetName[]} presets - Short name of presets
95
+ *
96
+ *
97
+ * -- Added by `setup-pkg`
98
+ *
99
+ * @property {ConfigBuilder} pkg - package.json builder
100
+ * @property {PackageManager} pkgManager - manager to execute npm or yarn commands
101
+ *
102
+ * -- Added by `setup-gasket-config`
103
+ *
104
+ * @property {ConfigBuilder} gasketConfig - gasket.config builder
105
+ *
106
+ * -- Added by `create-hooks`
107
+ *
108
+ * @property {Files} files - Use to add files and templates to generate
109
+ */
110
+ export class CreateContext {
111
+ constructor(initContext = {}) {
112
+ Object.assign(this, initContext);
113
+ }
114
+
115
+ runWith(plugin) {
116
+ return makeCreateRuntime(this, plugin);
117
+ }
118
+ }
119
+
120
+ /**
121
+ * Makes the initial context used through the create command flow.
122
+ *
123
+ * @param {String[]} argv - Commands args
124
+ * @param {Object} options - Command options
125
+ * @returns {CreateContext} context
126
+ */
127
+ export function makeCreateContext(argv = [], options = {}) {
128
+ const appName = argv[0] || 'templated-app';
129
+ const {
130
+ presets = [],
131
+ npmLink = [],
132
+ presetPath = [],
133
+ packageManager,
134
+ prompts,
135
+ config,
136
+ configFile
137
+ } = options;
138
+
139
+ // Flatten the array of array created by the plugins flag – it
140
+ // supports both multiple instances as well as comma-separated lists.
141
+ const rawPresets = presets.reduce(flatten, []);
142
+ const localPresets = presetPath.reduce(flatten, []);
143
+ const pkgLinks = npmLink.reduce(flatten, []);
144
+ const cwd = process.cwd();
145
+ const dest = path.join(cwd, appName);
146
+ const relDest = `.${path.sep}${path.relative(cwd, dest)}`;
147
+ // eslint-disable-next-line no-sync
148
+ const extant = fs.existsSync(dest);
149
+
150
+ /**
151
+ * Input context which will be appended by prompts and passed to create hooks
152
+ *
153
+ * @type {CreateContext}
154
+ */
155
+ const context = new CreateContext({
156
+ destOverride: true,
157
+ cwd,
158
+ dest,
159
+ relDest,
160
+ extant,
161
+ pkgLinks,
162
+ localPresets,
163
+ rawPresets,
164
+ messages: [],
165
+ warnings: [],
166
+ errors: [],
167
+ nextSteps: [],
168
+ generatedFiles: new Set(),
169
+ prompts,
170
+ presets: [],
171
+ presetConfig: {
172
+ plugins: []
173
+ }
174
+ });
175
+
176
+ readConfig(context, { config, configFile });
177
+
178
+ if (packageManager) {
179
+ context.packageManager = packageManager;
180
+ }
181
+ if (appName) {
182
+ context.appName = appName;
183
+ }
184
+
185
+ return context;
186
+ }
187
+