create-gasket-app 7.0.0-cli.7 → 7.0.0-next.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 (40) hide show
  1. package/README.md +222 -2
  2. package/lib/commands/create.js +192 -0
  3. package/lib/config/default-plugins.js +7 -0
  4. package/lib/index.d.ts +205 -0
  5. package/lib/index.js +22 -9
  6. package/lib/main.js +19 -0
  7. package/lib/scaffold/action-wrapper.js +36 -0
  8. package/lib/scaffold/actions/apply-preset-config.js +29 -0
  9. package/lib/scaffold/actions/cli-version.js +84 -0
  10. package/lib/scaffold/actions/create-hooks.js +26 -0
  11. package/lib/scaffold/actions/generate-files.js +183 -0
  12. package/lib/scaffold/actions/global-prompts.js +161 -0
  13. package/lib/scaffold/actions/index.js +36 -0
  14. package/lib/scaffold/actions/install-modules.js +18 -0
  15. package/lib/scaffold/actions/link-modules.js +21 -0
  16. package/lib/scaffold/actions/load-pkg-for-debug.js +24 -0
  17. package/lib/scaffold/actions/load-preset.js +82 -0
  18. package/lib/scaffold/actions/mkdir.js +30 -0
  19. package/lib/scaffold/actions/post-create-hooks.js +33 -0
  20. package/lib/scaffold/actions/print-report.js +88 -0
  21. package/lib/scaffold/actions/prompt-hooks.js +90 -0
  22. package/lib/scaffold/actions/setup-pkg.js +47 -0
  23. package/lib/scaffold/actions/write-gasket-config.js +32 -0
  24. package/lib/scaffold/actions/write-pkg.js +23 -0
  25. package/lib/scaffold/config-builder.js +448 -0
  26. package/lib/scaffold/create-context.js +193 -0
  27. package/lib/scaffold/create-engine.js +21 -0
  28. package/lib/scaffold/dump-error-context.js +28 -0
  29. package/lib/scaffold/fetcher.js +194 -0
  30. package/lib/scaffold/files.js +30 -0
  31. package/lib/scaffold/utils.js +115 -0
  32. package/lib/utils/create-option.js +20 -0
  33. package/lib/utils/index.js +16 -0
  34. package/lib/utils/logo.js +14 -0
  35. package/lib/utils/process-args.js +39 -0
  36. package/lib/utils/process-command.js +52 -0
  37. package/lib/utils/process-options.js +46 -0
  38. package/lib/utils/setup.js +25 -0
  39. package/lib/utils/warn-if-outdated.js +76 -0
  40. package/package.json +23 -3
