Skip to content

Commit 6fdbc91

Browse files
committed
drop environment.js
1 parent 592483a commit 6fdbc91

File tree

4 files changed

+279
-306
lines changed

4 files changed

+279
-306
lines changed

src/environment-base.ts

+59-50
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import EventEmitter from 'node:events';
12
import { createRequire } from 'node:module';
23
import { basename, isAbsolute, join, relative, resolve } from 'node:path';
34
import process from 'node:process';
@@ -34,15 +35,15 @@ import { type ConflicterOptions } from '@yeoman/conflicter';
3435
import { defaults, pick } from 'lodash-es';
3536
import { ComposedStore } from './composed-store.js';
3637
import Store from './store.js';
37-
import Environment from './environment.js';
3838
import type YeomanCommand from './util/command.js';
3939
import { asNamespace, defaultLookups } from './util/namespace.js';
4040
import { type LookupOptions, lookupGenerators } from './generator-lookup.js';
4141
import { UNKNOWN_NAMESPACE, UNKNOWN_RESOLVED, defaultQueues } from './constants.js';
4242
import { resolveModulePath } from './util/resolve.js';
4343
import { commitSharedFsTask } from './commit.js';
44-
// eslint-disable-next-line import/order
4544
import { packageManagerInstallTask } from './package-manager.js';
45+
// eslint-disable-next-line import/order
46+
import { splitArgsFromString } from './util/util.js';
4647

4748
const require = createRequire(import.meta.url);
4849

@@ -61,42 +62,6 @@ export type EnvironmentOptions = BaseEnvironmentOptions &
6162
nodePackageManager?: string;
6263
};
6364

