generator-easy-ui5 3.5.0 → 3.5.2

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.
@@ -21,641 +21,689 @@ const AdmZip = require("adm-zip");
21
21
  const NPM_CONFIG_PREFIX = "easy-ui5_";
22
22
  let npmConfig;
23
23
  const getNPMConfig = (configName) => {
24
- if (!npmConfig) {
25
- npmConfig = require("libnpmconfig").read();
26
- }
27
- return npmConfig && npmConfig[`${NPM_CONFIG_PREFIX}${configName}`]
28
- }
24
+ if (!npmConfig) {
25
+ npmConfig = require("libnpmconfig").read();
26
+ }
27
+ return npmConfig && npmConfig[`${NPM_CONFIG_PREFIX}${configName}`];
28
+ };
29
29
 
30
30
  // the command line options of the generator
31
31
  const generatorOptions = {
32
- ghAuthToken: {
33
- type: String,
34
- description:
35
- "GitHub authToken to optionally access private generator repositories",
36
- npmConfig: true
37
- },
38
- ghOrg: {
39
- type: String,
40
- description: "GitHub organization to lookup for available generators",
41
- default: "ui5-community",
42
- hide: true // we don't want to recommend to use this option
43
- },
44
- subGeneratorPrefix: {
45
- type: String,
46
- description: "Prefix used for the lookup of the available generators",
47
- default: "generator-ui5-",
48
- hide: true // we don't want to recommend to use this option
49
- },
50
- addGhOrg: {
51
- type: String,
52
- description: `GitHub organization to lookup for additional available generators`,
53
- hide: true, // we don't want to recommend to use this option
54
- npmConfig: true
55
- },
56
- addSubGeneratorPrefix: {
57
- type: String,
58
- description: `Prefix used for the lookup of the additional available generators`,
59
- default: "generator-",
60
- hide: true, // we don't want to recommend to use this option
61
- npmConfig: true
62
- },
63
- list: {
64
- type: Boolean,
65
- description: "List the available subcommands of the generator",
66
- },
67
- skipUpdate: {
68
- type: Boolean,
69
- description: "Skip the update of the plugin generator",
70
- },
71
- forceUpdate: {
72
- type: Boolean,
73
- description: "Force the update of the plugin generator",
74
- },
75
- offline: {
76
- type: Boolean,
77
- alias: "o",
78
- description: "Running easy-ui5 in offline mode",
79
- },
80
- verbose: {
81
- type: Boolean,
82
- description: "Enable detailed logging",
83
- },
84
- plugins: {
85
- type: Boolean,
86
- alias: "p",
87
- description: "Get detailed version information",
88
- },
89
- next: {
90
- type: Boolean,
91
- description: "Preview the next mode to consume templates from bestofui5.org",
92
- },
32
+ ghAuthToken: {
33
+ type: String,
34
+ description: "GitHub authToken to optionally access private generator repositories",
35
+ npmConfig: true,
36
+ },
37
+ ghOrg: {
38
+ type: String,
39
+ description: "GitHub organization to lookup for available generators",
40
+ default: "ui5-community",
41
+ hide: true, // we don't want to recommend to use this option
42
+ },
43
+ ghThreshold: {
44
+ type: Number,
45
+ default: 100,
46
+ hide: true, // shouldn't be needed
47
+ },
48
+ subGeneratorPrefix: {
49
+ type: String,
50
+ description: "Prefix used for the lookup of the available generators",
51
+ default: "generator-ui5-",
52
+ hide: true, // we don't want to recommend to use this option
53
+ },
54
+ addGhOrg: {
55
+ type: String,
56
+ description: "GitHub organization to lookup for additional available generators",
57
+ hide: true, // we don't want to recommend to use this option
58
+ npmConfig: true,
59
+ },
60
+ addSubGeneratorPrefix: {
61
+ type: String,
62
+ description: "Prefix used for the lookup of the additional available generators",
63
+ default: "generator-",
64
+ hide: true, // we don't want to recommend to use this option
65
+ npmConfig: true,
66
+ },
67
+ pluginsHome: {
68
+ type: String,
69
+ description: "Home directory of the plugin generators",
70
+ default: path.join(require("os").homedir(), ".npm", "_generator-easy-ui5", "plugin-generators"),
71
+ hide: true, // shouldn't be needed
72
+ npmConfig: true,
73
+ },
74
+ embed: {
75
+ type: Boolean,
76
+ description: "Embeds the selected plugin generator",
77
+ hide: true, // shouldn't be needed
78
+ },
79
+ list: {
80
+ type: Boolean,
81
+ description: "List the available subcommands of the generator",
82
+ },
83
+ skipUpdate: {
84
+ type: Boolean,
85
+ description: "Skip the update of the plugin generator",
86
+ },
87
+ forceUpdate: {
88
+ type: Boolean,
89
+ description: "Force the update of the plugin generator",
90
+ },
91
+ offline: {
92
+ type: Boolean,
93
+ alias: "o",
94
+ description: "Running easy-ui5 in offline mode",
95
+ },
96
+ verbose: {
97
+ type: Boolean,
98
+ description: "Enable detailed logging",
99
+ },
100
+ plugins: {
101
+ type: Boolean,
102
+ alias: "p",
103
+ description: "Get detailed version information",
104
+ },
105
+ next: {
106
+ type: Boolean,
107
+ description: "Preview the next mode to consume templates from bestofui5.org",
108
+ },
93
109
  };
94
110
 
95
111
  const generatorArgs = {
96
- generator: {
97
- type: String,
98
- required: false,
99
- description: "Name of the generator to invoke (without the \"generator-ui5-\" prefix)",
100
- },
101
- subcommand: {
102
- type: String,
103
- required: false,
104
- description: "Name of the subcommand to invoke (without the \"generator:\" prefix)",
105
- },
112
+ generator: {
113
+ type: String,
114
+ required: false,
115
+ description: 'Name of the generator to invoke (without the "generator-ui5-" prefix)',
116
+ },
117
+ subcommand: {
118
+ type: String,
119
+ required: false,
120
+ description: 'Name of the subcommand to invoke (without the "generator:" prefix)',
121
+ },
106
122
  };
107
123
 
