yeoman-environment 3.6.0 → 3.9.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.
package/cli/index.js CHANGED
@@ -1,8 +1,8 @@
1
1
  #!/usr/bin/env node
2
2
  const {YeomanCommand} = require('../lib/util/command');
3
- const {printGroupedGenerator, environmentAction} = require('./utils');
4
3
  const packageJson = require('../package.json');
5
4
  const Env = require('..');
5
+ const {printGroupedGenerator, environmentAction} = require('./utils');
6
6
 
7
7
  const program = new YeomanCommand();
8
8
 
package/cli/utils.js CHANGED
@@ -5,15 +5,15 @@ const Env = require('..');
5
5
 
6
6
  const printGroupedGenerator = (generators, env) => {
7
7
  const grouped = groupBy(generators, 'packagePath');
8
- Object.entries(grouped).forEach(([packagePath, group]) => {
8
+ for (const [packagePath, group] of Object.entries(grouped)) {
9
9
  const namespace = env.toNamespace(group[0].namespace);
10
10
  console.log(` ${namespace.packageNamespace} at ${packagePath}`);
11
- group.forEach(generator => {
11
+ for (const generator of group) {
12
12
  const generatorNamespace = env.toNamespace(generator.namespace);
13
13
  console.log(` :${generatorNamespace.generator || 'app'}`);
14
- });
14
+ }
15
15
  console.log('');
16
- });
16
+ }
17
17
  console.log(`${generators.length} generators`);
18
18
  };
19
19
 
package/lib/adapter.js CHANGED
@@ -1,4 +1,3 @@
1
- 'use strict';
2
1
  const inquirer = require('inquirer');
3
2
  const diff = require('diff');
4
3
  const chalk = require('chalk');
package/lib/command.js CHANGED
@@ -1,4 +1,3 @@
1
- 'use strict';
2
1
  const debug = require('debug')('yeoman:environment:command');
3
2
  const {Option} = require('commander');
4
3
 
@@ -22,7 +21,7 @@ module.exports = cls => class EnvironmentCommand extends cls {
22
21
  .option('--skip-yo-resolve', 'Ignore .yo-resolve files', false)
23
22
  /* Hidden options, used for api */
24
23
  .addOption(new Option('--skip-local-cache', 'Skip local answers cache').default(true).hideHelp())
25
- .addOption(new Option('--skip-parse-options', 'Skip legacy options parsing').default(true).hideHelp())
24
+ .addOption(new Option('--skip-parse-options', 'Skip legacy options parsing').default(false).hideHelp())
26
25
  .addOption(new Option('--experimental', 'Experimental features').default(false).hideHelp())
27
26
  .addOption(new Option('--log-cwd', 'Path for log purpose').hideHelp());
28
27
  }
@@ -1,4 +1,3 @@
1
- 'use strict';
2
1
  const assert = require('assert');
3
2
  const semver = require('semver');
4
3
  const pacote = require('pacote');
@@ -32,9 +31,9 @@ composability.installLocalGenerators = async function (packages) {
32
31
  return true;
33
32
  }
34
33
  const toInstall = {};
