generator-easy-ui5 3.5.1 → 3.6.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/CHANGELOG.md CHANGED
@@ -1,13 +1,14 @@
1
- ## [3.5.1](https://github.com/SAP/generator-easy-ui5/compare/v3.2.0...v3.5.1) (2022-09-10)
1
+ ## [3.5.2](https://github.com/SAP/generator-easy-ui5/compare/v3.5.1...v3.5.2) (2023-06-09)
2
2
 
3
3
  ### Bug Fixes
4
4
 
5
- - enable for Yeoman UI usage ([03c2e38](https://github.com/SAP/generator-easy-ui5/commit/03c2e38af4eebe108d6076710b74d8aaf7c31d8d))
5
+ - sort subgen list, remove threshold, fix permission issues ([#123](https://github.com/SAP/generator-easy-ui5/issues/123)) ([c5dd321](https://github.com/SAP/generator-easy-ui5/commit/c5dd3218a26870a61722a9675a81831cc8af50e5)), closes [#122](https://github.com/SAP/generator-easy-ui5/issues/122) [#118](https://github.com/SAP/generator-easy-ui5/issues/118) [#117](https://github.com/SAP/generator-easy-ui5/issues/117) [#111](https://github.com/SAP/generator-easy-ui5/issues/111)
6
6
 
7
- ## [3.5.0](https://github.com/SAP/generator-easy-ui5/compare/v3.2.0...v3.5.0) (2022-07-26)
7
+ ## [3.5.1](https://github.com/SAP/generator-easy-ui5/compare/v3.2.0...v3.5.1) (2022-09-10)
8
8
 
9
9
  ### Bug Fixes
10
10
 
11
+ - enable for Yeoman UI usage ([03c2e38](https://github.com/SAP/generator-easy-ui5/commit/03c2e38af4eebe108d6076710b74d8aaf7c31d8d))
11
12
  - **postinstall:** allow usage of NPM config for ghAuthToken to avoid rate limit ([#104](https://github.com/SAP/generator-easy-ui5/issues/104)) ([371d7fb](https://github.com/SAP/generator-easy-ui5/commit/371d7fbc82b8a59cef896197b99239b505b3cd90)), closes [#100](https://github.com/SAP/generator-easy-ui5/issues/100)
12
13
  - **postinstall:** avoid non-existing node_modules ([#98](https://github.com/SAP/generator-easy-ui5/issues/98)) ([f93c7c9](https://github.com/SAP/generator-easy-ui5/commit/f93c7c99b9e5df7af801a085afe72be85e462007))
13
14
  - usage of proper gh org for logging and download ([#94](https://github.com/SAP/generator-easy-ui5/issues/94)) ([a88d4db](https://github.com/SAP/generator-easy-ui5/commit/a88d4dbb0a1f5ed3cf60f7ed0297c651222a83fb))
package/README.md CHANGED
@@ -8,15 +8,15 @@
8
8
 
9
9
  ## Description
10
10
 
11
- Easy-ui5 💙 is a [Yeoman](http://yeoman.io/) generator which enables you to create simple [SAPUI5](https://sapui5.hana.ondemand.com/)/[OpenUI5](https://openui5.hana.ondemand.com/)-based web-apps and other UI5-related projects within seconds.
11
+ Easy UI5 (`easy-ui5`) 💙 is a [Yeoman](http://yeoman.io/) generator which enables you to create simple [SAPUI5](https://ui5.sap.com)/[OpenUI5](https://sdk.openui5.org)-based web-apps and other UI5-related projects within seconds.
12
12
 
13
13
  This generator has been created to simplify the creation of your UI5 prototypes. Now you can scaffold simple UI5 projects from the shell/terminal of your choice. The current best practices (such as [async](https://blogs.sap.com/2018/12/18/ui5ers-buzz-41-best-practices-for-async-loading-in-ui5/)) are already baked into our templates so you don't have to worry about the definition of the metadata files.
14
14
 
15
- The purpose of this generator is to guide you on your first steps with [SAPUI5](https://sapui5.hana.ondemand.com/) and [SAP BTP](https://www.sap.com/products/business-technology-platform.html) deployments. Once you are familiar with those technologies, you might want to tweak the projects to adapt them for productive use-cases (such as continuous deployment pipelines and full i18n).
15
+ The purpose of the `project` subgenerator is to guide you on your first steps with [SAPUI5](https://sapui5.hana.ondemand.com/) and [SAP BTP](https://www.sap.com/products/business-technology-platform.html) deployments. Once you are familiar with those technologies, you might want to tweak the projects to adapt them for productive use-cases (such as continuous deployment pipelines and full i18n).
16
16
 
17
- > :warning: Starting with easy-ui5 v3, all templates will be outsource to repositories in the [UI5 Community](https://github.com/ui5-community/). This project is from now on a shell that will offer all generators hosted on that GitHub org. Easy-ui5 will download and install these repositories when needed.
17
+ > :warning: Starting with Easy UI5 v3, all templates will be outsource to repositories in the [UI5 Community](https://github.com/ui5-community/). This project is from now on a shell that will offer all generators hosted on that GitHub org. Easy UI5 will download and install these repositories when needed.
18
18
  >
19
- > By default, it will download the repository [generator-ui5-project](https://github.com/ui5-community/generator-ui5-project/) which contains the templates that were previously integrated in easy-ui5 < 3.
19
+ > By default, it will download the repository [generator-ui5-project](https://github.com/ui5-community/generator-ui5-project/) which contains the templates that were previously integrated in Easy UI5 < 3.
20
20
 
21
21
  ## Requirements
22
22
 
@@ -63,7 +63,8 @@ During the prompting phase, the generator will ask on which target platform your
63
63
  ## More generators
64
64
 
65
65
  And this is just the start!
66
- We made easy-ui5 extensible, so that the entire [UI5 Community](https://github.com/ui5-community/) can build additional plugins to scaffold any UI5-related development activity.
66
+
67
+ We made Easy UI5 extensible, so that the entire [UI5 Community](https://github.com/ui5-community/) can build additional plugins to scaffold any UI5-related development activity.
67
68
 
68
69
  By default, this generator comes with the [project-creation-plugin](https://github.com/ui5-community/generator-ui5-project) but there are many others as well:
69
70
 
@@ -98,6 +99,14 @@ Run the following command to see all subgenerators of a given plugin
98
99
  yo easy-ui5 [project|library] <sub-generator-id>
99
100
  ```
100
101
 
102
+ ## Proxy settings
103
+
104
+ If you are running Easy UI5 behind a coporate proxy, just use the default proxy environment variables for Node.js to configure your corporate proxy:
105
+
106
+ - `HTTP_PROXY`: Specify the value to use as the HTTP proxy for all connections, e.g., `HTTP_PROXY="http://proxy.mycompany.com:8080/"`.
107
+ - `HTTPS_PROXY`: Specify the value to use as the HTTPS proxy for all connections, e.g., `HTTPS_PROXY="https://proxy.mycompany.com:8080/"`.
108
+ - `NO_PROXY`: Define the hosts that should bypass the proxy, e.g., `NO_PROXY="localhost,.mycompany.com,192.168.6.254:80"`.
109
+
101
110
  ## How to obtain support
102
111
 
103
112
  Please use the GitHub bug tracking system to post questions, bug reports or to create pull requests.
@@ -1,20 +1,33 @@
1
- "use strict";
2
- const Generator = require("yeoman-generator");
3
- const chalk = require("chalk");
4
- const yosay = require("yosay");
5
- const spawn = require("cross-spawn");
6
- const { hasYarn } = require("yarn-or-npm");
7
-
8
- const path = require("path");
9
- const fs = require("fs");
10
- const { rm } = require("fs").promises;
11
- const glob = require("glob");
12
-
13
- const { request } = require("@octokit/request");
14
- const { Octokit } = require("@octokit/rest");
15
- const { throttling } = require("@octokit/plugin-throttling");
1
+ import Generator from "yeoman-generator";
2
+
3
+ import path from "path";
4
+ import fs from "fs";
5
+ import os from "os";
6
+ import url from "url";
7
+
8
+ import { glob } from "glob";
9
+ import chalk from "chalk";
10
+ import yosay from "yosay";
11
+ import libnpmconfig from "libnpmconfig";
12
+ import yarnOrNpm from "yarn-or-npm";
13
+ const { hasYarn } = yarnOrNpm;
14
+ import AdmZip from "adm-zip";
15
+ import { request } from "@octokit/request";
16
+ import { Octokit } from "@octokit/rest";
17
+ import { throttling } from "@octokit/plugin-throttling";
16
18
  const MyOctokit = Octokit.plugin(throttling);
17
- const AdmZip = require("adm-zip");
19
+ import spawn from "cross-spawn";
20
+
21
+ const __dirname = url.fileURLToPath(new URL(".", import.meta.url));
22
+
23
+ // apply proxy settings to GLOBAL_AGENT to support the proxy configuration
24
+ // provided via the standard Node.js environment varibales (used for fetch API)
25
+ let HTTP_PROXY, HTTPS_PROXY, NO_PROXY;
26
+ if (global?.GLOBAL_AGENT) {
27
+ HTTP_PROXY = global.GLOBAL_AGENT.HTTP_PROXY = process.env.HTTP_PROXY || process.env.http_proxy;
28
+ HTTPS_PROXY = global.GLOBAL_AGENT.HTTPS_PROXY = process.env.HTTPS_PROXY || process.env.https_proxy;
29
+ NO_PROXY = global.GLOBAL_AGENT.NO_PROXY = process.env.NO_PROXY || process.env.no_proxy;
30
+ }
18
31
 
19
32
  // helper to retrieve config entries from npm
20
33
  // --> npm config set easy-ui5_addGhOrg XYZ
@@ -22,13 +35,31 @@ const NPM_CONFIG_PREFIX = "easy-ui5_";
22
35
  let npmConfig;
23
36
  const getNPMConfig = (configName) => {
24
37
  if (!npmConfig) {
25
- npmConfig = require("libnpmconfig").read();
38
+ npmConfig = libnpmconfig.read();
26
39
  }
27
40
  return npmConfig && npmConfig[`${NPM_CONFIG_PREFIX}${configName}`];
28
41
  };
29
42
 
30
43
  // the command line options of the generator
31
44
  const generatorOptions = {
45
+ pluginsHome: {
46
+ type: String,
47
+ description: "Home directory of the plugin generators",
48
+ default: path.join(os.homedir(), ".npm", "_generator-easy-ui5", "plugin-generators"),
49
+ hide: true, // shouldn't be needed
50
+ npmConfig: true,
51
+ },
52
+ plugins: {
53
+ type: Boolean,
54
+ alias: "p",
55
+ description: "List detailed information about installed plugin generators",
56
+ },
57
+ ghBaseUrl: {
58
+ type: String,
59
+ description: "Base URL for the Octokit API (defaults to https://api.github.com if undefined)",
60
+ hide: true, // shouldn't be needed
61
+ npmConfig: true,
62
+ },
32
63
  ghAuthToken: {
33
64
  type: String,
34
65
  description: "GitHub authToken to optionally access private generator repositories",
@@ -40,12 +71,23 @@ const generatorOptions = {
40
71
  default: "ui5-community",
41
72
  hide: true, // we don't want to recommend to use this option
42
73
  },
74
+ ghThreshold: {
75
+ type: Number,
76
+ default: 100,
77
+ hide: true, // shouldn't be needed
78
+ },
43
79
  subGeneratorPrefix: {
44
80
  type: String,
45
81
  description: "Prefix used for the lookup of the available generators",
46
82
  default: "generator-ui5-",
47
83
  hide: true, // we don't want to recommend to use this option
48
84
  },
85
+ addGhBaseUrl: {
86
+ type: String,
87
+ description: "Base URL for the Octokit API for the additional generators (defaults to https://api.github.com if undefined)",
88
+ hide: true, // shouldn't be needed
89
+ npmConfig: true,
90
+ },
49
91
  addGhOrg: {
50
92
  type: String,
51
93
  description: "GitHub organization to lookup for additional available generators",
@@ -59,6 +101,11 @@ const generatorOptions = {
59
101
  hide: true, // we don't want to recommend to use this option
60
102
  npmConfig: true,
61
103
  },
104
+ embed: {
105
+ type: Boolean,
106
+ description: "Embeds the selected plugin generator",
107
+ hide: true, // shouldn't be needed
108
+ },
62
109
  list: {
63
110
  type: Boolean,
64
111
  description: "List the available subcommands of the generator",
@@ -80,14 +127,9 @@ const generatorOptions = {
80
127
  type: Boolean,
81
128
  description: "Enable detailed logging",
82
129
  },
83
- plugins: {
84
- type: Boolean,
85
- alias: "p",
86
- description: "Get detailed version information",
87
- },
88
130
  next: {
89
131
  type: Boolean,
90
- description: "Preview the next mode to consume templates from bestofui5.org",
132
+ description: "Preview the next mode to consume subgenerators from bestofui5.org",
91
133
  },
92
134
  };
93
135
 
@@ -95,16 +137,16 @@ const generatorArgs = {
95
137
  generator: {
96
138
  type: String,
97
139
  required: false,
98
- description: 'Name of the generator to invoke (without the "generator-ui5-" prefix)',
140
+ description: `Name of the generator to invoke (without the ${chalk.yellow("generator-ui5-")} prefix)`,
99
141
  },
100
142
  subcommand: {
101
143
  type: String,
102
144
  required: false,
103
- description: 'Name of the subcommand to invoke (without the "generator:" prefix)',
145
+ description: `Name of the subcommand to invoke (without the ${chalk.yellow("generator:")} prefix)`,
104
146
  },
105
147
  };
106
148
 
107
- module.exports = class extends Generator {
149
+ export default class extends Generator {
108
150
  constructor(args, opts) {
109
151
  super(args, opts, {
110
152
  // disable the Yeoman 5 package-manager logic (auto install)!
@@ -151,31 +193,80 @@ module.exports = class extends Generator {
151
193
  }
152
194
  }
153
195
 
196
+ async _npmInstall(dir) {
197
+ return new Promise(
198
+ function (resolve, reject) {
199
+ spawn(hasYarn() ? "yarn" : "npm", ["install", "--no-progress", "--ignore-engines"], {
200
+ stdio: this.config.verbose ? "inherit" : "ignore",
201
+ cwd: dir,
202
+ env: {
203
+ ...process.env,
204
+ NO_UPDATE_NOTIFIER: true,
205
+ },
206
+ })
207
+ .on("exit", function (code) {
208
+ resolve(code);
209
+ })
210
+ .on("error", function (err) {
211
+ reject(err);
212
+ });
213
+ }.bind(this)
214
+ );
215
+ }
216
+
217
+ _unzip(pathOrBuffer, targetPath, zipInternalPath /* used for plugin generators from GitHub (e.g. TS tutorial) */) {
218
+ const zip = new AdmZip(pathOrBuffer);
219
+ const zipEntries = zip.getEntries();
220
+ zipEntries.forEach((entry) => {
221
+ const match = !entry.isDirectory && entry.entryName.match(/[^\/]+(\/.+)/);
222
+ let entryPath;
223
+ if (zipInternalPath && match && match[1].startsWith(zipInternalPath)) {
224
+ entryPath = path.dirname(match[1].substring(zipInternalPath.length));
225
+ } else if (!zipInternalPath && match) {
226
+ entryPath = path.dirname(match[1]);
227
+ }
228
+ if (entryPath) {
229
+ zip.extractEntryTo(entry, path.join(targetPath, entryPath), false, true);
230
+ }
231
+ });
232
+ }
233
+
234
+ determineAppname() {
235
+ return "Easy UI5";
236
+ }
237
+
154
238
  async prompting() {
155
239
  const home = path.join(__dirname, "..", "..");
156
- const pkgJson = require(path.join(home, "package.json"));
240
+ const pkgJson = JSON.parse(fs.readFileSync(path.join(home, "package.json"), "utf8"));
157
241
 
158
242
  // Have Yeoman greet the user.
159
243
  if (!this.options.embedded) {
160
244
  this.log(yosay(`Welcome to the ${chalk.red("easy-ui5")} ${chalk.yellow(pkgJson.version)} generator!`));
161
245
  }
162
246
 
163
- // check the permissions to Easy UI5s plugin directory which must
164
- // allow read/write to install additional plugin generators
165
- let pluginsHome = path.join(home, "plugin-generators");
166
- try {
167
- fs.accessSync(pluginsHome, fs.constants.R_OK | fs.constants.W_OK);
168
- } catch (e) {
169
- pluginsHome = path.join(require("os").homedir(), ".npm", "_generator-easy-ui5", "plugin-generators");
170
- if (this.options.verbose) {
171
- console.error(`Plugin directory: ${chalk.green(pluginsHome)}`);
172
- console.error(chalk.red(e.message));
173
- }
174
- fs.mkdirSync(pluginsHome, { recursive: true });
247
+ // by default we install the easy-ui5 plugin generators into the following folder:
248
+ // %user_dir%/.npm/_generator-easy-ui5/plugin-generators
249
+ let pluginsHome = this.options.pluginsHome;
250
+ if (this.options.verbose) {
251
+ console.info(" Context:");
252
+ console.info(` - sourceRoot = ${chalk.green(this.sourceRoot())}`);
253
+ console.info(` - destinationRoot = ${chalk.green(this.destinationRoot())}`);
254
+ console.info(" Options:");
255
+ Object.keys(generatorOptions).forEach((option) => {
256
+ console.info(` - ${option} = ${chalk.green(this.options[option])}`);
257
+ });
258
+ console.info(" Proxy:");
259
+ console.info(` - HTTP_PROXY = ${chalk.green(HTTP_PROXY)}`);
260
+ console.info(` - HTTPS_PROXY = ${chalk.green(HTTPS_PROXY)}`);
261
+ console.info(` - NO_PROXY = ${chalk.green(NO_PROXY)}`);
175
262
  }
263
+ fs.mkdirSync(pluginsHome, { recursive: true });
176
264
 
177
265
  // log the plugins and configuration
178
266
  if (this.options.plugins) {
267
+ const { createRequire } = await import("node:module");
268
+ const require = createRequire(import.meta.url);
269
+
179
270
  const yeoman = require("yeoman-environment/package.json");
180
271
 
181
272
  const components = {
@@ -206,9 +297,11 @@ module.exports = class extends Generator {
206
297
  if (this.options.offline) {
207
298
  this.log(`Running in ${chalk.yellow("offline")} mode!`);
208
299
  } else {
209
- octokit = new MyOctokit({
300
+ // define the options for the Octokit API
301
+ const octokitOptions = {
210
302
  userAgent: `${this.rootGeneratorName()}:${this.rootGeneratorVersion()}`,
211
303
  auth: this.options.ghAuthToken,
304
+ baseUrl: this.options.ghBaseUrl,
212
305
  throttle: {
213
306
  onRateLimit: (retryAfter, options) => {
214
307
  this.log(`${chalk.yellow("Hit the GitHub API limit!")} Request quota exhausted for this request.`);
@@ -220,12 +313,14 @@ module.exports = class extends Generator {
220
313
  return true;
221
314
  }
222
315
  },
223
- onAbuseLimit: () => {
316
+ onSecondaryRateLimit: () => {
224
317
  // does not retry, only logs a warning
225
318
  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\` `);
226
319
  },
227
320
  },
228
- });
321
+ };
322
+ // create the octokit instance
323
+ octokit = new MyOctokit(octokitOptions);
229
324
  }
230
325
 
231
326
  // helper for filtering repos with corresponding subGenerator prefix
@@ -246,9 +341,12 @@ module.exports = class extends Generator {
246
341
  };
247
342
 
248
343
  // helper to retrieve the available repositories for a GH org
249
- const listGeneratorsForOrg = async (ghOrg, subGeneratorPrefix) => {
344
+ const listGeneratorsForOrg = async (ghOrg, subGeneratorPrefix, threshold) => {
250
345
  const response = await octokit.repos.listForOrg({
251
346
  org: ghOrg,
347
+ sort: "name",
348
+ // eslint-disable-next-line camelcase
349
+ per_page: threshold,
252
350
  });
253
351
  return filterReposWithSubGeneratorPrefix(response?.data, subGeneratorPrefix);
254
352
  };
@@ -257,6 +355,9 @@ module.exports = class extends Generator {
257
355
  const listGeneratorsForUser = async (ghUser, subGeneratorPrefix) => {
258
356
  const response = await octokit.repos.listForUser({
259
357
  username: ghUser,
358
+ sort: "name",
359
+ // eslint-disable-next-line camelcase
360
+ per_page: threshold,
260
361
  });
261
362
  return filterReposWithSubGeneratorPrefix(response?.data, subGeneratorPrefix);
262
363
  };
@@ -276,12 +377,14 @@ module.exports = class extends Generator {
276
377
  if (matchGenerator) {
277
378
  // derive and path the generator information from command line
278
379
  const [owner, repo, dir = "/generator", branch] = matchGenerator.slice(1);
380
+ // the plugin path is derived from the owner, repo, dir and branch
381
+ const pluginPath = `_/${owner}/${repo}${dir.replace(/[\/\\]/g, "_")}${branch ? `#${branch.replace(/[\/\\]/g, "_")}` : ""}`;
279
382
  generator = {
280
383
  org: owner,
281
384
  name: repo,
282
385
  branch,
283
386
  dir,
284
- pluginPath: `_/${owner}/${repo}`,
387
+ pluginPath,
285
388
  };
286
389
  // log which generator is being used!
287
390
  if (this.options.verbose) {
@@ -293,6 +396,29 @@ module.exports = class extends Generator {
293
396
  // retrieve the available repositories (if no generator is specified specified directly)
294
397
  let availGenerators;
295
398
  if (!generator) {
399
+ // lookup the non-installed embedded generator(s)
400
+ const generatorsToBeInstalled = glob
401
+ .sync(`${path.join(__dirname, "../../plugins")}/generator-*.zip`)
402
+ .map((file) => {
403
+ const generatorName = path.basename(file, ".zip");
404
+ const generatorPath = path.join(pluginsHome, generatorName);
405
+ return {
406
+ file,
407
+ generatorName,
408
+ generatorPath,
409
+ };
410
+ })
411
+ .filter(({ generatorPath }) => !fs.existsSync(generatorPath));
412
+ // install (unzip) the missing embedded generator(s)
413
+ if (generatorsToBeInstalled.length > 0) {
414
+ this._showBusy(" Installing embedded subgenerators...");
415
+ generatorsToBeInstalled.map(({ file, generatorName, generatorPath }) => {
416
+ this._showBusy(` Installing embedded subgenerator ${chalk.yellow(generatorName)}...`);
417
+ this._unzip(file, generatorPath);
418
+ });
419
+ this._clearBusy(true);
420
+ }
421
+ // offline mode means local generators only versus only mode
296
422
  if (this.options.offline) {
297
423
  availGenerators = glob.sync(`${pluginsHome}/generator-*/package.json`).map((plugin) => {
298
424
  const match = plugin.match(/.*\/(generator-(.+))\/package\.json/);
@@ -304,6 +430,8 @@ module.exports = class extends Generator {
304
430
  };
305
431
  });
306
432
  } else {
433
+ // either lookup the generators from bestofui5.org (next option)
434
+ // or fetch it from the ui5-community gh organization
307
435
  if (this.options.next) {
308
436
  // check bestofui5.org for generators
309
437
  try {
@@ -334,7 +462,7 @@ module.exports = class extends Generator {
334
462
  } else {
335
463
  // check the main GH org for generators
336
464
  try {
337
- availGenerators = await listGeneratorsForOrg(this.options.ghOrg, this.options.subGeneratorPrefix);
465
+ availGenerators = await listGeneratorsForOrg(this.options.ghOrg, this.options.subGeneratorPrefix, this.options.ghThreshold);
338
466
  } catch (e) {
339
467
  console.error(`Failed to connect to GitHub to retrieve all available generators for "${this.options.ghOrg}" organization! Run with --verbose for details!`);
340
468
  if (this.options.verbose) {
@@ -346,14 +474,14 @@ module.exports = class extends Generator {
346
474
  // check the additional GH org for generators with a different prefix
347
475
  try {
348
476
  if (this.options.addGhOrg && this.options.addSubGeneratorPrefix) {
349
- availGenerators = availGenerators.concat(await listGeneratorsForOrg(this.options.addGhOrg, this.options.addSubGeneratorPrefix));
477
+ availGenerators = availGenerators.concat(await listGeneratorsForOrg(this.options.addGhOrg, this.options.addSubGeneratorPrefix, this.options.ghThreshold));
350
478
  }
351
479
  } catch (e) {
352
480
  if (this.options.verbose) {
353
481
  this.log(`Failed to connect to GitHub retrieve additional generators for "${this.options.addGhOrg}" organization! Try to retrieve for user...`);
354
482
  }
355
483
  try {
356
- availGenerators = availGenerators.concat(await listGeneratorsForUser(this.options.addGhOrg, this.options.addSubGeneratorPrefix));
484
+ availGenerators = availGenerators.concat(await listGeneratorsForUser(this.options.addGhOrg, this.options.addSubGeneratorPrefix, this.options.ghThreshold));
357
485
  } catch (e1) {
358
486
  console.error(`Failed to connect to GitHub to retrieve additional generators for organization or user "${this.options.addGhOrg}"! Run with --verbose for details!`);
359
487
  if (this.options.verbose) {
@@ -423,16 +551,16 @@ module.exports = class extends Generator {
423
551
  branch: generator.branch,
424
552
  });
425
553
  commitSHA = reqBranch.data.commit.sha;
426
- } catch (e) {
554
+ } catch (ex) {
427
555
  console.error(chalk.red(`Failed to retrieve the branch "${generator.branch}" for repository "${generator.name}" for "${generator.org}" organization! Run with --verbose for details!`));
428
556
  if (this.options.verbose) {
429
- console.error(chalk.red(e.message));
557
+ console.error(chalk.red(ex.message));
430
558
  }
431
559
  return;
432
560
  }
433
561
 
434
562
  if (this.options.verbose) {
435
- this.log(`Using commit ${commitSHA} from @${generator.org}/${generator.name}#${generator.branch}...`);
563
+ this.log(`Using commit ${commitSHA} from @${generator.org}/${generator.name}#${generator.branch}!`);
436
564
  }
437
565
  const shaMarker = path.join(generatorPath, `.${commitSHA}`);
438
566
 
@@ -440,189 +568,193 @@ module.exports = class extends Generator {
440
568
  // check if the SHA marker exists to know whether the generator is up-to-date or not
441
569
  if (this.options.forceUpdate || !fs.existsSync(shaMarker)) {
442
570
  if (this.options.verbose) {
443
- this.log(`Generator "${generator.name}" in "${generatorPath}" is outdated...`);
571
+ this.log(`Generator ${chalk.yellow(generator.name)} in "${generatorPath}" is outdated!`);
444
572
  }
445
573
  // remove if the SHA marker doesn't exist => outdated!
446
- this._showBusy(` Removing old "${generator.name}" templates`);
574
+ this._showBusy(` Deleting subgenerator ${chalk.yellow(generator.name)}...`);
447
575
  await rm(generatorPath, { recursive: true });
448
576
  }
449
577
  }
450
578
 
451
579
  // re-fetch the generator and extract into local plugin folder
452
580
  if (!fs.existsSync(generatorPath)) {
581
+ // unzip the archive
453
582
  if (this.options.verbose) {
454
583
  this.log(`Extracting ZIP to "${generatorPath}"...`);
455
584
  }
456
- this._showBusy(` Downloading and extracting "${generator.name}" templates`);
585
+ this._showBusy(` Downloading subgenerator ${chalk.yellow(generator.name)}...`);
457
586
  const reqZIPArchive = await octokit.repos.downloadZipballArchive({
458
587
  owner: generator.org,
459
588
  repo: generator.name,
460
589
  ref: commitSHA,
461
590
  });
591
+
592
+ this._showBusy(` Extracting subgenerator ${chalk.yellow(generator.name)}...`);
462
593
  const buffer = Buffer.from(new Uint8Array(reqZIPArchive.data));
463
- const zip = new AdmZip(buffer);
464
- const zipEntries = zip.getEntries();
465
- zipEntries.forEach((entry) => {
466
- const match = !entry.isDirectory && entry.entryName.match(/[^\/]+(\/.+)/);
467
- let entryPath;
468
- if (generator.dir && match && match[1].startsWith(generator.dir)) {
469
- entryPath = path.dirname(match[1].substring(generator.dir.length));
470
- } else if (!generator.dir && match) {
471
- entryPath = path.dirname(match[1]);
472
- }
473
- if (entryPath) {
474
- zip.extractEntryTo(entry, path.join(generatorPath, entryPath), false, true);
475
- }
476
- });
477
- fs.writeFileSync(shaMarker, commitSHA);
594
+ this._unzip(buffer, generatorPath, generator.dir);
478
595
 
479
- // run yarn/npm install
480
- if (this.options.verbose) {
481
- this.log("Installing the plugin dependencies...");
482
- }
483
- this._showBusy(` Preparing "${generator.name}"`);
484
- await new Promise(
485
- function (resolve, reject) {
486
- spawn(hasYarn() ? "yarn" : "npm", ["install", "--no-progress", "--ignore-engines"], {
487
- stdio: this.config.verbose ? "inherit" : "ignore",
488
- cwd: generatorPath,
489
- env: {
490
- ...process.env,
491
- NO_UPDATE_NOTIFIER: true,
492
- },
493
- })
494
- .on("exit", function (code) {
495
- resolve(code);
496
- })
497
- .on("error", function (err) {
498
- reject(err);
499
- });
500
- }.bind(this)
501
- );
596
+ // write the sha marker
597
+ fs.writeFileSync(shaMarker, commitSHA);
502
598
  }
503
599
 
504
- this._clearBusy(true);
505
- }
506
-
507
- // filter the local options and the help command
508
- const opts = Object.keys(this._options).filter((optionName) => !(generatorOptions.hasOwnProperty(optionName) || optionName === "help"));
509
-
510
- // create the env for the plugin generator
511
- let env = this.env; // in case of Yeoman UI the env is injected!
512
- if (!env) {
513
- const yeoman = require("yeoman-environment");
514
- env = yeoman.createEnv(this.args, opts);
600
+ // only when embedding we clear the busy state as otherwise
601
+ // the npm install will immediately again show the busy state
602
+ if (this.options.embed) {
603
+ this._clearBusy(true);
604
+ }
515
605
  }
516
606
 
517
- // helper to derive the subcommand
518
- function deriveSubcommand(namespace) {
519
- const match = namespace.match(/[^:]+:(.+)/);
520
- return match ? match[1] : namespace;
521
- }
607
+ // do not execute the plugin generator during the setup/embed mode
608
+ if (!this.options.embed) {
609
+ // filter the local options and the help command
610
+ const opts = Object.keys(this._options).filter((optionName) => !(generatorOptions.hasOwnProperty(optionName) || optionName === "help"));
522
611
 
523
- // filter the hidden subgenerators already
524
- // -> subgenerators must be found in env as they are returned by lookup!
525
- let subGenerators = env.lookup({ localOnly: true, packagePaths: generatorPath }).filter((sub) => {
526
- const subGenerator = env.get(sub.namespace);
527
- return !subGenerator.hidden;
528
- });
612
+ // run yarn/npm install (always for self-healing!)
613
+ if (this.options.verbose) {
614
+ this.log("Installing the subgenerator dependencies...");
615
+ }
616
+ this._showBusy(` Preparing ${chalk.yellow(generator.name)}...`);
617
+ await this._npmInstall(generatorPath);
618
+ this._clearBusy(true);
529
619
 
530
- // list the available subgenerators in the console (as help)
531
- if (this.options.list) {
532
- let maxLength = 0;
533
- this.log(
534
- subGenerators
535
- .map((sub) => {
536
- maxLength = Math.max(sub.namespace.length, maxLength);
537
- return sub;
538
- })
539
- .reduce((output, sub) => {
540
- const subGenerator = env.get(sub.namespace);
541
- const displayName = subGenerator.displayName || "";
542
- let line = ` ${deriveSubcommand(sub.namespace).padEnd(maxLength + 2)}`;
543
- if (displayName) {
544
- line += ` # ${subGenerator.displayName}`;
545
- }
546
- return `${output}\n${line}`;
547
- }, `Subcommands (${subGenerators.length}):`)
548
- );
549
- return;
550
- }
620
+ // create the env for the plugin generator
621
+ let env = this.env; // in case of Yeoman UI the env is injected!
622
+ if (!env) {
623
+ const yeoman = require("yeoman-environment");
624
+ env = yeoman.createEnv(this.args, opts);
625
+ }
551
626
 
552
- // if a subcommand is provided as argument, identify the matching subgenerator
553
- // and remove the rest of the subgenerators from the list for later steps
554
- if (this.options.subcommand) {
555
- const selectedSubGenerator = subGenerators.filter((sub) => {
556
- // identify the subgenerator by subcommand
557
- return new RegExp(`:${this.options.subcommand}$`).test(sub.namespace);
558
- });
559
- if (selectedSubGenerator.length == 1) {
560
- subGenerators = selectedSubGenerator;
561
- } else {
562
- this.log(`The generator ${chalk.red(this.options.generator)} has no subcommand ${chalk.red(this.options.subcommand)}. Please select an existing subcommand!`);
627
+ // helper to derive the subcommand
628
+ function deriveSubcommand(namespace) {
629
+ const match = namespace.match(/[^:]+:(.+)/);
630
+ return match ? match[1] : namespace;
563
631
  }
564
- }
565
632
 
566
- // transform the list of the subgenerators and identify the
567
- // default subgenerator for the default selection
568
- let defaultSubGenerator;
569
- let maxLength = 0;
570
- subGenerators = subGenerators
571
- .map((sub) => {
633
+ // filter the hidden subgenerators already
634
+ // -> subgenerators must be found in env as they are returned by lookup!
635
+ let subGenerators = env.lookup({ localOnly: true, packagePaths: generatorPath }).filter((sub) => {
572
636
  const subGenerator = env.get(sub.namespace);
573
- let subcommand = deriveSubcommand(sub.namespace);
574
- let displayName = subGenerator.displayName || subcommand;
575
- maxLength = Math.max(displayName.length, maxLength);
576
- return {
577
- subcommand,
578
- displayName,
579
- sub,
580
- };
581
- })
582
- .map(({ subcommand, displayName, sub }) => {
583
- const transformed = {
584
- name: `${displayName.padEnd(maxLength + 2)} [${subcommand}]`,
585
- value: sub.namespace,
586
- };
587
- if (/:app$/.test(sub.namespace)) {
588
- defaultSubGenerator = transformed;
589
- }
590
- return transformed;
637
+ return !subGenerator.hidden;
591
638
  });
592
639
 
593
- // at least 1 subgenerator must be present
594
- if (subGenerators.length >= 1) {
595
- // by default the 1st subgenerator is used
596
- let subGenerator = subGenerators[0].value;
597
-
598
- // if more than 1 subgenerator is present
599
- // ask the developer to select one!
600
- if (subGenerators.length > 1) {
601
- subGenerator = (
602
- await this.prompt([
603
- {
604
- type: "list",
605
- name: "subGenerator",
606
- message: "What do you want to do?",
607
- default: defaultSubGenerator && defaultSubGenerator.value,
608
- choices: subGenerators,
609
- },
610
- ])
611
- ).subGenerator;
640
+ // list the available subgenerators in the console (as help)
641
+ if (this.options.list) {
642
+ let maxLength = 0;
643
+ this.log(
644
+ subGenerators
645
+ .map((sub) => {
646
+ maxLength = Math.max(sub.namespace.length, maxLength);
647
+ return sub;
648
+ })
649
+ .reduce((output, sub) => {
650
+ const subGenerator = env.get(sub.namespace);
651
+ const displayName = subGenerator.displayName || "";
652
+ let line = ` ${deriveSubcommand(sub.namespace).padEnd(maxLength + 2)}`;
653
+ if (displayName) {
654
+ line += ` # ${subGenerator.displayName}`;
655
+ }
656
+ return `${output}\n${line}`;
657
+ }, `Subcommands (${subGenerators.length}):`)
658
+ );
659
+ return;
612
660
  }
613
661
 
614
- if (this.options.verbose) {
615
- this.log(`Calling ${chalk.red(subGenerator)}...\n \\_ in: ${generatorPath}`);
662
+ // if a subcommand is provided as argument, identify the matching subgenerator
663
+ // and remove the rest of the subgenerators from the list for later steps
664
+ if (this.options.subcommand) {
665
+ const selectedSubGenerator = subGenerators.filter((sub) => {
666
+ // identify the subgenerator by subcommand
667
+ return new RegExp(`:${this.options.subcommand}$`).test(sub.namespace);
668
+ });
669
+ if (selectedSubGenerator.length == 1) {
670
+ subGenerators = selectedSubGenerator;
671
+ } else {
672
+ this.log(`The generator ${chalk.red(this.options.generator)} has no subcommand ${chalk.red(this.options.subcommand)}. Please select an existing subcommand!`);
673
+ }
616
674
  }
617
675
 
618
- // finally, run the subgenerator
619
- env.run(subGenerator, {
620
- verbose: this.options.verbose,
621
- embedded: true,
622
- destinationRoot: this.destinationRoot(),
623
- });
676
+ // transform the list of the subgenerators and identify the
677
+ // default subgenerator for the default selection
678
+ let defaultSubGenerator;
679
+ let maxLength = 0;
680
+ subGenerators = subGenerators
681
+ .map((sub) => {
682
+ const subGenerator = env.get(sub.namespace);
683
+ let subcommand = deriveSubcommand(sub.namespace);
684
+ let displayName = subGenerator.displayName || subcommand;
685
+ maxLength = Math.max(displayName.length, maxLength);
686
+ return {
687
+ subcommand,
688
+ displayName,
689
+ sub,
690
+ };
691
+ })
692
+ .map(({ subcommand, displayName, sub }) => {
693
+ const transformed = {
694
+ name: `${displayName.padEnd(maxLength + 2)} [${subcommand}]`,
695
+ value: sub.namespace,
696
+ };
697
+ if (/:app$/.test(sub.namespace)) {
698
+ defaultSubGenerator = transformed;
699
+ }
700
+ return transformed;
701
+ });
702
+
703
+ // at least 1 subgenerator must be present
704
+ if (subGenerators.length >= 1) {
705
+ // by default the 1st subgenerator is used
706
+ let subGenerator = subGenerators[0].value;
707
+
708
+ // if more than 1 subgenerator is present
709
+ // ask the developer to select one!
710
+ if (subGenerators.length > 1) {
711
+ subGenerator = (
712
+ await this.prompt([
713
+ {
714
+ type: "list",
715
+ name: "subGenerator",
716
+ message: "What do you want to do?",
717
+ default: defaultSubGenerator && defaultSubGenerator.value,
718
+ choices: subGenerators,
719
+ },
720
+ ])
721
+ ).subGenerator;
722
+ }
723
+
724
+ if (this.options.verbose) {
725
+ this.log(`Calling ${chalk.red(subGenerator)}...\n \\_ in "${generatorPath}"`);
726
+ }
727
+
728
+ // finally, run the subgenerator
729
+ env.run(subGenerator, {
730
+ verbose: this.options.verbose,
731
+ embedded: true,
732
+ destinationRoot: this.destinationRoot(),
733
+ });
734
+ } else {
735
+ this.log(`The generator ${chalk.red(this.options.generator)} has no visible subgenerators!`);
736
+ }
624
737
  } else {
625
- this.log(`The generator ${chalk.red(this.options.generator)} has no visible subgenerators!`);
738
+ // zip the content of the plugin generator or
739
+ // install the dependencies of the generator
740
+ if (this.options.verbose) {
741
+ this.log(`Embedding plugin generator ${chalk.yellow(generator.name)}...`);
742
+ }
743
+ const generatorZIP = new AdmZip();
744
+ const addLocalFile = (file) => {
745
+ const filePath = path.join(generator.name, path.relative(generatorPath, file));
746
+ generatorZIP.addLocalFile(file, path.dirname(filePath), path.basename(filePath));
747
+ if (this.options.verbose) {
748
+ this.log(` + file: ${file}`);
749
+ }
750
+ };
751
+ glob.sync(`${generatorPath}/*`, { nodir: true, dot: true }).forEach(addLocalFile);
752
+ glob.sync(`${generatorPath}/!(node_modules)/**/*`, { nodir: true, dot: true }).forEach(addLocalFile);
753
+ const generatorZIPPath = path.join(__dirname, "../../plugins", `${generator.name}.zip`);
754
+ generatorZIP.writeZip(generatorZIPPath);
755
+ if (this.options.verbose) {
756
+ this.log(`Stored plugin generator ${chalk.yellow(generator.name)} zip to "${generatorZIPPath}"!`);
757
+ }
626
758
  }
627
759
  }
628
- };
760
+ }
package/package.json CHANGED
@@ -1,16 +1,18 @@
1
1
  {
2
2
  "name": "generator-easy-ui5",
3
- "version": "3.5.1",
3
+ "version": "3.6.0",
4
4
  "description": "Generator for UI5-based project",
5
5
  "main": "generators/app/index.js",
6
+ "type": "module",
6
7
  "files": [
7
- "generators"
8
+ "generators",
9
+ "plugins"
8
10
  ],
9
11
  "engines": {
10
12
  "node": ">=14.0.0"
11
13
  },
12
14
  "scripts": {
13
- "postinstall": "node generators/app/postinstall",
15
+ "prepublishOnly": "npx yo ./ project --embed",
14
16
  "start": "yo easy-ui5 project",
15
17
  "test": "mocha",
16
18
  "test:subgen:list": "yo easy-ui5 project --list",
@@ -46,33 +48,33 @@
46
48
  },
47
49
  "homepage": "https://github.com/SAP/generator-easy-ui5#readme",
48
50
  "dependencies": {
49
- "@octokit/plugin-throttling": "^4.2.0",
50
- "@octokit/rest": "^19.0.4",
51
- "adm-zip": "^0.5.9",
52
- "chalk": "^4.1.2",
53
- "colors": "1.4.0",
54
- "glob": "^7.2.0",
51
+ "@octokit/plugin-throttling": "^5.2.3",
52
+ "@octokit/rest": "^19.0.11",
53
+ "adm-zip": "^0.5.10",
54
+ "chalk": "^5.2.0",
55
+ "colors": "^1.4.0",
56
+ "glob": "^10.2.7",
55
57
  "libnpmconfig": "^1.2.1",
56
- "rimraf": "^3.0.2",
58
+ "rimraf": "^5.0.1",
57
59
  "yarn-or-npm": "^3.0.1",
58
- "yeoman-environment": "^3.10.0",
59
- "yeoman-generator": "^5.7.0",
60
+ "yeoman-environment": "^3.19.3",
61
+ "yeoman-generator": "^5.9.0",
60
62
  "yosay": "^2.0.2"
61
63
  },
62
64
  "devDependencies": {
63
- "@commitlint/cli": "17.0.3",
64
- "@commitlint/config-conventional": "17.0.3",
65
- "conventional-changelog-cli": "^2.2.2",
66
- "cz-conventional-changelog": "3.3.0",
67
- "eslint": "^8.18.0",
68
- "husky": "^8.0.1",
69
- "lint-staged": "^13.0.3",
70
- "mocha": "^10.0.0",
65
+ "@commitlint/cli": "^17.6.5",
66
+ "@commitlint/config-conventional": "^17.6.5",
67
+ "conventional-changelog-cli": "^3.0.0",
68
+ "cz-conventional-changelog": "^3.3.0",
69
+ "eslint": "^8.42.0",
70
+ "husky": "^8.0.3",
71
+ "lint-staged": "^13.2.2",
72
+ "mocha": "^10.2.0",
71
73
  "npm-run-all": "^4.1.5",
72
- "prettier": "2.7.1",
74
+ "prettier": "^2.8.8",
73
75
  "pretty-quick": "^3.1.3",
74
76
  "yeoman-assert": "^3.1.1",
75
- "yeoman-test": "^6.3.0"
77
+ "yeoman-test": "^7.4.0"
76
78
  },
77
79
  "config": {
78
80
  "commitizen": {
@@ -85,6 +87,7 @@
85
87
  ]
86
88
  },
87
89
  "overrides": {
90
+ "http-cache-semantics": "^4.1.1",
88
91
  "minimist": "^1.2.6"
89
92
  }
90
93
  }
@@ -1,143 +0,0 @@
1
- "use strict";
2
- const spawn = require("cross-spawn");
3
- const fs = require("fs");
4
- const { rm } = require("fs").promises;
5
- const path = require("path");
6
- const { hasYarn } = require("yarn-or-npm");
7
- const { Octokit } = require("@octokit/rest");
8
- const { throttling } = require("@octokit/plugin-throttling");
9
- const MyOctokit = Octokit.plugin(throttling);
10
- const AdmZip = require("adm-zip");
11
-
12
- // helper to retrieve config entries from npm
13
- // --> npm config set easy-ui5_addGhOrg XYZ
14
- const NPM_CONFIG_PREFIX = "easy-ui5_";
15
- let npmConfig;
16
- const getNPMConfig = (configName) => {
17
- if (!npmConfig) {
18
- npmConfig = require("libnpmconfig").read();
19
- }
20
- return npmConfig && npmConfig[`${NPM_CONFIG_PREFIX}${configName}`];
21
- };
22
-
23
- const ghOrg = "ui5-community",
24
- repoName = "generator-ui5-project",
25
- branch = "main",
26
- ghAuthToken = getNPMConfig("ghAuthToken");
27
-
28
- (async () => {
29
- let _busy;
30
-
31
- function showBusy(statusText) {
32
- clearBusy();
33
- const progressChars = ["\\", "|", "/", "-"];
34
- let i = 0;
35
- process.stdout.write(`\r${statusText} `);
36
- _busy = {
37
- text: statusText,
38
- timer: setInterval(() => {
39
- process.stdout.write(`\r${statusText} ${progressChars[i++]}`);
40
- i %= progressChars.length;
41
- }, 250),
42
- };
43
- }
44
-
45
- function clearBusy(newLine) {
46
- if (_busy) {
47
- clearInterval(_busy.timer);
48
- process.stdout.write("\r".padEnd(_busy.text.length + 3) + (newLine ? "\n" : ""));
49
- _busy = null;
50
- }
51
- }
52
-
53
- const pkg = require(path.join(__dirname, "../../package.json"));
54
- console.log(`${pkg.name}:${pkg.version} - ${ghAuthToken}`);
55
- const octokit = new MyOctokit({
56
- userAgent: `${pkg.name}:${pkg.version}`,
57
- auth: ghAuthToken,
58
- throttle: {
59
- onRateLimit: (retryAfter, options) => {
60
- console.log("Hit the GitHub API limit! Request quota exhausted for this request.");
61
- if (options.request.retryCount === 0) {
62
- // only retries once
63
- this.log(`Retrying after ${retryAfter} seconds. Alternatively, you can cancel this operation and supply an auth token with "npm config set easy-ui5_ghAuthToken ghp_xxxx".`);
64
- return true;
65
- }
66
- },
67
- onAbuseLimit: () => {
68
- // does not retry, only logs a warning
69
- console.error('Hit the GitHub API limit again! Please supply an auth token with with "npm config set easy-ui5_ghAuthToken ghp_xxxx".');
70
- },
71
- },
72
- });
73
-
74
- const reqBranch = await octokit.repos.getBranch({
75
- owner: ghOrg,
76
- repo: repoName,
77
- branch,
78
- });
79
-
80
- const commitSHA = reqBranch.data.commit.sha;
81
-
82
- // eslint-disable-next-line
83
- console.log(`Using commit ${commitSHA} from @${ghOrg}/${repoName}#${branch}...`);
84
- const generatorPath = path.join(__dirname, "../../plugin-generators", repoName);
85
- const shaMarker = path.join(generatorPath, `.${commitSHA}`);
86
-
87
- if (fs.existsSync(generatorPath)) {
88
- // check if the SHA marker exists to know whether the generator is up-to-date or not
89
- if (!fs.existsSync(shaMarker)) {
90
- // eslint-disable-next-line
91
- console.log(`Fetching new ZIP as the default generator is outdated...`);
92
- // remove if the SHA marker doesn't exist => outdated!
93
- showBusy(" Removing old default templates");
94
- await rm(generatorPath, { recursive: true });
95
- }
96
- }
97
-
98
- // re-fetch the generator and extract into local plugin folder
99
- if (!fs.existsSync(generatorPath)) {
100
- console.log("Extracting default templates...");
101
- showBusy(" Downloading and extracting default templates");
102
- const reqZIPArchive = await octokit.repos.downloadZipballArchive({
103
- owner: ghOrg,
104
- repo: repoName,
105
- ref: commitSHA,
106
- });
107
- const buffer = Buffer.from(new Uint8Array(reqZIPArchive.data));
108
- const zip = new AdmZip(buffer);
109
- const zipEntries = zip.getEntries();
110
- zipEntries.forEach((entry) => {
111
- const match = !entry.isDirectory && entry.entryName.match(/[^\/]+\/(.+)/);
112
- if (match) {
113
- const entryPath = match[1].slice(0, entry.name.length * -1);
114
- zip.extractEntryTo(entry, path.join(generatorPath, entryPath), false, true);
115
- }
116
- });
117
- fs.writeFileSync(shaMarker, commitSHA);
118
-
119
- // run yarn/npm install
120
- console.log("Installing the plugin dependencies...");
121
- showBusy(" Preparing the default templates");
122
- await new Promise(
123
- function (resolve, reject) {
124
- spawn(hasYarn() ? "yarn" : "npm", ["install", "--no-progress", "--ignore-engines"], {
125
- stdio: "inherit",
126
- cwd: generatorPath,
127
- env: {
128
- ...process.env,
129
- NO_UPDATE_NOTIFIER: true,
130
- },
131
- })
132
- .on("exit", function (code) {
133
- resolve(code);
134
- })
135
- .on("error", function (err) {
136
- reject(err);
137
- });
138
- }.bind(this)
139
- );
140
- }
141
-
142
- clearBusy(true);
143
- })();