yeoman-environment 8.0.0 → 8.2.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 +19 -4
- package/dist/environment-base.js +13 -13
- package/dist/environment-full.js +8 -3
- package/dist/generator-lookup.d.ts +7 -2
- package/dist/generator-lookup.js +4 -4
- package/dist/index.d.ts +5 -5
- package/dist/index.js +4 -4
- package/dist/store.d.ts +52 -1
- package/dist/store.js +121 -21
- package/dist/util/namespace.d.ts +11 -1
- package/dist/util/namespace.js +19 -4
- 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, { type StoreGeneratorMeta } from './store.ts';
|
|
12
|
+
import Store, { type StoreGeneratorMeta, type StoreSharedLookupOptions } 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';
|
|
@@ -26,10 +26,22 @@ export type EnvironmentOptions = ConflicterOptions & BaseEnvironmentOptions & Om
|
|
|
26
26
|
yeomanRepository?: string;
|
|
27
27
|
arboristRegistry?: string;
|
|
28
28
|
nodePackageManager?: string;
|
|
29
|
+
/**
|
|
30
|
+
* @deprecated Use `storeOptions`.
|
|
31
|
+
*/
|
|
29
32
|
generatorLookupOptions?: Pick<EnvironmentLookupOptions, 'customizeNamespace' | 'lookups'>;
|
|
30
33
|
/**
|
|
31
|
-
*
|
|
32
|
-
*
|
|
34
|
+
* Lookup options of the store of the environment, shared by every lookup. When a `store` is passed, the
|
|
35
|
+
* environment works on a clone of it with these options merged over its own.
|
|
36
|
+
*
|
|
37
|
+
* @experimental
|
|
38
|
+
*/
|
|
39
|
+
storeOptions?: StoreSharedLookupOptions;
|
|
40
|
+
/**
|
|
41
|
+
* Generators store to start from instead of an empty one, to reuse the generators already looked up and registered
|
|
42
|
+
* in it. The environment works on a clone of it (see `Store#clone`), with `storeOptions` merged over its options:
|
|
43
|
+
* the generators the environment looks up or registers are added to its clone only, they are neither added to the
|
|
44
|
+
* store passed nor seen by the other environments created with it. The generators already imported are shared.
|
|
33
45
|
*
|
|
34
46
|
* @experimental
|
|
35
47
|
*/
|
|
@@ -57,7 +69,6 @@ export default class EnvironmentBase extends EventEmitter implements BaseEnviron
|
|
|
57
69
|
protected command?: YeomanCommand;
|
|
58
70
|
protected runLoop: GroupedQueue;
|
|
59
71
|
protected composedStore: ComposedStore;
|
|
60
|
-
protected lookups: string[];
|
|
61
72
|
protected repository: FlyRepository;
|
|
62
73
|
protected experimental: boolean;
|
|
63
74
|
protected _rootGenerator?: BaseGenerator;
|
|
@@ -141,6 +152,10 @@ export default class EnvironmentBase extends EventEmitter implements BaseEnviron
|
|
|
141
152
|
* @param {Array} lookups paths
|
|
142
153
|
*/
|
|
143
154
|
namespace(filepath: string, lookups?: string[]): string;
|
|
155
|
+
/**
|
|
156
|
+
* @deprecated Use the lookups of the store.
|
|
157
|
+
*/
|
|
158
|
+
protected get lookups(): string[];
|
|
144
159
|
/**
|
|
145
160
|
* Returns the environment or dependency version.
|
|
146
161
|
* @param {String} packageName - Module to get version.
|
package/dist/environment-base.js
CHANGED
|
@@ -16,7 +16,6 @@ import {} from '@yeoman/conflicter';
|
|
|
16
16
|
import { defaults, pick } from 'lodash-es';
|
|
17
17
|
import { ComposedStore } from "./composed-store.js";
|
|
18
18
|
import Store, {} from "./store.js";
|
|
19
|
-
import { asNamespace, defaultLookups } from "./util/namespace.js";
|
|
20
19
|
import {} from "./generator-lookup.js";
|
|
21
20
|
import { UNKNOWN_NAMESPACE, UNKNOWN_RESOLVED, defaultQueues } from "./constants.js";
|
|
22
21
|
import { resolveModulePath } from "./util/resolve.js";
|
|
@@ -97,7 +96,6 @@ export default class EnvironmentBase extends EventEmitter {
|
|
|
97
96
|
command;
|
|
98
97
|
runLoop;
|
|
99
98
|
composedStore;
|
|
100
|
-
lookups;
|
|
101
99
|
repository;
|
|
102
100
|
experimental;
|
|
103
101
|
_rootGenerator;
|
|
@@ -106,12 +104,14 @@ export default class EnvironmentBase extends EventEmitter {
|
|
|
106
104
|
constructor(options = {}) {
|
|
107
105
|
super();
|
|
108
106
|
this.setMaxListeners(100);
|
|
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;
|
|
107
|
+
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, storeOptions, ...remainingOptions } = options;
|
|
110
108
|
this.options = remainingOptions;
|
|
111
109
|
this.adapter = adapter;
|
|
112
110
|
this.cwd = resolve(cwd);
|
|
113
111
|
this.logCwd = logCwd;
|
|
114
|
-
|
|
112
|
+
const lookupOptions = { ...remainingOptions.generatorLookupOptions, ...storeOptions };
|
|
113
|
+
// A store passed is cloned: what this environment looks up or registers does not change it.
|
|
114
|
+
this.store = store ? store.clone(this, lookupOptions) : new Store(this, lookupOptions);
|
|
115
115
|
this.command = command;
|
|
116
116
|
this.runLoop = new GroupedQueue(defaultQueues, false);
|
|
117
117
|
this.composedStore = new ComposedStore({ log: this.adapter.log });
|
|
@@ -120,7 +120,6 @@ export default class EnvironmentBase extends EventEmitter {
|
|
|
120
120
|
// Node won't complain about event listeners leaks.
|
|
121
121
|
this.runLoop.setMaxListeners(0);
|
|
122
122
|
this.sharedFs.setMaxListeners(0);
|
|
123
|
-
this.lookups = this.options.generatorLookupOptions?.lookups ?? defaultLookups;
|
|
124
123
|
this.sharedOptions = sharedOptions;
|
|
125
124
|
// Create a default sharedData.
|
|
126
125
|
this.sharedOptions.sharedData = this.sharedOptions.sharedData ?? {};
|
|
@@ -297,8 +296,14 @@ export default class EnvironmentBase extends EventEmitter {
|
|
|
297
296
|
* @param {String} filepath
|
|
298
297
|
* @param {Array} lookups paths
|
|
299
298
|
*/
|
|
300
|
-
namespace(filepath, lookups
|
|
301
|
-
return
|
|
299
|
+
namespace(filepath, lookups) {
|
|
300
|
+
return this.store.namespace(filepath, lookups);
|
|
301
|
+
}
|
|
302
|
+
/**
|
|
303
|
+
* @deprecated Use the lookups of the store.
|
|
304
|
+
*/
|
|
305
|
+
get lookups() {
|
|
306
|
+
return this.store.lookups;
|
|
302
307
|
}
|
|
303
308
|
getVersion(packageName) {
|
|
304
309
|
if (packageName && packageName !== 'yeoman-environment') {
|
|
@@ -426,12 +431,7 @@ export default class EnvironmentBase extends EventEmitter {
|
|
|
426
431
|
* registered as `dummy:yo` generator.
|
|
427
432
|
*/
|
|
428
433
|
async lookup(options) {
|
|
429
|
-
return this.store.lookupSync({
|
|
430
|
-
customizeNamespace: this.options.generatorLookupOptions?.customizeNamespace,
|
|
431
|
-
lookups: this.lookups,
|
|
432
|
-
...(options ?? { localOnly: false }),
|
|
433
|
-
env: this.asEnvironment(),
|
|
434
|
-
});
|
|
434
|
+
return this.store.lookupSync({ ...options, env: this.asEnvironment() });
|
|
435
435
|
}
|
|
436
436
|
/**
|
|
437
437
|
* Verify if a package namespace already have been registered.
|
package/dist/environment-full.js
CHANGED
|
@@ -8,6 +8,7 @@ import {} from "./generator-lookup.js";
|
|
|
8
8
|
import YeomanCommand from "./util/command.js";
|
|
9
9
|
import EnvironmentBase, {} from "./environment-base.js";
|
|
10
10
|
import { splitArgsFromString as splitArgumentsFromString } from "./util/util.js";
|
|
11
|
+
import { nestedGeneratorsFolder } from "./util/namespace.js";
|
|
11
12
|
class FullEnvironment extends EnvironmentBase {
|
|
12
13
|
constructor(options = {}, adapterCompat) {
|
|
13
14
|
if (adapterCompat) {
|
|
@@ -262,9 +263,13 @@ class FullEnvironment extends EnvironmentBase {
|
|
|
262
263
|
const nsOptions = { packagePatterns: [ns.generatorHint] };
|
|
263
264
|
if (ns.generator) {
|
|
264
265
|
// Build filePatterns to look specifically for the namespace.
|
|
265
|
-
const
|
|
266
|
-
|
|
267
|
-
|
|
266
|
+
const generatorPaths = [ns.generator.split(':').join('/')];
|
|
267
|
+
if (options.nestedGenerators ?? this.store.lookupOptions.nestedGenerators) {
|
|
268
|
+
// `app:sub` is `app/generators/sub` too.
|
|
269
|
+
generatorPaths.push(ns.generator.split(':').join(`/${nestedGeneratorsFolder}/`));
|
|
270
|
+
}
|
|
271
|
+
let filePatterns = generatorPaths.flatMap(genPath => [`${genPath}/index.?s`, `${genPath}.?s`]);
|
|
272
|
+
const lookups = options.lookups ?? this.store.lookups;
|
|
268
273
|
filePatterns = lookups.flatMap(prefix => filePatterns.map(pattern => join(prefix, pattern)));
|
|
269
274
|
nsOptions.filePatterns = filePatterns;
|
|
270
275
|
nsOptions.singleResult = true;
|
|
@@ -2,6 +2,11 @@ import type { LookupOptions as LookupOptionsApi } from '@yeoman/types';
|
|
|
2
2
|
import { type ModuleLookupOptions } from './module-lookup.ts';
|
|
3
3
|
export type LookupOptions = LookupOptionsApi & ModuleLookupOptions & {
|
|
4
4
|
lookups?: string[];
|
|
5
|
+
/**
|
|
6
|
+
* Look up the generators nested in the `generators` folder of a generator too:
|
|
7
|
+
* `generators/server/generators/cache` is registered as `server:cache`.
|
|
8
|
+
*/
|
|
9
|
+
nestedGenerators?: boolean;
|
|
5
10
|
};
|
|
6
11
|
type LookupMeta = {
|
|
7
12
|
filePath: string;
|
|
@@ -49,13 +54,13 @@ export declare function lookupGeneratorsSync(options?: LookupOptions, register?:
|
|
|
49
54
|
* @param {Boolean} [options.singleResult=true] - Set false to return multiple values.
|
|
50
55
|
* @return {String} generator
|
|
51
56
|
*/
|
|
52
|
-
export declare function lookupGenerator(namespace: string, options?: ModuleLookupOptions & {
|
|
57
|
+
export declare function lookupGenerator(namespace: string, options?: ModuleLookupOptions & Pick<LookupOptions, 'nestedGenerators'> & {
|
|
53
58
|
packagePath?: boolean;
|
|
54
59
|
generatorPath?: boolean;
|
|
55
60
|
} & {
|
|
56
61
|
singleResult?: true;
|
|
57
62
|
}): string;
|
|
58
|
-
export declare function lookupGenerator(namespace: string, options?: ModuleLookupOptions & {
|
|
63
|
+
export declare function lookupGenerator(namespace: string, options?: ModuleLookupOptions & Pick<LookupOptions, 'nestedGenerators'> & {
|
|
59
64
|
packagePath?: boolean;
|
|
60
65
|
generatorPath?: boolean;
|
|
61
66
|
} & {
|
package/dist/generator-lookup.js
CHANGED
|
@@ -2,7 +2,7 @@ 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 { defaultLookups } from "./util/namespace.js";
|
|
5
|
+
import { defaultLookups, withNestedLookups } from "./util/namespace.js";
|
|
6
6
|
import Store from "./store.js";
|
|
7
7
|
export const defaultExtensions = ['.ts', '.cts', '.mts', '.js', '.cjs', '.mjs'];
|
|
8
8
|
/**
|
|
@@ -30,10 +30,10 @@ export const defaultExtensions = ['.ts', '.cts', '.mts', '.js', '.cjs', '.mjs'];
|
|
|
30
30
|
* @return {Object[]} List of generators
|
|
31
31
|
*/
|
|
32
32
|
export function lookupGeneratorsSync(options = {}, register) {
|
|
33
|
-
const { lookups = defaultLookups } = options;
|
|
33
|
+
const { lookups = defaultLookups, nestedGenerators } = options;
|
|
34
34
|
options = {
|
|
35
35
|
// Js generators should be after, last will override registered one.
|
|
36
|
-
filePatterns: lookups.flatMap(prefix => defaultExtensions.map(extension => `${prefix}/*/index${extension}`)),
|
|
36
|
+
filePatterns: withNestedLookups(lookups, nestedGenerators).flatMap(prefix => defaultExtensions.map(extension => `${prefix}/*/index${extension}`)),
|
|
37
37
|
filterPaths: false,
|
|
38
38
|
packagePatterns: ['generator-*'],
|
|
39
39
|
reverse: !options.singleResult,
|
|
@@ -64,7 +64,7 @@ export function lookupGenerator(namespace, options) {
|
|
|
64
64
|
}
|
|
65
65
|
// The generators are looked up by a store of their own, which keeps the ones asked for.
|
|
66
66
|
const generators = new Store().lookupSync({
|
|
67
|
-
filePatterns: defaultLookups.map(prefix => join(prefix, '*/index.{js,ts}')),
|
|
67
|
+
filePatterns: withNestedLookups(defaultLookups, lookupOptions.nestedGenerators).map(prefix => join(prefix, '*/index.{js,ts}')),
|
|
68
68
|
reverse: false,
|
|
69
69
|
...lookupOptions,
|
|
70
70
|
lookups: defaultLookups,
|
package/dist/index.d.ts
CHANGED
|
@@ -2,11 +2,11 @@ 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
|
+
export { default as Store, type StoreEnvironmentOptions, type StoreGeneratorMeta, type StoreLookupGeneratorMeta, type StoreLookupOptions, type StoreSharedLookupOptions, } from './store.ts';
|
|
6
6
|
export declare const createEnv: (options?: EnvironmentOptions) => Environment;
|
|
7
7
|
export declare const enforceUpdate: () => void;
|
|
8
|
-
export
|
|
9
|
-
export
|
|
10
|
-
export
|
|
11
|
-
export
|
|
8
|
+
export { type CommandPreparation, prepareCommand, prepareGeneratorCommand } from './commands.ts';
|
|
9
|
+
export { addEnvironmentOptions } from './util/command.ts';
|
|
10
|
+
export { type InstallTask, type PackageManagerInstallTaskOptions, packageManagerInstallTask } from './package-manager.ts';
|
|
11
|
+
export { commitSharedFsTask } from './commit.ts';
|
|
12
12
|
export { lookupGenerator } from './generator-lookup.ts';
|
package/dist/index.js
CHANGED
|
@@ -7,8 +7,8 @@ export { default as Store, } from "./store.js";
|
|
|
7
7
|
export const createEnv = (options) => new Environment(options);
|
|
8
8
|
// Backward compatibility
|
|
9
9
|
export const enforceUpdate = () => { };
|
|
10
|
-
export
|
|
11
|
-
export
|
|
12
|
-
export
|
|
13
|
-
export
|
|
10
|
+
export { prepareCommand, prepareGeneratorCommand } from "./commands.js";
|
|
11
|
+
export { addEnvironmentOptions } from "./util/command.js";
|
|
12
|
+
export { packageManagerInstallTask } from "./package-manager.js";
|
|
13
|
+
export { commitSharedFsTask } from "./commit.js";
|
|
14
14
|
export { lookupGenerator } from "./generator-lookup.js";
|
package/dist/store.d.ts
CHANGED
|
@@ -13,9 +13,16 @@ export type StoreEnvironmentOptions = {
|
|
|
13
13
|
export type StoreLookupOptions = LookupOptions & StoreEnvironmentOptions & {
|
|
14
14
|
registerToScope?: string;
|
|
15
15
|
customizeNamespace?: (ns?: string) => string | undefined;
|
|
16
|
+
/** Derive the namespace from the name in the package.json instead of the package directory. */
|
|
17
|
+
usePackageName?: boolean;
|
|
16
18
|
/** Generators to keep, the others are neither registered nor returned. */
|
|
17
19
|
filter?: (generator: FoundGenerator) => boolean;
|
|
18
20
|
};
|
|
21
|
+
/**
|
|
22
|
+
* Lookup options shared by every lookup of a store, the options passed to a lookup take precedence.
|
|
23
|
+
* What to look up (paths, package patterns and `singleResult`) is given by each lookup.
|
|
24
|
+
*/
|
|
25
|
+
export type StoreSharedLookupOptions = Pick<StoreLookupOptions, 'lookups' | 'localOnly' | 'filterPaths' | 'customizeNamespace' | 'filter' | 'usePackageName' | 'nestedGenerators'>;
|
|
19
26
|
/** A generator found by a lookup, `registered` tells if it was added to the store. */
|
|
20
27
|
export type StoreLookupGeneratorMeta = FoundGenerator & ((StoreGeneratorMeta & {
|
|
21
28
|
registered: true;
|
|
@@ -53,7 +60,39 @@ export default class Store {
|
|
|
53
60
|
private readonly _boundMetas;
|
|
54
61
|
/** The environment used when none is passed to `importGenerator` or `instantiate`. */
|
|
55
62
|
readonly environment?: BaseEnvironment;
|
|
56
|
-
|
|
63
|
+
/** Options passed to every lookup, so consecutive lookups share the same configuration. */
|
|
64
|
+
readonly lookupOptions: StoreSharedLookupOptions;
|
|
65
|
+
constructor(environment?: BaseEnvironment, lookupOptions?: StoreSharedLookupOptions);
|
|
66
|
+
/** The lookups shared by the store, or the default ones. */
|
|
67
|
+
get lookups(): string[];
|
|
68
|
+
/**
|
|
69
|
+
* Given a String `filepath`, tries to figure out the relative namespace.
|
|
70
|
+
*
|
|
71
|
+
* ### Examples:
|
|
72
|
+
*
|
|
73
|
+
* store.namespace('backbone/all/index.js');
|
|
74
|
+
* // => backbone:all
|
|
75
|
+
*
|
|
76
|
+
* store.namespace('generator-backbone/model');
|
|
77
|
+
* // => backbone:model
|
|
78
|
+
*
|
|
79
|
+
* store.namespace('backbone.js');
|
|
80
|
+
* // => backbone
|
|
81
|
+
*
|
|
82
|
+
* store.namespace('generator-mocha/backbone/model/index.js');
|
|
83
|
+
* // => mocha:backbone:model
|
|
84
|
+
*
|
|
85
|
+
* @param filepath
|
|
86
|
+
* @param lookups - The lookups of the store if omitted
|
|
87
|
+
*/
|
|
88
|
+
namespace(filepath: string, lookups?: string[]): string;
|
|
89
|
+
/**
|
|
90
|
+
* A copy of the store with the same generators, packages and lookup options, whose metas default to the given
|
|
91
|
+
* environment. The generators already imported are shared, what is added after cloning goes only to its store.
|
|
92
|
+
* @param environment - The environment of the clone, the environment of the store if omitted
|
|
93
|
+
* @param options - Lookup options of the clone, merged over the ones of the store
|
|
94
|
+
*/
|
|
95
|
+
clone(environment?: BaseEnvironment | undefined, options?: StoreSharedLookupOptions): Store;
|
|
57
96
|
/**
|
|
58
97
|
* Store a module under the namespace key
|
|
59
98
|
* @param meta
|
|
@@ -65,6 +104,7 @@ export default class Store {
|
|
|
65
104
|
* The meta bound to an environment: its functions default to it. The meta of the environment the store was created
|
|
66
105
|
* with is the meta itself, as it already defaults to it. A meta is bound once by environment, and delegates to the
|
|
67
106
|
* meta of the store instead of copying it, so it keeps showing what the store has.
|
|
107
|
+
* The meta of the store a clone was created from, as the one a generator class keeps, is the meta of the clone.
|
|
68
108
|
*/
|
|
69
109
|
bindMeta<M extends StoreGeneratorMeta>(meta: M, options?: StoreEnvironmentOptions): M;
|
|
70
110
|
bindMeta<M extends StoreGeneratorMeta>(meta: M | undefined, options?: StoreEnvironmentOptions): M | undefined;
|
|
@@ -80,6 +120,8 @@ export default class Store {
|
|
|
80
120
|
*
|
|
81
121
|
* So this index file `node_modules/generator-dummy/lib/generators/yo/index.js` would be
|
|
82
122
|
* registered as `dummy:yo` generator.
|
|
123
|
+
*
|
|
124
|
+
* The options are merged over the lookup options the store was created with.
|
|
83
125
|
*/
|
|
84
126
|
lookupSync(options?: StoreLookupOptions): StoreLookupGeneratorMeta[];
|
|
85
127
|
/**
|
|
@@ -131,10 +173,19 @@ export default class Store {
|
|
|
131
173
|
* Read a package.json from packagePath. Parsed results are cached by packagePath.
|
|
132
174
|
*/
|
|
133
175
|
private getPackageJson;
|
|
176
|
+
/**
|
|
177
|
+
* The meta of the store sharing its operations with the given meta, coming from the store it was cloned from or from
|
|
178
|
+
* one of its clones, or the given meta.
|
|
179
|
+
*/
|
|
180
|
+
private ownMeta;
|
|
134
181
|
/**
|
|
135
182
|
* The functions of a meta that need an environment, defaulting to the given one.
|
|
136
183
|
*/
|
|
137
184
|
private bindMetaFunctions;
|
|
185
|
+
/**
|
|
186
|
+
* The functions of a meta, running its operations in the environment passed as `{ env }`, or the default one.
|
|
187
|
+
*/
|
|
188
|
+
private createMetaFunctions;
|
|
138
189
|
private getFactory;
|
|
139
190
|
private _getGenerator;
|
|
140
191
|
}
|
package/dist/store.js
CHANGED
|
@@ -10,12 +10,25 @@ import { pathToFileURL } from 'node:url';
|
|
|
10
10
|
import { basename, extname, join, relative } from 'node:path';
|
|
11
11
|
import { createRequire } from 'node:module';
|
|
12
12
|
import { readFileSync, realpathSync } from 'node:fs';
|
|
13
|
-
import { toNamespace } from '@yeoman/namespace';
|
|
13
|
+
import { namespaceFromPackageName, toNamespace } from '@yeoman/namespace';
|
|
14
14
|
import createDebug from 'debug';
|
|
15
15
|
import { lookupGeneratorsSync } from "./generator-lookup.js";
|
|
16
16
|
import { asNamespace, defaultLookups } from "./util/namespace.js";
|
|
17
17
|
const debug = createDebug('yeoman:environment:store');
|
|
18
18
|
const require = createRequire(import.meta.url);
|
|
19
|
+
// The operations of a meta, kept on the meta itself: the meta of a clone has the operations of the meta it was cloned from.
|
|
20
|
+
const operationsKey = Symbol('operations');
|
|
21
|
+
const setOperations = (meta, operations) => Object.defineProperty(meta, operationsKey, { value: operations });
|
|
22
|
+
const getOperations = (meta) => meta[operationsKey];
|
|
23
|
+
/** The namespace of a generator package, like `@scope/foo` for `@scope/generator-foo`, undefined for another name. */
|
|
24
|
+
const namespaceOfPackage = (packageName) => {
|
|
25
|
+
try {
|
|
26
|
+
return namespaceFromPackageName(packageName).namespace;
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
return undefined;
|
|
30
|
+
}
|
|
31
|
+
};
|
|
19
32
|
/**
|
|
20
33
|
* The Generator store
|
|
21
34
|
* This is used to store generator (npm packages) reference and instantiate them when
|
|
@@ -39,8 +52,60 @@ export default class Store {
|
|
|
39
52
|
_boundMetas = new WeakMap();
|
|
40
53
|
/** The environment used when none is passed to `importGenerator` or `instantiate`. */
|
|
41
54
|
environment;
|
|
42
|
-
|
|
55
|
+
/** Options passed to every lookup, so consecutive lookups share the same configuration. */
|
|
56
|
+
lookupOptions;
|
|
57
|
+
constructor(environment, lookupOptions = {}) {
|
|
43
58
|
this.environment = environment;
|
|
59
|
+
this.lookupOptions = lookupOptions;
|
|
60
|
+
}
|
|
61
|
+
/** The lookups shared by the store, or the default ones. */
|
|
62
|
+
get lookups() {
|
|
63
|
+
return this.lookupOptions.lookups ?? defaultLookups;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Given a String `filepath`, tries to figure out the relative namespace.
|
|
67
|
+
*
|
|
68
|
+
* ### Examples:
|
|
69
|
+
*
|
|
70
|
+
* store.namespace('backbone/all/index.js');
|
|
71
|
+
* // => backbone:all
|
|
72
|
+
*
|
|
73
|
+
* store.namespace('generator-backbone/model');
|
|
74
|
+
* // => backbone:model
|
|
75
|
+
*
|
|
76
|
+
* store.namespace('backbone.js');
|
|
77
|
+
* // => backbone
|
|
78
|
+
*
|
|
79
|
+
* store.namespace('generator-mocha/backbone/model/index.js');
|
|
80
|
+
* // => mocha:backbone:model
|
|
81
|
+
*
|
|
82
|
+
* @param filepath
|
|
83
|
+
* @param lookups - The lookups of the store if omitted
|
|
84
|
+
*/
|
|
85
|
+
namespace(filepath, lookups = this.lookups) {
|
|
86
|
+
return asNamespace(filepath, { lookups, nestedGenerators: this.lookupOptions.nestedGenerators });
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* A copy of the store with the same generators, packages and lookup options, whose metas default to the given
|
|
90
|
+
* environment. The generators already imported are shared, what is added after cloning goes only to its store.
|
|
91
|
+
* @param environment - The environment of the clone, the environment of the store if omitted
|
|
92
|
+
* @param options - Lookup options of the clone, merged over the ones of the store
|
|
93
|
+
*/
|
|
94
|
+
clone(environment = this.environment, options) {
|
|
95
|
+
const store = new Store(environment, { ...this.lookupOptions, ...options });
|
|
96
|
+
for (const [namespace, meta] of Object.entries(this._meta)) {
|
|
97
|
+
const operations = getOperations(meta);
|
|
98
|
+
store._meta[namespace] = setOperations({
|
|
99
|
+
...meta,
|
|
100
|
+
...store.createMetaFunctions(operations, () => store.environment),
|
|
101
|
+
getPackageJson: () => store.getPackageJson(meta.packagePath),
|
|
102
|
+
}, operations);
|
|
103
|
+
}
|
|
104
|
+
for (const [packageNamespace, packagePaths] of Object.entries(this._packagesPaths)) {
|
|
105
|
+
store._packagesPaths[packageNamespace] = [...packagePaths];
|
|
106
|
+
}
|
|
107
|
+
store._packagesNS.push(...this._packagesNS);
|
|
108
|
+
return store;
|
|
44
109
|
}
|
|
45
110
|
/**
|
|
46
111
|
* Store a module under the namespace key
|
|
@@ -138,32 +203,31 @@ export default class Store {
|
|
|
138
203
|
createdGenerators.set(environment, creating);
|
|
139
204
|
return creating;
|
|
140
205
|
};
|
|
141
|
-
const importGenerator = (
|
|
206
|
+
const importGenerator = (environment) => {
|
|
142
207
|
const importing = importGeneratorModule();
|
|
143
208
|
return importing ? importing.then(() => getGenerator(environment)) : getGenerator(environment);
|
|
144
|
-
}
|
|
145
|
-
const instantiate = async (arguments_ = [], options = {},
|
|
209
|
+
};
|
|
210
|
+
const instantiate = async (arguments_ = [], options = {}, environment) => {
|
|
146
211
|
if (!environment) {
|
|
147
212
|
throw new Error(`An environment is required to instantiate the generator ${meta.namespace}`);
|
|
148
213
|
}
|
|
149
|
-
return environment.instantiate(await importGenerator(
|
|
214
|
+
return environment.instantiate(await importGenerator(environment), {
|
|
150
215
|
generatorArgs: arguments_,
|
|
151
216
|
generatorOptions: options,
|
|
152
217
|
});
|
|
153
218
|
};
|
|
154
|
-
const
|
|
219
|
+
const operations = { importGenerator, instantiate };
|
|
155
220
|
const getPackageJson = () => this.getPackageJson(meta.packagePath);
|
|
156
221
|
const { packageNamespace } = toNamespace(meta.namespace) ?? {};
|
|
157
222
|
generatorMeta = {
|
|
158
223
|
...meta,
|
|
159
|
-
|
|
224
|
+
...this.createMetaFunctions(operations, () => this.environment),
|
|
160
225
|
requireModule,
|
|
161
226
|
importModule,
|
|
162
|
-
instantiate,
|
|
163
|
-
instantiateHelp,
|
|
164
227
|
getPackageJson,
|
|
165
228
|
packageNamespace,
|
|
166
229
|
};
|
|
230
|
+
setOperations(generatorMeta, operations);
|
|
167
231
|
this._meta[meta.namespace] = generatorMeta;
|
|
168
232
|
if (packageNamespace) {
|
|
169
233
|
this.addPackageNamespace(packageNamespace);
|
|
@@ -174,6 +238,7 @@ export default class Store {
|
|
|
174
238
|
return this.bindMeta(generatorMeta, { env });
|
|
175
239
|
}
|
|
176
240
|
bindMeta(meta, { env } = {}) {
|
|
241
|
+
meta = this.ownMeta(meta);
|
|
177
242
|
if (!meta || !env || env === this.environment) {
|
|
178
243
|
return meta;
|
|
179
244
|
}
|
|
@@ -201,22 +266,40 @@ export default class Store {
|
|
|
201
266
|
*
|
|
202
267
|
* So this index file `node_modules/generator-dummy/lib/generators/yo/index.js` would be
|
|
203
268
|
* registered as `dummy:yo` generator.
|
|
269
|
+
*
|
|
270
|
+
* The options are merged over the lookup options the store was created with.
|
|
204
271
|
*/
|
|
205
272
|
lookupSync(options) {
|
|
206
|
-
const { registerToScope, customizeNamespace = (ns) => ns, filter, env, lookups =
|
|
273
|
+
const { registerToScope, customizeNamespace = (ns) => ns, usePackageName, filter, env, lookups = this.lookups, ...remainingOptions } = { ...this.lookupOptions, ...options };
|
|
207
274
|
const lookupOptions = { ...remainingOptions, lookups };
|
|
208
275
|
const generators = [];
|
|
276
|
+
const { nestedGenerators } = lookupOptions;
|
|
209
277
|
lookupGeneratorsSync(lookupOptions, ({ packagePath, filePath, lookups }) => {
|
|
210
|
-
let
|
|
211
|
-
if (basename(repositoryPath).startsWith('@')) {
|
|
212
|
-
// Scoped package
|
|
213
|
-
repositoryPath = join(repositoryPath, '..');
|
|
214
|
-
}
|
|
215
|
-
let namespace = customizeNamespace(asNamespace(relative(repositoryPath, filePath), { lookups }));
|
|
278
|
+
let namespace;
|
|
216
279
|
try {
|
|
280
|
+
const packageName = usePackageName ? this.getPackageJson(packagePath)?.name : undefined;
|
|
281
|
+
const packageNamespace = packageName ? namespaceOfPackage(packageName) : undefined;
|
|
282
|
+
let foundNamespace;
|
|
283
|
+
if (packageNamespace) {
|
|
284
|
+
// The namespace of the package, then the generator from its path in the package.
|
|
285
|
+
const generatorPath = join('generator-package', relative(packagePath, filePath));
|
|
286
|
+
foundNamespace = `${packageNamespace}${asNamespace(generatorPath, { lookups, nestedGenerators }).slice('package'.length)}`;
|
|
287
|
+
}
|
|
288
|
+
else if (packageName) {
|
|
289
|
+
foundNamespace = asNamespace(join(packageName, relative(packagePath, filePath)), { lookups, nestedGenerators });
|
|
290
|
+
}
|
|
291
|
+
else {
|
|
292
|
+
let repositoryPath = join(packagePath, '..');
|
|
293
|
+
if (basename(repositoryPath).startsWith('@')) {
|
|
294
|
+
// Scoped package
|
|
295
|
+
repositoryPath = join(repositoryPath, '..');
|
|
296
|
+
}
|
|
297
|
+
foundNamespace = asNamespace(relative(repositoryPath, filePath), { lookups, nestedGenerators });
|
|
298
|
+
}
|
|
299
|
+
namespace = customizeNamespace(foundNamespace);
|
|
217
300
|
const resolved = realpathSync(filePath);
|
|
218
301
|
if (!namespace) {
|
|
219
|
-
namespace = customizeNamespace(asNamespace(resolved, { lookups }));
|
|
302
|
+
namespace = customizeNamespace(asNamespace(resolved, { lookups, nestedGenerators }));
|
|
220
303
|
}
|
|
221
304
|
namespace = namespace;
|
|
222
305
|
if (registerToScope && !namespace.startsWith('@')) {
|
|
@@ -348,14 +431,31 @@ export default class Store {
|
|
|
348
431
|
}
|
|
349
432
|
return this._packagesJson.get(packagePath);
|
|
350
433
|
}
|
|
434
|
+
/**
|
|
435
|
+
* The meta of the store sharing its operations with the given meta, coming from the store it was cloned from or from
|
|
436
|
+
* one of its clones, or the given meta.
|
|
437
|
+
*/
|
|
438
|
+
ownMeta(meta) {
|
|
439
|
+
if (!meta) {
|
|
440
|
+
return meta;
|
|
441
|
+
}
|
|
442
|
+
const own = this._meta[meta.namespace];
|
|
443
|
+
return own && own !== meta && getOperations(own) === getOperations(meta) ? own : meta;
|
|
444
|
+
}
|
|
351
445
|
/**
|
|
352
446
|
* The functions of a meta that need an environment, defaulting to the given one.
|
|
353
447
|
*/
|
|
354
448
|
bindMetaFunctions(meta, env) {
|
|
449
|
+
return this.createMetaFunctions(getOperations(meta), () => env);
|
|
450
|
+
}
|
|
451
|
+
/**
|
|
452
|
+
* The functions of a meta, running its operations in the environment passed as `{ env }`, or the default one.
|
|
453
|
+
*/
|
|
454
|
+
createMetaFunctions(operations, getEnvironment) {
|
|
355
455
|
return {
|
|
356
|
-
importGenerator: (({ env
|
|
357
|
-
instantiate: (arguments_, options, { env
|
|
358
|
-
instantiateHelp: ({ env
|
|
456
|
+
importGenerator: (({ env = getEnvironment() } = {}) => operations.importGenerator(env)),
|
|
457
|
+
instantiate: async (arguments_, options, { env = getEnvironment() } = {}) => operations.instantiate(arguments_, options, env),
|
|
458
|
+
instantiateHelp: async ({ env = getEnvironment() } = {}) => operations.instantiate([], { help: true }, env),
|
|
359
459
|
};
|
|
360
460
|
}
|
|
361
461
|
getFactory(module) {
|
package/dist/util/namespace.d.ts
CHANGED
|
@@ -1,6 +1,15 @@
|
|
|
1
1
|
type AsNamespaceOptions = {
|
|
2
2
|
lookups?: string[];
|
|
3
|
+
/** Generators nested in the `generators` folder of a generator: `app/generators/sub` is `app:sub`. */
|
|
4
|
+
nestedGenerators?: boolean;
|
|
3
5
|
};
|
|
6
|
+
/** The folder holding the generators nested in a generator. */
|
|
7
|
+
export declare const nestedGeneratorsFolder = "generators";
|
|
8
|
+
/**
|
|
9
|
+
* The lookups of the generators, with the lookups of the generators nested in them if `nestedGenerators`:
|
|
10
|
+
* `generators` and `generators/*\/generators`.
|
|
11
|
+
*/
|
|
12
|
+
export declare const withNestedLookups: (lookups: string[], nestedGenerators?: boolean) => string[];
|
|
4
13
|
export declare const defaultLookups: string[];
|
|
5
14
|
/**
|
|
6
15
|
* Given a String `filepath`, tries to figure out the relative namespace.
|
|
@@ -21,6 +30,7 @@ export declare const defaultLookups: string[];
|
|
|
21
30
|
*
|
|
22
31
|
* @param filepath
|
|
23
32
|
* @param lookups paths
|
|
33
|
+
* @throws if the path is in a scoped package that is not a `generator-` one
|
|
24
34
|
*/
|
|
25
|
-
export declare const asNamespace: (filepath: string, { lookups }: AsNamespaceOptions) => string;
|
|
35
|
+
export declare const asNamespace: (filepath: string, { lookups, nestedGenerators }: AsNamespaceOptions) => string;
|
|
26
36
|
export {};
|
package/dist/util/namespace.js
CHANGED
|
@@ -1,6 +1,13 @@
|
|
|
1
|
-
import { parse } from 'node:path';
|
|
1
|
+
import { parse, posix } from 'node:path';
|
|
2
2
|
import slash from 'slash';
|
|
3
3
|
import { escapeRegExp, findLast } from 'lodash-es';
|
|
4
|
+
/** The folder holding the generators nested in a generator. */
|
|
5
|
+
export const nestedGeneratorsFolder = 'generators';
|
|
6
|
+
/**
|
|
7
|
+
* The lookups of the generators, with the lookups of the generators nested in them if `nestedGenerators`:
|
|
8
|
+
* `generators` and `generators/*\/generators`.
|
|
9
|
+
*/
|
|
10
|
+
export const withNestedLookups = (lookups, nestedGenerators) => nestedGenerators ? lookups.flatMap(lookup => [lookup, posix.join(lookup, '*', nestedGeneratorsFolder)]) : lookups;
|
|
4
11
|
export const defaultLookups = ['.', 'generators', 'lib/generators', 'dist/generators'];
|
|
5
12
|
/**
|
|
6
13
|
* Given a String `filepath`, tries to figure out the relative namespace.
|
|
@@ -21,8 +28,9 @@ export const defaultLookups = ['.', 'generators', 'lib/generators', 'dist/genera
|
|
|
21
28
|
*
|
|
22
29
|
* @param filepath
|
|
23
30
|
* @param lookups paths
|
|
31
|
+
* @throws if the path is in a scoped package that is not a `generator-` one
|
|
24
32
|
*/
|
|
25
|
-
export const asNamespace = (filepath, { lookups = defaultLookups }) => {
|
|
33
|
+
export const asNamespace = (filepath, { lookups = defaultLookups, nestedGenerators }) => {
|
|
26
34
|
if (!filepath) {
|
|
27
35
|
throw new Error('Missing file path');
|
|
28
36
|
}
|
|
@@ -37,7 +45,8 @@ export const asNamespace = (filepath, { lookups = defaultLookups }) => {
|
|
|
37
45
|
const parsed = parse(ns);
|
|
38
46
|
ns = parsed.dir ? `${parsed.dir}/${parsed.name}` : parsed.name;
|
|
39
47
|
// Sort lookups by length so biggest are removed first
|
|
40
|
-
|
|
48
|
+
// The folder of the nested generators is removed like a lookup.
|
|
49
|
+
const nsLookups = [...lookups, ...(nestedGenerators ? [nestedGeneratorsFolder] : []), '..']
|
|
41
50
|
.map(found => slash(found))
|
|
42
51
|
.toSorted((a, b) => a.split('/').length - b.split('/').length)
|
|
43
52
|
.toReversed();
|
|
@@ -51,11 +60,17 @@ export const asNamespace = (filepath, { lookups = defaultLookups }) => {
|
|
|
51
60
|
// Cleanup `ns` from unwanted parts and then normalize slashes to `:`
|
|
52
61
|
ns = ns
|
|
53
62
|
.replaceAll('//', '') // Remove double `/`
|
|
54
|
-
|
|
63
|
+
// Remove the path before the package, the last folder starting with `generator-`, and its `generator-` prefix:
|
|
64
|
+
// a `generator-` inside the name of the package or of a generator is kept.
|
|
65
|
+
.replace(/^(?:.*\/)?generator-/, '')
|
|
55
66
|
.replace(/\/(index|main)$/, '') // Remove `/index` or `/main`
|
|
56
67
|
.replace(/^\//, '') // Remove leading `/`
|
|
57
68
|
.replaceAll(/\/+/g, ':'); // Replace slashes by `:`
|
|
58
69
|
if (scope) {
|
|
70
|
+
// The path up to the scope is left when the package is not a `generator-` one.
|
|
71
|
+
if (new RegExp(`(?:^|:)${escapeRegExp(scope)}:`).test(ns)) {
|
|
72
|
+
throw new Error(`The scoped package of ${filepath} is not a generator- package, it has no namespace.`);
|
|
73
|
+
}
|
|
59
74
|
ns = `${scope}/${ns}`;
|
|
60
75
|
}
|
|
61
76
|
return ns;
|