108
124
  module.exports = class extends Generator {
109
- constructor(args, opts) {
110
- super(args, opts, {
111
- // disable the Yeoman 5 package-manager logic (auto install)!
112
- customInstallTask: "disabled"
113
- });
114
-
115
- Object.keys(generatorArgs).forEach((argName) => {
116
- // register the argument for being displayed in the help
117
- this.argument(argName, generatorArgs[argName]);
118
- });
119
-
120
- Object.keys(generatorOptions).forEach((optionName) => {
121
- const initialValue = this.options[optionName];
122
- // register the option for being displayed in the help
123
- this.option(optionName, generatorOptions[optionName]);
124
- const defaultedValue = this.options[optionName];
125
- if (generatorOptions[optionName].npmConfig) {
126
- // if a value is set, use the set value (parameter has higher precedence than npm config)
127
- // => this.option(...) applies the default value to this.options[...] used as last resort
128
- this.options[optionName] = initialValue || getNPMConfig(optionName) || defaultedValue;
129
- }
130
- });
131
- }
132
-
133
- _showBusy(statusText) {
134
- this._clearBusy();
135
- const progressChars = ["\\", "|", "/", "-"];
136
- let i = 0;
137
- process.stdout.write(`\r${statusText} `);
138
- this._busy = {
139
- text: statusText,
140
- timer: setInterval(() => {
141
- process.stdout.write(`\r${statusText} ${progressChars[i++]}`);
142
- i %= progressChars.length;
143
- }, 250),
144
- };
145
- }
146
-
147
- _clearBusy(newLine) {
148
- if (this._busy) {
149
- clearInterval(this._busy.timer);
150
- process.stdout.write("\r".padEnd(this._busy.text.length + 3) + (newLine ? "\n" : ""));
151
- delete this._busy;
152
- }
153
- }
154
-
155
- async prompting() {
156
-
157
- // Have Yeoman greet the user.
158
- if (!this.options.embedded) {
159
- this.log(yosay(`Welcome to the ${chalk.red("easy-ui5")} generator!`));
160
- }
161
-
162
- const home = path.join(__dirname, "..", "..");
163
-
164
- // check the permissions to Easy UI5s plugin directory which must
165
- // allow read/write to install additional plugin generators
166
- let pluginsHome = path.join(home, "plugin-generators");
167
- try {
168
- fs.accessSync(pluginsHome, fs.constants.R_OK | fs.constants.W_OK);
169
- } catch (e) {
170
- pluginsHome = path.join(require("os").homedir(), ".npm", "_generator-easy-ui5", "plugin-generators");
171
- if (this.options.verbose) {
172
- console.error(`Plugin directory: ${chalk.green(pluginsHome)}`);
173
- console.error(chalk.red(e.message));
174
- }
175
- fs.mkdirSync(pluginsHome, { recursive: true });
176
- }
177
-
178
- // log the plugins and configuration
179
- if (this.options.plugins) {
180
- const yeoman = require("yeoman-environment/package.json");
181
-
182
- const components = {
183
- "Node.js": process.version,
184
- "yeoman-environment": yeoman.version,
185
- "generator-easy-ui5": require(path.join(home, "package.json")).version,
186
- "home": home,
187
- "pluginsHome": pluginsHome,
188
- };
189
-
190
- Object.keys(components).forEach((component) => {
191
- this.log(`${chalk.green(component)}: ${components[component]}`);
192
- });
193
-
194
- this.log(chalk.green("\nAvailable generators:"));
195
- glob.sync(`${pluginsHome}/*/package.json`).forEach((plugin) => {
196
- const name = plugin.match(/.*\/generator-(.+)\/package\.json/)[1];
197
- const lib = require(plugin);
198
- this.log(` - ${chalk.green(name)}: ${lib.version}`);
199
- });
200
-
201
- return;
202
- }
203
-
204
- // create the octokit client to retrieve the generators from GH org
205
- // when not running in offline mode!
206
- let octokit;
207
- if (this.options.offline) {
208
- this.log(
209
- `Running in ${chalk.yellow(
210
- "offline"
211
- )} mode!`
212
- );
213
- } else {
214
- octokit = new MyOctokit({
215
- userAgent: `${this.rootGeneratorName()}:${this.rootGeneratorVersion()}`,
216
- auth: this.options.ghAuthToken,
217
- throttle: {
218
- onRateLimit: (retryAfter, options) => {
219
- this.log(
220
- `${chalk.yellow("Hit the GitHub API limit!")} Request quota exhausted for this request.`
221
- );
222
- if (options.request.retryCount === 0) {
223
- // only retries once
224
- this.log(`Retrying after ${retryAfter} seconds. Alternatively, you can cancel this operation and supply an auth token with the \`--ghAuthToken\` option. For more details, run \`yo easy-ui5 --help\`. `);
225
- return true;
226
- }
227
- },
228
- onAbuseLimit: () => {
229
- // does not retry, only logs a warning
230
- this.log(
231
- `${chalk.red("Hit the GitHub API limit again!")} Please supply an auth token with the \`--ghAuthToken\` option. For more details, run \`yo easy-ui5 --help\` `
232
- );
233
- },
234
- }
235
- });
236
- }
237
-
238
- // helper for filtering repos with corresponding subGenerator prefix
239
- const filterReposWithSubGeneratorPrefix = (repos, subGeneratorPrefix) => {
240
- if (!Array.isArray(repos)) {
241
- return [];
242
- }
243
- return repos.filter((repo) =>
244
- repo.name.startsWith(`${subGeneratorPrefix}`)
245
- ).map((repo) => {
246
- return {
247
- org: repo.owner?.login,
248
- name: repo.name,
249
- branch: repo.default_branch,
250
- subGeneratorName: repo.name.substring(subGeneratorPrefix.length),
251
- };
252
- });
253
- };
254
-
255
- // helper to retrieve the available repositories for a GH org
256
- const listGeneratorsForOrg = async (ghOrg, subGeneratorPrefix) => {
257
- const response = await octokit.repos.listForOrg({
258
- org: ghOrg,
259
- });
260
- return filterReposWithSubGeneratorPrefix(response?.data, subGeneratorPrefix);
261
- };
262
-
263
- // helper to retrieve the available repositories for a GH user
264
- const listGeneratorsForUser = async (ghUser, subGeneratorPrefix) => {
265
- const response = await octokit.repos.listForUser({
266
- username: ghUser,
267
- });
268
- return filterReposWithSubGeneratorPrefix(response?.data, subGeneratorPrefix);
269
- };
270
-
271
- // determine the generator to be used
272
- let generator;
273
-
274
- // try to identify whether concrete generator is defined
275
- if (!generator) {
276
- // determine generator by ${owner}/${repo}(!${dir})? syntax, e.g.:
277
- // > yo easy-ui5 SAP-samples/ui5-typescript-tutorial
278
- // > yo easy-ui5 SAP-samples/ui5-typescript-tutorial#1.0
279
- // > yo easy-ui5 SAP-samples/ui5-typescript-tutorial\!/generator
280
- // > yo easy-ui5 SAP-samples/ui5-typescript-tutorial\!/generator#1.0
281
- const reGenerator = /([^\/]+)\/([^\!\#]+)(?:\!([^\#]+))?(?:\#(.+))?/;
282
- const matchGenerator = reGenerator.exec(this.options.generator);
283
- if (matchGenerator) {
284
- // derive and path the generator information from command line
285
- const [owner, repo, dir = "/generator", branch] = matchGenerator.slice(1);
286
- generator = {
287
- org: owner,
288
- name: repo,
289
- branch,
290
- dir,
291
- pluginPath: `_/${owner}/${repo}`,
292
- };
293
- // log which generator is being used!
294
- if (this.options.verbose) {
295
- this.log(
296
- `Using generator ${chalk.green(
297
- `${owner}/${repo}!${dir}${branch ? "#" + branch : ""}`
298
- )}`
299
- );
300
- }
301
- }
302
- }
303
-
304
- // retrieve the available repositories (if no generator is specified specified directly)
305
- let availGenerators;
306
- if (!generator) {
307
- if (this.options.offline) {
308
- availGenerators = glob.sync(`${pluginsHome}/generator-*/package.json`).map((plugin) => {
309
- const match = plugin.match(/.*\/(generator-(.+))\/package\.json/);
310
- return {
311
- org: "local",
312
- name: match[1],
313
- subGeneratorName: match[2].match(/(?:ui5-)?(.*)/)?.[1] || match[2],
314
- local: true,
315
- }
316
- });
317
- } else {
318
- if (this.options.next) {
319
- // check bestofui5.org for generators
320
- try {
321
- const response = await request({
322
- method: "GET",
323
- url: "https://raw.githubusercontent.com/ui5-community/bestofui5-data/live-data/data/data.json",
324
- });
325
- const data = JSON.parse(response.data);
326
-
327
- availGenerators = data?.packages?.filter(entry => {
328
- return entry.type === "generator";
329
- }).map(entry => {
330
- return {
331
- org: entry.gitHubOwner,
332
- name: entry.gitHubRepo,
333
- subGeneratorName: entry.gitHubRepo.match(/(?:generator-(?:ui5-)?)(.*)/)?.[1] || entry.gitHubRepo,
334
- };
335
- });
336
- } catch (e) {
337
- console.error(`Failed to connect to bestofui5.org to retrieve all available generators! Run with --verbose for details!`);
338
- if (this.options.verbose) {
339
- console.error(e);
340
- }
341
- return;
342
- }
343
- } else {
344
- // check the main GH org for generators
345
- try {
346
- availGenerators = await listGeneratorsForOrg(this.options.ghOrg, this.options.subGeneratorPrefix);
347
- } catch (e) {
348
- console.error(`Failed to connect to GitHub to retrieve all available generators for "${this.options.ghOrg}" organization! Run with --verbose for details!`);
349
- if (this.options.verbose) {
350
- console.error(e);
351
- }
352
- return;
353
- }
354
-
355
- // check the additional GH org for generators with a different prefix
356
- try {
357
- if (this.options.addGhOrg && this.options.addSubGeneratorPrefix) {
358
- availGenerators = availGenerators.concat(await listGeneratorsForOrg(this.options.addGhOrg, this.options.addSubGeneratorPrefix));
359
- }
360
- } catch (e) {
361
- if (this.options.verbose) {
362
- this.log(`Failed to connect to GitHub retrieve additional generators for "${this.options.addGhOrg}" organization! Try to retrieve for user...`);
363
- }
364
- try {
365
- availGenerators = availGenerators.concat(await listGeneratorsForUser(this.options.addGhOrg, this.options.addSubGeneratorPrefix));
366
- } catch (e) {
367
- console.error(`Failed to connect to GitHub to retrieve additional generators for organization or user "${this.options.addGhOrg}"! Run with --verbose for details!`);
368
- if (this.options.verbose) {
369
- console.error(e);
370
- }
371
- return;
372
- }
373
- }
374
- }
375
- }
376
- }
377
-
378
- // if no generator is provided and doesn't exist, ask for generator name
379
- if (!generator) {
380
- // check for provided generator being available on GH
381
- generator = this.options.generator && availGenerators.find((repo) =>
382
- repo.subGeneratorName === this.options.generator
383
- );
384
-
385
- // if no generator is provided and doesn't exist, ask for generator name
386
- if (this.options.generator && !generator) {
387
- this.log(
388
- `The generator ${chalk.red(
389
- this.options.generator
390
- )} was not found. Please select an existing generator!`
391
- );
392
- }
393
-
394
- // still not found, select a generator
395
- if (!generator) {
396
- const generatorIdx = (
397
- await this.prompt([
398
- {
399
- type: "list",
400
- name: "generator",
401
- message: "Select your generator?",
402
- choices: availGenerators.map((availGenerator, idx) => ({
403
- name: `${availGenerator.subGeneratorName}${this.options.addGhOrg ? ` [${availGenerator.org}]` : ""}`,
404
- value: idx,
405
- })),
406
- },
407
- ])
408
- ).generator;
409
- generator = availGenerators[generatorIdx];
410
- }
411
- }
412
-
413
- let generatorPath = path.join(pluginsHome, generator.pluginPath || generator.name);
414
- if (!this.options.offline) {
415
- // lookup the default path of the generator if not set
416
- if (!generator.branch) {
417
- try {
418
- const repoInfo = await octokit.repos.get({
419
- owner: generator.org,
420
- repo: generator.name,
421
- });
422
- generator.branch = repoInfo.data.default_branch;
423
- } catch (e) {
424
- console.error(`Generator "${owner}/${repo}!${dir}${branch ? "#" + branch : ""}" not found! Run with --verbose for details!`);
425
- if (this.options.verbose) {
426
- console.error(e);
427
- }
428
- return;
429
- }
430
- }
431
- // fetch the branch to retrieve the latest commit SHA
432
- let commitSHA;
433
- try {
434
- // determine the commitSHA
435
- const reqBranch = await octokit.repos.getBranch({
436
- owner: generator.org,
437
- repo: generator.name,
438
- branch: generator.branch,
439
- });
440
- commitSHA = reqBranch.data.commit.sha;
441
- } catch (e) {
442
- console.error(chalk.red(`Failed to retrieve the branch "${generator.branch}" for repository "${generator.name}" for "${generator.org}" organization! Run with --verbose for details!`));
443
- if (this.options.verbose) {
444
- console.error(chalk.red(e.message));
445
- }
446
- return;
447
- }
448
-
449
- if (this.options.verbose) {
450
- this.log(
451
- `Using commit ${commitSHA} from @${generator.org}/${generator.name}#${generator.branch}...`
452
- );
453
- }
454
- const shaMarker = path.join(generatorPath, `.${commitSHA}`);
455
-
456
- if (fs.existsSync(generatorPath) && !this.options.skipUpdate) {
457
- // check if the SHA marker exists to know whether the generator is up-to-date or not
458
- if (this.options.forceUpdate || !fs.existsSync(shaMarker)) {
459
- if (this.options.verbose) {
460
- this.log(`Generator "${generator.name}" in "${generatorPath}" is outdated...`);
461
- }
462
- // remove if the SHA marker doesn't exist => outdated!
463
- this._showBusy(` Removing old "${generator.name}" templates`);
464
- await rm(generatorPath, { recursive: true });
465
- }
466
- }
467
-
468
- // re-fetch the generator and extract into local plugin folder
469
- if (!fs.existsSync(generatorPath)) {
470
- if (this.options.verbose) {
471
- this.log(`Extracting ZIP to "${generatorPath}"...`);
472
- }
473
- this._showBusy(` Downloading and extracting "${generator.name}" templates`);
474
- const reqZIPArchive = await octokit.repos.downloadZipballArchive({
475
- owner: generator.org,
476
- repo: generator.name,
477
- ref: commitSHA,
478
- });
479
- const buffer = Buffer.from(new Uint8Array(reqZIPArchive.data));
480
- const zip = new AdmZip(buffer);
481
- const zipEntries = zip.getEntries();
482
- zipEntries.forEach((entry) => {
483
- const match =
484
- !entry.isDirectory && entry.entryName.match(/[^\/]+(\/.+)/);
485
- let entryPath;
486
- if (generator.dir && match && match[1].startsWith(generator.dir)) {
487
- entryPath = path.dirname(match[1].substring(generator.dir.length));
488
- } else if (!generator.dir && match) {
489
- entryPath = path.dirname(match[1]);
490
- }
491
- if (entryPath) {
492
- zip.extractEntryTo(
493
- entry,
494
- path.join(generatorPath, entryPath),
495
- false,
496
- true
497
- );
498
- }
499
- });
500
- fs.writeFileSync(shaMarker, commitSHA);
501
-
502
- // run yarn/npm install
503
- if (this.options.verbose) {
504
- this.log("Installing the plugin dependencies...");
505
- }
506
- this._showBusy(` Preparing "${generator.name}"`);
507
- await new Promise(function (resolve, reject) {
508
- spawn((hasYarn() ? "yarn" : "npm"), ["install", "--no-progress", "--ignore-engines"], {
509
- stdio: this.config.verbose ? "inherit" : "ignore",
510
- cwd: generatorPath,
511
- env: {
512
- ...process.env,
513
- "NO_UPDATE_NOTIFIER": true
514
- }
515
- }).on("exit", function (code) {
516
- resolve(code);
517
- }).on("error", function (err) {
518
- reject(err);
519
- });
520
- }.bind(this));
521
- }
522
-
523
- this._clearBusy(true);
524
-
525
- }
526
-
527
- // filter the local options and the help command
528
- const opts = Object.keys(this._options).filter(
529
- (optionName) =>
530
- !(generatorOptions.hasOwnProperty(optionName) || optionName === "help")
531
- );
532
-
533
- // create the env for the plugin generator
534
- const yeoman = require("yeoman-environment");
535
- const env = yeoman.createEnv(this.args, opts);
536
-
537
- // helper to derive the subcommand
538
- function deriveSubcommand(namespace) {
539
- const match = namespace.match(/[^:]+:(.+)/);
540
- return match ? match[1] : namespace;
541
- }
542
-
543
- // filter the hidden subgenerators already
544
- // -> subgenerators must be found in env as they are returned by lookup!
545
- let subGenerators = env
546
- .lookup({ localOnly: true, packagePaths: generatorPath })
547
- .filter((sub) => {
548
- const subGenerator = env.get(sub.namespace);
549
- return !subGenerator.hidden;
550
- });
551
-
552
- // list the available subgenerators in the console (as help)
553
- if (this.options.list) {
554
- let maxLength = 0;
555
- this.log(subGenerators
556
- .map(sub => {
557
- maxLength = Math.max(sub.namespace.length, maxLength);
558
- return sub;
559
- })
560
- .reduce((output, sub) => {
561
- const subGenerator = env.get(sub.namespace);
562
- const displayName = subGenerator.displayName || "";
563
- let line = ` ${deriveSubcommand(sub.namespace).padEnd(maxLength + 2)}`;
564
- if (displayName) {
565
- line += ` # ${subGenerator.displayName}`;
566
- }
567
- return `${output}\n${line}`;
568
- }, `Subcommands (${subGenerators.length}):`));
569
- return;
570
- }
571
-
572
- // if a subcommand is provided as argument, identify the matching subgenerator
573
- // and remove the rest of the subgenerators from the list for later steps
574
- if (this.options.subcommand) {
575
- const selectedSubGenerator = subGenerators
576
- .filter((sub) => {
577
- // identify the subgenerator by subcommand
578
- return new RegExp(`:${this.options.subcommand}$`).test(sub.namespace);
579
- });
580
- if (selectedSubGenerator.length == 1) {
581
- subGenerators = selectedSubGenerator;
582
- } else {
583
- this.log(
584
- `The generator ${chalk.red(
585
- this.options.generator
586
- )} has no subcommand ${chalk.red(
587
- this.options.subcommand
588
- )}. Please select an existing subcommand!`
589
- );
590
- }
591
- }
592
-
593
- // transform the list of the subgenerators and identify the
594
- // default subgenerator for the default selection
595
- let defaultSubGenerator;
596
- let maxLength = 0;
597
- subGenerators = subGenerators
598
- .map(sub => {
599
- const generator = env.get(sub.namespace);
600
- let subcommand = deriveSubcommand(sub.namespace);
601
- let displayName = generator.displayName || subcommand;
602
- maxLength = Math.max(displayName.length, maxLength);
603
- return {
604
- subcommand,
605
- displayName,
606
- sub,
607
- };
608
- })
609
- .map(({ subcommand, displayName, sub }) => {
610
- const transformed = {
611
- name: `${displayName.padEnd(maxLength + 2)} [${subcommand}]`,
612
- value: sub.namespace,
613
- };
614
- if (/:app$/.test(sub.namespace)) {
615
- defaultSubGenerator = transformed;
616
- }
617
- return transformed;
618
- });
619
-
620
- // at least 1 subgenerator must be present
621
- if (subGenerators.length >= 1) {
622
-
623
- // by default the 1st subgenerator is used
624
- let subGenerator = subGenerators[0].value;
625
-
626
- // if more than 1 subgenerator is present
627
- // ask the developer to select one!
628
- if (subGenerators.length > 1) {
629
- subGenerator = (
630
- await this.prompt([
631
- {
632
- type: "list",
633
- name: "subGenerator",
634
- message: "What do you want to do?",
635
- default: defaultSubGenerator && defaultSubGenerator.value,
636
- choices: subGenerators,
637
- },
638
- ])
639
- ).subGenerator;
640
- }
641
-
642
- if (this.options.verbose) {
643
- this.log(`Calling ${chalk.red(subGenerator)}...\n \\_ in: ${generatorPath}`);
644
- }
645
-
646
- // finally, run the subgenerator
647
- env.run(subGenerator, {
648
- verbose: this.options.verbose,
649
- embedded: true,
650
- });
651
-
652
- } else {
653
- this.log(
654
- `The generator ${chalk.red(
655
- this.options.generator
656
- )} has no visible subgenerators!`
657
- );
658
- }
659
-
660
- }
125
+ constructor(args, opts) {
126
+ super(args, opts, {
127
+ // disable the Yeoman 5 package-manager logic (auto install)!
128
+ customInstallTask: "disabled",
129
+ });
130
+
131
+ Object.keys(generatorArgs).forEach((argName) => {
132
+ // register the argument for being displayed in the help
133
+ this.argument(argName, generatorArgs[argName]);
134
+ });
135
+
136
+ Object.keys(generatorOptions).forEach((optionName) => {
137
+ const initialValue = this.options[optionName];
138
+ // register the option for being displayed in the help
139
+ this.option(optionName, generatorOptions[optionName]);
140
+ const defaultedValue = this.options[optionName];
141
+ if (generatorOptions[optionName].npmConfig) {
142
+ // if a value is set, use the set value (parameter has higher precedence than npm config)
143
+ // => this.option(...) applies the default value to this.options[...] used as last resort
144
+ this.options[optionName] = initialValue || getNPMConfig(optionName) || defaultedValue;
145
+ }
146
+ });
147
+ }
148
+
149
+ _showBusy(statusText) {
150
+ this._clearBusy();
151
+ const progressChars = ["\\", "|", "/", "-"];
152
+ let i = 0;
153
+ process.stdout.write(`\r${statusText} `);
154
+ this._busy = {
155
+ text: statusText,
156
+ timer: setInterval(() => {
157
+ process.stdout.write(`\r${statusText} ${progressChars[i++]}`);
158
+ i %= progressChars.length;
159
+ }, 250),
160
+ };
161
+ }
162
+
163
+ _clearBusy(newLine) {
164
+ if (this._busy) {
165
+ clearInterval(this._busy.timer);
166
+ process.stdout.write("\r".padEnd(this._busy.text.length + 3) + (newLine ? "\n" : ""));
167
+ delete this._busy;
168
+ }
169
+ }
170
+
171
+ async _npmInstall(dir) {
172
+ return new Promise(
173
+ function (resolve, reject) {
174
+ spawn(hasYarn() ? "yarn" : "npm", ["install", "--no-progress", "--ignore-engines"], {
175
+ stdio: this.config.verbose ? "inherit" : "ignore",
176
+ cwd: dir,
177
+ env: {
178
+ ...process.env,
179
+ NO_UPDATE_NOTIFIER: true,
180
+ },
181
+ })
182
+ .on("exit", function (code) {
183
+ resolve(code);
184
+ })
185
+ .on("error", function (err) {
186
+ reject(err);
187
+ });
188
+ }.bind(this)
189
+ );
190
+ }
191
+
192
+ _unzip(zip, targetPath, zipInternalPath /* used for plugin generators from GitHub (e.g. TS tutorial) */) {
193
+ const zipEntries = zip.getEntries();
194
+ zipEntries.forEach((entry) => {
195
+ const match = !entry.isDirectory && entry.entryName.match(/[^\/]+(\/.+)/);
196
+ let entryPath;
197
+ if (zipInternalPath && match && match[1].startsWith(zipInternalPath)) {
198
+ entryPath = path.dirname(match[1].substring(zipInternalPath.length));
199
+ } else if (!zipInternalPath && match) {
200
+ entryPath = path.dirname(match[1]);
201
+ }
202
+ if (entryPath) {
203
+ zip.extractEntryTo(entry, path.join(targetPath, entryPath), false, true);
204
+ }
205
+ });
206
+ }
207
+
208
+ async prompting() {
209
+ const home = path.join(__dirname, "..", "..");
210
+ const pkgJson = require(path.join(home, "package.json"));
211
+
212
+ // Have Yeoman greet the user.
213
+ if (!this.options.embedded) {
214
+ this.log(yosay(`Welcome to the ${chalk.red("easy-ui5")} ${chalk.yellow(pkgJson.version)} generator!`));
215
+ }
216
+
217
+ // by default we install the easy-ui5 plugin generators into the following folder:
218
+ // %user_dir%/.npm/_generator-easy-ui5/plugin-generators
219
+ let pluginsHome = this.options.pluginsHome;
220
+ if (this.options.verbose) {
221
+ console.error(`Plugin directory: ${chalk.green(pluginsHome)}`);
222
+ console.error(chalk.red(e.message));
223
+ }
224
+ fs.mkdirSync(pluginsHome, { recursive: true });
225
+
226
+ // log the plugins and configuration
227
+ if (this.options.plugins) {
228
+ const yeoman = require("yeoman-environment/package.json");
229
+
230
+ const components = {
231
+ "Node.js": process.version,
232
+ "yeoman-environment": yeoman.version,
233
+ "generator-easy-ui5": pkgJson.version,
234
+ home: home,
235
+ pluginsHome: pluginsHome,
236
+ };
237
+
238
+ Object.keys(components).forEach((component) => {
239
+ this.log(`${chalk.green(component)}: ${components[component]}`);
240
+ });
241
+
242
+ this.log(chalk.green("\nAvailable generators:"));
243
+ glob.sync(`${pluginsHome}/*/package.json`).forEach((plugin) => {
244
+ const name = plugin.match(/.*\/generator-(.+)\/package\.json/)[1];
245
+ const lib = require(plugin);
246
+ this.log(` - ${chalk.green(name)}: ${lib.version}`);
247
+ });
248
+
249
+ return;
250
+ }
251
+
252
+ // create the octokit client to retrieve the generators from GH org
253
+ // when not running in offline mode!
254
+ let octokit;
255
+ if (this.options.offline) {
256
+ this.log(`Running in ${chalk.yellow("offline")} mode!`);
257
+ } else {
258
+ octokit = new MyOctokit({
259
+ userAgent: `${this.rootGeneratorName()}:${this.rootGeneratorVersion()}`,
260
+ auth: this.options.ghAuthToken,
261
+ throttle: {
262
+ onRateLimit: (retryAfter, options) => {
263
+ this.log(`${chalk.yellow("Hit the GitHub API limit!")} Request quota exhausted for this request.`);
264
+ if (options.request.retryCount === 0) {
265
+ // only retries once
266
+ this.log(
267
+ `Retrying after ${retryAfter} seconds. Alternatively, you can cancel this operation and supply an auth token with the \`--ghAuthToken\` option. For more details, run \`yo easy-ui5 --help\`. `
268
+ );
269
+ return true;
270
+ }
271
+ },
272
+ onAbuseLimit: () => {
273
+ // does not retry, only logs a warning
274
+ this.log(`${chalk.red("Hit the GitHub API limit again!")} Please supply an auth token with the \`--ghAuthToken\` option. For more details, run \`yo easy-ui5 --help\` `);
275
+ },
276
+ },
277
+ });
278
+ }
279
+
280
+ // helper for filtering repos with corresponding subGenerator prefix
281
+ const filterReposWithSubGeneratorPrefix = (repos, subGeneratorPrefix) => {
282
+ if (!Array.isArray(repos)) {
283
+ return [];
284
+ }
285
+ return repos
286
+ .filter((repo) => repo.name.startsWith(`${subGeneratorPrefix}`))
287
+ .map((repo) => {
288
+ return {
289
+ org: repo.owner?.login,
290
+ name: repo.name,
291
+ branch: repo.default_branch,
292
+ subGeneratorName: repo.name.substring(subGeneratorPrefix.length),
293
+ };
294
+ });
295
+ };
296
+
297
+ // helper to retrieve the available repositories for a GH org
298
+ const listGeneratorsForOrg = async (ghOrg, subGeneratorPrefix, threshold) => {
299
+ const response = await octokit.repos.listForOrg({
300
+ org: ghOrg,
301
+ sort: "name",
302
+ per_page: threshold,
303
+ });
304
+ return filterReposWithSubGeneratorPrefix(response?.data, subGeneratorPrefix);
305
+ };
306
+
307
+ // helper to retrieve the available repositories for a GH user
308
+ const listGeneratorsForUser = async (ghUser, subGeneratorPrefix) => {
309
+ const response = await octokit.repos.listForUser({
310
+ username: ghUser,
311
+ sort: "name",
312
+ per_page: threshold,
313
+ });
314
+ return filterReposWithSubGeneratorPrefix(response?.data, subGeneratorPrefix);
315
+ };
316
+
317
+ // determine the generator to be used
318
+ let generator;
319
+
320
+ // try to identify whether concrete generator is defined
321
+ if (!generator) {
322
+ // determine generator by ${owner}/${repo}(!${dir})? syntax, e.g.:
323
+ // > yo easy-ui5 SAP-samples/ui5-typescript-tutorial
324
+ // > yo easy-ui5 SAP-samples/ui5-typescript-tutorial#1.0
325
+ // > yo easy-ui5 SAP-samples/ui5-typescript-tutorial\!/generator
326
+ // > yo easy-ui5 SAP-samples/ui5-typescript-tutorial\!/generator#1.0
327
+ const reGenerator = /([^\/]+)\/([^\!\#]+)(?:\!([^\#]+))?(?:\#(.+))?/;
328
+ const matchGenerator = reGenerator.exec(this.options.generator);
329
+ if (matchGenerator) {
330
+ // derive and path the generator information from command line
331
+ const [owner, repo, dir = "/generator", branch] = matchGenerator.slice(1);
332
+ // the plugin path is derived from the owner, repo, dir and branch
333
+ const pluginPath = `_/${owner}/${repo}${dir.replace(/[\/\\]/g, "_")}${branch ? `#${branch.replace(/[\/\\]/g, "_")}` : ""}`;
334
+ generator = {
335
+ org: owner,
336
+ name: repo,
337
+ branch,
338
+ dir,
339
+ pluginPath,
340
+ };
341
+ // log which generator is being used!
342
+ if (this.options.verbose) {
343
+ this.log(`Using generator ${chalk.green(`${owner}/${repo}!${dir}${branch ? "#" + branch : ""}`)}`);
344
+ }
345
+ }
346
+ }
347
+
348
+ // retrieve the available repositories (if no generator is specified specified directly)
349
+ let availGenerators;
350
+ if (!generator) {
351
+ // lookup the non-installed embedded generator(s)
352
+ const generatorsToBeInstalled = glob
353
+ .sync(`${path.join(__dirname, "../../plugins")}/generator-*.zip`)
354
+ .map((file) => {
355
+ const generatorName = path.basename(file, ".zip");
356
+ const generatorPath = path.join(pluginsHome, generatorName);
357
+ return {
358
+ file,
359
+ generatorPath,
360
+ };
361
+ })
362
+ .filter(({ generatorPath }) => !fs.existsSync(generatorPath));
363
+ // install the missing embedded generator(s)
364
+ if (generatorsToBeInstalled.length > 0) {
365
+ this._showBusy(`Installing embedded generators of ${chalk.red("easy-ui5")}...`);
366
+ await Promise.all(
367
+ generatorsToBeInstalled.map(({ file, generatorPath }) => {
368
+ this._unzip(new AdmZip(file), generatorPath);
369
+ return this._npmInstall(generatorPath);
370
+ })
371
+ );
372
+ this._clearBusy(true);
373
+ }
374
+ // offline mode means local generators only versus only mode
375
+ if (this.options.offline) {
376
+ availGenerators = glob.sync(`${pluginsHome}/generator-*/package.json`).map((plugin) => {
377
+ const match = plugin.match(/.*\/(generator-(.+))\/package\.json/);
378
+ return {
379
+ org: "local",
380
+ name: match[1],
381
+ subGeneratorName: match[2].match(/(?:ui5-)?(.*)/)?.[1] || match[2],
382
+ local: true,
383
+ };
384
+ });
385
+ } else {
386
+ // either lookup the generators from bestofui5.org (next option)
387
+ // or fetch it from the ui5-community gh organization
388
+ if (this.options.next) {
389
+ // check bestofui5.org for generators
390
+ try {
391
+ const response = await request({
392
+ method: "GET",
393
+ url: "https://raw.githubusercontent.com/ui5-community/bestofui5-data/live-data/data/data.json",
394
+ });
395
+ const data = JSON.parse(response.data);
396
+
397
+ availGenerators = data?.packages
398
+ ?.filter((entry) => {
399
+ return entry.type === "generator";
400
+ })
401
+ .map((entry) => {
402
+ return {
403
+ org: entry.gitHubOwner,
404
+ name: entry.gitHubRepo,
405
+ subGeneratorName: entry.gitHubRepo.match(/(?:generator-(?:ui5-)?)(.*)/)?.[1] || entry.gitHubRepo,
406
+ };
407
+ });
408
+ } catch (e) {
409
+ console.error("Failed to connect to bestofui5.org to retrieve all available generators! Run with --verbose for details!");
410
+ if (this.options.verbose) {
411
+ console.error(e);
412
+ }
413
+ return;
414
+ }
415
+ } else {
416
+ // check the main GH org for generators
417
+ try {
418
+ availGenerators = await listGeneratorsForOrg(this.options.ghOrg, this.options.subGeneratorPrefix, this.options.ghThreshold);
419
+ } catch (e) {
420
+ console.error(`Failed to connect to GitHub to retrieve all available generators for "${this.options.ghOrg}" organization! Run with --verbose for details!`);
421
+ if (this.options.verbose) {
422
+ console.error(e);
423
+ }
424
+ return;
425
+ }
426
+
427
+ // check the additional GH org for generators with a different prefix
428
+ try {
429
+ if (this.options.addGhOrg && this.options.addSubGeneratorPrefix) {
430
+ availGenerators = availGenerators.concat(await listGeneratorsForOrg(this.options.addGhOrg, this.options.addSubGeneratorPrefix, this.options.ghThreshold));
431
+ }
432
+ } catch (e) {
433
+ if (this.options.verbose) {
434
+ this.log(`Failed to connect to GitHub retrieve additional generators for "${this.options.addGhOrg}" organization! Try to retrieve for user...`);
435
+ }
436
+ try {
437
+ availGenerators = availGenerators.concat(await listGeneratorsForUser(this.options.addGhOrg, this.options.addSubGeneratorPrefix, this.options.ghThreshold));
438
+ } catch (e1) {
439
+ console.error(`Failed to connect to GitHub to retrieve additional generators for organization or user "${this.options.addGhOrg}"! Run with --verbose for details!`);
440
+ if (this.options.verbose) {
441
+ console.error(e1);
442
+ }
443
+ return;
444
+ }
445
+ }
446
+ }
447
+ }
448
+ }
449
+
450
+ // if no generator is provided and doesn't exist, ask for generator name
451
+ if (!generator) {
452
+ // check for provided generator being available on GH
453
+ generator = this.options.generator && availGenerators.find((repo) => repo.subGeneratorName === this.options.generator);
454
+
455
+ // if no generator is provided and doesn't exist, ask for generator name
456
+ if (this.options.generator && !generator) {
457
+ this.log(`The generator ${chalk.red(this.options.generator)} was not found. Please select an existing generator!`);
458
+ }
459
+
460
+ // still not found, select a generator
461
+ if (!generator) {
462
+ const generatorIdx = (
463
+ await this.prompt([
464
+ {
465
+ type: "list",
466
+ name: "generator",
467
+ message: "Select your generator?",
468
+ choices: availGenerators.map((availGenerator, idx) => ({
469
+ name: `${availGenerator.subGeneratorName}${this.options.addGhOrg ? ` [${availGenerator.org}]` : ""}`,
470
+ value: idx,
471
+ })),
472
+ },
473
+ ])
474
+ ).generator;
475
+ generator = availGenerators[generatorIdx];
476
+ }
477
+ }
478
+
479
+ let generatorPath = path.join(pluginsHome, generator.pluginPath || generator.name);
480
+ if (!this.options.offline) {
481
+ // lookup the default path of the generator if not set
482
+ if (!generator.branch) {
483
+ try {
484
+ const repoInfo = await octokit.repos.get({
485
+ owner: generator.org,
486
+ repo: generator.name,
487
+ });
488
+ generator.branch = repoInfo.data.default_branch;
489
+ } catch (e) {
490
+ console.error(`Generator "${owner}/${repo}!${dir}${branch ? "#" + branch : ""}" not found! Run with --verbose for details!`);
491
+ if (this.options.verbose) {
492
+ console.error(e);
493
+ }
494
+ return;
495
+ }
496
+ }
497
+ // fetch the branch to retrieve the latest commit SHA
498
+ let commitSHA;
499
+ try {
500
+ // determine the commitSHA
501
+ const reqBranch = await octokit.repos.getBranch({
502
+ owner: generator.org,
503
+ repo: generator.name,
504
+ branch: generator.branch,
505
+ });
506
+ commitSHA = reqBranch.data.commit.sha;
507
+ } catch (e) {
508
+ console.error(chalk.red(`Failed to retrieve the branch "${generator.branch}" for repository "${generator.name}" for "${generator.org}" organization! Run with --verbose for details!`));
509
+ if (this.options.verbose) {
510
+ console.error(chalk.red(e.message));
511
+ }
512
+ return;
513
+ }
514
+
515
+ if (this.options.verbose) {
516
+ this.log(`Using commit ${commitSHA} from @${generator.org}/${generator.name}#${generator.branch}!`);
517
+ }
518
+ const shaMarker = path.join(generatorPath, `.${commitSHA}`);
519
+
520
+ if (fs.existsSync(generatorPath) && !this.options.skipUpdate) {
521
+ // check if the SHA marker exists to know whether the generator is up-to-date or not
522
+ if (this.options.forceUpdate || !fs.existsSync(shaMarker)) {
523
+ if (this.options.verbose) {
524
+ this.log(`Generator ${chalk.yellow(generator.name)} in "${generatorPath}" is outdated!`);
525
+ }
526
+ // remove if the SHA marker doesn't exist => outdated!
527
+ this._showBusy(` Removing old ${chalk.yellow(generator.name)} templates...`);
528
+ await rm(generatorPath, { recursive: true });
529
+ }
530
+ }
531
+
532
+ // re-fetch the generator and extract into local plugin folder
533
+ if (!fs.existsSync(generatorPath)) {
534
+ // unzip the archive
535
+ if (this.options.verbose) {
536
+ this.log(`Extracting ZIP to "${generatorPath}"...`);
537
+ }
538
+ this._showBusy(` Downloading ${chalk.yellow(generator.name)} templates...`);
539
+ const reqZIPArchive = await octokit.repos.downloadZipballArchive({
540
+ owner: generator.org,
541
+ repo: generator.name,
542
+ ref: commitSHA,
543
+ });
544
+
545
+ this._showBusy(` Extracting ${chalk.yellow(generator.name)} templates...`);
546
+ const buffer = Buffer.from(new Uint8Array(reqZIPArchive.data));
547
+ const zip = new AdmZip(buffer);
548
+ this._unzip(zip, generatorPath, generator.dir);
549
+
550
+ // write the sha marker
551
+ fs.writeFileSync(shaMarker, commitSHA);
552
+
553
+ // run yarn/npm install
554
+ if (this.options.verbose) {
555
+ this.log("Installing the plugin dependencies...");
556
+ }
557
+ this._showBusy(` Preparing ${chalk.yellow(generator.name)}...`);
558
+ await this._npmInstall(generatorPath);
559
+ }
560
+
561
+ this._clearBusy(true);
562
+ }
563
+
564
+ // do not execute the plugin generator during the setup/embed mode
565
+ if (!this.options.embed) {
566
+ // filter the local options and the help command
567
+ const opts = Object.keys(this._options).filter((optionName) => !(generatorOptions.hasOwnProperty(optionName) || optionName === "help"));
568
+
569
+ // create the env for the plugin generator
570
+ let env = this.env; // in case of Yeoman UI the env is injected!
571
+ if (!env) {
572
+ const yeoman = require("yeoman-environment");
573
+ env = yeoman.createEnv(this.args, opts);
574
+ }
575
+
576
+ // helper to derive the subcommand
577
+ function deriveSubcommand(namespace) {
578
+ const match = namespace.match(/[^:]+:(.+)/);
579
+ return match ? match[1] : namespace;
580
+ }
581
+
582
+ // filter the hidden subgenerators already
583
+ // -> subgenerators must be found in env as they are returned by lookup!
584
+ let subGenerators = env.lookup({ localOnly: true, packagePaths: generatorPath }).filter((sub) => {
585
+ const subGenerator = env.get(sub.namespace);
586
+ return !subGenerator.hidden;
587
+ });
588
+
589
+ // list the available subgenerators in the console (as help)
590
+ if (this.options.list) {
591
+ let maxLength = 0;
592
+ this.log(
593
+ subGenerators
594
+ .map((sub) => {
595
+ maxLength = Math.max(sub.namespace.length, maxLength);
596
+ return sub;
597
+ })
598
+ .reduce((output, sub) => {
599
+ const subGenerator = env.get(sub.namespace);
600
+ const displayName = subGenerator.displayName || "";
601
+ let line = ` ${deriveSubcommand(sub.namespace).padEnd(maxLength + 2)}`;
602
+ if (displayName) {
603
+ line += ` # ${subGenerator.displayName}`;
604
+ }
605
+ return `${output}\n${line}`;
606
+ }, `Subcommands (${subGenerators.length}):`)
607
+ );
608
+ return;
609
+ }
610
+
611
+ // if a subcommand is provided as argument, identify the matching subgenerator
612
+ // and remove the rest of the subgenerators from the list for later steps
613
+ if (this.options.subcommand) {
614
+ const selectedSubGenerator = subGenerators.filter((sub) => {
615
+ // identify the subgenerator by subcommand
616
+ return new RegExp(`:${this.options.subcommand}$`).test(sub.namespace);
617
+ });
618
+ if (selectedSubGenerator.length == 1) {
619
+ subGenerators = selectedSubGenerator;
620
+ } else {
621
+ this.log(`The generator ${chalk.red(this.options.generator)} has no subcommand ${chalk.red(this.options.subcommand)}. Please select an existing subcommand!`);
622
+ }
623
+ }
624
+
625
+ // transform the list of the subgenerators and identify the
626
+ // default subgenerator for the default selection
627
+ let defaultSubGenerator;
628
+ let maxLength = 0;
629
+ subGenerators = subGenerators
630
+ .map((sub) => {
631
+ const subGenerator = env.get(sub.namespace);
632
+ let subcommand = deriveSubcommand(sub.namespace);
633
+ let displayName = subGenerator.displayName || subcommand;
634
+ maxLength = Math.max(displayName.length, maxLength);
635
+ return {
636
+ subcommand,
637
+ displayName,
638
+ sub,
639
+ };
640
+ })
641
+ .map(({ subcommand, displayName, sub }) => {
642
+ const transformed = {
643
+ name: `${displayName.padEnd(maxLength + 2)} [${subcommand}]`,
644
+ value: sub.namespace,
645
+ };
646
+ if (/:app$/.test(sub.namespace)) {
647
+ defaultSubGenerator = transformed;
648
+ }
649
+ return transformed;
650
+ });
651
+
652
+ // at least 1 subgenerator must be present
653
+ if (subGenerators.length >= 1) {
654
+ // by default the 1st subgenerator is used
655
+ let subGenerator = subGenerators[0].value;
656
+
657
+ // if more than 1 subgenerator is present
658
+ // ask the developer to select one!
659
+ if (subGenerators.length > 1) {
660
+ subGenerator = (
661
+ await this.prompt([
662
+ {
663
+ type: "list",
664
+ name: "subGenerator",
665
+ message: "What do you want to do?",
666
+ default: defaultSubGenerator && defaultSubGenerator.value,
667
+ choices: subGenerators,
668
+ },
669
+ ])
670
+ ).subGenerator;
671
+ }
672
+
673
+ if (this.options.verbose) {
674
+ this.log(`Calling ${chalk.red(subGenerator)}...\n \\_ in "${generatorPath}"`);
675
+ }
676
+
677
+ // finally, run the subgenerator
678
+ env.run(subGenerator, {
679
+ verbose: this.options.verbose,
680
+ embedded: true,
681
+ destinationRoot: this.destinationRoot(),
682
+ });
683
+ } else {
684
+ this.log(`The generator ${chalk.red(this.options.generator)} has no visible subgenerators!`);
685
+ }
686
+ } else {
687
+ // zip the content of the plugin generator or
688
+ // install the dependencies of the generator
689
+ if (this.options.verbose) {
690
+ this.log(`Embedding plugin generator ${chalk.yellow(generator.name)}...`);
691
+ }
692
+ const generatorZIP = new AdmZip();
693
+ const addLocalFile = (file) => {
694
+ const filePath = path.join(generator.name, path.relative(generatorPath, file));
695
+ generatorZIP.addLocalFile(file, path.dirname(filePath), path.basename(filePath));
696
+ if (this.options.verbose) {
697
+ this.log(` + file: ${file}`);
698
+ }
699
+ };
700
+ glob.sync(`${generatorPath}/*`, { nodir: true, dot: true }).forEach(addLocalFile);
701
+ glob.sync(`${generatorPath}/!(node_modules)/**/*`, { nodir: true, dot: true }).forEach(addLocalFile);
702
+ const generatorZIPPath = path.join(__dirname, "../../plugins", `${generator.name}.zip`);
703
+ generatorZIP.writeZip(generatorZIPPath);
704
+ if (this.options.verbose) {
705
+ this.log(`Stored plugin generator ${chalk.yellow(generator.name)} zip to "${generatorZIPPath}"!`);
706
+ }
707
+ }
708
+ }
661
709
  };