create-gasket-app 7.0.3-cli.0 → 7.0.4
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.
- package/README.md +223 -3
- package/lib/commands/create.js +149 -0
- package/lib/config/default-plugins.js +15 -0
- package/lib/index.d.ts +310 -0
- package/lib/index.js +24 -9
- package/lib/scaffold/action-wrapper.js +37 -0
- package/lib/scaffold/actions/create-hooks.js +27 -0
- package/lib/scaffold/actions/generate-files.js +189 -0
- package/lib/scaffold/actions/global-prompts.js +162 -0
- package/lib/scaffold/actions/index.js +33 -0
- package/lib/scaffold/actions/install-modules.js +15 -0
- package/lib/scaffold/actions/link-modules.js +19 -0
- package/lib/scaffold/actions/load-preset.js +60 -0
- package/lib/scaffold/actions/mkdir.js +30 -0
- package/lib/scaffold/actions/post-create-hooks.js +31 -0
- package/lib/scaffold/actions/preset-config-hooks.js +13 -0
- package/lib/scaffold/actions/preset-prompt-hooks.js +17 -0
- package/lib/scaffold/actions/print-report.js +89 -0
- package/lib/scaffold/actions/prompt-hooks.js +43 -0
- package/lib/scaffold/actions/setup-pkg.js +34 -0
- package/lib/scaffold/actions/write-gasket-config.js +119 -0
- package/lib/scaffold/actions/write-pkg.js +19 -0
- package/lib/scaffold/config-builder.js +526 -0
- package/lib/scaffold/create-context.js +188 -0
- package/lib/scaffold/dump-error-context.js +28 -0
- package/lib/scaffold/files.js +30 -0
- package/lib/scaffold/readme.js +51 -0
- package/lib/scaffold/utils.js +22 -0
- package/lib/utils/create-option.js +18 -0
- package/lib/utils/index.js +5 -0
- package/lib/utils/logo.js +12 -0
- package/lib/utils/process-args.js +37 -0
- package/lib/utils/process-command.js +50 -0
- package/lib/utils/process-options.js +44 -0
- package/package.json +36 -9
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import path from 'path';
|
|
2
|
+
import { writeFile } from 'fs/promises';
|
|
3
|
+
import action from '../action-wrapper.js';
|
|
4
|
+
|
|
5
|
+
/**
|
|
6
|
+
* Writes the contents of `pkg` to the app's package.json.
|
|
7
|
+
*
|
|
8
|
+
* @param {CreateContext} context - Create context
|
|
9
|
+
* @returns {Promise} promise
|
|
10
|
+
*/
|
|
11
|
+
async function writePkg({ context }) {
|
|
12
|
+
const { dest, pkg, generatedFiles } = context;
|
|
13
|
+
const fileName = 'package.json';
|
|
14
|
+
const filePath = path.join(dest, fileName);
|
|
15
|
+
await writeFile(filePath, JSON.stringify(pkg, null, 2), 'utf8');
|
|
16
|
+
generatedFiles.add(fileName);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export default action('Write package.json', writePkg);
|
|
@@ -0,0 +1,526 @@
|
|
|
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
|
+
// If a prerelease version, strip the range prefix
|
|
380
|
+
const version = ver.includes('-') ? ver.replace(/^[\^~]/, '') : ver;
|
|
381
|
+
|
|
382
|
+
const blameId = `${key}.${dep}`;
|
|
383
|
+
if (!prev) {
|
|
384
|
+
existing[dep] = version;
|
|
385
|
+
setBlame(blameId);
|
|
386
|
+
return;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
const blamed = this.blame.get(blameId);
|
|
390
|
+
const forced = this.force.has(blameId);
|
|
391
|
+
|
|
392
|
+
if (prev === version) {
|
|
393
|
+
if (forced) return;
|
|
394
|
+
if (force) {
|
|
395
|
+
setBlame(blameId);
|
|
396
|
+
} else {
|
|
397
|
+
blamed.push(name);
|
|
398
|
+
}
|
|
399
|
+
return;
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
const prevName = blamed.join(', ');
|
|
403
|
+
if (!forced) {
|
|
404
|
+
const newer = force ? version : this.tryGetNewerRange(prev, version);
|
|
405
|
+
const overridden = newer === version;
|
|
406
|
+
if (overridden) {
|
|
407
|
+
existing[dep] = version;
|
|
408
|
+
setBlame(blameId);
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
if (!semver.validRange(prev) || !semver.validRange(version) || !semver.intersects(prev, version)) {
|
|
413
|
+
let forceMsg = force ? '(forced)' : '';
|
|
414
|
+
forceMsg = forced && force ? '(cannot be forced)' : forceMsg;
|
|
415
|
+
this.warn(`
|
|
416
|
+
Conflicting versions for ${dep} in "${key}":
|
|
417
|
+
- ${prev} provided by ${prevName} ${forced && '(forced)' || ''}
|
|
418
|
+
- ${version} provided by ${name} ${forceMsg}
|
|
419
|
+
Using ${existing[dep]}, but this may cause unexpected behavior.
|
|
420
|
+
`);
|
|
421
|
+
}
|
|
422
|
+
});
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
/**
|
|
426
|
+
* Normalizes a potential semver range into a semver string
|
|
427
|
+
* and returns the newest version
|
|
428
|
+
* @param {String} r1 Semver string (potentially invalid).
|
|
429
|
+
* @param {String} r2 Semver string (potentially invalid).
|
|
430
|
+
* @returns {String|undefined} Newest semver version.
|
|
431
|
+
*
|
|
432
|
+
* Adapted from @vue/cli under MIT License:
|
|
433
|
+
* https://github.com/vuejs/vue-cli/blob/f09722c/packages/%40vue/cli/lib/util/mergeDeps.js#L58-L64
|
|
434
|
+
*/
|
|
435
|
+
tryGetNewerRange(r1, r2) {
|
|
436
|
+
if (r1 === 'latest' || r2 === 'latest') {
|
|
437
|
+
return r1 === 'latest' ? r1 : r2;
|
|
438
|
+
}
|
|
439
|
+
|
|
440
|
+
const v1 = this.rangeToVersion(r1);
|
|
441
|
+
const v2 = this.rangeToVersion(r2);
|
|
442
|
+
if (semver.valid(v1) && semver.valid(v2)) {
|
|
443
|
+
return semver.gt(v1, v2) ? r1 : r2;
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
/**
|
|
448
|
+
* Performs a naive attempt to take a transform a semver range
|
|
449
|
+
* into a concrete version that may be used for "newness"
|
|
450
|
+
* comparison.
|
|
451
|
+
*
|
|
452
|
+
* @param {string} range Valid "basic" semver: ^X.Y.Z, ~A.B.C, >=2.3.x, 1.x.x
|
|
453
|
+
* @returns {string} Concrete as possible version: X.Y.Z, A.B.C, 2.3.0, 1.0.0
|
|
454
|
+
*/
|
|
455
|
+
rangeToVersion(range) {
|
|
456
|
+
return range
|
|
457
|
+
.replace(semverPrefixes, '')
|
|
458
|
+
.replace(/x/g, '0')
|
|
459
|
+
.trim();
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
/**
|
|
463
|
+
* Orders top-level keys by `orderBy` options with any fields specified in
|
|
464
|
+
* the `orderFields` options having their keys sorted.
|
|
465
|
+
* @returns {Object} Ready to be serialized JavaScript object.
|
|
466
|
+
*/
|
|
467
|
+
toJSON() {
|
|
468
|
+
if (Array.isArray(this.orderedFields)) {
|
|
469
|
+
this.orderedFields.forEach(k => {
|
|
470
|
+
if (this.fields[k]) {
|
|
471
|
+
this.fields[k] = this.toOrderedKeys(this.fields[k]);
|
|
472
|
+
}
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
|
|
476
|
+
return this.toOrderedKeys(this.fields, this.orderBy);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
/**
|
|
480
|
+
* Orders the given object, `obj`, applying any (optional)
|
|
481
|
+
* key order specified via `orderBy`. If no `orderBy` is provided
|
|
482
|
+
* keys are ordered lexographically.
|
|
483
|
+
* @param {Object} obj Object to transform to ordered keys
|
|
484
|
+
* @param {string[]} [orderBy] Explicit key order to use.
|
|
485
|
+
* @returns {Object} Shallow clone of `obj` with ordered keys
|
|
486
|
+
*
|
|
487
|
+
* Adapted from @vue/cli under MIT License:
|
|
488
|
+
* https://github.com/vuejs/vue-cli/blob/f09722c/packages/%40vue/cli/lib/util/sortObject.js
|
|
489
|
+
*/
|
|
490
|
+
toOrderedKeys(obj, orderBy) {
|
|
491
|
+
if (!obj) return;
|
|
492
|
+
|
|
493
|
+
// Create an optional order if necessary
|
|
494
|
+
let order;
|
|
495
|
+
if (Array.isArray(orderBy)) {
|
|
496
|
+
order = orderBy.reduce((acc, key, i) => {
|
|
497
|
+
acc[key] = i;
|
|
498
|
+
return acc;
|
|
499
|
+
}, {});
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
/*
|
|
503
|
+
* Sorts based on the `order` defined above.
|
|
504
|
+
*/
|
|
505
|
+
function sortByOrder(a, b) {
|
|
506
|
+
const indexA = typeof order[a] === 'undefined'
|
|
507
|
+
? Infinity
|
|
508
|
+
: order[a];
|
|
509
|
+
|
|
510
|
+
const indexB = typeof order[b] === 'undefined'
|
|
511
|
+
? Infinity
|
|
512
|
+
: order[b];
|
|
513
|
+
|
|
514
|
+
return indexA - indexB;
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
const keys = order
|
|
518
|
+
? Object.keys(obj).sort(sortByOrder)
|
|
519
|
+
: Object.keys(obj).sort();
|
|
520
|
+
|
|
521
|
+
return keys.reduce((acc, key) => {
|
|
522
|
+
acc[key] = obj[key];
|
|
523
|
+
return acc;
|
|
524
|
+
}, {});
|
|
525
|
+
}
|
|
526
|
+
}
|