@@ -0,0 +1,448 @@
1
+ /* eslint-disable complexity, max-statements */
2
+ const deepmerge = require('deepmerge');
3
+ const semver = require('semver');
4
+ const debug = require('diagnostics')('gasket:create-gasket-app:package');
5
+
6
+ /**
7
+ * Simple object check without bringing in a large
8
+ * utility library.
9
+ *
10
+ * @param {*} value - What to test if an object
11
+ * @returns {Boolean} results
12
+ */
13
+ function isObject(value) {
14
+ return value && typeof value === 'object';
15
+ }
16
+
17
+ /*
18
+ * Known semver prefix values.
19
+ * Adapted from @vue/cli under MIT
20
+ * https://github.com/vuejs/vue-cli/blob/f09722c/packages/%40vue/cli/lib/util/mergeDeps.js#L53
21
+ */
22
+ const semverPrefixes = /^(~|\^|>=?)/;
23
+
24
+ /*
25
+ * Known package.json dependency values
26
+ * Adapted from @vue/cli under MIT
27
+ * https://github.com/vuejs/vue-cli/blob/f09722c/packages/%40vue/cli/lib/util/mergeDeps.js#L10-L11
28
+ */
29
+ const versionTypes = {
30
+ uri: /^(?:file|git|git\+ssh|git\+http|git\+https|git\+file|https?):/,
31
+ github: /^[^/]+\/[^/]+/
32
+ };
33
+
34
+ /**
35
+ * Validates if the version `v` is valid for `package.json`
36
+ * dependencies, devDependencies, etc.
37
+ *
38
+ * @param {string} v Version in package.json field.
39
+ * @returns {boolean} Value indicating if npm accepts the value
40
+ */
41
+ function isValidVersion(v) {
42
+ //
43
+ // Remark: this explicitly forbids using npm dist-tags
44
+ // as valid versions. To support this every call must hit an npm
45
+ // registry to see what dist-tags are available. This is not feasible.
46
+ //
47
+ return v === 'latest'
48
+ || v.match(versionTypes.uri) != null // eslint-disable-line eqeqeq
49
+ || v.match(versionTypes.github) != null // eslint-disable-line eqeqeq
50
+ || semver.validRange(v);
51
+ }
52
+
53
+ /**
54
+ * ConfigBuilder is an extensible data structure for **specifically**
55
+ * managing `package.json` data.
56
+ *
57
+ * @type {ConfigBuilder}
58
+ */
59
+ class ConfigBuilder {
60
+ /**
61
+ * ConfigBuilder
62
+ *
63
+ * @param {Object} [fields] - Initial fields
64
+ * @param {Object} [options] - Additional setup options
65
+ * @param {String[]} [options.orderBy] - Preferred order to sort top-level keys
66
+ * @param {String[]} [options.orderedFields] - Fields that should be sorted
67
+ * @param {String[]} [options.objectFields] - Fields that are required to be object type
68
+ * @param {String[]} [options.semverFields] - Fields that are aware of semantic versioning
69
+ */
70
+ constructor(fields = {}, options = {}) {
71
+ this.fields = Object.assign({}, fields);
72
+ this.original = fields;
73
+
74
+ this.blame = new Map();
75
+ this.force = new Set();
76
+
77
+ this.orderBy = options.orderBy;
78
+ this.orderedFields = options.orderedFields;
79
+ this.objectFields = options.objectFields;
80
+ this.semverFields = options.semverFields;
81
+ this.warnings = options.warnings;
82
+
83
+ // Any semverFields are also object fields and ordered fields.
84
+ if (Array.isArray(this.semverFields)) {
85
+ this.orderedFields = (this.orderedFields || []).concat(this.semverFields);
86
+ this.objectFields = (this.objectFields || []).concat(this.semverFields);
87
+ }
88
+ }
89
+
90
+ /**
91
+ * Creator method to get a new instance
92
+ *
93
+ * @param {Object} [fields] - Initial fields
94
+ * @param {Object} [options] - Additional setup options
95
+ * @returns {ConfigBuilder} instance
96
+ */
97
+ static create(fields = {}, options = {}) {
98
+ return new ConfigBuilder(fields, options);
99
+ }
100
+
101
+ /**
102
+ * Create an instance configured with options for package.json files
103
+ *
104
+ * @param {Object} [fields] - Initial fields
105
+ * @param {Object} [options] - Additional setup options
106
+ * @returns {ConfigBuilder} instance
107
+ */
108
+ static createPackageJson(fields = {}, options = {}) {
109
+ return new ConfigBuilder(fields, {
110
+ ...options,
111
+ orderBy: [
112
+ 'name',
113
+ 'version',
114
+ 'description',
115
+ 'license',
116
+ 'repository',
117
+ 'scripts',
118
+ 'dependencies',
119
+ 'devDependencies',
120
+ 'peerDependencies',
121
+ 'optionalDependencies'
122
+ ],
123
+ semverFields: [
124
+ 'dependencies',
125
+ 'devDependencies',
126
+ 'peerDependencies',
127
+ 'optionalDependencies'
128
+ ],
129
+ objectFields: [
130
+ 'scripts'
131
+ ]
132
+ });
133
+ }
134
+
135
+ warn(message) {
136
+ if (this.warnings) {
137
+ this.warnings.push(message);
138
+ } else {
139
+ console.warn(message);
140
+ }
141
+ }
142
+
143
+ /**
144
+ * Adds all `[key, value]` pairs in the `fields` provided.
145
+ * @param {object|function(current)} fields - Object to merge.
146
+ * Can be a function that accepts the current fields and object to merge.
147
+ * @param {Object} source Plugin to blame if conflicts arise from this operation.
148
+ *
149
+ * Adapted from @vue/cli under MIT License:
150
+ * https://github.com/vuejs/vue-cli/blob/f09722c/packages/%40vue/cli/lib/GeneratorAPI.js#L117-L150
151
+ */
152
+ extend(fields, source) {
153
+ const current = this.fields;
154
+ const toMerge = typeof fields === 'function'
155
+ ? fields(current)
156
+ : fields;
157
+
158
+ // Silently ignore any falsey values
159
+ if (!toMerge || typeof toMerge !== 'object') {
160
+ return;
161
+ }
162
+
163
+ Object.entries(toMerge).forEach(([k, v]) => {
164
+ this.add(k, v, source || this.source);
165
+ });
166
+ }
167
+
168
+ /**
169
+ * Performs an intelligent, domain-aware merge of the `value` for
170
+ * the given `key` into the package.json fields associated with this instance.
171
+ * @param {string} key - Field in package.json to add or extend.
172
+ * @param {*} value - Target value to set for key provided.
173
+ * @param {Object} source - Plugin to blame if conflicts arise from this operation.
174
+ * @param {object} [options] - Optional arguments for add behavior
175
+ * @param {boolean} [options.force] - Should the semver version override other attempts
176
+ *
177
+ * Adapted from @vue/cli under MIT License:
178
+ * https://github.com/vuejs/vue-cli/blob/f09722c/packages/%40vue/cli/lib/GeneratorAPI.js#L117-L150
179
+ */
180
+ add(key, value, source, options = {}) {
181
+ if (typeof value === 'undefined') return;
182
+
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
+ * Checks if a dependency has been already added
231
+ * @param {String} key Dependency bucket
232
+ * @param {String} value Dependency to search
233
+ * @returns {Bool} True if the dependency exists on the bucket
234
+ */
235
+ has(key, value) {
236
+ const existing = this.fields[key];
237
+
238
+ if (!existing) {
239
+ return false;
240
+ }
241
+
242
+ if (Array.isArray(existing)) {
243
+ return existing.includes(value);
244
+ } else if (isObject(existing)) {
245
+ return (value in existing);
246
+ }
247
+
248
+ return value === existing;
249
+ }
250
+
251
+ /**
252
+ * Returns the existing and target array merged without duplicates
253
+ * @param {Array} existing Partial lattice to merge.
254
+ * @param {Array} target Partial lattice to merge.
255
+ * @returns {Array} existing ∪ (i.e. union) target
256
+ *
257
+ * Adapted from @vue/cli under MIT License:
258
+ * https://github.com/vuejs/vue-cli/blob/f09722c/packages/%40vue/cli/lib/GeneratorAPI.js#L15
259
+ */
260
+ mergeArrayDeduped(existing, target) {
261
+ return Array.from(new Set([...existing, ...target]));
262
+ }
263
+
264
+ /**
265
+ * Attempts to merge all entries within the `value` provided by
266
+ * the plugin specified by `name` into the `existing` semver-aware
267
+ * Object `key` (e.g. dependencies, etc.) for this instance.
268
+ *
269
+ * Merge algorithm:
270
+ *
271
+ * - ∀ [dep, ver] := Object.entries(value)
272
+ * and [prev] := any existing version for dep
273
+ *
274
+ * - If ver is not valid semver ––> ■
275
+ * - If ¬∃ prev ––> set and blame [dep, ver]
276
+ * - If ver > prev ––> set and blame [dep, ver]
277
+ * - If ¬(ver ∩ prev) ––> Conflict. Print.
278
+ *
279
+ * @param {string} options.key {devD,peerD,optionalD,d}ependencies
280
+ * @param {Object} options.value Updates for { name: version } pairs
281
+ * @param {Object} options.existing Existing { name: version } pairs
282
+ * @param {string} options.name Plugin name providing merge `value``
283
+ * @param {boolean} [options.force] Should the semver version override other attempts
284
+ *
285
+ * Adapted from @vue/cli under MIT License:
286
+ * https://github.com/vuejs/vue-cli/blob/f09722c/packages/%40vue/cli/lib/util/mergeDeps.js
287
+ */
288
+ semanticMerge({ key, value, existing, name, force = false }) {
289
+
290
+ const setBlame = blameId => {
291
+ this.blame.set(blameId, [name]);
292
+ if (force) this.force.add(blameId);
293
+ };
294
+
295
+ Object.entries(value).forEach(([dep, ver]) => {
296
+ const prev = existing[dep];
297
+ if (!isValidVersion(ver)) {
298
+ this.warn(`Invalid "${key}" provided by ${name}: ${dep}@${ver}.`);
299
+ return;
300
+ }
301
+
302
+ const blameId = `${key}.${dep}`;
303
+ if (!prev) {
304
+ existing[dep] = ver;
305
+ setBlame(blameId);
306
+ return;
307
+ }
308
+
309
+ const blamed = this.blame.get(blameId);
310
+ const forced = this.force.has(blameId);
311
+
312
+ if (prev === ver) {
313
+ if (forced) return;
314
+ if (force) {
315
+ setBlame(blameId);
316
+ } else {
317
+ blamed.push(name);
318
+ }
319
+ return;
320
+ }
321
+
322
+ const prevName = blamed.join(', ');
323
+ if (!forced) {
324
+ const newer = force ? ver : this.tryGetNewerRange(prev, ver);
325
+ const overridden = newer === ver;
326
+ if (overridden) {
327
+ existing[dep] = ver;
328
+ setBlame(blameId);
329
+ }
330
+ }
331
+
332
+ if (!semver.validRange(prev) || !semver.validRange(ver) || !semver.intersects(prev, ver)) {
333
+ let forceMsg = force ? '(forced)' : '';
334
+ forceMsg = forced && force ? '(cannot be forced)' : forceMsg;
335
+ this.warn(`
336
+ Conflicting versions for ${dep} in "${key}":
337
+ - ${prev} provided by ${prevName} ${forced && '(forced)' || ''}
338
+ - ${ver} provided by ${name} ${forceMsg}
339
+ Using ${existing[dep]}, but this may cause unexpected behavior.
340
+ `);
341
+ }
342
+ });
343
+ }
344
+
345
+ /**
346
+ * Normalizes a potential semver range into a semver string
347
+ * and returns the newest version
348
+ * @param {String} r1 Semver string (potentially invalid).
349
+ * @param {String} r2 Semver string (potentially invalid).
350
+ * @returns {String|undefined} Newest semver version.
351
+ *
352
+ * Adapted from @vue/cli under MIT License:
353
+ * https://github.com/vuejs/vue-cli/blob/f09722c/packages/%40vue/cli/lib/util/mergeDeps.js#L58-L64
354
+ */
355
+ tryGetNewerRange(r1, r2) {
356
+ if (r1 === 'latest' || r2 === 'latest') {
357
+ return r1 === 'latest' ? r1 : r2;
358
+ }
359
+
360
+ const v1 = this.rangeToVersion(r1);
361
+ const v2 = this.rangeToVersion(r2);
362
+ if (semver.valid(v1) && semver.valid(v2)) {
363
+ return semver.gt(v1, v2) ? r1 : r2;
364
+ }
365
+ }
366
+
367
+ /**
368
+ * Performs a naive attempt to take a transform a semver range
369
+ * into a concrete version that may be used for "newness"
370
+ * comparison.
371
+ *
372
+ * @param {string} range Valid "basic" semver: ^X.Y.Z, ~A.B.C, >=2.3.x, 1.x.x
373
+ * @returns {string} Concrete as possible version: X.Y.Z, A.B.C, 2.3.0, 1.0.0
374
+ */
375
+ rangeToVersion(range) {
376
+ return range
377
+ .replace(semverPrefixes, '')
378
+ .replace(/x/g, '0')
379
+ .trim();
380
+ }
381
+
382
+ /**
383
+ * Orders top-level keys by `orderBy` options with any fields specified in
384
+ * the `orderFields` options having their keys sorted.
385
+ * @returns {Object} Ready to be serialized JavaScript object.
386
+ */
387
+ toJSON() {
388
+ if (Array.isArray(this.orderedFields)) {
389
+ this.orderedFields.forEach(k => {
390
+ if (this.fields[k]) {
391
+ this.fields[k] = this.toOrderedKeys(this.fields[k]);
392
+ }
393
+ });
394
+ }
395
+
396
+ return this.toOrderedKeys(this.fields, this.orderBy);
397
+ }
398
+
399
+ /**
400
+ * Orders the given object, `obj`, applying any (optional)
401
+ * key order specified via `orderBy`. If no `orderBy` is provided
402
+ * keys are ordered lexographically.
403
+ * @param {Object} obj Object to transform to ordered keys
404
+ * @param {string[]} [orderBy] Explicit key order to use.
405
+ * @returns {Object} Shallow clone of `obj` with ordered keys
406
+ *
407
+ * Adapted from @vue/cli under MIT License:
408
+ * https://github.com/vuejs/vue-cli/blob/f09722c/packages/%40vue/cli/lib/util/sortObject.js
409
+ */
410
+ toOrderedKeys(obj, orderBy) {
411
+ if (!obj) return;
412
+
413
+ // Create an optional order if necessary
414
+ let order;
415
+ if (Array.isArray(orderBy)) {
416
+ order = orderBy.reduce((acc, key, i) => {
417
+ acc[key] = i;
418
+ return acc;
419
+ }, {});
420
+ }
421
+
422
+ /*
423
+ * Sorts based on the `order` defined above.
424
+ */
425
+ function sortByOrder(a, b) {
426
+ const indexA = typeof order[a] === 'undefined'
427
+ ? Infinity
428
+ : order[a];
429
+
430
+ const indexB = typeof order[b] === 'undefined'
431
+ ? Infinity
432
+ : order[b];
433
+
434
+ return indexA - indexB;
435
+ }
436
+
437
+ const keys = order
438
+ ? Object.keys(obj).sort(sortByOrder)
439
+ : Object.keys(obj).sort();
440
+
441
+ return keys.reduce((acc, key) => {
442
+ acc[key] = obj[key];
443
+ return acc;
444
+ }, {});
445
+ }
446
+ }
447
+
448
+ module.exports = ConfigBuilder;
@@ -0,0 +1,193 @@
1
+ /* eslint-disable max-len, max-statements */
2
+ const fs = require('fs');
3
+ const path = require('path');
4
+ const { addPluginsToContext, readConfig } = require('../scaffold/utils');
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 {PluginDesc[]} rawPlugins - Raw plugin desc from options, prompts, etc. Can include constraints.
74
+ * @property {PluginName[]} plugins - Short names of plugins
75
+ * @property {String[]} pkgLinks - Local packages that should be linked
76
+ * @property {String[]} messages - non-error/warning messages to report
77
+ * @property {String[]} warnings - warnings messages to report
78
+ * @property {String[]} errors - error messages to report but do not exit process
79
+ * @property {String[]} nextSteps - any next steps to report for user
80
+ * @property {Set<String>} generatedFiles - any generated files to show in report
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} testPlugin - Name of the plugin for unit tests
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
+ * @property {PresetInfo[]} presetInfos - Shallow load of presets with meta data
96
+ *
97
+ * -- Added by `cli-version`
98
+ *
99
+ * @property {string} cliVersion - Version of current CLI used to issue `create` command
100
+ * @property {string} cliVersionRequired - Version of CLI to install, either current or min compatible version required by preset(s)
101
+ *
102
+ * -- Added by `setup-pkg`
103
+ *
104
+ * @property {ConfigBuilder} pkg - package.json builder
105
+ * @property {PackageManager} pkgManager - manager to execute npm or yarn commands
106
+ *
107
+ * -- Added by `setup-gasket-config`
108
+ *
109
+ * @property {ConfigBuilder} gasketConfig - gasket.config builder
110
+ *
111
+ * -- Added by `create-hooks`
112
+ *
113
+ * @property {Files} files - Use to add files and templates to generate
114
+ */
115
+ class CreateContext {
116
+ constructor(initContext = {}) {
117
+ Object.assign(this, initContext);
118
+ }
119
+
120
+ runWith(plugin) {
121
+ return makeCreateRuntime(this, plugin);
122
+ }
123
+ }
124
+
125
+ /**
126
+ * Makes the initial context used through the create command flow.
127
+ *
128
+ * @param {String[]} argv - Commands args
129
+ * @param {Object} options - Command options
130
+ * @returns {CreateContext} context
131
+ */
132
+ module.exports = function makeCreateContext(argv = [], options = {}) {
133
+ const appName = argv[0] || 'templated-app';
134
+ const {
135
+ plugins = [],
136
+ presets = [],
137
+ npmLink = [],
138
+ presetPath = [],
139
+ packageManager,
140
+ prompts,
141
+ config,
142
+ 'config-file': configFile
143
+ } = options;
144
+
145
+ // Flatten the array of array created by the plugins flag – it
146
+ // supports both multiple instances as well as comma-separated lists.
147
+ const rawPresets = presets.reduce(flatten, []);
148
+ const localPresets = presetPath.reduce(flatten, []);
149
+ const rawPlugins = plugins.reduce(flatten, []);
150
+ const pkgLinks = npmLink.reduce(flatten, []);
151
+ const cwd = process.cwd();
152
+ const dest = path.join(cwd, appName);
153
+ const relDest = `.${path.sep}${path.relative(cwd, dest)}`;
154
+ // eslint-disable-next-line no-sync
155
+ const extant = fs.existsSync(dest);
156
+
157
+ /**
158
+ * Input context which will be appended by prompts and passed to create hooks
159
+ *
160
+ * @type {CreateContext}
161
+ */
162
+ const context = new CreateContext({
163
+ destOverride: true,
164
+ cwd,
165
+ dest,
166
+ relDest,
167
+ extant,
168
+ pkgLinks,
169
+ localPresets,
170
+ rawPresets,
171
+ messages: [],
172
+ warnings: [],
173
+ errors: [],
174
+ nextSteps: [],
175
+ generatedFiles: new Set(),
176
+ prompts
177
+ });
178
+
179
+ readConfig(context, { config, configFile });
180
+
181
+ if (packageManager) {
182
+ context.packageManager = packageManager;
183
+ }
184
+ if (appName) {
185
+ context.appName = appName;
186
+ }
187
+
188
+ addPluginsToContext(rawPlugins, context);
189
+
190
+ return context;
191
+ };
192
+
193
+ module.exports.CreateContext = CreateContext;
@@ -0,0 +1,21 @@
1
+ const path = require('path');
2
+ const GasketEngine = require('@gasket/engine');
3
+ const defaultPlugins = require('../config/default-plugins');
4
+
5
+ module.exports = async function createEngine({ dest, presets = [], plugins = [] }) {
6
+ const resolveFrom = path.join(dest, 'node_modules');
7
+
8
+ const engineConfig = {
9
+ plugins: {
10
+ presets,
11
+ add: [...defaultPlugins, ...plugins]
12
+ },
13
+ root: dest
14
+ };
15
+
16
+ const engine = new GasketEngine(engineConfig, { resolveFrom });
17
+ engine.command = { id: 'create' };
18
+ await engine.exec('init');
19
+
20
+ return engine;
21
+ };
@@ -0,0 +1,28 @@
1
+ const path = require('path');
2
+ const fs = require('fs').promises;
3
+
4
+ /**
5
+ * If an error occurs during create, dump the context for debugging.
6
+ * The error which cause the exit is also included in the log.
7
+ *
8
+ * @param {CreateContext} context - Create context
9
+ * @param {Error} error - Exiting error
10
+ * @returns {Promise<void>} promise
11
+ */
12
+ module.exports = async function dumpErrorContext(context, error) {
13
+ const { cwd } = context;
14
+
15
+ try {
16
+ const report = { exitError: error.stack, ...context };
17
+ const fileName = 'gasket-create-error.log';
18
+
19
+ const filePath = path.join(cwd, fileName);
20
+ await fs.writeFile(filePath, JSON.stringify(report, null, 2), 'utf8');
21
+ console.log(`Error log dumped to: ${filePath}`);
22
+ } catch (err) {
23
+ //
24
+ // eat but print any errors during writing log file
25
+ //
26
+ console.error(`Error writing error log: ${err}`);
27
+ }
28
+ };