35
- entries.forEach(([packageName, version]) => {
34
+ for (const [packageName, version] of entries) {
36
35
  toInstall[packageName] = version;
37
- });
36
+ }
38
37
  if (await this.repository.installPackages(toInstall)) {
39
38
  const packagesToLookup = entries.map(([packageName, _]) => packageName);
40
39
  this.lookupLocalPackages(packagesToLookup);
@@ -1,7 +1,7 @@
1
- 'use strict';
2
1
  const fs = require('fs');
3
2
  const path = require('path');
4
3
  const EventEmitter = require('events');
4
+ const crypto = require('crypto');
5
5
  const chalk = require('chalk');
6
6
  const _ = require('lodash');
7
7
  const GroupedQueue = require('grouped-queue');
@@ -11,13 +11,11 @@ const memFs = require('mem-fs');
11
11
  const FileEditor = require('mem-fs-editor');
12
12
  const debug = require('debug')('yeoman:environment');
13
13
  const isScoped = require('is-scoped');
14
- const crypto = require('crypto');
15
14
  const npmlog = require('npmlog');
15
+ const semver = require('semver');
16
+ const slash = require('slash');
16
17
  const {TrackerGroup} = require('are-we-there-yet');
17
-
18
- const {promisify} = require('util');
19
- const {pipeline: _pipeline} = require('stream');
20
- const pipeline = promisify(_pipeline);
18
+ const {pipeline, transform} = require('p-transform');
21
19
 
22
20
  const ENVIRONMENT_VERSION = require('../package.json').version;
23
21
  const Store = require('./store');
@@ -31,7 +29,6 @@ const {
31
29
  createCommitTransform,
32
30
  createConflicterCheckTransform,
33
31
  createConflicterStatusTransform,
34
- createEachFileTransform,
35
32
  createModifiedTransform,
36
33
  createYoRcTransform,
37
34
  createYoResolveTransform
@@ -47,13 +44,13 @@ function splitArgsFromString(argsString) {
47
44
  return result;
48
45
  }
49
46
  const quoteSeparatedArgs = argsString.split(/("[^"]*")/).filter(x => x);
50
- quoteSeparatedArgs.forEach(arg => {
47
+ for (const arg of quoteSeparatedArgs) {
51
48
  if (arg.match('\x22')) {
52
49
  result.push(arg.replace(/"/g, ''));
53
50
  } else {
54
51
  result = result.concat(arg.trim().split(' '));
55
52
  }
56
- });
53
+ }
57
54
  return result;
58
55
  }
59
56
 
@@ -150,19 +147,22 @@ class Environment extends Base {
150
147
  * @param {Class} GeneratorClass - Generator to create Command
151
148
  * @return {Command} return command
152
149
  */
153
- static prepareGeneratorCommand(command, GeneratorClass) {
150
+ static prepareGeneratorCommand(command, GeneratorClass, namespace) {
154
151
  const generator = new GeneratorClass([], {help: true, env: {}});
155
152
  Base.addGeneratorOptions(command, generator);
156
153
 
157
154
  command.action(async function () {
158
- command.env = Environment.createEnv(this.opts());
159
-
160
155
  let rootCommand = this;
161
156
  while (rootCommand.parent) {
162
157
  rootCommand = rootCommand.parent;
163
158
  }
159
+ command.env = Environment.createEnv(rootCommand.opts());
160
+
164
161
  rootCommand.emit('yeoman:environment', command.env);
165
162
 
163
+ if (namespace) {
164
+ return command.env.run([namespace, ...(this.args || [])], this.opts()).then(() => command.env);
165
+ }
166
166
  const generator = command.env.instantiate(GeneratorClass, this.args, this.opts());
167
167
  await command.env.queueGenerator(generator);
168
168
  return command.env.start().then(() => command.env);
@@ -332,12 +332,12 @@ class Environment extends Base {
332
332
  this.repository = new YeomanRepository(this.options.yeomanRepository);
333
333
 
334
334
  if (!this.options.experimental) {
335
- process.argv.forEach(value => {
335
+ for (const value of process.argv) {
336
336
  if (value === '--experimental') {
337
337
  this.options.experimental = true;
338
338
  debug('Set environment as experimental');
339
339
  }
340
- });
340
+ }
341
341
  }
342
342
 
343
343
  this.loadSharedOptions(this.options);
@@ -351,6 +351,8 @@ class Environment extends Base {
351
351
 
352
352
  // Store the YeomanCompose by paths and uniqueBy feature.
353
353
  this._composeStore = {};
354
+
355
+ this.enableConflicterIgnore = semver.satisfies(this.getVersion('mem-fs-editor'), '>= 9.2.0');
354
356
  }
355
357
 
356
358
  /**
@@ -583,6 +585,9 @@ class Environment extends Base {
583
585
  getPackagePath(namespace) {
584
586
  if (namespace.includes(':')) {
585
587
  const generator = this.get(namespace) || {};
588
+ if (generator.then) {
589
+ return generator.then(generator => generator.packagePath);
590
+ }
586
591
  return generator.packagePath;
587
592
  }
588
593
  const packagePaths = this.getPackagePaths(namespace) || [];
@@ -750,7 +755,7 @@ class Environment extends Base {
750
755
  'You can see available generators via ' +
751
756
  chalk.yellow('npm search yeoman-generator') + ' or via ' + chalk.yellow('http://yeoman.io/generators/') + '. \n' +
752
757
  'Install them with ' + chalk.yellow(`npm install ${generatorHint}`) + '.\n\n' +
753
- 'To see all your installed generators run ' + chalk.yellow('yo') + ' without any arguments. ' +
758
+ 'To see all your installed generators run ' + chalk.yellow('yo --generators') + '. ' +
754
759
  'Adding the ' + chalk.yellow('--help') + ' option will also show subgenerators. \n\n' +
755
760
  'If ' + chalk.yellow('yo') + ' cannot find the generator, run ' + chalk.yellow('yo doctor') + ' to troubleshoot your system.'
756
761
  );
@@ -863,9 +868,7 @@ class Environment extends Base {
863
868
  getAllGenerators() {
864
869
  return Object.fromEntries([
865
870
  ...Object.entries(this._generators),
866
- ...Object.entries(this._generatorsForPath).map(([root, generatorStore]) => {
867
- return Object.entries(generatorStore).map(([namespace, generator]) => ([`${root}#${namespace}`, generator]));
868
- }).flat()
871
+ ...Object.entries(this._generatorsForPath).flatMap(([root, generatorStore]) => Object.entries(generatorStore).map(([namespace, generator]) => ([`${root}#${namespace}`, generator])))
869
872
  ]);
870
873
  }
871
874
 
@@ -1120,28 +1123,39 @@ class Environment extends Base {
1120
1123
  throw new Error('Missing namespace');
1121
1124
  }
1122
1125
 
1126
+ // Normalize path
1127
+ let ns = slash(filepath);
1128
+
1129
+ // Ignore path before latest node_modules
1130
+ const REPOSITORY_PATH = '/node_modules/';
1131
+ if (ns.includes(REPOSITORY_PATH)) {
1132
+ ns = ns.slice(ns.lastIndexOf(REPOSITORY_PATH) + REPOSITORY_PATH.length, ns.length);
1133
+ }
1134
+
1123
1135
  // Cleanup extension and normalize path for differents OS
1124
- let ns = path.normalize(filepath.replace(new RegExp(escapeStrRe(path.extname(filepath)) + '$'), ''));
1136
+ const parsed = path.parse(ns);
1137
+ ns = parsed.dir ? `${parsed.dir}/${parsed.name}` : parsed.name;
1125
1138
 
1126
1139
  // Sort lookups by length so biggest are removed first
1127
- const nsLookups = _(lookups.concat(['..'])).map(found => path.normalize(found)).sortBy('length').value().reverse();
1140
+ const nsLookups = _([...lookups, '..']).map(found => slash(found)).sortBy('length').value().reverse();
1128
1141
 
1129
1142
  // If `ns` contains a lookup dir in its path, remove it.
1130
- ns = nsLookups.reduce((ns, lookup) => {
1143
+ for (let lookup of nsLookups) {
1131
1144
  // Only match full directory (begin with leading slash or start of input, end with trailing slash)
1132
- lookup = new RegExp(`(?:\\\\|/|^)${escapeStrRe(lookup)}(?=\\\\|/)`, 'g');
1133
- return ns.replace(lookup, '');
1134
- }, ns);
1145
+ lookup = new RegExp(`(?:/|^)${escapeStrRe(lookup)}(?=/)`, 'g');
1146
+ ns = ns.replace(lookup, '');
1147
+ }
1135
1148
 
1136
- const folders = ns.split(path.sep);
1149
+ const folders = ns.split('/');
1137
1150
  const scope = _.findLast(folders, folder => folder.indexOf('@') === 0);
1138
1151
 
1139
1152
  // Cleanup `ns` from unwanted parts and then normalize slashes to `:`
1140
1153
  ns = ns
1154
+ .replace(/\/\//g, '') // Remove double `/`
1141
1155
  .replace(/(.*generator-)/, '') // Remove before `generator-`
1142
- .replace(/[/\\](index|main)$/, '') // Remove `/index` or `/main`
1143
- .replace(/^[/\\]+/, '') // Remove leading `/`
1144
- .replace(/[/\\]+/g, ':'); // Replace slashes by `:`
1156
+ .replace(/\/(index|main)$/, '') // Remove `/index` or `/main`
1157
+ .replace(/^\//, '') // Remove leading `/`
1158
+ .replace(/\/+/g, ':'); // Replace slashes by `:`
1145
1159
 
1146
1160
  if (scope) {
1147
1161
  ns = `${scope}/${ns}`;
@@ -1207,12 +1221,12 @@ class Environment extends Base {
1207
1221
  stream,
1208
1222
  createModifiedTransform(),
1209
1223
  ...transformStreams,
1210
- createEachFileTransform(file => {
1224
+ transform(file => {
1211
1225
  if (log) {
1212
1226
  log.completeWork(10);
1213
1227
  npmlog.info('Completed', path.relative(this.logCwd, file.path));
1214
1228
  }
1215
- }, {autoForward: false, logName: 'environment:log'})
1229
+ }, 'environment:log')
1216
1230
  ).then(() => {
1217
1231
  if (log) {
1218
1232
  log.finish();
@@ -1229,10 +1243,23 @@ class Environment extends Base {
1229
1243
  commitSharedFs(stream = this.sharedFs.stream()) {
1230
1244
  return new Promise((resolve, reject) => {
1231
1245
  debug('committing files');
1246
+
1247
+ const conflicterStatus = {};
1248
+ if (this.enableConflicterIgnore) {
1249
+ conflicterStatus.fileActions = [{
1250
+ key: 'i',
1251
+ name: 'ignore, do not overwrite and remember (experimental)',
1252
+ value: ({relativeFilePath}) => {
1253
+ this.fs.append(`${this.cwd}/.yo-resolve`, `${relativeFilePath} skip`, {create: true});
1254
+ return 'skip';
1255
+ }
1256
+ }];
1257
+ }
1258
+
1232
1259
  this.fs.commit([
1233
1260
  createYoResolveTransform(this.conflicter),
1234
1261
  createYoRcTransform(),
1235
- createConflicterCheckTransform(this.conflicter),
1262
+ createConflicterCheckTransform(this.conflicter, conflicterStatus),
1236
1263
  createConflicterStatusTransform(),
1237
1264
  // Use custom commit transform due to out of order transform.
1238
1265
  createCommitTransform(this.fs)
@@ -1244,10 +1271,7 @@ class Environment extends Base {
1244
1271
  reject(error);
1245
1272
  return;
1246
1273
  }
1247
-
1248
- // Force to empty Conflicter queue.
1249
- this.conflicter.queue.once('end', () => resolve(value));
1250
- this.conflicter.queue.run();
1274
+ resolve(value);
1251
1275
  });
1252
1276
  });
1253
1277
  }
@@ -1268,14 +1292,21 @@ class Environment extends Base {
1268
1292
  } else {
1269
1293
  customCommitTask = this.commitSharedFs.bind(this);
1270
1294
  }
1295
+
1296
+ if (this.enableConflicterIgnore) {
1297
+ debug('Adding queueCommit event listener');
1298
+ this.sharedFs.once('change', queueCommit);
1299
+ }
1271
1300
  const result = customCommitTask();
1272
1301
  if (!result || !result.then) {
1273
1302
  done();
1274
1303
  return;
1275
1304
  }
1276
1305
  return result.then(() => {
1277
- debug('Adding queueCommit event listener');
1278
- this.sharedFs.once('change', queueCommit);
1306
+ if (!this.enableConflicterIgnore) {
1307
+ debug('Adding queueCommit event listener');
1308
+ this.sharedFs.once('change', queueCommit);
1309
+ }
1279
1310
  done();
1280
1311
  }
1281
1312
  , stop);
@@ -1,4 +1,3 @@
1
- 'use strict';
2
1
  const debug = require('debug')('yeoman:environment:features');
3
2
 
4
3
  module.exports = cls => class extends cls {
@@ -1,4 +1,3 @@
1
- 'use strict';
2
1
  const debug = require('debug')('yeoman:environment:compose');
3
2
  const EventEmitter = require('events');
4
3
  const path = require('path');
@@ -39,9 +38,7 @@ composability.lookupLocalNamespaces = function (namespacesToLookup) {
39
38
  namespacesToLookup = Array.isArray(namespacesToLookup) ? namespacesToLookup : [namespacesToLookup];
40
39
  namespacesToLookup = namespacesToLookup.map(ns => this.requireNamespace(ns));
41
40
  // Keep only those packages that has a compatible version.
42
- namespacesToLookup = namespacesToLookup.filter(ns => {
43
- return this.repository.verifyInstalledVersion(ns.generatorHint, ns.semver) !== undefined;
44
- });
41
+ namespacesToLookup = namespacesToLookup.filter(ns => this.repository.verifyInstalledVersion(ns.generatorHint, ns.semver) !== undefined);
45
42
  return this.lookupLocalPackages(namespacesToLookup.map(ns => ns.generatorHint));
46
43
  };
47
44
 
@@ -66,18 +63,13 @@ composability.lookupNamespaces = function (namespaces, options = {}) {
66
63
  const genPath = ns.generator.split(':').join('/');
67
64
  let filePatterns = [`${genPath}/index.?s`, `${genPath}.?s`];
68
65
  const lookups = options.lookups || this.lookups;
69
- filePatterns = lookups.map(prefix => {
70
- return filePatterns.map(pattern => path.join(prefix, pattern));
71
- }).reduce(
72
- (accumulator, currentValue) => accumulator.concat(currentValue),
73
- []
74
- );
66
+ filePatterns = lookups.flatMap(prefix => filePatterns.map(pattern => path.join(prefix, pattern)));
75
67
  nsOptions.filePatterns = filePatterns;
76
68
  nsOptions.singleResult = true;
77
69
  }
78
70
  return nsOptions;
79
71
  });
80
- return options_.map(opt => this.lookup({...opt, ...options})).reduce((acc, cur) => acc.concat(cur), []);
72
+ return options_.flatMap(opt => this.lookup({...opt, ...options}));
81
73
  };
82
74
 
83
75
  /**
@@ -100,7 +92,7 @@ composability.prepareEnvironment = async function (namespaces) {
100
92
 
101
93
  const assertMissing = missing => {
102
94
  if (missing.length > 0) {
103
- throw new Error(`Error preparing environment for ${missing.map(ns => ns.complete).join()}`);
95
+ throw new Error(`Error preparing environment for ${missing.map(ns => ns.complete).join(',')}`);
104
96
  }
105
97
  };
106
98
 
@@ -143,7 +135,7 @@ composability.prepareEnvironment = async function (namespaces) {
143
135
  return true;
144
136
  }
145
137
  // At last, try to lookup if install failed.
146
- this.lookupLocalNamespaces(missing.concat(toLookup));
138
+ this.lookupLocalNamespaces([...missing, ...toLookup]);
147
139
 
148
140
  assertMissing(updateMissing());
149
141
  return true;
@@ -276,18 +268,14 @@ class YeomanCompose extends EventEmitter {
276
268
  * @return {Promise} Promise YeomanWith
277
269
  */
278
270
  async with(namespace, generatorOptions, ...methodArgs) {
279
- let namespaces = [].concat(namespace);
271
+ let namespaces = [namespace].flat();
280
272
  debug(`Compose with generator ${namespace} at ${this._destinationRoot}`);
281
273
  namespaces = namespaces.map(namespace => this.env.requireNamespace(namespace));
282
274
  const brokenNamespace = namespaces.find(namespace => !namespace.generator);
283
275
  if (brokenNamespace) {
284
276
  throw new Error(`Generator part in namespace is missing: ${brokenNamespace.id}`);
285
277
  }
286
- const namespacesId = namespaces.map(namespace => {
287
- return namespace.instanceId && namespace.instanceId === '*' ? this._getInstanceNames(namespace.namespace).map(instanceId => {
288
- return namespace.with({instanceId}).id;
289
- }) : [namespace.id];
290
- }).flat();
278
+ const namespacesId = namespaces.flatMap(namespace => namespace.instanceId && namespace.instanceId === '*' ? this._getInstanceNames(namespace.namespace).map(instanceId => namespace.with({instanceId}).id) : [namespace.id]);
291
279
 
292
280
  const composedGenerators = await Promise.all(namespacesId.map(nsId => this.require(nsId, generatorOptions)));
293
281
  const composedProxy = new Proxy(composedGenerators, multipleGeneratorProxyHandler);
package/lib/namespace.js CHANGED
@@ -1,5 +1,3 @@
1
- 'use strict';
2
-
3
1
  const YeomanNamespace = require('./util/namespace');
4
2
 
5
3
  module.exports = cls => class extends cls {
@@ -1,4 +1,3 @@
1
- 'use strict';
2
1
  const path = require('path');
3
2
  const debug = require('debug')('yeoman:environment:package-manager');
4
3
  const preferredPm = require('preferred-pm');
package/lib/resolver.js CHANGED
@@ -1,7 +1,6 @@
1
- 'use strict';
2
- const arrify = require('arrify');
3
1
  const path = require('path');
4
2
  const fs = require('fs');
3
+ const arrify = require('arrify');
5
4
  const _ = require('lodash');
6
5
  const globby = require('globby');
7
6
  const debug = require('debug')('yeoman:environment');
@@ -324,8 +323,7 @@ packageLookup._getGlobalNpmPaths = function (filterPaths = true) {
324
323
  } else if (win32) {
325
324
  paths.push(path.join(process.env.APPDATA, 'npm/node_modules'));
326
325
  } else {
327
- paths.push('/usr/lib/node_modules');
328
- paths.push('/usr/local/lib/node_modules');
326
+ paths.push('/usr/lib/node_modules', '/usr/local/lib/node_modules');
329
327
  }
330
328
 
331
329
  // Add NVM prefix directory
@@ -406,7 +404,7 @@ resolver.alias = function (match, value) {
406
404
  return this;
407
405
  }
408
406
 
409
- const aliases = this.aliases.slice(0).reverse();
407
+ const aliases = [...this.aliases].reverse();
410
408
 
411
409
  return aliases.reduce((resolved, alias) => {
412
410
  if (!alias.match.test(resolved)) {
@@ -1,4 +1,3 @@
1
- 'use strict';
2
1
  const spawn = require('execa');
3
2
 
4
3
  /**
package/lib/store.js CHANGED
@@ -1,5 +1,3 @@
1
- 'use strict';
2
-
3
1
  const debug = require('debug')('yeoman:environment:store');
4
2
 
5
3
  const {requireOrImport} = require('./util/esm');
@@ -76,9 +74,7 @@ class Store {
76
74
  }
77
75
 
78
76
  if (maybeGenerator.then) {
79
- return maybeGenerator.then(esmGenerator => {
80
- return [esmGenerator, {...this._meta[namespace]}];
81
- });
77
+ return maybeGenerator.then(esmGenerator => [esmGenerator, {...this._meta[namespace]}]);
82
78
  }
83
79
 
84
80
  Object.assign(maybeGenerator, this._meta[namespace]);
@@ -1,8 +1,7 @@
1
- 'use strict';
2
- const Table = require('cli-table');
3
1
  const fs = require('fs');
4
- const dateFormat = require('dateformat');
5
2
  const path = require('path');
3
+ const Table = require('cli-table');
4
+ const dateFormat = require('dateformat');
6
5
  const prettyBytes = require('pretty-bytes');
7
6
 
8
7
  const {isBinaryFileSync} = require('isbinaryfile');
@@ -54,9 +54,9 @@ class YeomanCommand extends Command {
54
54
  */
55
55
  addGeneratorOptions(options) {
56
56
  options = options || {};
57
- Object.entries(options).forEach(([key, value]) => {
57
+ for (const [key, value] of Object.entries(options)) {
58
58
  this._addGeneratorOption(key, value);
59
- });
59
+ }
60
60
  return this;
61
61
  }
62
62
 
@@ -1,4 +1,3 @@
1
- 'use strict';
2
1
  const fs = require('fs');
3
2
  const path = require('path');
4
3
  const jsdiff = require('diff');
@@ -174,10 +173,10 @@ class Conflicter {
174
173
  * 4. If diverged, prepare and show up the file collision menu
175
174
  *
176
175
  * @param {Object} file - Vinyl file
177
- * @return {Promise} Promise a status string ('identical', 'create',
178
- * 'skip', 'force')
176
+ * @param {Object} [conflicterStatus] - Conflicter status
177
+ * @return {Promise<Vinyl>} Promise the Vinyl file
179
178
  */
180
- checkForCollision(file) {
179
+ checkForCollision(file, conflicterStatus) {
181
180
  const rfilepath = path.relative(this.cwd, file.path);
182
181
  if (file.conflicter) {
183
182
  this._log(file.conflicter, rfilepath);
@@ -195,8 +194,9 @@ class Conflicter {
195
194
  file.conflicterLog = 'create';
196
195
  return Promise.resolve(file);
197
196
  }
197
+ const isForce = () => this.force || (conflicterStatus && conflicterStatus.force);
198
198
 
199
- if (this.force) {
199
+ if (isForce()) {
200
200
  this._log('force', rfilepath);
201
201
  file.conflicter = 'force';
202
202
  return Promise.resolve(file);
@@ -222,7 +222,7 @@ class Conflicter {
222
222
 
223
223
  return new Promise((resolve, reject) => {
224
224
  this.queue.add('conflicts', next => {
225
- if (this.force) {
225
+ if (isForce()) {
226
226
  file.conflicter = 'force';
227
227
  this.adapter.log.force(rfilepath);
228
228
  resolve(file);
@@ -230,7 +230,7 @@ class Conflicter {
230
230
  return;
231
231
  }
232
232
  this.adapter.log.conflict(rfilepath);
233
- return this._ask(file, 1).then(action => {
233
+ return this._ask(file, 1, conflicterStatus).then(action => {
234
234
  this.adapter.log[action || 'force'](rfilepath);
235
235
  file.conflicter = action;
236
236
  resolve(file);
@@ -257,7 +257,7 @@ class Conflicter {
257
257
  * @param {Object} file vinyl file object
258
258
  * @param {Number} counter prompts
259
259
  */
260
- _ask(file, counter) {
260
+ _ask(file, counter, conflicterStatus) {
261
261
  if (file.conflicter) {
262
262
  return Promise.resolve(file.conflicter);
263
263
  }
@@ -266,6 +266,7 @@ class Conflicter {
266
266
  name: 'action',
267
267
  type: 'expand',
268
268
  message: `Overwrite ${rfilepath}?`,
269
+ pageSize: 20,
269
270
  choices: [
270
271
  {
271
272
  key: 'y',
@@ -309,9 +310,16 @@ class Conflicter {
309
310
  value: 'edit'
310
311
  }
311
312
  );
313
+ if (conflicterStatus && conflicterStatus.fileActions) {
314
+ prompt.choices.push(...conflicterStatus.fileActions);
315
+ }
312
316
  }
313
317
 
314
318
  return this.adapter.prompt([prompt]).then(result => {
319
+ if (typeof result.action === 'function') {
320
+ return result.action.call(this, {file, relativeFilePath: rfilepath});
321
+ }
322
+
315
323
  if (result.action === 'abort') {
316
324
  this.adapter.log.writeln('Aborting ...');
317
325
  throw AbortedError.create('Process aborted by user');
@@ -325,11 +333,15 @@ class Conflicter {
325
333
  throw new Error(`Recursive error ${prompt.message}`);
326
334
  }
327
335
 
328
- return this._ask(file, counter);
336
+ return this._ask(file, counter, conflicterStatus);
329
337
  }
330
338
 
331
339
  if (result.action === 'force') {
332
- this.force = true;
340
+ if (conflicterStatus) {
341
+ conflicterStatus.force = true;
342
+ } else {
343
+ this.force = true;
344
+ }
333
345
  }
334
346
 
335
347
  if (result.action === 'write') {
@@ -338,7 +350,7 @@ class Conflicter {
338
350
 
339
351
  if (result.action === 'reload') {
340
352
  if (this._detectConflict(file)) {
341
- return this._ask(file, counter);
353
+ return this._ask(file, counter, conflicterStatus);
342
354
  }
343
355
  return 'identical';
344
356
  }
@@ -353,7 +365,7 @@ class Conflicter {
353
365
  }]).then(answers => {
354
366
  file.contents = Buffer.from(answers.content || '', 'utf8');
355
367
  if (this._detectConflict(file)) {
356
- return this._ask(file, counter);
368
+ return this._ask(file, counter, conflicterStatus);
357
369
  }
358
370
  return 'skip';
359
371
  });
package/lib/util/log.js CHANGED
@@ -1,5 +1,4 @@
1
1
  /** @module env/log */
2
- 'use strict';
3
2
  const util = require('util');
4
3
  const EventEmitter = require('events');
5
4
  const _ = require('lodash');
@@ -14,22 +14,18 @@ module.exports = class YeomanNamespace {
14
14
  this.generator = parsed.generator;
15
15
  this.instanceId = parsed.instanceId;
16
16
  this.semver = parsed.semver;
17
- if (parsed.method) {
18
- this.methods = parsed.method.split('+');
19
- } else {
20
- this.methods = parsed.methods;
21
- }
17
+ this.methods = parsed.method ? parsed.method.split('+') : parsed.methods;
22
18
  this.flags = parsed.flags;
23
19
 
24
20
  // Populate flags
25
21
  if (this.flags) {
26
- Object.entries(flags).forEach(([name, value]) => {
22
+ for (const [name, value] of Object.entries(flags)) {
27
23
  if (this.flags === value) {
28
24
  this[name] = true;
29
25
  } else {
30
26
  delete this[name];
31
27
  }
32
- });
28
+ }
33
29
  }
34
30
 
35
31
  debug('Parsed namespace %o', this);
@@ -44,11 +40,11 @@ module.exports = class YeomanNamespace {
44
40
 
45
41
  const parsed = {complete};
46
42
  // Populate fields
47
- Object.entries(groups).forEach(([name, value]) => {
43
+ for (const [name, value] of Object.entries(groups)) {
48
44
  if (result[value]) {
49
45
  parsed[name] = result[value];
50
46
  }
51
- });
47
+ }
52
48
  return parsed;
53
49
  }
54
50
 
@@ -1,10 +1,11 @@
1
+ const {Transform} = require('stream');
1
2
  const debug = require('debug');
2
3
  const {default: PQueue} = require('p-queue');
3
- const {Transform} = require('stream');
4
4
 
5
5
  module.exports = class OOOTransform extends Transform {
6
6
  /**
7
7
  * @private
8
+ * @deprecated
8
9
  * Out Of Order Transform
9
10
  */
10
11
  constructor(options) {
@@ -1,5 +1,4 @@
1
1
  /** @module env/repository */
2
- 'use strict';
3
2
  const fs = require('fs');
4
3
  const path = require('path');
5
4
  const semver = require('semver');
@@ -92,9 +91,9 @@ class YeomanRepository {
92
91
  throw new Error(`Package ${packageName} already loaded`);
93
92
  }
94
93
 
95
- toCleanup.forEach(cache => {
94
+ for (const cache of toCleanup) {
96
95
  delete require.cache[cache];
97
- });
96
+ }
98
97
  }
99
98
 
100
99
  /**
@@ -177,14 +176,14 @@ class YeomanRepository {
177
176
  try {
178
177
  await this.reifyRepository({add: [...packagesArgs]});
179
178
  } catch (error) {
180
- Object.keys(packages).forEach(packageName => {
179
+ for (const packageName of Object.keys(packages)) {
181
180
  this.log.error(`${packageName} cannot be installed. ${error}`);
182
- });
181
+ }
183
182
  return false;
184
183
  }
185
- Object.keys(packages).forEach(packageName => {
184
+ for (const packageName of Object.keys(packages)) {
186
185
  this.log.ok(`${packageName} installed.`);
187
- });
186
+ }
188
187
  return true;
189
188
  }
190
189
 
@@ -1,15 +1,18 @@
1
- 'use strict';
2
-
3
- const {WError} = require('error');
4
1
  const fs = require('fs');
5
2
  const path = require('path');
3
+ const {WError} = require('error');
6
4
  const findUp = require('find-up');
7
5
  const minimatch = require('minimatch');
6
+ const {transform, passthrough, filter} = require('p-transform');
8
7
  const OOOTransform = require('./out-of-order-transform');
9
8
 
9
+ const {Minimatch} = minimatch;
10
+
10
11
  class YoResolveError extends WError {}
11
12
 
12
13
  /**
14
+ * @deprecated
15
+ *
13
16
  * Transform api should be avoided by generators without a executable.
14
17
  * May break between major yo versions.
15
18
  *
@@ -46,6 +49,8 @@ function fileIsModified(file) {
46
49
  }
47
50
 
48
51
  /**
52
+ * @deprecated
53
+ *
49
54
  * Transform api should be avoided by generators without a executable.
50
55
  * May break between major yo versions.
51
56
  *
@@ -103,8 +108,8 @@ function parseYoAttributesFile(yoAttributeFileName) {
103
108
  * Transform api should be avoided by generators without a executable.
104
109
  * May break between major yo versions.
105
110
  */
106
- function createConflicterCheckTransform(conflicter) {
107
- return createEachFileTransform(file => conflicter.checkForCollision(file), {logName: 'environment:conflicter-check'});
111
+ function createConflicterCheckTransform(conflicter, conflicterStatus) {
112
+ return passthrough(file => conflicter.checkForCollision(file, conflicterStatus), 'environment:conflicter-check');
108
113
  }
109
114
 
110
115
  /**
@@ -122,23 +127,21 @@ function getConflicterStatusForFile(conflicter, filePath, yoAttributeFileName =
122
127
 
123
128
  let fileStatus;
124
129
  if (yoResolveFiles) {
125
- yoResolveFiles.forEach(yoResolveFile => {
130
+ for (const yoResolveFile of yoResolveFiles) {
126
131
  if (conflicter.yoResolveByFile[yoResolveFile] === undefined) {
127
132
  conflicter.yoResolveByFile[yoResolveFile] = parseYoAttributesFile(yoResolveFile);
128
133
  }
129
- });
134
+ }
130
135
  yoResolveFiles
131
136
  .map(yoResolveFile => conflicter.yoResolveByFile[yoResolveFile])
132
137
  .map(attributes => attributes)
133
- .find(yoResolve => {
134
- return Object.entries(yoResolve).some(([pattern, status]) => {
135
- if (minimatch(filePath, pattern)) {
136
- fileStatus = status;
137
- return true;
138
- }
139
- return false;
140
- });
141
- });
138
+ .find(yoResolve => Object.entries(yoResolve).some(([pattern, status]) => {
139
+ if (minimatch(filePath, pattern)) {
140
+ fileStatus = status;
141
+ return true;
142
+ }
143
+ return false;
144
+ }));
142
145
  }
143
146
  return fileStatus;
144
147
  }
@@ -154,9 +157,9 @@ function getConflicterStatusForFile(conflicter, filePath, yoAttributeFileName =
154
157
  * @return {Transform} A Transform https://nodejs.org/api/stream.html#stream_class_stream_transform
155
158
  */
156
159
  function createYoResolveTransform(conflicter, yoResolveFileName) {
157
- return createEachFileTransform(file => {
160
+ return passthrough(file => {
158
161
  file.conflicter = file.conflicter || getConflicterStatusForFile(conflicter, file.path, yoResolveFileName);
159
- }, {logName: 'environment:yo-resolve'});
162
+ }, 'environment:yo-resolve');
160
163
  }
161
164
 
162
165
  /**
@@ -167,13 +170,10 @@ function createYoResolveTransform(conflicter, yoResolveFileName) {
167
170
  * @return {Transform} A Transform https://nodejs.org/api/stream.html#stream_class_stream_transform
168
171
  */
169
172
  function createYoRcTransform() {
170
- return createEachFileTransform(file => {
171
- const filename = path.basename(file.path);
172
- // Config file should not be processed by the conflicter. Force override.
173
- if (filename === '.yo-rc.json' || filename === '.yo-rc-global.json') {
174
- file.conflicter = 'force';
175
- }
176
- }, {logName: 'environment:yo-rc'});
173
+ // Config files should not be processed by the conflicter. Force override.
174
+ return patternSpy(file => {
175
+ file.conflicter = 'force';
176
+ }, '**/{.yo-rc.json,.yo-resolve,.yo-rc-global.json}', 'environment:yo-rc');
177
177
  }
178
178
 
179
179
  /**
@@ -185,7 +185,7 @@ function createYoRcTransform() {
185
185
  * @return {Transform} A Transform https://nodejs.org/api/stream.html#stream_class_stream_transform
186
186
  */
187
187
  function createConflicterStatusTransform() {
188
- return createFileTransform(function (file) {
188
+ return transform(file => {
189
189
  const action = file.conflicter;
190
190
 
191
191
  delete file.conflicter;
@@ -194,17 +194,16 @@ function createConflicterStatusTransform() {
194
194
  delete file.conflicterLog;
195
195
 
196
196
  if (!action && file.state) {
197
- this.push(file);
198
- return;
197
+ return file;
199
198
  }
200
199
 
201
200
  if (action === 'skip') {
202
201
  delete file.state;
203
202
  delete file.isNew;
204
- } else {
205
- this.push(file);
203
+ return undefined;
206
204
  }
207
- }, {logName: 'environment:conflicter-status'});
205
+ return file;
206
+ }, 'environment:conflicter-status');
208
207
  }
209
208
 
210
209
  /**
@@ -212,7 +211,7 @@ function createConflicterStatusTransform() {
212
211
  * May break between major yo versions.
213
212
  */
214
213
  function createModifiedTransform() {
215
- return createEachFileTransform({forwardUmodified: false, logName: 'environment:modified'});
214
+ return filter(file => fileIsModified(file), 'environment:modified');
216
215
  }
217
216
 
218
217
  /**
@@ -220,7 +219,30 @@ function createModifiedTransform() {
220
219
  * May break between major yo versions.
221
220
  */
222
221
  function createCommitTransform(memFsEditor) {
223
- return createFileTransform(file => memFsEditor.commitFileAsync(file), {logName: 'environment:commit'});
222
+ return transform(file => memFsEditor.commitFileAsync(file), 'environment:commit');
223
+ }
224
+
225
+ /**
226
+ * Conditional filter on pattern.
227
+ *
228
+ * @param {String} pattern - Minimatch pattern.
229
+ * @param {Object} options - Minimatch options.
230
+ */
231
+ function patternFilter(pattern, options) {
232
+ const minimatch = new Minimatch(pattern, options);
233
+ return filter(file => minimatch.match(file.path));
234
+ }
235
+
236
+ /**
237
+ * Conditional spy on pattern.
238
+ *
239
+ * @param {Function} spy.
240
+ * @param {String} pattern - Minimatch pattern.
241
+ * @param {Object} options - Minimatch options.
242
+ */
243
+ function patternSpy(spy, pattern, options) {
244
+ const minimatch = new Minimatch(pattern, options);
245
+ return passthrough(file => (minimatch.match(file.path) ? spy(file) : undefined));
224
246
  }
225
247
 
226
248
  module.exports = {
@@ -233,5 +255,7 @@ module.exports = {
233
255
  createModifiedTransform,
234
256
  createCommitTransform,
235
257
  createConflicterCheckTransform,
236
- createConflicterStatusTransform
258
+ createConflicterStatusTransform,
259
+ patternFilter,
260
+ patternSpy
237
261
  };
package/lib/util/util.js CHANGED
@@ -1,5 +1,4 @@
1
1
  /** @module env/util */
2
- 'use strict';
3
2
  const execa = require('execa');
4
3
  const GroupedQueue = require('grouped-queue');
5
4
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yeoman-environment",
3
- "version": "3.6.0",
3
+ "version": "3.9.0",
4
4
  "description": "Handles the lifecyle and bootstrapping of generators in a specific environment",
5
5
  "homepage": "http://yeoman.io",
6
6
  "author": "Yeoman",
@@ -12,6 +12,17 @@
12
12
  "lib"
13
13
  ],
14
14
  "main": "lib/environment.js",
15
+ "exports": {
16
+ ".": "./lib/environment.js",
17
+ "./cli/": "./cli/",
18
+ "./lib/": "./lib/",
19
+ "./lib/util/": "./lib/util/",
20
+ "./adapter": "./lib/adapter.js",
21
+ "./conflicter": "./lib/util/conflicter.js",
22
+ "./log": "./lib/util/log.js",
23
+ "./transform": "./lib/util/transform.js",
24
+ "./package.json": "./package.json"
25
+ },
15
26
  "keywords": [
16
27
  "development",
17
28
  "dev",
@@ -50,8 +61,8 @@
50
61
  "coverage": "nyc report --reporter=text-lcov | coveralls"
51
62
  },
52
63
  "dependencies": {
53
- "@npmcli/arborist": "^2.2.2",
54
- "are-we-there-yet": "^1.1.5",
64
+ "@npmcli/arborist": "^4.0.4",
65
+ "are-we-there-yet": "^2.0.0",
55
66
  "arrify": "^2.0.1",
56
67
  "binaryextensions": "^4.15.0",
57
68
  "chalk": "^4.1.0",
@@ -73,9 +84,10 @@
73
84
  "mem-fs": "^1.2.0 || ^2.0.0",
74
85
  "mem-fs-editor": "^8.1.2 || ^9.0.0",
75
86
  "minimatch": "^3.0.4",
76
- "npmlog": "^4.1.2",
87
+ "npmlog": "^5.0.1",
77
88
  "p-queue": "^6.6.2",
78
- "pacote": "^11.2.6",
89
+ "p-transform": "^1.3.0",
90
+ "pacote": "^12.0.2",
79
91
  "preferred-pm": "^3.0.3",
80
92
  "pretty-bytes": "^5.3.0",
81
93
  "semver": "^7.1.3",
@@ -92,19 +104,19 @@
92
104
  "devDependencies": {
93
105
  "coveralls": "^3.0.2",
94
106
  "cross-spawn": "^7.0.1",
95
- "fs-extra": "^9.0.1",
107
+ "fs-extra": "^10.0.0",
96
108
  "jsdoc": "^3.6.6",
97
- "mocha": "^8.0.1",
109
+ "mocha": "^9.1.3",
98
110
  "nyc": "^15.0.0",
99
111
  "prettier": "^2.2.1",
100
112
  "proxyquire": "^2.1.3",
101
- "sinon": "^9.0.2",
102
- "sinon-test": "^3.0.0",
113
+ "sinon": "^11.1.2",
114
+ "sinon-test": "^3.1.1",
103
115
  "tui-jsdoc-template": "^1.2.2",
104
- "xo": "^0.37.1",
116
+ "xo": "0.37.1",
105
117
  "yeoman-assert": "^3.1.1",
106
118
  "yeoman-generator": "^5.1.0",
107
- "yeoman-test": "^5.1.0"
119
+ "yeoman-test": "^6.2.0"
108
120
  },
109
121
  "xo": {
110
122
  "space": true,
@@ -114,6 +126,7 @@
114
126
  ],
115
127
  "rules": {
116
128
  "import/no-dynamic-require": "off",
129
+ "import/extensions": "off",
117
130
  "prefer-spread": "off",
118
131
  "padding-line-between-statements": "off",
119
132
  "unicorn/no-hex-escape": "off",