64-
/**
65-
* Two-step argument splitting function that first splits arguments in quotes,
66-
* and then splits up the remaining arguments if they are not part of a quote.
67-
*/
68-
function splitArgsFromString(argsString: string | string[]): string[] {
69-
if (Array.isArray(argsString)) {
70-
return argsString;
71-
}
72-
73-
let result: string[] = [];
74-
if (!argsString) {
75-
return result;
76-
}
77-
78-
const quoteSeparatedArgs = argsString.split(/("[^"]*")/).filter(Boolean);
79-
for (const arg of quoteSeparatedArgs) {
80-
if (arg.includes('"')) {
81-
result.push(arg.replace(/"/g, ''));
82-
} else {
83-
result = result.concat(arg.trim().split(' '));
84-
}
85-
}
86-
87-
return result;
88-
}
89-
90-
const getComposeOptions = (args?: any, options?: any, composeOptions?: any): ComposeOptions => {
91-
if (typeof composeOptions === 'object') {
92-
composeOptions.generatorArgs = args;
93-
composeOptions.generatorOptions = options;
94-
return composeOptions;
95-
}
96-
97-
return composeOptions;
98-
};
99-
10065
const getInstantiateOptions = (args?: any, options?: any): InstantiateOptions => {
10166
if (Array.isArray(args) || typeof args === 'string') {
10267
return { generatorArgs: splitArgsFromString(args), generatorOptions: options };
@@ -116,7 +81,42 @@ const getInstantiateOptions = (args?: any, options?: any): InstantiateOptions =>
11681
return { generatorOptions: options };
11782
};
11883

119-
export default class EnvironmentBase extends Environment implements BaseEnvironment {
84+
const getComposeOptions = (...varargs: any[]): ComposeOptions => {
85+
if (varargs.filter(Boolean).length === 0) return {};
86+
87+
const [args, options, composeOptions] = varargs;
88+
if (typeof args === 'boolean') {
89+
return { schedule: args };
90+
}
91+
92+
let generatorArgs;
93+
let generatorOptions;
94+
if (args !== undefined) {
95+
if (typeof args === 'object') {
96+
if ('generatorOptions' in args || 'generatorArgs' in args || 'schedule' in args) {
97+
return args;
98+
}
99+
100+
generatorOptions = args;
101+
} else {
102+
generatorArgs = Array.isArray(args) ? args : splitArgsFromString(String(args));
103+
}
104+
}
105+
106+
if (typeof options === 'boolean') {
107+
return { generatorArgs, generatorOptions, schedule: options };
108+
}
109+
110+
generatorOptions = generatorOptions ?? options;
111+
112+
if (typeof composeOptions === 'boolean') {
113+
return { generatorArgs, generatorOptions, schedule: composeOptions };
114+
}
115+
116+
return composeOptions;
117+
};
118+
119+
export default class EnvironmentBase extends EventEmitter implements BaseEnvironment {
120120
cwd: string;
121121
adapter: QueuedAdapter;
122122
sharedFs: MemFs<MemFsEditorFile>;
@@ -137,12 +137,7 @@ export default class EnvironmentBase extends Environment implements BaseEnvironm
137137
protected _rootGenerator?: BaseGenerator;
138138
protected compatibilityMode?: false | 'v4';
139139

140-
constructor(options?: EnvironmentOptions);
141-
constructor(options: EnvironmentOptions = {}, adapterCompat?: InputOutputAdapter) {
142-
if (adapterCompat) {
143-
options.adapter = adapterCompat;
144-
}
145-
140+
constructor(options: EnvironmentOptions = {}) {
146141
super();
147142

148143
this.setMaxListeners(100);
@@ -203,11 +198,6 @@ export default class EnvironmentBase extends Environment implements BaseEnvironm
203198
this.experimental = experimental || process.argv.includes('--experimental');
204199

205200
this.alias(/^([^:]+)$/, '$1:app');
206-
207-
this.loadSharedOptions(this.options);
208-
if (this.sharedOptions.skipLocalCache === undefined) {
209-
this.sharedOptions.skipLocalCache = true;
210-
}
211201
}
212202

213203
async applyTransforms(transformStreams: Transform[], options: ApplyTransformsOptions = {}): Promise<void> {
@@ -383,6 +373,25 @@ export default class EnvironmentBase extends Environment implements BaseEnvironm
383373
return generator as unknown as G;
384374
}
385375

376+
/**
377+
* @protected
378+
* Compose with the generator.
379+
*
380+
* @param {String} namespaceOrPath
381+
* @return {Generator} The instantiated generator or the singleton instance.
382+
*/
383+
async composeWith<G extends BaseGenerator = BaseGenerator>(
384+
generator: string | GetGeneratorConstructor<G>,
385+
composeOptions?: ComposeOptions<G>,
386+
): Promise<G>;
387+
async composeWith<G extends BaseGenerator = BaseGenerator>(generator: string | GetGeneratorConstructor<G>, ...args: any[]): Promise<G> {
388+
const options = getComposeOptions(...args) as ComposeOptions<G>;
389+
const { schedule = true, ...instantiateOptions } = options;
390+
391+
const generatorInstance = await this.create(generator, instantiateOptions);
392+
return this.queueGenerator(generatorInstance, { schedule });
393+
}
394+
386395
/**
387396
* Given a String `filepath`, tries to figure out the relative namespace.
388397
*

src/environment-full.ts

+194-2
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,163 @@
11
import { createHash } from 'node:crypto';
22
import { join } from 'node:path';
3+
import type { InputOutputAdapter } from '@yeoman/types';
4+
import { type YeomanNamespace, requireNamespace, toNamespace } from '@yeoman/namespace';
35
import { flyImport } from 'fly-import';
6+
import { defaults, pick, uniq } from 'lodash-es';
47
import semver from 'semver';
5-
import { type YeomanNamespace, requireNamespace } from '@yeoman/namespace';
68
import { type LookupOptions } from './generator-lookup.js';
79
import YeomanCommand from './util/command.js';
8-
import EnvironmentBase from './environment-base.js';
10+
import EnvironmentBase, { type EnvironmentOptions } from './environment-base.js';
11+
import { splitArgsFromString } from './util/util.js';
912

1013
class FullEnvironment extends EnvironmentBase {
14+
constructor(options?: EnvironmentOptions);
15+
constructor(options: EnvironmentOptions = {}, adapterCompat?: InputOutputAdapter) {
16+
if (adapterCompat) {
17+
options.adapter = adapterCompat;
18+
}
19+
20+
super(options);
21+
22+
this.loadSharedOptions(this.options);
23+
if (this.sharedOptions.skipLocalCache === undefined) {
24+
this.sharedOptions.skipLocalCache = true;
25+
}
26+
}
27+
28+
/**
29+
* Load options passed to the Generator that should be used by the Environment.
30+
*
31+
* @param {Object} options
32+
*/
33+
loadEnvironmentOptions(options: EnvironmentOptions) {
34+
const environmentOptions = pick(options, ['skipInstall', 'nodePackageManager']);
35+
defaults(this.options, environmentOptions);
36+
return environmentOptions;
37+
}
38+
39+
/**
40+
* Load options passed to the Environment that should be forwarded to the Generator.
41+
*
42+
* @param {Object} options
43+
*/
44+
loadSharedOptions(options: EnvironmentOptions) {
45+
const optionsToShare = pick(options, [
46+
'skipInstall',
47+
'forceInstall',
48+
'skipCache',
49+
'skipLocalCache',
50+
'skipParseOptions',
51+
'localConfigOnly',
52+
'askAnswered',
53+
]);
54+
Object.assign(this.sharedOptions, optionsToShare);
55+
return optionsToShare;
56+
}
57+
58+
/**
59+
* @protected
60+
* Outputs the general help and usage. Optionally, if generators have been
61+
* registered, the list of available generators is also displayed.
62+
*
63+
* @param {String} name
64+
*/
65+
help(name = 'init') {
66+
const out = [
67+
'Usage: :binary: GENERATOR [args] [options]',
68+
'',
69+
'General options:',
70+
" --help # Print generator's options and usage",
71+
' -f, --force # Overwrite files that already exist',
72+
'',
73+
'Please choose a generator below.',
74+
'',
75+
];
76+
77+
const ns = this.namespaces();
78+
79+
const groups: Record<string, string[]> = {};
80+
for (const namespace of ns) {
81+
const base = namespace.split(':')[0];
82+
83+
if (!groups[base]) {
84+
groups[base] = [];
85+
}
86+
87+
groups[base].push(namespace);
88+
}
89+
90+
for (const key of Object.keys(groups).sort()) {
91+
const group = groups[key];
92+
93+
if (group.length > 0) {
94+
out.push('', key.charAt(0).toUpperCase() + key.slice(1));
95+
}
96+
97+
for (const ns of groups[key]) {
98+
out.push(` ${ns}`);
99+
}
100+
}
101+
102+
return out.join('\n').replace(/:binary:/g, name);
103+
}
104+
105+
/**
106+
* @protected
107+
* Returns the list of registered namespace.
108+
* @return {Array}
109+
*/
110+
namespaces() {
111+
return this.store.namespaces();
112+
}
113+
114+
/**
115+
* @protected
116+
* Returns stored generators meta
117+
* @return {Object}
118+
*/
119+
getGeneratorsMeta() {
120+
return this.store.getGeneratorsMeta();
121+
}
122+
123+
/**
124+
* @protected
125+
* Get registered generators names
126+
*
127+
* @return {Array}
128+
*/
129+
getGeneratorNames() {
130+
return uniq(Object.keys(this.getGeneratorsMeta()).map(namespace => toNamespace(namespace)?.packageNamespace));
131+
}
132+
133+
/**
134+
* Get last added path for a namespace
135+
*
136+
* @param {String} - namespace
137+
* @return {String} - path of the package
138+
*/
139+
getPackagePath(namespace: string) {
140+
if (namespace.includes(':')) {
141+
const generator = this.getGeneratorMeta(namespace);
142+
return generator?.packagePath;
143+
}
144+
145+
const packagePaths = this.getPackagePaths(namespace) || [];
146+
return packagePaths[0];
147+
}
148+
149+
/**
150+
* Get paths for a namespace
151+
*
152+
* @param - namespace
153+
* @return array of paths.
154+
*/
155+
getPackagePaths(namespace: string) {
156+
return (
157+
this.store.getPackagesPaths()[namespace] || this.store.getPackagesPaths()[requireNamespace(this.alias(namespace)).packageNamespace]
158+
);
159+
}
160+
11161
/**
12162
* Generate a command for the generator and execute.
13163
*
@@ -179,6 +329,48 @@ class FullEnvironment extends EnvironmentBase {
179329

180330
throw new Error(`Error preparing environment for ${missing.map(ns => ns.complete).join(',')}`);
181331
}
332+
333+
/**
334+
* Tries to locate and run a specific generator. The lookup is done depending
335+
* on the provided arguments, options and the list of registered generators.
336+
*
337+
* When the environment was unable to resolve a generator, an error is raised.
338+
*
339+
* @param {String|Array} args
340+
* @param {Object} [options]
341+
*/
342+
async run(args?: string[], options?: any) {
343+
args = Array.isArray(args) ? args : splitArgsFromString(args as unknown as string);
344+
options = { ...options };
345+
346+
const name = args.shift();
347+
if (!name) {
348+
throw new Error('Must provide at least one argument, the generator namespace to invoke.');
349+
}
350+
351+
this.loadEnvironmentOptions(options);
352+
353+
if (this.experimental && !this.getGeneratorMeta(name)) {
354+
try {
355+
await this.prepareEnvironment(name);
356+
} catch {}
357+
}
358+
359+
const generator = await this.create(name, {
360+
generatorArgs: args,
361+
generatorOptions: {
362+
...options,
363+
initialGenerator: true,
364+
},
365+
});
366+
367+
if (options.help) {
368+
console.log((generator as any).help());
369+
return undefined;
370+
}
371+
372+
return this.runGenerator(generator);
373+
}
182374
}
183375

184376
export default FullEnvironment;

0 commit comments

Comments
 (0)