yeoman-environment 4.0.0-alpha.5 → 4.0.0-alpha.7

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