generator-easy-ui5 3.1.2 → 3.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -10,36 +10,69 @@ const fs = require("fs");
10
10
  const { rmdir } = require("fs").promises;
11
11
 
12
12
  const { Octokit } = require("@octokit/rest");
13
+ const { throttling } = require("@octokit/plugin-throttling");
14
+ const MyOctokit = Octokit.plugin(throttling);
13
15
  const AdmZip = require("adm-zip");
14
16
 
17
+ // helper to retrieve config entries from npm
18
+ // --> npm config set easy-ui5_addGhOrg XYZ
19
+ const NPM_CONFIG_PREFIX = "easy-ui5_";
20
+ let npmConfig;
21
+ const getNPMConfig = (configName) => {
22
+ if (!npmConfig) {
23
+ npmConfig = require("libnpmconfig").read();
24
+ }
25
+ return npmConfig && npmConfig[`${NPM_CONFIG_PREFIX}${configName}`]
26
+ }
27
+
28
+ // the command line options of the generator
15
29
  const generatorOptions = {
16
30
  ghAuthToken: {
17
31
  type: String,
18
32
  description:
19
- `GitHub authToken to optionally access private generator repositories`,
33
+ "GitHub authToken to optionally access private generator repositories",
20
34
  },
21
35
  ghOrg: {
22
36
  type: String,
23
- description: `GitHub organization to lookup for available generators`,
37
+ description: "GitHub organization to lookup for available generators",
24
38
  default: "ui5-community",
25
- hidden: true // we don't want to recommend to use this option
39
+ hide: true // we don't want to recommend to use this option
40
+ },
41
+ subGeneratorPrefix: {
42
+ type: String,
43
+ description: "Prefix used for the lookup of the available generators",
44
+ default: "generator-ui5-",
45
+ hide: true // we don't want to recommend to use this option
46
+ },
47
+ addGhOrg: {
48
+ type: String,
49
+ description: `GitHub organization to lookup for additional available generators`,
50
+ hide: true, // we don't want to recommend to use this option
51
+ npmConfig: true
52
+ },
53
+ addSubGeneratorPrefix: {
54
+ type: String,
55
+ description: `Prefix used for the lookup of the additional available generators`,
56
+ default: "generator-",
57
+ hide: true, // we don't want to recommend to use this option
58
+ npmConfig: true
26
59
  },
27
60
  list: {
28
61
  type: Boolean,
29
- description: `List the available subcommands of the generator`,
62
+ description: "List the available subcommands of the generator",
30
63
  },
31
64
  verbose: {
32
65
  type: Boolean,
33
- description: `Enable detailed logging`,
66
+ description: "Enable detailed logging",
34
67
  },
35
68
  skipUpdate: {
36
69
  type: Boolean,
37
- description: `Skip the update of the plugin generators`,
70
+ description: "Skip the update of the plugin generators",
38
71
  },
39
72
  plugins: {
40
73
  type: Boolean,
41
74
  alias: "p",
42
- description: `Get detailed version information`,
75
+ description: "Get detailed version information",
43
76
  }
44
77
  };
45
78
 
@@ -47,18 +80,21 @@ const generatorArgs = {
47
80
  generator: {
48
81
  type: String,
49
82
  required: false,
50
- description: `Name of the generator to invoke (without the "generator-ui5-" prefix)`,
83
+ description: "Name of the generator to invoke (without the \"generator-ui5-\" prefix)",
51
84
  },
52
85
  subcommand: {
53
86
  type: String,
54
87
  required: false,
55
- description: `Name of the subcommand to invoke (without the "generator:" prefix)`,
88
+ description: "Name of the subcommand to invoke (without the \"generator:\" prefix)",
56
89
  },
57
90
  };
58
91
 
