yeoman-environment 7.0.0 → 7.1.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/dist/environment-base.d.ts +15 -1
- package/dist/environment-base.js +36 -56
- package/dist/environment-full.d.ts +2 -2
- package/dist/environment-full.js +1 -1
- package/dist/generator-lookup.d.ts +3 -3
- package/dist/generator-lookup.js +25 -30
- package/dist/index.d.ts +1 -0
- package/dist/index.js +2 -0
- package/dist/store.d.ts +79 -11
- package/dist/store.js +175 -55
- package/package.json +1 -1
|
@@ -9,7 +9,7 @@ import { type FilePipelineTransform } from '@yeoman/transform';
|
|
|
9
9
|
import { type YeomanNamespace } from '@yeoman/namespace';
|
|
10
10
|
import { type ConflicterOptions } from '@yeoman/conflicter';
|
|
11
11
|
import { ComposedStore } from './composed-store.ts';
|
|
12
|
-
import Store from './store.ts';
|
|
12
|
+
import Store, { type StoreGeneratorMeta } from './store.ts';
|
|
13
13
|
import type YeomanCommand from './util/command.ts';
|
|
14
14
|
import { type LookupOptions } from './generator-lookup.ts';
|
|
15
15
|
import { type InstallTask } from './package-manager.ts';
|
|
@@ -27,6 +27,13 @@ export type EnvironmentOptions = ConflicterOptions & BaseEnvironmentOptions & Om
|
|
|
27
27
|
arboristRegistry?: string;
|
|
28
28
|
nodePackageManager?: string;
|
|
29
29
|
generatorLookupOptions?: Pick<EnvironmentLookupOptions, 'customizeNamespace' | 'lookups'>;
|
|
30
|
+
/**
|
|
31
|
+
* Generators store to use instead of a new one, to share looked up and registered generators with other
|
|
32
|
+
* environments. Generators registered through this environment are added to it.
|
|
33
|
+
*
|
|
34
|
+
* @experimental
|
|
35
|
+
*/
|
|
36
|
+
store?: Store;
|
|
30
37
|
};
|
|
31
38
|
/**
|
|
32
39
|
* Copy and remove null and undefined values
|
|
@@ -62,6 +69,13 @@ export default class EnvironmentBase extends EventEmitter implements BaseEnviron
|
|
|
62
69
|
feature: any;
|
|
63
70
|
}>;
|
|
64
71
|
applyTransforms(transformStreams: FilePipelineTransform[], options?: ApplyTransformsOptions): Promise<void>;
|
|
72
|
+
/**
|
|
73
|
+
* The meta of a generator of the store for this environment.
|
|
74
|
+
*/
|
|
75
|
+
protected bindMeta<M extends StoreGeneratorMeta>(meta: M): M & GeneratorMeta;
|
|
76
|
+
protected bindMeta<M extends StoreGeneratorMeta>(meta: M | undefined): (M & GeneratorMeta) | undefined;
|
|
77
|
+
private getStoreMeta;
|
|
78
|
+
protected asEnvironment(): BaseEnvironment;
|
|
65
79
|
/**
|
|
66
80
|
* @param namespaceOrPath
|
|
67
81
|
* @return the generator meta registered under the namespace
|
package/dist/environment-base.js
CHANGED
|
@@ -1,8 +1,7 @@
|
|
|
1
1
|
import EventEmitter from 'node:events';
|
|
2
2
|
import { createRequire } from 'node:module';
|
|
3
|
-
import {
|
|
3
|
+
import { isAbsolute, join, relative, resolve } from 'node:path';
|
|
4
4
|
import process from 'node:process';
|
|
5
|
-
import { realpathSync } from 'node:fs';
|
|
6
5
|
import { QueuedAdapter } from '@yeoman/adapter';
|
|
7
6
|
import { create as createMemFs } from 'mem-fs';
|
|
8
7
|
import {} from 'mem-fs-editor';
|
|
@@ -16,9 +15,9 @@ import chalk from 'chalk';
|
|
|
16
15
|
import {} from '@yeoman/conflicter';
|
|
17
16
|
import { defaults, pick } from 'lodash-es';
|
|
18
17
|
import { ComposedStore } from "./composed-store.js";
|
|
19
|
-
import Store from "./store.js";
|
|
18
|
+
import Store, {} from "./store.js";
|
|
20
19
|
import { asNamespace, defaultLookups } from "./util/namespace.js";
|
|
21
|
-
import {
|
|
20
|
+
import {} from "./generator-lookup.js";
|
|
22
21
|
import { UNKNOWN_NAMESPACE, UNKNOWN_RESOLVED, defaultQueues } from "./constants.js";
|
|
23
22
|
import { resolveModulePath } from "./util/resolve.js";
|
|
24
23
|
import { commitSharedFsTask } from "./commit.js";
|
|
@@ -107,12 +106,12 @@ export default class EnvironmentBase extends EventEmitter {
|
|
|
107
106
|
constructor(options = {}) {
|
|
108
107
|
super();
|
|
109
108
|
this.setMaxListeners(100);
|
|
110
|
-
const { cwd = process.cwd(), logCwd = cwd, sharedFs = createMemFs(), command, yeomanRepository, arboristRegistry, sharedOptions = {}, experimental, console: adapterConsole, stdin, stderr, stdout, adapter = new QueuedAdapter({ console: adapterConsole, stdin, stdout, stderr }), ...remainingOptions } = options;
|
|
109
|
+
const { cwd = process.cwd(), logCwd = cwd, sharedFs = createMemFs(), command, yeomanRepository, arboristRegistry, sharedOptions = {}, experimental, console: adapterConsole, stdin, stderr, stdout, adapter = new QueuedAdapter({ console: adapterConsole, stdin, stdout, stderr }), store, ...remainingOptions } = options;
|
|
111
110
|
this.options = remainingOptions;
|
|
112
111
|
this.adapter = adapter;
|
|
113
112
|
this.cwd = resolve(cwd);
|
|
114
113
|
this.logCwd = logCwd;
|
|
115
|
-
this.store = new Store(this);
|
|
114
|
+
this.store = store ?? new Store(this);
|
|
116
115
|
this.command = command;
|
|
117
116
|
this.runLoop = new GroupedQueue(defaultQueues, false);
|
|
118
117
|
this.composedStore = new ComposedStore({ log: this.adapter.log });
|
|
@@ -155,6 +154,15 @@ export default class EnvironmentBase extends EventEmitter {
|
|
|
155
154
|
]);
|
|
156
155
|
}, { name, disabled: !(options?.log ?? true) });
|
|
157
156
|
}
|
|
157
|
+
bindMeta(meta) {
|
|
158
|
+
return this.store.bindMeta(meta, { env: this.asEnvironment() });
|
|
159
|
+
}
|
|
160
|
+
getStoreMeta(namespace) {
|
|
161
|
+
return this.store.getMeta(namespace, { env: this.asEnvironment() });
|
|
162
|
+
}
|
|
163
|
+
asEnvironment() {
|
|
164
|
+
return this;
|
|
165
|
+
}
|
|
158
166
|
/**
|
|
159
167
|
* @param namespaceOrPath
|
|
160
168
|
* @return the generator meta registered under the namespace
|
|
@@ -167,16 +175,16 @@ export default class EnvironmentBase extends EventEmitter {
|
|
|
167
175
|
const parsed = toNamespace(namespaceOrPath);
|
|
168
176
|
if (typeof namespaceOrPath !== 'string' || parsed) {
|
|
169
177
|
const ns = parsed.namespace;
|
|
170
|
-
return this.
|
|
178
|
+
return this.getStoreMeta(ns) ?? this.getStoreMeta(this.alias(ns));
|
|
171
179
|
}
|
|
172
|
-
const maybeMeta = this.
|
|
180
|
+
const maybeMeta = this.getStoreMeta(namespaceOrPath) ?? this.getStoreMeta(this.alias(namespaceOrPath));
|
|
173
181
|
if (maybeMeta) {
|
|
174
182
|
return maybeMeta;
|
|
175
183
|
}
|
|
176
184
|
try {
|
|
177
185
|
const resolved = resolveModulePath(namespaceOrPath);
|
|
178
186
|
if (resolved) {
|
|
179
|
-
return this.store.add({ resolved, namespace: this.namespace(resolved) });
|
|
187
|
+
return this.store.add({ resolved, namespace: this.namespace(resolved) }, undefined, { env: this.asEnvironment() });
|
|
180
188
|
}
|
|
181
189
|
}
|
|
182
190
|
catch {
|
|
@@ -241,7 +249,9 @@ export default class EnvironmentBase extends EventEmitter {
|
|
|
241
249
|
}
|
|
242
250
|
instantiate(constructor, ...arguments_) {
|
|
243
251
|
const composeOptions = arguments_.length > 0 ? getInstantiateOptions(...arguments_) : {};
|
|
244
|
-
const { namespace = UNKNOWN_NAMESPACE, resolved = UNKNOWN_RESOLVED
|
|
252
|
+
const { namespace = UNKNOWN_NAMESPACE, resolved = UNKNOWN_RESOLVED } = constructor;
|
|
253
|
+
// The generator class keeps the meta of the store, which may be shared: the generator gets the one of this environment.
|
|
254
|
+
const _meta = this.bindMeta(constructor._meta);
|
|
245
255
|
const environmentOptions = { env: this, resolved, namespace, _meta };
|
|
246
256
|
const generator = new constructor(composeOptions.generatorArgs ?? [], {
|
|
247
257
|
...this.sharedOptions,
|
|
@@ -416,49 +426,12 @@ export default class EnvironmentBase extends EventEmitter {
|
|
|
416
426
|
* registered as `dummy:yo` generator.
|
|
417
427
|
*/
|
|
418
428
|
async lookup(options) {
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
const generators = [];
|
|
425
|
-
await lookupGenerators(options, ({ packagePath, filePath, lookups }) => {
|
|
426
|
-
let repositoryPath = join(packagePath, '..');
|
|
427
|
-
if (basename(repositoryPath).startsWith('@')) {
|
|
428
|
-
// Scoped package
|
|
429
|
-
repositoryPath = join(repositoryPath, '..');
|
|
430
|
-
}
|
|
431
|
-
let namespace = customizeNamespace(asNamespace(relative(repositoryPath, filePath), { lookups }));
|
|
432
|
-
try {
|
|
433
|
-
const resolved = realpathSync(filePath);
|
|
434
|
-
if (!namespace) {
|
|
435
|
-
namespace = customizeNamespace(asNamespace(resolved, { lookups }));
|
|
436
|
-
}
|
|
437
|
-
namespace = namespace;
|
|
438
|
-
if (registerToScope && !namespace.startsWith('@')) {
|
|
439
|
-
namespace = `@${registerToScope}/${namespace}`;
|
|
440
|
-
}
|
|
441
|
-
const meta = this.store.add({ namespace, packagePath, resolved });
|
|
442
|
-
if (meta) {
|
|
443
|
-
generators.push({
|
|
444
|
-
...meta,
|
|
445
|
-
registered: true,
|
|
446
|
-
});
|
|
447
|
-
return Boolean(options?.singleResult);
|
|
448
|
-
}
|
|
449
|
-
}
|
|
450
|
-
catch (error) {
|
|
451
|
-
console.error('Unable to register %s (Error: %s)', filePath, error);
|
|
452
|
-
}
|
|
453
|
-
generators.push({
|
|
454
|
-
resolved: filePath,
|
|
455
|
-
namespace: namespace,
|
|
456
|
-
packagePath,
|
|
457
|
-
registered: false,
|
|
458
|
-
});
|
|
459
|
-
return false;
|
|
429
|
+
return this.store.lookupSync({
|
|
430
|
+
customizeNamespace: this.options.generatorLookupOptions?.customizeNamespace,
|
|
431
|
+
lookups: this.lookups,
|
|
432
|
+
...(options ?? { localOnly: false }),
|
|
433
|
+
env: this.asEnvironment(),
|
|
460
434
|
});
|
|
461
|
-
return generators;
|
|
462
435
|
}
|
|
463
436
|
/**
|
|
464
437
|
* Verify if a package namespace already have been registered.
|
|
@@ -487,7 +460,14 @@ export default class EnvironmentBase extends EventEmitter {
|
|
|
487
460
|
if (!meta) {
|
|
488
461
|
return;
|
|
489
462
|
}
|
|
490
|
-
|
|
463
|
+
// A copy with the functions bound to this environment, the bound meta itself delegates to the meta of the store.
|
|
464
|
+
const boundMeta = this.bindMeta(meta);
|
|
465
|
+
return {
|
|
466
|
+
...meta,
|
|
467
|
+
importGenerator: boundMeta.importGenerator,
|
|
468
|
+
instantiate: boundMeta.instantiate,
|
|
469
|
+
instantiateHelp: boundMeta.instantiateHelp,
|
|
470
|
+
};
|
|
491
471
|
}
|
|
492
472
|
alias(match, value) {
|
|
493
473
|
if (match && value) {
|
|
@@ -662,9 +642,9 @@ export default class EnvironmentBase extends EventEmitter {
|
|
|
662
642
|
// Generator is already registered and matches the current namespace.
|
|
663
643
|
const generatorMeta = this.store.getMeta(namespace);
|
|
664
644
|
if (generatorMeta && generatorMeta.resolved === generatorPath) {
|
|
665
|
-
return generatorMeta;
|
|
645
|
+
return this.bindMeta(generatorMeta);
|
|
666
646
|
}
|
|
667
|
-
const meta = this.store.add({ namespace, resolved: generatorPath, packagePath });
|
|
647
|
+
const meta = this.store.add({ namespace, resolved: generatorPath, packagePath }, undefined, { env: this.asEnvironment() });
|
|
668
648
|
debug('Registered %s (%s) on package %s (%s)', namespace, generatorPath, meta.packageNamespace, packagePath);
|
|
669
649
|
return meta;
|
|
670
650
|
}
|
|
@@ -685,7 +665,7 @@ export default class EnvironmentBase extends EventEmitter {
|
|
|
685
665
|
if (typeof namespace !== 'string') {
|
|
686
666
|
throw new TypeError('You must provide a namespace to register.');
|
|
687
667
|
}
|
|
688
|
-
const meta = this.store.add({ namespace, resolved, packagePath }, Generator);
|
|
668
|
+
const meta = this.store.add({ namespace, resolved, packagePath }, Generator, { env: this.asEnvironment() });
|
|
689
669
|
debug('Registered %s (%s) on package (%s)', namespace, resolved, packagePath);
|
|
690
670
|
return meta;
|
|
691
671
|
}
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { BaseGeneratorConstructor, BaseGeneratorOptions, GeneratorEnvironmentOptions } from '@yeoman/types';
|
|
1
|
+
import type { BaseGeneratorConstructor, BaseGeneratorOptions, GeneratorEnvironmentOptions, GeneratorMeta } from '@yeoman/types';
|
|
2
2
|
import { type LookupOptions } from './generator-lookup.ts';
|
|
3
3
|
import EnvironmentBase, { type EnvironmentOptions } from './environment-base.ts';
|
|
4
4
|
declare class FullEnvironment extends EnvironmentBase {
|
|
@@ -34,7 +34,7 @@ declare class FullEnvironment extends EnvironmentBase {
|
|
|
34
34
|
* Returns stored generators meta
|
|
35
35
|
* @return {Object}
|
|
36
36
|
*/
|
|
37
|
-
getGeneratorsMeta(): Record<string,
|
|
37
|
+
getGeneratorsMeta(): Record<string, GeneratorMeta>;
|
|
38
38
|
/**
|
|
39
39
|
* @protected
|
|
40
40
|
* Get registered generators names
|
package/dist/environment-full.js
CHANGED
|
@@ -31,12 +31,12 @@ export declare const defaultExtensions: string[];
|
|
|
31
31
|
* @param {string|Array} [options.packagePatterns='generator-*'] - Package pattern to look for.
|
|
32
32
|
* @param {boolean} [options.singleResult=false] - Set true to stop lookup on the first match.
|
|
33
33
|
* @param {Number} [options.globbyDeep] - Deep option to be passed to globby.
|
|
34
|
-
* @return {
|
|
34
|
+
* @return {Object[]} List of generators
|
|
35
35
|
*/
|
|
36
|
-
export declare function
|
|
36
|
+
export declare function lookupGeneratorsSync(options?: LookupOptions, register?: (meta: LookupMeta) => boolean): {
|
|
37
37
|
filePath: string;
|
|
38
38
|
packagePath: string;
|
|
39
|
-
}[]
|
|
39
|
+
}[];
|
|
40
40
|
/**
|
|
41
41
|
* Lookup for a specific generator.
|
|
42
42
|
*
|
package/dist/generator-lookup.js
CHANGED
|
@@ -2,7 +2,8 @@ import { extname, isAbsolute, join, posix } from 'node:path';
|
|
|
2
2
|
import { pathToFileURL } from 'node:url';
|
|
3
3
|
import { requireNamespace, toNamespace } from '@yeoman/namespace';
|
|
4
4
|
import { findPackagesIn, getNpmPaths, moduleLookupSync } from "./module-lookup.js";
|
|
5
|
-
import {
|
|
5
|
+
import { defaultLookups } from "./util/namespace.js";
|
|
6
|
+
import Store from "./store.js";
|
|
6
7
|
export const defaultExtensions = ['.ts', '.cts', '.mts', '.js', '.cjs', '.mjs'];
|
|
7
8
|
/**
|
|
8
9
|
* Search for generators and their sub generators.
|
|
@@ -26,9 +27,9 @@ export const defaultExtensions = ['.ts', '.cts', '.mts', '.js', '.cjs', '.mjs'];
|
|
|
26
27
|
* @param {string|Array} [options.packagePatterns='generator-*'] - Package pattern to look for.
|
|
27
28
|
* @param {boolean} [options.singleResult=false] - Set true to stop lookup on the first match.
|
|
28
29
|
* @param {Number} [options.globbyDeep] - Deep option to be passed to globby.
|
|
29
|
-
* @return {
|
|
30
|
+
* @return {Object[]} List of generators
|
|
30
31
|
*/
|
|
31
|
-
export
|
|
32
|
+
export function lookupGeneratorsSync(options = {}, register) {
|
|
32
33
|
const { lookups = defaultLookups } = options;
|
|
33
34
|
options = {
|
|
34
35
|
// Js generators should be after, last will override registered one.
|
|
@@ -53,33 +54,27 @@ export async function lookupGenerators(options = {}, register) {
|
|
|
53
54
|
}
|
|
54
55
|
export function lookupGenerator(namespace, options) {
|
|
55
56
|
options = typeof options === 'boolean' ? { localOnly: options } : (options ?? {});
|
|
56
|
-
|
|
57
|
-
options.filePatterns = options.filePatterns ?? defaultLookups.map(prefix => join(prefix, '*/index.{js,ts}'));
|
|
57
|
+
const { packagePath: returnPackagePath, generatorPath: returnGeneratorPath, singleResult = true, ...lookupOptions } = options;
|
|
58
58
|
const ns = requireNamespace(namespace);
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
moduleLookupSync(options, ({ files, packagePath }) => {
|
|
65
|
-
for (const filename of files) {
|
|
66
|
-
const fileNs = asNamespace(filename, { lookups: defaultLookups });
|
|
67
|
-
const ns = toNamespace(fileNs);
|
|
68
|
-
if (namespace === fileNs || (options.packagePath && namespace === ns?.packageNamespace)) {
|
|
69
|
-
// Version 2.6.0 returned pattern instead of modulePath for options.packagePath
|
|
70
|
-
const returnPath = options.packagePath ? packagePath : options.generatorPath ? posix.join(filename, '../../') : filename;
|
|
71
|
-
if (options.singleResult) {
|
|
72
|
-
paths = returnPath;
|
|
73
|
-
return filename;
|
|
74
|
-
}
|
|
75
|
-
paths.push(returnPath);
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
|
-
return;
|
|
79
|
-
});
|
|
80
|
-
if (options.singleResult) {
|
|
81
|
-
const generatorPath = paths;
|
|
82
|
-
return generatorPath && isAbsolute(generatorPath) ? pathToFileURL(generatorPath).toString() : generatorPath;
|
|
59
|
+
lookupOptions.packagePatterns = lookupOptions.packagePatterns ?? [ns.generatorHint];
|
|
60
|
+
if (!lookupOptions.packagePaths) {
|
|
61
|
+
// Looking the npm paths up runs the package managers, skip it when the packages are given.
|
|
62
|
+
lookupOptions.npmPaths = lookupOptions.npmPaths ?? getNpmPaths({ localOnly: lookupOptions.localOnly }).toReversed();
|
|
63
|
+
lookupOptions.packagePaths = findPackagesIn(lookupOptions.npmPaths, lookupOptions.packagePatterns);
|
|
83
64
|
}
|
|
84
|
-
|
|
65
|
+
// The generators are looked up by a store of their own, which keeps the ones asked for.
|
|
66
|
+
const generators = new Store().lookupSync({
|
|
67
|
+
filePatterns: defaultLookups.map(prefix => join(prefix, '*/index.{js,ts}')),
|
|
68
|
+
reverse: false,
|
|
69
|
+
...lookupOptions,
|
|
70
|
+
lookups: defaultLookups,
|
|
71
|
+
singleResult,
|
|
72
|
+
filter: generator => generator.namespace === namespace || Boolean(returnPackagePath && toNamespace(generator.namespace)?.packageNamespace === namespace),
|
|
73
|
+
});
|
|
74
|
+
const paths = generators.map(({ filePath, packagePath }) => {
|
|
75
|
+
// Version 2.6.0 returned pattern instead of modulePath for options.packagePath
|
|
76
|
+
const returnPath = returnPackagePath ? packagePath : returnGeneratorPath ? posix.join(filePath, '../../') : filePath;
|
|
77
|
+
return isAbsolute(returnPath) ? pathToFileURL(returnPath).toString() : returnPath;
|
|
78
|
+
});
|
|
79
|
+
return singleResult ? paths[0] : paths;
|
|
85
80
|
}
|
package/dist/index.d.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { type EnvironmentOptions } from './environment-base.ts';
|
|
|
2
2
|
import Environment from './environment-full.ts';
|
|
3
3
|
export { default } from './environment-full.ts';
|
|
4
4
|
export { default as EnvironmentBase, type EnvironmentOptions } from './environment-base.ts';
|
|
5
|
+
export { default as Store, type StoreEnvironmentOptions, type StoreGeneratorMeta, type StoreLookupGeneratorMeta, type StoreLookupOptions, } from './store.ts';
|
|
5
6
|
export declare const createEnv: (options?: EnvironmentOptions) => Environment;
|
|
6
7
|
export declare const enforceUpdate: () => void;
|
|
7
8
|
export * from './commands.ts';
|
package/dist/index.js
CHANGED
|
@@ -2,6 +2,8 @@ import {} from "./environment-base.js";
|
|
|
2
2
|
import Environment from "./environment-full.js";
|
|
3
3
|
export { default } from "./environment-full.js";
|
|
4
4
|
export { default as EnvironmentBase } from "./environment-base.js";
|
|
5
|
+
// Experimental: the Store API is not stable yet.
|
|
6
|
+
export { default as Store, } from "./store.js";
|
|
5
7
|
export const createEnv = (options) => new Environment(options);
|
|
6
8
|
// Backward compatibility
|
|
7
9
|
export const enforceUpdate = () => { };
|
package/dist/store.d.ts
CHANGED
|
@@ -1,36 +1,98 @@
|
|
|
1
|
-
import type { BaseEnvironment, BaseGeneratorMeta, GeneratorMeta, GetGeneratorConstructor } from '@yeoman/types';
|
|
1
|
+
import type { BaseEnvironment, BaseGenerator, BaseGeneratorConstructorMeta, BaseGeneratorMeta, GeneratorMeta, GetGeneratorConstructor } from '@yeoman/types';
|
|
2
|
+
import { type LookupOptions } from './generator-lookup.ts';
|
|
3
|
+
type FoundGenerator = {
|
|
4
|
+
namespace: string;
|
|
5
|
+
/** Path of the generator as it was found, `resolved` is its real path. */
|
|
6
|
+
filePath: string;
|
|
7
|
+
packagePath: string;
|
|
8
|
+
};
|
|
9
|
+
/** The environment to import or instantiate a generator in. */
|
|
10
|
+
export type StoreEnvironmentOptions = {
|
|
11
|
+
env?: BaseEnvironment;
|
|
12
|
+
};
|
|
13
|
+
export type StoreLookupOptions = LookupOptions & StoreEnvironmentOptions & {
|
|
14
|
+
registerToScope?: string;
|
|
15
|
+
customizeNamespace?: (ns?: string) => string | undefined;
|
|
16
|
+
/** Generators to keep, the others are neither registered nor returned. */
|
|
17
|
+
filter?: (generator: FoundGenerator) => boolean;
|
|
18
|
+
};
|
|
19
|
+
/** A generator found by a lookup, `registered` tells if it was added to the store. */
|
|
20
|
+
export type StoreLookupGeneratorMeta = FoundGenerator & ((StoreGeneratorMeta & {
|
|
21
|
+
registered: true;
|
|
22
|
+
}) | (Required<BaseGeneratorMeta> & {
|
|
23
|
+
registered: false;
|
|
24
|
+
}));
|
|
25
|
+
/**
|
|
26
|
+
* Generator meta as the store keeps it. `importGenerator`, `instantiate` and `instantiateHelp` take the environment
|
|
27
|
+
* to use as `{ env }`, falling back to the one the meta is bound to: the environment the store was created with, if
|
|
28
|
+
* any, or the one passed to the store method that returned the meta.
|
|
29
|
+
*/
|
|
30
|
+
export type StoreGeneratorMeta = Omit<GeneratorMeta, 'importGenerator' | 'instantiate' | 'instantiateHelp'> & {
|
|
31
|
+
importGenerator: <G extends BaseGenerator = BaseGenerator>(options?: StoreEnvironmentOptions) => Promise<GetGeneratorConstructor<G> & BaseGeneratorConstructorMeta> | (GetGeneratorConstructor<G> & BaseGeneratorConstructorMeta);
|
|
32
|
+
instantiate: <G extends BaseGenerator = BaseGenerator>(arguments_?: string[], options?: any, environmentOptions?: StoreEnvironmentOptions) => Promise<G>;
|
|
33
|
+
instantiateHelp: <G extends BaseGenerator = BaseGenerator>(options?: StoreEnvironmentOptions) => Promise<G>;
|
|
34
|
+
};
|
|
2
35
|
/**
|
|
3
36
|
* The Generator store
|
|
4
37
|
* This is used to store generator (npm packages) reference and instantiate them when
|
|
5
38
|
* requested.
|
|
6
|
-
*
|
|
7
|
-
*
|
|
39
|
+
*
|
|
40
|
+
* A store does not need an environment: generators are looked up and registered on their own, and the environment
|
|
41
|
+
* that imports or instantiates one is passed at that time. The same store can then serve several environments: the
|
|
42
|
+
* methods returning metas take `{ env }` and return metas bound to it.
|
|
43
|
+
*
|
|
44
|
+
* @experimental The Store API is not stable yet and may change in a minor release.
|
|
8
45
|
*/
|
|
9
46
|
export default class Store {
|
|
10
47
|
private readonly _meta;
|
|
11
48
|
private readonly _packagesJson;
|
|
12
49
|
private readonly _packagesPaths;
|
|
13
50
|
private readonly _packagesNS;
|
|
14
|
-
private readonly
|
|
15
|
-
|
|
51
|
+
private readonly _boundMetas;
|
|
52
|
+
/** The environment used when none is passed to `importGenerator` or `instantiate`. */
|
|
53
|
+
readonly environment?: BaseEnvironment;
|
|
54
|
+
constructor(environment?: BaseEnvironment);
|
|
16
55
|
/**
|
|
17
56
|
* Store a module under the namespace key
|
|
18
57
|
* @param meta
|
|
19
58
|
* @param generator - A generator module or a module path
|
|
59
|
+
* @param options.env - The environment to bind the returned meta to
|
|
60
|
+
*/
|
|
61
|
+
add<M extends BaseGeneratorMeta>(meta: M, Generator?: unknown, { env }?: StoreEnvironmentOptions): StoreGeneratorMeta & M;
|
|
62
|
+
/**
|
|
63
|
+
* The meta bound to an environment: its functions default to it. The meta of the environment the store was created
|
|
64
|
+
* with is the meta itself, as it already defaults to it. A meta is bound once by environment, and delegates to the
|
|
65
|
+
* meta of the store instead of copying it, so it keeps showing what the store has.
|
|
66
|
+
*/
|
|
67
|
+
bindMeta<M extends StoreGeneratorMeta>(meta: M, options?: StoreEnvironmentOptions): M;
|
|
68
|
+
bindMeta<M extends StoreGeneratorMeta>(meta: M | undefined, options?: StoreEnvironmentOptions): M | undefined;
|
|
69
|
+
/**
|
|
70
|
+
* Search for generators and their sub generators, and add them to the store.
|
|
71
|
+
*
|
|
72
|
+
* A generator is a `:lookup/:name/index.js` file placed inside an npm package.
|
|
73
|
+
*
|
|
74
|
+
* Defaults lookups are:
|
|
75
|
+
* - ./
|
|
76
|
+
* - generators/
|
|
77
|
+
* - lib/generators/
|
|
78
|
+
*
|
|
79
|
+
* So this index file `node_modules/generator-dummy/lib/generators/yo/index.js` would be
|
|
80
|
+
* registered as `dummy:yo` generator.
|
|
20
81
|
*/
|
|
21
|
-
|
|
82
|
+
lookupSync(options?: StoreLookupOptions): StoreLookupGeneratorMeta[];
|
|
22
83
|
/**
|
|
23
84
|
* Get the module registered under the given namespace
|
|
24
85
|
* @param {String} namespace
|
|
86
|
+
* @param options.env - The environment to import the generator in
|
|
25
87
|
* @return {Module}
|
|
26
88
|
*/
|
|
27
|
-
get(namespace: string): Promise<GetGeneratorConstructor | undefined>;
|
|
89
|
+
get(namespace: string, options?: StoreEnvironmentOptions): Promise<GetGeneratorConstructor | undefined>;
|
|
28
90
|
/**
|
|
29
|
-
* Get the
|
|
91
|
+
* Get the meta registered under the given namespace
|
|
30
92
|
* @param {String} namespace
|
|
31
|
-
* @
|
|
93
|
+
* @param options.env - The environment to bind the meta to
|
|
32
94
|
*/
|
|
33
|
-
getMeta(namespace: string):
|
|
95
|
+
getMeta(namespace: string, options?: StoreEnvironmentOptions): StoreGeneratorMeta | undefined;
|
|
34
96
|
/**
|
|
35
97
|
* Returns the list of registered namespace.
|
|
36
98
|
* @return {Array} Namespaces array
|
|
@@ -38,9 +100,10 @@ export default class Store {
|
|
|
38
100
|
namespaces(): string[];
|
|
39
101
|
/**
|
|
40
102
|
* Get the stored generators meta data
|
|
103
|
+
* @param options.env - The environment to bind the metas to
|
|
41
104
|
* @return {Object} Generators metadata
|
|
42
105
|
*/
|
|
43
|
-
getGeneratorsMeta(): Record<string,
|
|
106
|
+
getGeneratorsMeta(options?: StoreEnvironmentOptions): Record<string, StoreGeneratorMeta>;
|
|
44
107
|
/**
|
|
45
108
|
* Store a package under the namespace key
|
|
46
109
|
* @param {String} packageNS - The key under which the generator can be retrieved
|
|
@@ -66,6 +129,11 @@ export default class Store {
|
|
|
66
129
|
* Read a package.json from packagePath. Parsed results are cached by packagePath.
|
|
67
130
|
*/
|
|
68
131
|
private getPackageJson;
|
|
132
|
+
/**
|
|
133
|
+
* The functions of a meta that need an environment, defaulting to the given one.
|
|
134
|
+
*/
|
|
135
|
+
private bindMetaFunctions;
|
|
69
136
|
private getFactory;
|
|
70
137
|
private _getGenerator;
|
|
71
138
|
}
|
|
139
|
+
export {};
|
package/dist/store.js
CHANGED
|
@@ -7,19 +7,25 @@ var __rewriteRelativeImportExtension = (this && this.__rewriteRelativeImportExte
|
|
|
7
7
|
return path;
|
|
8
8
|
};
|
|
9
9
|
import { pathToFileURL } from 'node:url';
|
|
10
|
-
import { extname, join } from 'node:path';
|
|
10
|
+
import { basename, extname, join, relative } from 'node:path';
|
|
11
11
|
import { createRequire } from 'node:module';
|
|
12
|
-
import { readFileSync } from 'node:fs';
|
|
12
|
+
import { readFileSync, realpathSync } from 'node:fs';
|
|
13
13
|
import { toNamespace } from '@yeoman/namespace';
|
|
14
14
|
import createDebug from 'debug';
|
|
15
|
+
import { lookupGeneratorsSync } from "./generator-lookup.js";
|
|
16
|
+
import { asNamespace, defaultLookups } from "./util/namespace.js";
|
|
15
17
|
const debug = createDebug('yeoman:environment:store');
|
|
16
18
|
const require = createRequire(import.meta.url);
|
|
17
19
|
/**
|
|
18
20
|
* The Generator store
|
|
19
21
|
* This is used to store generator (npm packages) reference and instantiate them when
|
|
20
22
|
* requested.
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
+
*
|
|
24
|
+
* A store does not need an environment: generators are looked up and registered on their own, and the environment
|
|
25
|
+
* that imports or instantiates one is passed at that time. The same store can then serve several environments: the
|
|
26
|
+
* methods returning metas take `{ env }` and return metas bound to it.
|
|
27
|
+
*
|
|
28
|
+
* @experimental The Store API is not stable yet and may change in a minor release.
|
|
23
29
|
*/
|
|
24
30
|
export default class Store {
|
|
25
31
|
_meta = {};
|
|
@@ -29,6 +35,9 @@ export default class Store {
|
|
|
29
35
|
_packagesPaths = {};
|
|
30
36
|
// Store packages ns
|
|
31
37
|
_packagesNS = [];
|
|
38
|
+
// Metas bound to an environment, by the meta of the store.
|
|
39
|
+
_boundMetas = new WeakMap();
|
|
40
|
+
/** The environment used when none is passed to `importGenerator` or `instantiate`. */
|
|
32
41
|
environment;
|
|
33
42
|
constructor(environment) {
|
|
34
43
|
this.environment = environment;
|
|
@@ -37,8 +46,9 @@ export default class Store {
|
|
|
37
46
|
* Store a module under the namespace key
|
|
38
47
|
* @param meta
|
|
39
48
|
* @param generator - A generator module or a module path
|
|
49
|
+
* @param options.env - The environment to bind the returned meta to
|
|
40
50
|
*/
|
|
41
|
-
add(meta, Generator) {
|
|
51
|
+
add(meta, Generator, { env } = {}) {
|
|
42
52
|
if (typeof meta.resolved === 'string') {
|
|
43
53
|
if (extname(meta.resolved)) {
|
|
44
54
|
meta.resolved = join(meta.resolved);
|
|
@@ -74,52 +84,72 @@ export default class Store {
|
|
|
74
84
|
}
|
|
75
85
|
};
|
|
76
86
|
}
|
|
77
|
-
let
|
|
87
|
+
let moduleImport;
|
|
88
|
+
const importGeneratorModule = () => {
|
|
89
|
+
if (!importModule || Generator) {
|
|
90
|
+
return undefined;
|
|
91
|
+
}
|
|
92
|
+
if (moduleImport) {
|
|
93
|
+
return moduleImport;
|
|
94
|
+
}
|
|
95
|
+
const maybeModule = importModule();
|
|
96
|
+
if (maybeModule.then) {
|
|
97
|
+
moduleImport = maybeModule
|
|
98
|
+
.then((module_) => {
|
|
99
|
+
Generator = module_;
|
|
100
|
+
})
|
|
101
|
+
.finally(() => {
|
|
102
|
+
moduleImport = undefined;
|
|
103
|
+
});
|
|
104
|
+
return moduleImport;
|
|
105
|
+
}
|
|
106
|
+
Generator = maybeModule;
|
|
107
|
+
return undefined;
|
|
108
|
+
};
|
|
78
109
|
// eslint-disable-next-line prefer-const
|
|
79
110
|
let generatorMeta;
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
Generator = maybeModule;
|
|
96
|
-
}
|
|
97
|
-
}
|
|
98
|
-
};
|
|
99
|
-
const handleModule = () => {
|
|
100
|
-
const factory = this.getFactory(Generator);
|
|
101
|
-
if (typeof factory === 'function') {
|
|
102
|
-
importPromise = factory(this.environment);
|
|
103
|
-
return Promise.resolve(importPromise).then((mod) => {
|
|
104
|
-
const generator = this._getGenerator(mod, meta, generatorMeta);
|
|
105
|
-
Generator = generator;
|
|
106
|
-
importPromise = undefined;
|
|
107
|
-
return generator;
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
|
-
return this._getGenerator(Generator, meta, generatorMeta);
|
|
111
|
-
};
|
|
112
|
-
if (importPromise) {
|
|
113
|
-
return importPromise.then(() => Promise.resolve(handleImport()).then(() => handleModule()));
|
|
111
|
+
// A generator exported as a class is the same for every environment.
|
|
112
|
+
let generatorConstructor;
|
|
113
|
+
// A `createGenerator(environment)` factory builds the generator for an environment, so its result is kept by environment.
|
|
114
|
+
const createdGenerators = new WeakMap();
|
|
115
|
+
const getGenerator = (environment) => {
|
|
116
|
+
if (generatorConstructor) {
|
|
117
|
+
return generatorConstructor;
|
|
118
|
+
}
|
|
119
|
+
const factory = this.getFactory(Generator);
|
|
120
|
+
if (typeof factory !== 'function') {
|
|
121
|
+
generatorConstructor = this._getGenerator(Generator, meta, generatorMeta);
|
|
122
|
+
return generatorConstructor;
|
|
123
|
+
}
|
|
124
|
+
if (!environment) {
|
|
125
|
+
throw new Error(`An environment is required to create the generator ${meta.namespace}`);
|
|
114
126
|
}
|
|
115
|
-
const
|
|
116
|
-
if (
|
|
117
|
-
return
|
|
127
|
+
const created = createdGenerators.get(environment);
|
|
128
|
+
if (created) {
|
|
129
|
+
return created;
|
|
130
|
+
}
|
|
131
|
+
const creating = Promise.resolve(factory(environment)).then((module_) => {
|
|
132
|
+
const generator = this._getGenerator(module_, meta, generatorMeta);
|
|
133
|
+
createdGenerators.set(environment, generator);
|
|
134
|
+
return generator;
|
|
135
|
+
});
|
|
136
|
+
createdGenerators.set(environment, creating);
|
|
137
|
+
return creating;
|
|
138
|
+
};
|
|
139
|
+
const importGenerator = (({ env: environment = this.environment } = {}) => {
|
|
140
|
+
const importing = importGeneratorModule();
|
|
141
|
+
return importing ? importing.then(() => getGenerator(environment)) : getGenerator(environment);
|
|
142
|
+
});
|
|
143
|
+
const instantiate = async (arguments_ = [], options = {}, { env: environment = this.environment } = {}) => {
|
|
144
|
+
if (!environment) {
|
|
145
|
+
throw new Error(`An environment is required to instantiate the generator ${meta.namespace}`);
|
|
118
146
|
}
|
|
119
|
-
return
|
|
147
|
+
return environment.instantiate(await importGenerator({ env: environment }), {
|
|
148
|
+
generatorArgs: arguments_,
|
|
149
|
+
generatorOptions: options,
|
|
150
|
+
});
|
|
120
151
|
};
|
|
121
|
-
const
|
|
122
|
-
const instantiateHelp = async () => instantiate([], { help: true });
|
|
152
|
+
const instantiateHelp = async (options) => instantiate([], { help: true }, options);
|
|
123
153
|
const getPackageJson = () => this.getPackageJson(meta.packagePath);
|
|
124
154
|
const { packageNamespace } = toNamespace(meta.namespace) ?? {};
|
|
125
155
|
generatorMeta = {
|
|
@@ -138,23 +168,91 @@ export default class Store {
|
|
|
138
168
|
this.addPackage(packageNamespace, meta.packagePath);
|
|
139
169
|
}
|
|
140
170
|
}
|
|
141
|
-
return generatorMeta;
|
|
171
|
+
return this.bindMeta(generatorMeta, { env });
|
|
172
|
+
}
|
|
173
|
+
bindMeta(meta, { env } = {}) {
|
|
174
|
+
if (!meta || !env || env === this.environment) {
|
|
175
|
+
return meta;
|
|
176
|
+
}
|
|
177
|
+
let boundMetas = this._boundMetas.get(meta);
|
|
178
|
+
if (!boundMetas) {
|
|
179
|
+
boundMetas = new WeakMap();
|
|
180
|
+
this._boundMetas.set(meta, boundMetas);
|
|
181
|
+
}
|
|
182
|
+
let boundMeta = boundMetas.get(env);
|
|
183
|
+
if (!boundMeta) {
|
|
184
|
+
boundMeta = Object.assign(Object.create(meta), this.bindMetaFunctions(meta, env));
|
|
185
|
+
boundMetas.set(env, boundMeta);
|
|
186
|
+
}
|
|
187
|
+
return boundMeta;
|
|
188
|
+
}
|
|
189
|
+
/**
|
|
190
|
+
* Search for generators and their sub generators, and add them to the store.
|
|
191
|
+
*
|
|
192
|
+
* A generator is a `:lookup/:name/index.js` file placed inside an npm package.
|
|
193
|
+
*
|
|
194
|
+
* Defaults lookups are:
|
|
195
|
+
* - ./
|
|
196
|
+
* - generators/
|
|
197
|
+
* - lib/generators/
|
|
198
|
+
*
|
|
199
|
+
* So this index file `node_modules/generator-dummy/lib/generators/yo/index.js` would be
|
|
200
|
+
* registered as `dummy:yo` generator.
|
|
201
|
+
*/
|
|
202
|
+
lookupSync(options) {
|
|
203
|
+
const { registerToScope, customizeNamespace = (ns) => ns, filter, env, lookups = defaultLookups, ...remainingOptions } = options ?? { localOnly: false };
|
|
204
|
+
const lookupOptions = { ...remainingOptions, lookups };
|
|
205
|
+
const generators = [];
|
|
206
|
+
lookupGeneratorsSync(lookupOptions, ({ packagePath, filePath, lookups }) => {
|
|
207
|
+
let repositoryPath = join(packagePath, '..');
|
|
208
|
+
if (basename(repositoryPath).startsWith('@')) {
|
|
209
|
+
// Scoped package
|
|
210
|
+
repositoryPath = join(repositoryPath, '..');
|
|
211
|
+
}
|
|
212
|
+
let namespace = customizeNamespace(asNamespace(relative(repositoryPath, filePath), { lookups }));
|
|
213
|
+
try {
|
|
214
|
+
const resolved = realpathSync(filePath);
|
|
215
|
+
if (!namespace) {
|
|
216
|
+
namespace = customizeNamespace(asNamespace(resolved, { lookups }));
|
|
217
|
+
}
|
|
218
|
+
namespace = namespace;
|
|
219
|
+
if (registerToScope && !namespace.startsWith('@')) {
|
|
220
|
+
namespace = `@${registerToScope}/${namespace}`;
|
|
221
|
+
}
|
|
222
|
+
if (filter && !filter({ namespace, filePath, packagePath })) {
|
|
223
|
+
return false;
|
|
224
|
+
}
|
|
225
|
+
const meta = this.add({ namespace, packagePath, resolved });
|
|
226
|
+
if (meta) {
|
|
227
|
+
const boundFunctions = env && env !== this.environment ? this.bindMetaFunctions(meta, env) : {};
|
|
228
|
+
generators.push({ ...meta, ...boundFunctions, filePath, packagePath, registered: true });
|
|
229
|
+
return Boolean(lookupOptions.singleResult);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
catch (error) {
|
|
233
|
+
console.error('Unable to register %s (Error: %s)', filePath, error);
|
|
234
|
+
}
|
|
235
|
+
generators.push({ resolved: filePath, namespace: namespace, filePath, packagePath, registered: false });
|
|
236
|
+
return false;
|
|
237
|
+
});
|
|
238
|
+
return generators;
|
|
142
239
|
}
|
|
143
240
|
/**
|
|
144
241
|
* Get the module registered under the given namespace
|
|
145
242
|
* @param {String} namespace
|
|
243
|
+
* @param options.env - The environment to import the generator in
|
|
146
244
|
* @return {Module}
|
|
147
245
|
*/
|
|
148
|
-
async get(namespace) {
|
|
149
|
-
return this.getMeta(namespace)?.importGenerator();
|
|
246
|
+
async get(namespace, options) {
|
|
247
|
+
return this.getMeta(namespace)?.importGenerator(options);
|
|
150
248
|
}
|
|
151
249
|
/**
|
|
152
|
-
* Get the
|
|
250
|
+
* Get the meta registered under the given namespace
|
|
153
251
|
* @param {String} namespace
|
|
154
|
-
* @
|
|
252
|
+
* @param options.env - The environment to bind the meta to
|
|
155
253
|
*/
|
|
156
|
-
getMeta(namespace) {
|
|
157
|
-
return this._meta[namespace];
|
|
254
|
+
getMeta(namespace, options) {
|
|
255
|
+
return this.bindMeta(this._meta[namespace], options);
|
|
158
256
|
}
|
|
159
257
|
/**
|
|
160
258
|
* Returns the list of registered namespace.
|
|
@@ -165,10 +263,22 @@ export default class Store {
|
|
|
165
263
|
}
|
|
166
264
|
/**
|
|
167
265
|
* Get the stored generators meta data
|
|
266
|
+
* @param options.env - The environment to bind the metas to
|
|
168
267
|
* @return {Object} Generators metadata
|
|
169
268
|
*/
|
|
170
|
-
getGeneratorsMeta() {
|
|
171
|
-
|
|
269
|
+
getGeneratorsMeta(options) {
|
|
270
|
+
const env = options?.env;
|
|
271
|
+
if (!env || env === this.environment) {
|
|
272
|
+
return this._meta;
|
|
273
|
+
}
|
|
274
|
+
// Bound as they are read. It is a view of the record of the store, as the record itself is what is returned
|
|
275
|
+
// otherwise: what is set goes to the store.
|
|
276
|
+
return new Proxy(this._meta, {
|
|
277
|
+
get: (target, property, receiver) => {
|
|
278
|
+
const meta = Reflect.get(target, property, receiver);
|
|
279
|
+
return typeof property === 'string' && meta ? this.bindMeta(meta, { env }) : meta;
|
|
280
|
+
},
|
|
281
|
+
});
|
|
172
282
|
}
|
|
173
283
|
/**
|
|
174
284
|
* Store a package under the namespace key
|
|
@@ -235,6 +345,16 @@ export default class Store {
|
|
|
235
345
|
}
|
|
236
346
|
return this._packagesJson.get(packagePath);
|
|
237
347
|
}
|
|
348
|
+
/**
|
|
349
|
+
* The functions of a meta that need an environment, defaulting to the given one.
|
|
350
|
+
*/
|
|
351
|
+
bindMetaFunctions(meta, env) {
|
|
352
|
+
return {
|
|
353
|
+
importGenerator: (({ env: environment = env } = {}) => meta.importGenerator({ env: environment })),
|
|
354
|
+
instantiate: (arguments_, options, { env: environment = env } = {}) => meta.instantiate(arguments_, options, { env: environment }),
|
|
355
|
+
instantiateHelp: ({ env: environment = env } = {}) => meta.instantiateHelp({ env: environment }),
|
|
356
|
+
};
|
|
357
|
+
}
|
|
238
358
|
getFactory(module) {
|
|
239
359
|
// CJS is imported in default, for backward compatibility we support a Generator exported as `module.exports = { default }`
|
|
240
360
|
return module.createGenerator ?? module.default?.createGenerator ?? module.default?.default?.createGenerator;
|