yeoman-environment 4.0.0-alpha.4 → 4.0.0-alpha.6

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 (86) hide show
  1. package/bin/bin.cjs +4 -0
  2. package/dist/cli/index.d.ts +0 -1
  3. package/dist/cli/index.js +18 -15
  4. package/dist/cli/utils.js +6 -7
  5. package/dist/commands.d.ts +23 -0
  6. package/dist/commands.js +45 -0
  7. package/dist/commit.d.ts +17 -0
  8. package/dist/commit.js +22 -0
  9. package/dist/composed-store.d.ts +26 -0
  10. package/dist/composed-store.js +68 -0
  11. package/dist/constants.d.ts +3 -0
  12. package/dist/constants.js +17 -0
  13. package/dist/environment-base.d.ts +259 -0
  14. package/dist/environment-base.js +638 -0
  15. package/dist/environment-full.d.ts +115 -0
  16. package/dist/environment-full.js +321 -0
  17. package/dist/generator-lookup.d.ts +56 -0
  18. package/dist/generator-lookup.js +97 -0
  19. package/dist/index.d.ts +10 -4
  20. package/dist/index.js +9 -5
  21. package/dist/module-lookup.d.ts +59 -0
  22. package/dist/module-lookup.js +227 -0
  23. package/dist/package-manager.d.ts +17 -31
  24. package/dist/package-manager.js +52 -69
  25. package/dist/store.d.ts +19 -19
  26. package/dist/store.js +76 -47
  27. package/dist/util/command.d.ts +17 -14
  28. package/dist/util/command.js +32 -17
  29. package/dist/util/namespace.d.ts +19 -73
  30. package/dist/util/namespace.js +56 -190
  31. package/dist/util/resolve.d.ts +6 -0
  32. package/dist/util/resolve.js +40 -0
  33. package/dist/util/util.d.ts +5 -12
  34. package/dist/util/util.js +24 -23
  35. package/package.json +20 -33
  36. package/readme.md +2 -3
  37. package/dist/adapter.d.ts +0 -54
  38. package/dist/adapter.js +0 -97
  39. package/dist/adapter.js.map +0 -1
  40. package/dist/cli/index.js.map +0 -1
  41. package/dist/cli/utils.js.map +0 -1
  42. package/dist/command.d.ts +0 -24
  43. package/dist/command.js +0 -74
  44. package/dist/command.js.map +0 -1
  45. package/dist/composability.d.ts +0 -25
  46. package/dist/composability.js +0 -78
  47. package/dist/composability.js.map +0 -1
  48. package/dist/environment.d.ts +0 -409
  49. package/dist/environment.js +0 -1221
  50. package/dist/environment.js.map +0 -1
  51. package/dist/generator-features.d.ts +0 -39
  52. package/dist/generator-features.js +0 -69
  53. package/dist/generator-features.js.map +0 -1
  54. package/dist/index.js.map +0 -1
  55. package/dist/namespace-composability.d.ts +0 -36
  56. package/dist/namespace-composability.js +0 -340
  57. package/dist/namespace-composability.js.map +0 -1
  58. package/dist/package-manager.js.map +0 -1
  59. package/dist/resolver.d.ts +0 -146
  60. package/dist/resolver.js +0 -421
  61. package/dist/resolver.js.map +0 -1
  62. package/dist/spawn-command.d.ts +0 -21
  63. package/dist/spawn-command.js +0 -30
  64. package/dist/spawn-command.js.map +0 -1
  65. package/dist/store.js.map +0 -1
  66. package/dist/util/binary-diff.d.ts +0 -2
  67. package/dist/util/binary-diff.js +0 -36
  68. package/dist/util/binary-diff.js.map +0 -1
  69. package/dist/util/command.js.map +0 -1
  70. package/dist/util/conflicter.d.ts +0 -76
  71. package/dist/util/conflicter.js +0 -346
  72. package/dist/util/conflicter.js.map +0 -1
  73. package/dist/util/esm.d.ts +0 -1
  74. package/dist/util/esm.js +0 -22
  75. package/dist/util/esm.js.map +0 -1
  76. package/dist/util/log.d.ts +0 -12
  77. package/dist/util/log.js +0 -165
  78. package/dist/util/log.js.map +0 -1
  79. package/dist/util/namespace.js.map +0 -1
  80. package/dist/util/repository.d.ts +0 -116
  81. package/dist/util/repository.js +0 -223
  82. package/dist/util/repository.js.map +0 -1
  83. package/dist/util/transform.d.ts +0 -55
  84. package/dist/util/transform.js +0 -149
  85. package/dist/util/transform.js.map +0 -1
  86. package/dist/util/util.js.map +0 -1