59
92
  module.exports = class extends Generator {
60
93
  constructor(args, opts) {
61
- super(args, opts);
94
+ super(args, opts, {
95
+ // disable the Yeoman 5 package-manager logic (auto install)!
96
+ customInstallTask: "disabled"
97
+ });
62
98
 
63
99
  Object.keys(generatorArgs).forEach((argName) => {
64
100
  // register the argument for being displayed in the help
@@ -66,19 +102,21 @@ module.exports = class extends Generator {
66
102
  });
67
103
 
68
104
  Object.keys(generatorOptions).forEach((optionName) => {
69
- if (!generatorOptions[optionName].hidden) {
70
- // register the option for being displayed in the help
71
- this.option(optionName, generatorOptions[optionName]);
72
- } else {
73
- // apply the default value for hidden options if needed
74
- this.options[optionName] = this.options[optionName] || generatorOptions[optionName].default;
105
+ const initialValue = this.options[optionName];
106
+ // register the option for being displayed in the help
107
+ this.option(optionName, generatorOptions[optionName]);
108
+ const defaultedValue = this.options[optionName];
109
+ if (generatorOptions[optionName].npmConfig) {
110
+ // if a value is set, use the set value (parameter has higher precedence than npm config)
111
+ // => this.option(...) applies the default value to this.options[...] used as last resort
112
+ this.options[optionName] = initialValue || getNPMConfig(optionName) || defaultedValue;
75
113
  }
76
114
  });
77
115
  }
78
116
 
79
117
  _showBusy(statusText) {
80
118
  this._clearBusy();
81
- const progressChars = ['\\', '|', '/', '-'];
119
+ const progressChars = ["\\", "|", "/", "-"];
82
120
  let i = 0;
83
121
  process.stdout.write(`\r${statusText} `);
84
122
  this._busy = {
@@ -93,56 +131,146 @@ module.exports = class extends Generator {
93
131
  _clearBusy(newLine) {
94
132
  if (this._busy) {
95
133
  clearInterval(this._busy.timer);
96
- process.stdout.write(`\r`.padEnd(this._busy.text.length + 3) + (newLine ? "\n" : ""));
134
+ process.stdout.write("\r".padEnd(this._busy.text.length + 3) + (newLine ? "\n" : ""));
97
135
  delete this._busy;
98
136
  }
99
137
  }
100
138
 
101
139
  async prompting() {
102
140
 
141
+ this.log(yosay(`Welcome to the ${chalk.red("easy-ui5")} generator!`));
142
+
143
+ const home = path.join(__dirname, "..", "..");
144
+
145
+ // check the permissions to Easy UI5s plugin directory which must
146
+ // allow read/write to install additional plugin generators
147
+ let pluginsHome = path.join(home, "plugin-generators");
148
+ try {
149
+ fs.accessSync(pluginsHome, fs.constants.R_OK | fs.constants.W_OK);
150
+ } catch (e) {
151
+ pluginsHome = path.join(require("os").homedir(), ".npm", "_generator-easy-ui5", "plugin-generators");
152
+ if (this.options.verbose) {
153
+ console.error(`Plugin directory: ${chalk.green(pluginsHome)}`);
154
+ console.error(chalk.red(e.message));
155
+ }
156
+ fs.mkdirSync(pluginsHome, { recursive: true });
157
+ }
158
+
159
+ // log the plugins and configuration
103
160
  if (this.options.plugins) {
104
161
  const glob = require("glob");
105
162
  const yeoman = require("yeoman-environment/package.json");
106
- const home = __dirname.slice(0, -14)
107
163
 
108
164
  const components = {
109
- 'Node.js': process.version,
110
- 'home': home,
111
- "yeoman-environment": yeoman.version
165
+ "Node.js": process.version,
166
+ "yeoman-environment": yeoman.version,
167
+ "generator-easy-ui5": require(path.join(home, "package.json")).version,
168
+ "home": home,
169
+ "pluginsHome": pluginsHome,
112
170
  };
113
- glob.sync(path.join(home, "plugin-generators/*/package.json")).forEach(function (plugin) {
114
- const name = plugin.match(/plugin-generators\/(.+)\/package\.json/)[1];
171
+
172
+ Object.keys(components).forEach((component) => {
173
+ this.log(`${chalk.green(component)}: ${components[component]}`);
174
+ });
175
+
176
+ this.log(chalk.green("\nAvailable generators:"));
177
+ glob.sync(`${pluginsHome}/*/package.json`).forEach((plugin) => {
178
+ const name = plugin.match(/.*\/(.+)\/package\.json/)[1];
115
179
  const lib = require(plugin);
116
- components[name] = lib.version;
180
+ this.log(` - ${chalk.green(name)}: ${lib.version}`);
117
181
  });
118
182
 
119
- const log = this.log;
120
- return Object.keys(components).forEach(function (component) {
121
- log(`${chalk.green(component)}: ${components[component]}`);
122
- })
183
+ return;
123
184
  }
124
185
 
125
- this.log(yosay(`Welcome to the ${chalk.red("easy-ui5")} generator!`));
126
-
127
186
  // create the octokit client to retrieve the generators from GH org
128
- const octokit = new Octokit({
187
+ const octokit = new MyOctokit({
129
188
  userAgent: `${this.rootGeneratorName()}:${this.rootGeneratorVersion()}`,
130
189
  auth: this.options.ghAuthToken,
190
+ throttle: {
191
+ onRateLimit: (retryAfter, options) => {
192
+ this.log(
193
+ `${chalk.yellow("Hit the GitHub API limit!")} Request quota exhausted for this request.`
194
+ );
195
+
196
+ if (options.request.retryCount === 0) {
197
+ // only retries once
198
+ 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\`. `);
199
+ return true;
200
+ }
201
+ },
202
+ onAbuseLimit: () => {
203
+ // does not retry, only logs a warning
204
+ this.log(
205
+ `${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\` `
206
+ );
207
+
208
+ },
209
+ }
131
210
  });
132
211
 
212
+ // helper for filtering repos with corresponding subGenerator prefix
213
+ const filterReposWithSubGeneratorPrefix = (repos, subGeneratorPrefix) => {
214
+ if (!Array.isArray(repos)) {
215
+ return [];
216
+ }
217
+ return repos.filter((repo) =>
218
+ repo.name.startsWith(`${subGeneratorPrefix}`)
219
+ ).map((repo) => {
220
+ return {
221
+ org: repo.owner?.login,
222
+ name: repo.name,
223
+ branch: repo.default_branch,
224
+ subGeneratorName: repo.name.substring(subGeneratorPrefix.length),
225
+ };
226
+ });
227
+ }
228
+
229
+ // helper to retrieve the available repositories for a GH org
230
+ const listGeneratorsForOrg = async (ghOrg, subGeneratorPrefix) => {
231
+ const response = await octokit.repos.listForOrg({
232
+ org: ghOrg,
233
+ });
234
+ return filterReposWithSubGeneratorPrefix(response?.data, subGeneratorPrefix);
235
+ }
236
+
237
+ // helper to retrieve the available repositories for a GH user
238
+ const listGeneratorsForUser = async (ghUser, subGeneratorPrefix) => {
239
+ const response = await octokit.repos.listForUser({
240
+ username: ghUser,
241
+ });
242
+ return filterReposWithSubGeneratorPrefix(response?.data, subGeneratorPrefix);
243
+ }
244
+
133
245
  // retrieve the available repositories
134
- let reqRepos;
246
+ let availGenerators;
135
247
  try {
136
- reqRepos = await octokit.repos.listForOrg({
137
- org: this.options.ghOrg,
138
- });
248
+ availGenerators = await listGeneratorsForOrg(this.options.ghOrg, this.options.subGeneratorPrefix);
139
249
  } catch (e) {
140
- console.error(`Failed to connect to GitHub to retrieve available repository for "${this.options.ghOrg}" organization! Run with --verbose for details!`);
250
+ console.error(`Failed to connect to GitHub to retrieve available generators for "${this.options.ghOrg}" organization! Run with --verbose for details!`);
141
251
  if (this.options.verbose) {
142
252
  console.error(e);
143
253
  }
144
254
  return;
145
255
  }
256
+ try {
257
+ if (this.options.addGhOrg && this.options.addSubGeneratorPrefix) {
258
+ availGenerators = availGenerators.concat(await listGeneratorsForOrg(this.options.addGhOrg, this.options.addSubGeneratorPrefix));
259
+ }
260
+ } catch (e) {
261
+ if (this.options.verbose) {
262
+ this.log(`Failed to connect to GitHub retrieve additional generators for "${this.options.addGhOrg}" organization! Try to retrieve for user...`);
263
+ }
264
+ try {
265
+ availGenerators = availGenerators.concat(await listGeneratorsForUser(this.options.addGhOrg, this.options.addSubGeneratorPrefix));
266
+ } catch (e) {
267
+ console.error(`Failed to connect to GitHub to retrieve additional generators for organization or user "${this.options.addGhOrg}"! Run with --verbose for details!`);
268
+ if (this.options.verbose) {
269
+ console.error(e);
270
+ }
271
+ return;
272
+ }
273
+ }
146
274
 
147
275
  // download the generator from GH (or the test generator)
148
276
  let generatorPath;
@@ -154,11 +282,9 @@ module.exports = class extends Generator {
154
282
  } else {
155
283
 
156
284
  // check for provided generator being available on GH
157
- let generator =
158
- this.options.generator &&
159
- reqRepos.data.find(
160
- (repo) => repo.name === `generator-ui5-${this.options.generator}`
161
- );
285
+ let generator = this.options.generator && availGenerators.find((repo) =>
286
+ repo.subGeneratorName === this.options.generator
287
+ );
162
288
 
163
289
  // if no generator is provided and doesn't exist, ask for generator name
164
290
  if (!generator) {
@@ -169,37 +295,35 @@ module.exports = class extends Generator {
169
295
  )} was not found. Please select an existing generator!`
170
296
  );
171
297
  }
172
- const generatorRepos = reqRepos.data.filter((repo) =>
173
- /^generator-ui5-.+/.test(repo.name)
174
- );
298
+
175
299
  const generatorIdx = (
176
300
  await this.prompt([
177
301
  {
178
302
  type: "list",
179
303
  name: "generator",
180
304
  message: "Select your generator?",
181
- choices: generatorRepos.map((repo, idx) => ({
182
- name: repo.name,
305
+ choices: availGenerators.map((availGenerator, idx) => ({
306
+ name: `${availGenerator.subGeneratorName}${this.options.addGhOrg ? ` [${availGenerator.org}]` : ""}`,
183
307
  value: idx,
184
308
  })),
185
309
  },
186
310
  ])
187
311
  ).generator;
188
- generator = generatorRepos[generatorIdx];
312
+ generator = availGenerators[generatorIdx];
189
313
  }
190
314
 
191
315
  // fetch the available branches to retrieve the latest commit SHA
192
316
  let reqBranch;
193
317
  try {
194
318
  reqBranch = await octokit.repos.getBranch({
195
- owner: this.options.ghOrg,
319
+ owner: generator.org,
196
320
  repo: generator.name,
197
- branch: generator.default_branch,
321
+ branch: generator.branch,
198
322
  });
199
323
  } catch (e) {
200
- console.error(`Failed to retrieve the default branch for repository "${generator.name}" for "${this.options.ghOrg}" organization! Run with --verbose for details!`);
324
+ console.error(chalk.red(`Failed to retrieve the default branch for repository "${generator.name}" for "${this.options.ghOrg}" organization! Run with --verbose for details!`));
201
325
  if (this.options.verbose) {
202
- console.error(e);
326
+ console.error(chalk.red(e.message));
203
327
  }
204
328
  return;
205
329
  }
@@ -211,14 +335,10 @@ module.exports = class extends Generator {
211
335
  `Using commit ${commitSHA} from @${this.options.ghOrg}/${generator.name}#${generator.default_branch}...`
212
336
  );
213
337
  }
214
- generatorPath = path.join(
215
- __dirname,
216
- "../../plugin-generators",
217
- generator.name
218
- );
338
+ generatorPath = path.join(pluginsHome, generator.name);
219
339
  const shaMarker = path.join(generatorPath, `.${commitSHA}`);
220
340
 
221
- if (fs.existsSync(generatorPath) && this.options.skipUpdate) {
341
+ if (fs.existsSync(generatorPath) && !this.options.skipUpdate) {
222
342
  // check if the SHA marker exists to know whether the generator is up-to-date or not
223
343
  if (!fs.existsSync(shaMarker)) {
224
344
  if (this.options.verbose) {
@@ -272,9 +392,9 @@ module.exports = class extends Generator {
272
392
  ...process.env,
273
393
  "NO_UPDATE_NOTIFIER": true
274
394
  }
275
- }).on('exit', function (code) {
395
+ }).on("exit", function (code) {
276
396
  resolve(code);
277
- }).on('error', function (err) {
397
+ }).on("error", function (err) {
278
398
  reject(err);
279
399
  });
280
400
  }.bind(this));
@@ -349,7 +469,7 @@ module.exports = class extends Generator {
349
469
  }
350
470
  }
351
471
 
352
- // transform the list of the subgenerators and identify the
472
+ // transform the list of the subgenerators and identify the
353
473
  // default subgenerator for the default selection
354
474
  let defaultSubGenerator;
355
475
  let maxLength = 0;
@@ -17,7 +17,7 @@ const ghOrg = "ui5-community",
17
17
 
18
18
  function showBusy(statusText) {
19
19
  clearBusy();
20
- const progressChars = ['\\', '|', '/', '-'];
20
+ const progressChars = ["\\", "|", "/", "-"];
21
21
  let i = 0;
22
22
  process.stdout.write(`\r${statusText} `);
23
23
  _busy = {
@@ -32,7 +32,7 @@ const ghOrg = "ui5-community",
32
32
  function clearBusy(newLine) {
33
33
  if (_busy) {
34
34
  clearInterval(_busy.timer);
35
- process.stdout.write(`\r`.padEnd(_busy.text.length + 3) + (newLine ? "\n" : ""));
35
+ process.stdout.write("\r".padEnd(_busy.text.length + 3) + (newLine ? "\n" : ""));
36
36
  _busy = null;
37
37
  }
38
38
  }
@@ -63,9 +63,9 @@ const ghOrg = "ui5-community",
63
63
  if (fs.existsSync(generatorPath)) {
64
64
  // check if the SHA marker exists to know whether the generator is up-to-date or not
65
65
  if (!fs.existsSync(shaMarker)) {
66
- console.log(`The default generator is outdated...`);
66
+ console.log("The default generator is outdated...");
67
67
  // remove if the SHA marker doesn't exist => outdated!
68
- showBusy(` Removing old default templates`);
68
+ showBusy(" Removing old default templates");
69
69
  await rmdir(generatorPath, { recursive: true });
70
70
  }
71
71
  }
@@ -73,8 +73,8 @@ const ghOrg = "ui5-community",
73
73
 
74
74
  // re-fetch the generator and extract into local plugin folder
75
75
  if (!fs.existsSync(generatorPath)) {
76
- console.log(`Extracting default templates...`);
77
- showBusy(` Downloading and extracting default templates`);
76
+ console.log("Extracting default templates...");
77
+ showBusy(" Downloading and extracting default templates");
78
78
  const reqZIPArchive = await octokit.repos.downloadZipballArchive({
79
79
  owner: ghOrg,
80
80
  repo: repoName,
@@ -100,7 +100,7 @@ const ghOrg = "ui5-community",
100
100
 
101
101
  // run yarn/npm install
102
102
  console.log("Installing the plugin dependencies...");
103
- showBusy(` Preparing the default templates`);
103
+ showBusy(" Preparing the default templates");
104
104
  await new Promise(function (resolve, reject) {
105
105
  spawn((hasYarn() ? "yarn" : "npm"), ["install", "--no-progress"], {
106
106
  stdio: "inherit",
@@ -109,9 +109,9 @@ const ghOrg = "ui5-community",
109
109
  ...process.env,
110
110
  "NO_UPDATE_NOTIFIER": true
111
111
  }
112
- }).on('exit', function (code) {
112
+ }).on("exit", function (code) {
113
113
  resolve(code);
114
- }).on('error', function (err) {
114
+ }).on("error", function (err) {
115
115
  reject(err);
116
116
  });
117
117
  }.bind(this));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "generator-easy-ui5",
3
- "version": "3.1.2",
3
+ "version": "3.2.0",
4
4
  "description": "Generator for UI5-based project",
5
5
  "main": "generators/app/index.js",
6
6
  "files": [
@@ -9,7 +9,7 @@
9
9
  "scripts": {
10
10
  "test": "mocha",
11
11
  "postinstall": "node generators/app/postinstall",
12
- "lint": "eslint .",
12
+ "lint": "eslint . --fix",
13
13
  "prettier": "prettier --write .",
14
14
  "start": "yo easy-ui5 project",
15
15
  "listSubGen": "yo easy-ui5 project --list",
@@ -36,21 +36,23 @@
36
36
  },
37
37
  "homepage": "https://github.com/SAP/generator-easy-ui5#readme",
38
38
  "dependencies": {
39
+ "@octokit/plugin-throttling": "^3.5.2",
39
40
  "@octokit/rest": "^18.12.0",
40
41
  "adm-zip": "^0.5.9",
41
42
  "chalk": "^4.1.2",
42
- "colors": "^1.4.0",
43
+ "colors": "1.4.0",
43
44
  "glob": "^7.2.0",
44
- "mocha": "^9.1.3",
45
+ "libnpmconfig": "^1.2.1",
46
+ "mocha": "^9.2.0",
45
47
  "rimraf": "^3.0.2",
46
48
  "yarn-or-npm": "^3.0.1",
47
49
  "yeoman-assert": "^3.1.1",
48
- "yeoman-environment": "^3.8.0",
49
- "yeoman-generator": "^5.4.2",
50
- "yeoman-test": "^6.2.0",
50
+ "yeoman-environment": "^3.9.1",
51
+ "yeoman-generator": "^5.6.1",
52
+ "yeoman-test": "^6.3.0",
51
53
  "yosay": "^2.0.2"
52
54
  },
53
55
  "devDependencies": {
54
- "prettier": "2.4.1"
56
+ "prettier": "2.5.1"
55
57
  }
56
58
  }