yeoman-environment 8.0.0 → 8.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.
@@ -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
- * 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.
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.
@@ -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
- this.store = store ?? new Store(this);
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 = this.lookups) {
301
- return asNamespace(filepath, { lookups });
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.
@@ -264,7 +264,7 @@ class FullEnvironment extends EnvironmentBase {
264
264
  // Build filePatterns to look specifically for the namespace.
265
265
  const genPath = ns.generator.split(':').join('/');
266
266
  let filePatterns = [`${genPath}/index.?s`, `${genPath}.?s`];
267
- const lookups = options.lookups ?? this.lookups;
267
+ const lookups = options.lookups ?? this.store.lookups;
268
268
  filePatterns = lookups.flatMap(prefix => filePatterns.map(pattern => join(prefix, pattern)));
269
269
  nsOptions.filePatterns = filePatterns;
270
270
  nsOptions.singleResult = true;
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 * from './commands.ts';
9
- export * from './util/command.ts';
10
- export * from './package-manager.ts';
11
- export * from './commit.ts';
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 * from "./commands.js";
11
- export * from "./util/command.js";
12
- export * from "./package-manager.js";
13
- export * from "./commit.js";
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'>;
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
- constructor(environment?: BaseEnvironment);
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
@@ -16,6 +16,10 @@ 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];
19
23
  /**
20
24
  * The Generator store
21
25
  * This is used to store generator (npm packages) reference and instantiate them when
@@ -39,8 +43,60 @@ export default class Store {
39
43
  _boundMetas = new WeakMap();
40
44
  /** The environment used when none is passed to `importGenerator` or `instantiate`. */
41
45
  environment;
42
- constructor(environment) {
46
+ /** Options passed to every lookup, so consecutive lookups share the same configuration. */
47
+ lookupOptions;
48
+ constructor(environment, lookupOptions = {}) {
43
49
  this.environment = environment;
50
+ this.lookupOptions = lookupOptions;
51
+ }
52
+ /** The lookups shared by the store, or the default ones. */
53
+ get lookups() {
54
+ return this.lookupOptions.lookups ?? defaultLookups;
55
+ }
56
+ /**
57
+ * Given a String `filepath`, tries to figure out the relative namespace.
58
+ *
59
+ * ### Examples:
60
+ *
61
+ * store.namespace('backbone/all/index.js');
62
+ * // => backbone:all
63
+ *
64
+ * store.namespace('generator-backbone/model');
65
+ * // => backbone:model
66
+ *
67
+ * store.namespace('backbone.js');
68
+ * // => backbone
69
+ *
70
+ * store.namespace('generator-mocha/backbone/model/index.js');
71
+ * // => mocha:backbone:model
72
+ *
73
+ * @param filepath
74
+ * @param lookups - The lookups of the store if omitted
75
+ */
76
+ namespace(filepath, lookups = this.lookups) {
77
+ return asNamespace(filepath, { lookups });
78
+ }
79
+ /**
80
+ * A copy of the store with the same generators, packages and lookup options, whose metas default to the given
81
+ * environment. The generators already imported are shared, what is added after cloning goes only to its store.
82
+ * @param environment - The environment of the clone, the environment of the store if omitted
83
+ * @param options - Lookup options of the clone, merged over the ones of the store
84
+ */
85
+ clone(environment = this.environment, options) {
86
+ const store = new Store(environment, { ...this.lookupOptions, ...options });
87
+ for (const [namespace, meta] of Object.entries(this._meta)) {
88
+ const operations = getOperations(meta);
89
+ store._meta[namespace] = setOperations({
90
+ ...meta,
91
+ ...store.createMetaFunctions(operations, () => store.environment),
92
+ getPackageJson: () => store.getPackageJson(meta.packagePath),
93
+ }, operations);
94
+ }
95
+ for (const [packageNamespace, packagePaths] of Object.entries(this._packagesPaths)) {
96
+ store._packagesPaths[packageNamespace] = [...packagePaths];
97
+ }
98
+ store._packagesNS.push(...this._packagesNS);
99
+ return store;
44
100
  }
45
101
  /**
46
102
  * Store a module under the namespace key
@@ -138,32 +194,31 @@ export default class Store {
138
194
  createdGenerators.set(environment, creating);
139
195
  return creating;
140
196
  };
141
- const importGenerator = (({ env: environment = this.environment } = {}) => {
197
+ const importGenerator = (environment) => {
142
198
  const importing = importGeneratorModule();
143
199
  return importing ? importing.then(() => getGenerator(environment)) : getGenerator(environment);
144
- });
145
- const instantiate = async (arguments_ = [], options = {}, { env: environment = this.environment } = {}) => {
200
+ };
201
+ const instantiate = async (arguments_ = [], options = {}, environment) => {
146
202
  if (!environment) {
147
203
  throw new Error(`An environment is required to instantiate the generator ${meta.namespace}`);
148
204
  }
149
- return environment.instantiate(await importGenerator({ env: environment }), {
205
+ return environment.instantiate(await importGenerator(environment), {
150
206
  generatorArgs: arguments_,
151
207
  generatorOptions: options,
152
208
  });
153
209
  };
154
- const instantiateHelp = async (options) => instantiate([], { help: true }, options);
210
+ const operations = { importGenerator, instantiate };
155
211
  const getPackageJson = () => this.getPackageJson(meta.packagePath);
156
212
  const { packageNamespace } = toNamespace(meta.namespace) ?? {};
157
213
  generatorMeta = {
158
214
  ...meta,
159
- importGenerator,
215
+ ...this.createMetaFunctions(operations, () => this.environment),
160
216
  requireModule,
161
217
  importModule,
162
- instantiate,
163
- instantiateHelp,
164
218
  getPackageJson,
165
219
  packageNamespace,
166
220
  };
221
+ setOperations(generatorMeta, operations);
167
222
  this._meta[meta.namespace] = generatorMeta;
168
223
  if (packageNamespace) {
169
224
  this.addPackageNamespace(packageNamespace);
@@ -174,6 +229,7 @@ export default class Store {
174
229
  return this.bindMeta(generatorMeta, { env });
175
230
  }
176
231
  bindMeta(meta, { env } = {}) {
232
+ meta = this.ownMeta(meta);
177
233
  if (!meta || !env || env === this.environment) {
178
234
  return meta;
179
235
  }
@@ -201,18 +257,28 @@ export default class Store {
201
257
  *
202
258
  * So this index file `node_modules/generator-dummy/lib/generators/yo/index.js` would be
203
259
  * registered as `dummy:yo` generator.
260
+ *
261
+ * The options are merged over the lookup options the store was created with.
204
262
  */
205
263
  lookupSync(options) {
206
- const { registerToScope, customizeNamespace = (ns) => ns, filter, env, lookups = defaultLookups, ...remainingOptions } = options ?? { localOnly: false };
264
+ const { registerToScope, customizeNamespace = (ns) => ns, usePackageName, filter, env, lookups = this.lookups, ...remainingOptions } = { ...this.lookupOptions, ...options };
207
265
  const lookupOptions = { ...remainingOptions, lookups };
208
266
  const generators = [];
209
267
  lookupGeneratorsSync(lookupOptions, ({ packagePath, filePath, lookups }) => {
210
- let repositoryPath = join(packagePath, '..');
211
- if (basename(repositoryPath).startsWith('@')) {
212
- // Scoped package
213
- repositoryPath = join(repositoryPath, '..');
268
+ const packageName = usePackageName ? this.getPackageJson(packagePath)?.name : undefined;
269
+ let namespacePath;
270
+ if (packageName) {
271
+ namespacePath = join(packageName, relative(packagePath, filePath));
214
272
  }
215
- let namespace = customizeNamespace(asNamespace(relative(repositoryPath, filePath), { lookups }));
273
+ else {
274
+ let repositoryPath = join(packagePath, '..');
275
+ if (basename(repositoryPath).startsWith('@')) {
276
+ // Scoped package
277
+ repositoryPath = join(repositoryPath, '..');
278
+ }
279
+ namespacePath = relative(repositoryPath, filePath);
280
+ }
281
+ let namespace = customizeNamespace(asNamespace(namespacePath, { lookups }));
216
282
  try {
217
283
  const resolved = realpathSync(filePath);
218
284
  if (!namespace) {
@@ -348,14 +414,31 @@ export default class Store {
348
414
  }
349
415
  return this._packagesJson.get(packagePath);
350
416
  }
417
+ /**
418
+ * The meta of the store sharing its operations with the given meta, coming from the store it was cloned from or from
419
+ * one of its clones, or the given meta.
420
+ */
421
+ ownMeta(meta) {
422
+ if (!meta) {
423
+ return meta;
424
+ }
425
+ const own = this._meta[meta.namespace];
426
+ return own && own !== meta && getOperations(own) === getOperations(meta) ? own : meta;
427
+ }
351
428
  /**
352
429
  * The functions of a meta that need an environment, defaulting to the given one.
353
430
  */
354
431
  bindMetaFunctions(meta, env) {
432
+ return this.createMetaFunctions(getOperations(meta), () => env);
433
+ }
434
+ /**
435
+ * The functions of a meta, running its operations in the environment passed as `{ env }`, or the default one.
436
+ */
437
+ createMetaFunctions(operations, getEnvironment) {
355
438
  return {
356
- importGenerator: (({ env: environment = env } = {}) => meta.importGenerator({ env: environment })),
357
- instantiate: (arguments_, options, { env: environment = env } = {}) => meta.instantiate(arguments_, options, { env: environment }),
358
- instantiateHelp: ({ env: environment = env } = {}) => meta.instantiateHelp({ env: environment }),
439
+ importGenerator: (({ env = getEnvironment() } = {}) => operations.importGenerator(env)),
440
+ instantiate: async (arguments_, options, { env = getEnvironment() } = {}) => operations.instantiate(arguments_, options, env),
441
+ instantiateHelp: async ({ env = getEnvironment() } = {}) => operations.instantiate([], { help: true }, env),
359
442
  };
360
443
  }
361
444
  getFactory(module) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "yeoman-environment",
3
- "version": "8.0.0",
3
+ "version": "8.1.0",
4
4
  "description": "Handles the lifecyle and bootstrapping of generators in a specific environment",
5
5
  "keywords": [
6
6
  "development",