@@ -0,0 +1,638 @@
1
+ import EventEmitter from 'node:events';
2
+ import { createRequire } from 'node:module';
3
+ import { basename, isAbsolute, join, relative, resolve } from 'node:path';
4
+ import process from 'node:process';
5
+ import { realpathSync } from 'node:fs';
6
+ import { QueuedAdapter } from '@yeoman/adapter';
7
+ import { create as createMemFs } from 'mem-fs';
8
+ import { create as createMemFsEditor } from 'mem-fs-editor';
9
+ import { FlyRepository } from 'fly-import';
10
+ import createdLogger from 'debug';
11
+ // @ts-expect-error grouped-queue don't have types
12
+ import GroupedQueue from 'grouped-queue';
13
+ // eslint-disable-next-line n/file-extension-in-import
14
+ import { isFilePending } from 'mem-fs-editor/state';
15
+ import { passthrough, pipeline } from '@yeoman/transform';
16
+ import { toNamespace } from '@yeoman/namespace';
17
+ import chalk from 'chalk';
18
+ import { defaults, pick } from 'lodash-es';
19
+ import { ComposedStore } from './composed-store.js';
20
+ import Store from './store.js';
21
+ import { asNamespace, defaultLookups } from './util/namespace.js';
22
+ import { lookupGenerators } from './generator-lookup.js';
23
+ import { UNKNOWN_NAMESPACE, UNKNOWN_RESOLVED, defaultQueues } from './constants.js';
24
+ import { resolveModulePath } from './util/resolve.js';
25
+ import { commitSharedFsTask } from './commit.js';
26
+ import { packageManagerInstallTask } from './package-manager.js';
27
+ // eslint-disable-next-line import/order
28
+ import { splitArgsFromString } from './util/util.js';
29
+ const require = createRequire(import.meta.url);
30
+ // eslint-disable-next-line @typescript-eslint/naming-convention
31
+ const ENVIRONMENT_VERSION = require('../package.json').version;
32
+ const debug = createdLogger('yeoman:environment');
33
+ const getInstantiateOptions = (args, options) => {
34
+ if (Array.isArray(args) || typeof args === 'string') {
35
+ return { generatorArgs: splitArgsFromString(args), generatorOptions: options };
36
+ }
37
+ if (args !== undefined) {
38
+ if ('generatorOptions' in args || 'generatorArgs' in args) {
39
+ return args;
40
+ }
41
+ if ('options' in args || 'arguments' in args || 'args' in args) {
42
+ const { args: insideArgs, arguments: generatorArgs = insideArgs, options: generatorOptions, ...remainingOptions } = args;
43
+ return { generatorArgs: splitArgsFromString(generatorArgs), generatorOptions: generatorOptions ?? remainingOptions };
44
+ }
45
+ }
46
+ return { generatorOptions: options };
47
+ };
48
+ const getComposeOptions = (...varargs) => {
49
+ if (varargs.filter(Boolean).length === 0)
50
+ return {};
51
+ const [args, options, composeOptions] = varargs;
52
+ if (typeof args === 'boolean') {
53
+ return { schedule: args };
54
+ }
55
+ let generatorArgs;
56
+ let generatorOptions;
57
+ if (args !== undefined) {
58
+ if (typeof args === 'object') {
59
+ if ('generatorOptions' in args || 'generatorArgs' in args || 'schedule' in args) {
60
+ return args;
61
+ }
62
+ generatorOptions = args;
63
+ }
64
+ else {
65
+ generatorArgs = Array.isArray(args) ? args : splitArgsFromString(String(args));
66
+ }
67
+ }
68
+ if (typeof options === 'boolean') {
69
+ return { generatorArgs, generatorOptions, schedule: options };
70
+ }
71
+ generatorOptions = generatorOptions ?? options;
72
+ if (typeof composeOptions === 'boolean') {
73
+ return { generatorArgs, generatorOptions, schedule: composeOptions };
74
+ }
75
+ return composeOptions;
76
+ };
77
+ export default class EnvironmentBase extends EventEmitter {
78
+ cwd;
79
+ adapter;
80
+ sharedFs;
81
+ conflicterOptions;
82
+ logCwd;
83
+ options;
84
+ aliases = [];
85
+ store;
86
+ command;
87
+ runLoop;
88
+ composedStore;
89
+ fs;
90
+ lookups;
91
+ sharedOptions;
92
+ repository;
93
+ experimental;
94
+ _rootGenerator;
95
+ compatibilityMode;
96
+ constructor(options = {}) {
97
+ super();
98
+ this.setMaxListeners(100);
99
+ const { cwd = process.cwd(), logCwd = cwd, sharedFs = createMemFs(), command, yeomanRepository, arboristRegistry, sharedOptions = {}, experimental, console, stdin, stderr, stdout, adapter = new QueuedAdapter({ console, stdin, stdout, stderr }), ...remainingOptions } = options;
100
+ this.options = remainingOptions;
101
+ this.adapter = adapter;
102
+ this.cwd = resolve(cwd);
103
+ this.logCwd = logCwd;
104
+ this.store = new Store(this);
105
+ this.command = command;
106
+ this.runLoop = new GroupedQueue(defaultQueues, false);
107
+ this.composedStore = new ComposedStore({ log: this.adapter.log });
108
+ this.sharedFs = sharedFs;
109
+ // Each composed generator might set listeners on these shared resources. Let's make sure
110
+ // Node won't complain about event listeners leaks.
111
+ this.runLoop.setMaxListeners(0);
112
+ this.sharedFs.setMaxListeners(0);
113
+ this.fs = createMemFsEditor(sharedFs);
114
+ this.lookups = defaultLookups;
115
+ this.sharedOptions = sharedOptions;
116
+ // Create a default sharedData.
117
+ this.sharedOptions.sharedData = this.sharedOptions.sharedData ?? {};
118
+ // Pass forwardErrorToEnvironment to generators.
119
+ this.sharedOptions.forwardErrorToEnvironment = false;
120
+ this.repository = new FlyRepository({
121
+ repositoryPath: yeomanRepository ?? `${this.cwd}/.yo-repository`,
122
+ arboristConfig: {
123
+ registry: arboristRegistry,
124
+ },
125
+ });
126
+ // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing
127
+ this.experimental = experimental || process.argv.includes('--experimental');
128
+ this.alias(/^([^:]+)$/, '$1:app');
129
+ }
130
+ async applyTransforms(transformStreams, options = {}) {
131
+ const { streamOptions = { filter: file => isFilePending(file) }, stream = this.sharedFs.stream(streamOptions), name = 'Transforming', } = options;
132
+ if (!Array.isArray(transformStreams)) {
133
+ transformStreams = [transformStreams];
134
+ }
135
+ await this.adapter.progress(async ({ step }) => {
136
+ await pipeline(stream, ...transformStreams, passthrough(file => {
137
+ step('Completed', relative(this.logCwd, file.path));
138
+ }));
139
+ }, { name, disabled: !(options?.log ?? true) });
140
+ }
141
+ /**
142
+ * Get a single generator from the registered list of generators. The lookup is
143
+ * based on generator's namespace, "walking up" the namespaces until a matching
144
+ * is found. Eg. if an `angular:common` namespace is registered, and we try to
145
+ * get `angular:common:all` then we get `angular:common` as a fallback (unless
146
+ * an `angular:common:all` generator is registered).
147
+ *
148
+ * @param namespaceOrPath
149
+ * @return the generator registered under the namespace
150
+ */
151
+ async get(namespaceOrPath) {
152
+ // Stop the recursive search if nothing is left
153
+ if (!namespaceOrPath) {
154
+ return;
155
+ }
156
+ const parsed = toNamespace(namespaceOrPath);
157
+ if (typeof namespaceOrPath !== 'string' || parsed) {
158
+ const ns = parsed.namespace;
159
+ const maybeGenerator = (await this.store.get(ns)) ?? this.store.get(this.alias(ns));
160
+ return maybeGenerator;
161
+ }
162
+ const maybeGenerator = (await this.store.get(namespaceOrPath)) ?? (await this.store.get(this.alias(namespaceOrPath)));
163
+ if (maybeGenerator) {
164
+ return maybeGenerator;
165
+ }
166
+ try {
167
+ const resolved = await resolveModulePath(namespaceOrPath);
168
+ if (resolved) {
169
+ const namespace = this.namespace(resolved);
170
+ this.store.add({ resolved, namespace });
171
+ return (await this.store.get(namespace));
172
+ }
173
+ }
174
+ catch { }
175
+ return undefined;
176
+ }
177
+ async create(namespaceOrPath, ...args) {
178
+ let constructor;
179
+ const namespace = typeof namespaceOrPath === 'string' ? toNamespace(namespaceOrPath) : undefined;
180
+ if (typeof namespaceOrPath === 'string') {
181
+ constructor = await this.get(namespaceOrPath);
182
+ if (namespace && !constructor) {
183
+ // Await this.lookupLocalNamespaces(namespace);
184
+ // constructor = await this.get(namespace);
185
+ }
186
+ }
187
+ else {
188
+ constructor = namespaceOrPath;
189
+ }
190
+ const checkGenerator = (Generator) => {
191
+ const generatorNamespace = Generator?.namespace;
192
+ if (namespace && generatorNamespace !== namespace.namespace && generatorNamespace !== UNKNOWN_NAMESPACE) {
193
+ // Update namespace object in case of aliased namespace.
194
+ try {
195
+ namespace.namespace = Generator.namespace;
196
+ }
197
+ catch {
198
+ // Invalid namespace can be aliased to a valid one.
199
+ }
200
+ }
201
+ if (typeof Generator !== 'function') {
202
+ throw new TypeError(chalk.red(`You don't seem to have a generator with the name “${namespace?.generatorHint}” installed.`) +
203
+ '\n' +
204
+ 'But help is on the way:\n\n' +
205
+ 'You can see available generators via ' +
206
+ chalk.yellow('npm search yeoman-generator') +
207
+ ' or via ' +
208
+ chalk.yellow('http://yeoman.io/generators/') +
209
+ '. \n' +
210
+ 'Install them with ' +
211
+ chalk.yellow(`npm install ${namespace?.generatorHint}`) +
212
+ '.\n\n' +
213
+ 'To see all your installed generators run ' +
214
+ chalk.yellow('yo --generators') +
215
+ '. ' +
216
+ 'Adding the ' +
217
+ chalk.yellow('--help') +
218
+ ' option will also show subgenerators. \n\n' +
219
+ 'If ' +
220
+ chalk.yellow('yo') +
221
+ ' cannot find the generator, run ' +
222
+ chalk.yellow('yo doctor') +
223
+ ' to troubleshoot your system.');
224
+ }
225
+ return Generator;
226
+ };
227
+ return this.instantiate(checkGenerator(constructor), ...args);
228
+ }
229
+ async instantiate(constructor, ...args) {
230
+ const composeOptions = args.length > 0 ? getInstantiateOptions(...args) : {};
231
+ const { namespace = UNKNOWN_NAMESPACE, resolved = UNKNOWN_RESOLVED } = constructor;
232
+ const environmentOptions = { env: this, resolved, namespace };
233
+ const generator = new constructor(composeOptions.generatorArgs ?? [], {
234
+ ...this.sharedOptions,
235
+ ...composeOptions.generatorOptions,
236
+ ...environmentOptions,
237
+ });
238
+ generator._environmentOptions = {
239
+ ...this.options,
240
+ ...this.sharedOptions,
241
+ ...environmentOptions,
242
+ };
243
+ if (!composeOptions.generatorOptions?.help && generator._postConstruct) {
244
+ await generator._postConstruct();
245
+ }
246
+ return generator;
247
+ }
248
+ async composeWith(generator, ...args) {
249
+ const options = getComposeOptions(...args);
250
+ const { schedule = true, ...instantiateOptions } = options;
251
+ const generatorInstance = await this.create(generator, instantiateOptions);
252
+ return this.queueGenerator(generatorInstance, { schedule });
253
+ }
254
+ /**
255
+ * Given a String `filepath`, tries to figure out the relative namespace.
256
+ *
257
+ * ### Examples:
258
+ *
259
+ * this.namespace('backbone/all/index.js');
260
+ * // => backbone:all
261
+ *
262
+ * this.namespace('generator-backbone/model');
263
+ * // => backbone:model
264
+ *
265
+ * this.namespace('backbone.js');
266
+ * // => backbone
267
+ *
268
+ * this.namespace('generator-mocha/backbone/model/index.js');
269
+ * // => mocha:backbone:model
270
+ *
271
+ * @param {String} filepath
272
+ * @param {Array} lookups paths
273
+ */
274
+ namespace(filepath, lookups = this.lookups) {
275
+ return asNamespace(filepath, { lookups });
276
+ }
277
+ getVersion(packageName) {
278
+ if (packageName && packageName !== 'yeoman-environment') {
279
+ try {
280
+ return require(`${packageName}/package.json`).version;
281
+ }
282
+ catch {
283
+ return undefined;
284
+ }
285
+ }
286
+ return ENVIRONMENT_VERSION;
287
+ }
288
+ /**
289
+ * Queue generator run (queue itself tasks).
290
+ *
291
+ * @param {Generator} generator Generator instance
292
+ * @param {boolean} [schedule=false] Whether to schedule the generator run.
293
+ * @return {Generator} The generator or singleton instance.
294
+ */
295
+ async queueGenerator(generator, queueOptions) {
296
+ const schedule = typeof queueOptions === 'boolean' ? queueOptions : queueOptions?.schedule ?? false;
297
+ const { added, identifier, generator: composedGenerator } = this.composedStore.addGenerator(generator);
298
+ if (!added) {
299
+ debug(`Using existing generator for namespace ${identifier}`);
300
+ return composedGenerator;
301
+ }
302
+ this.emit('compose', identifier, generator);
303
+ this.emit(`compose:${identifier}`, generator);
304
+ const runGenerator = async () => {
305
+ if (generator.queueTasks) {
306
+ // Generator > 5
307
+ this.once('run', () => generator.emit('run'));
308
+ this.once('end', () => generator.emit('end'));
309
+ await generator.queueTasks();
310
+ return;
311
+ }
312
+ if (!generator.options.forwardErrorToEnvironment) {
313
+ generator.on('error', (error) => this.emit('error', error));
314
+ }
315
+ generator.promise = generator.run();
316
+ };
317
+ if (schedule) {
318
+ this.queueTask('environment:run', async () => runGenerator());
319
+ }
320
+ else {
321
+ await runGenerator();
322
+ }
323
+ return generator;
324
+ }
325
+ /**
326
+ * Get the first generator that was queued to run in this environment.
327
+ *
328
+ * @return {Generator} generator queued to run in this environment.
329
+ */
330
+ rootGenerator() {
331
+ return this._rootGenerator;
332
+ }
333
+ async runGenerator(generator) {
334
+ generator = await this.queueGenerator(generator);
335
+ this.compatibilityMode = generator.queueTasks ? false : 'v4';
336
+ this._rootGenerator = this._rootGenerator ?? generator;
337
+ return this.start(generator.options);
338
+ }
339
+ /**
340
+ * Start Environment queue
341
+ * @param {Object} options - Conflicter options.
342
+ */
343
+ async start(options) {
344
+ return new Promise((resolve, reject) => {
345
+ this.conflicterOptions = pick(defaults({}, this.options, options), ['force', 'bail', 'ignoreWhitespace', 'dryRun', 'skipYoResolve']);
346
+ this.conflicterOptions.cwd = this.logCwd;
347
+ this.queueCommit();
348
+ this.queuePackageManagerInstall();
349
+ /*
350
+ * Listen to errors and reject if emmited.
351
+ * Some cases the generator relied at the behavior that the running process
352
+ * would be killed if an error is thrown to environment.
353
+ * Make sure to not rely on that behavior.
354
+ */
355
+ this.on('error', error => {
356
+ reject(error);
357
+ this.adapter.close();
358
+ });
359
+ this.once('end', () => {
360
+ resolve();
361
+ this.adapter.close();
362
+ });
363
+ /*
364
+ * For backward compatibility
365
+ */
366
+ this.on('generator:reject', error => {
367
+ this.emit('error', error);
368
+ });
369
+ /*
370
+ * For backward compatibility
371
+ */
372
+ this.on('generator:resolve', () => {
373
+ this.emit('end');
374
+ });
375
+ this.runLoop.on('error', (error) => {
376
+ this.emit('error', error);
377
+ });
378
+ this.runLoop.on('paused', () => {
379
+ this.emit('paused');
380
+ });
381
+ /* If runLoop has ended, the environment has ended too. */
382
+ this.runLoop.once('end', () => {
383
+ this.emit('end');
384
+ });
385
+ this.emit('run');
386
+ this.runLoop.start();
387
+ });
388
+ }
389
+ register(pathOrStub, meta, ...args) {
390
+ if (typeof pathOrStub === 'string') {
391
+ if (typeof meta === 'object') {
392
+ return this.registerGeneratorPath(pathOrStub, meta.namespace, meta.packagePath);
393
+ }
394
+ // Backward compatibility
395
+ return this.registerGeneratorPath(pathOrStub, meta, ...args);
396
+ }
397
+ if (pathOrStub) {
398
+ if (typeof meta === 'object') {
399
+ return this.registerStub(pathOrStub, meta.namespace, meta.resolved, meta.packagePath);
400
+ }
401
+ // Backward compatibility
402
+ return this.registerStub(pathOrStub, meta, ...args);
403
+ }
404
+ throw new TypeError('You must provide a generator name to register.');
405
+ }
406
+ /**
407
+ * Queue tasks
408
+ * @param {string} priority
409
+ * @param {(...args: any[]) => void | Promise<void>} task
410
+ * @param {{ once?: string, startQueue?: boolean }} [options]
411
+ */
412
+ queueTask(priority, task, options) {
413
+ this.runLoop.add(priority, async (done, stop) => {
414
+ try {
415
+ await task();
416
+ done();
417
+ }
418
+ catch (error) {
419
+ stop(error);
420
+ }
421
+ }, {
422
+ once: options?.once,
423
+ run: options?.startQueue ?? false,
424
+ });
425
+ }
426
+ /**
427
+ * Add priority
428
+ * @param {string} priority
429
+ * @param {string} [before]
430
+ */
431
+ addPriority(priority, before) {
432
+ if (this.runLoop.queueNames.includes(priority)) {
433
+ return;
434
+ }
435
+ this.runLoop.addSubQueue(priority, before);
436
+ }
437
+ /**
438
+ * Search for generators and their sub generators.
439
+ *
440
+ * A generator is a `:lookup/:name/index.js` file placed inside an npm package.
441
+ *
442
+ * Defaults lookups are:
443
+ * - ./
444
+ * - generators/
445
+ * - lib/generators/
446
+ *
447
+ * So this index file `node_modules/generator-dummy/lib/generators/yo/index.js` would be
448
+ * registered as `dummy:yo` generator.
449
+ */
450
+ async lookup(options) {
451
+ const { registerToScope, lookups = this.lookups, ...remainingOptions } = options ?? { localOnly: false };
452
+ options = {
453
+ ...remainingOptions,
454
+ lookups,
455
+ };
456
+ const generators = [];
457
+ await lookupGenerators(options, ({ packagePath, filePath, lookups }) => {
458
+ try {
459
+ let repositoryPath = join(packagePath, '..');
460
+ if (basename(repositoryPath).startsWith('@')) {
461
+ // Scoped package
462
+ repositoryPath = join(repositoryPath, '..');
463
+ }
464
+ let namespace = asNamespace(relative(repositoryPath, filePath), { lookups });
465
+ const resolved = realpathSync(filePath);
466
+ if (!namespace) {
467
+ namespace = asNamespace(resolved, { lookups });
468
+ }
469
+ if (registerToScope && !namespace.startsWith('@')) {
470
+ namespace = `@${registerToScope}/${namespace}`;
471
+ }
472
+ this.store.add({ namespace, packagePath, resolved });
473
+ const meta = this.getGeneratorMeta(namespace);
474
+ if (meta) {
475
+ generators.push({
476
+ ...meta,
477
+ generatorPath: meta.resolved,
478
+ registered: true,
479
+ });
480
+ return Boolean(options?.singleResult);
481
+ }
482
+ }
483
+ catch (error) {
484
+ console.error('Unable to register %s (Error: %s)', filePath, error);
485
+ }
486
+ generators.push({
487
+ generatorPath: filePath,
488
+ resolved: filePath,
489
+ packagePath,
490
+ registered: false,
491
+ });
492
+ return false;
493
+ });
494
+ return generators;
495
+ }
496
+ /**
497
+ * Verify if a package namespace already have been registered.
498
+ *
499
+ * @param packageNS - namespace of the package.
500
+ * @return true if any generator of the package has been registered
501
+ */
502
+ isPackageRegistered(packageNamespace) {
503
+ const registeredPackages = this.getRegisteredPackages();
504
+ return registeredPackages.includes(packageNamespace) || registeredPackages.includes(this.alias(packageNamespace).split(':', 2)[0]);
505
+ }
506
+ /**
507
+ * Get all registered packages namespaces.
508
+ *
509
+ * @return array of namespaces.
510
+ */
511
+ getRegisteredPackages() {
512
+ return this.store.getPackagesNS();
513
+ }
514
+ /**
515
+ * Returns stored generators meta
516
+ * @param namespace
517
+ */
518
+ getGeneratorMeta(namespace) {
519
+ const meta = this.store.getMeta(namespace) ?? this.store.getMeta(this.alias(namespace));
520
+ if (!meta) {
521
+ return;
522
+ }
523
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
524
+ return { ...meta };
525
+ }
526
+ alias(match, value) {
527
+ if (match && value) {
528
+ this.aliases.push({
529
+ match: match instanceof RegExp ? match : new RegExp(`^${match}$`),
530
+ value,
531
+ });
532
+ return this;
533
+ }
534
+ if (typeof match !== 'string') {
535
+ throw new TypeError('string is required');
536
+ }
537
+ const aliases = [...this.aliases].reverse();
538
+ return aliases.reduce((resolved, alias) => {
539
+ if (!alias.match.test(resolved)) {
540
+ return resolved;
541
+ }
542
+ return resolved.replace(alias.match, alias.value);
543
+ }, match);
544
+ }
545
+ /**
546
+ * Queue environment's commit task.
547
+ */
548
+ queueCommit() {
549
+ const queueCommit = () => {
550
+ debug('Queueing conflicts task');
551
+ this.queueTask('environment:conflicts', async () => {
552
+ const { customCommitTask = () => commitSharedFsTask(this) } = this.composedStore;
553
+ if (typeof customCommitTask !== 'function') {
554
+ // There is a custom commit task or just disabled
555
+ return;
556
+ }
557
+ await customCommitTask();
558
+ debug('Adding queueCommit event listener');
559
+ this.sharedFs.once('change', queueCommit);
560
+ }, {
561
+ once: 'write memory fs to disk',
562
+ });
563
+ };
564
+ queueCommit();
565
+ }
566
+ /**
567
+ * Queue environment's package manager install task.
568
+ */
569
+ queuePackageManagerInstall() {
570
+ const { adapter, sharedFs: memFs } = this;
571
+ const { skipInstall, nodePackageManager } = this.options;
572
+ const { customInstallTask } = this.composedStore;
573
+ this.queueTask('install', async () => {
574
+ if (this.compatibilityMode === 'v4') {
575
+ debug('Running in generator < 5 compatibility. Package manager install is done by the generator.');
576
+ return;
577
+ }
578
+ await packageManagerInstallTask({
579
+ adapter,
580
+ memFs,
581
+ packageJsonLocation: this.cwd,
582
+ skipInstall,
583
+ nodePackageManager,
584
+ customInstallTask,
585
+ });
586
+ }, { once: 'package manager install' });
587
+ }
588
+ /**
589
+ * Registers a specific `generator` to this environment. This generator is stored under
590
+ * provided namespace, or a default namespace format if none if available.
591
+ *
592
+ * @param name - Filepath to the a generator or a npm package name
593
+ * @param namespace - Namespace under which register the generator (optional)
594
+ * @param packagePath - PackagePath to the generator npm package (optional)
595
+ * @return environment - This environment
596
+ */
597
+ registerGeneratorPath(generatorPath, namespace, packagePath) {
598
+ if (typeof generatorPath !== 'string') {
599
+ throw new TypeError('You must provide a generator name to register.');
600
+ }
601
+ if (!isAbsolute(generatorPath)) {
602
+ throw new Error(`An absolute path is required to register`);
603
+ }
604
+ namespace = namespace ?? this.namespace(generatorPath);
605
+ if (!namespace) {
606
+ throw new Error('Unable to determine namespace.');
607
+ }
608
+ // Generator is already registered and matches the current namespace.
609
+ const generatorMeta = this.store.getMeta(namespace);
610
+ if (generatorMeta && generatorMeta.resolved === generatorPath) {
611
+ return generatorMeta;
612
+ }
613
+ const meta = this.store.add({ namespace, resolved: generatorPath, packagePath });
614
+ debug('Registered %s (%s) on package %s (%s)', namespace, generatorPath, meta.packageNamespace, packagePath);
615
+ return meta;
616
+ }
617
+ /**
618
+ * Register a stubbed generator to this environment. This method allow to register raw
619
+ * functions under the provided namespace. `registerStub` will enforce the function passed
620
+ * to extend the Base generator automatically.
621
+ *
622
+ * @param Generator - A Generator constructor or a simple function
623
+ * @param namespace - Namespace under which register the generator
624
+ * @param resolved - The file path to the generator
625
+ * @param packagePath - The generator's package path
626
+ */
627
+ registerStub(Generator, namespace, resolved = UNKNOWN_RESOLVED, packagePath) {
628
+ if (typeof Generator !== 'function' && typeof Generator.createGenerator !== 'function') {
629
+ throw new TypeError('You must provide a stub function to register.');
630
+ }
631
+ if (typeof namespace !== 'string') {
632
+ throw new TypeError('You must provide a namespace to register.');
633
+ }
634
+ const meta = this.store.add({ namespace, resolved, packagePath }, Generator);
635
+ debug('Registered %s (%s) on package (%s)', namespace, resolved, packagePath);
636
+ return meta;
637
+ }
638
+ }