@sap/cds-dk 8.7.1 → 8.7.3

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.
@@ -1,42 +1,185 @@
1
1
  const term = require("../util/term");
2
-
3
2
  const io = require("./io");
4
3
  const checks = require("./checks");
5
4
  const generators = require("./generators");
6
-
7
5
  const cds = require("../../lib/cds");
8
6
  const { path, exists, isdir, isfile, readdir } = cds.utils
9
7
  const { exit } = require("process");
8
+
10
9
  const LOG = cds.debug("lint");
11
10
  const LOG_CONFIG = cds.debug("lint:config");
12
-
13
11
  const GENERAL_ERROR = 1;
14
12
 
15
- class Linter {
16
- constructor() {
17
- this.eslintCmd = "";
18
- this.eslintCmdShort = "eslint";
19
- this.eslintCmdFileExpr = "";
20
- this.isFile = false;
21
- this.help = "";
22
- this.debug = "";
23
- this.flags = [];
24
- this.configPath = "";
25
- this.configContents = {};
26
- this.cdsdkPath = path.join(__dirname, "../..");
27
- this.globalPath = path.join(this.cdsdkPath, "../..");
28
- this.extendsPlugin = false;
29
- this.fileExtensions = [];
30
- this.pluginPath = "";
31
- this.pluginApi = {};
32
- this.lintType = "global";
33
- this.ruleOpts = {};
34
- this.customRulesOpts = [];
35
- this.pluginRules = [];
36
- this.init = this.init.bind(this);
37
- this.lint = this.lint.bind(this);
13
+ /**
14
+ * Safely require a module, return empty object if module not found
15
+ * @param {string} module - module to require
16
+ * @param {boolean} esm - whether to use dynamic import to accomodate ESM
17
+ * @returns {Promise<ReturnType<NodeRequire> | {}>} module or empty object
18
+ */
19
+ async function tryRequire(module, esm = false) {
20
+ try {
21
+ return esm ? import(module) : require(module)
22
+ } catch (e) {
23
+ if (e.code === 'MODULE_NOT_FOUND' && e.message.includes(module)) return {}
24
+ // target module is either .mjs (etc) or user project is ESM
25
+ if (['ERR_REQUIRE_ASYNC_MODULE', 'ERR_REQUIRE_ESM'].includes(e.code)) return tryRequire(module, true)
26
+ throw e
27
+ }
28
+ }
29
+
30
+ /**
31
+ * Load ESLint module
32
+ * @param {string} currentDir - current directory to start search
33
+ * @returns {Promise<ReturnType<NodeRequire>>} - ESLint module
34
+ */
35
+ async function _loadEslint(currentDir) {
36
+ const { loadESLint, ESLint } = require('eslint')
37
+ const DefaultESLint = loadESLint
38
+ ? await loadESLint({ cwd: currentDir }) // ESLint 9
39
+ : new ESLint({ cwd: currentDir }) // ESLint 8
40
+ return DefaultESLint;
41
+ }
42
+
43
+ /**
44
+ * @param {string[]} configFiles - possible config file paths
45
+ * @param {string} currentDir - current directory to start search
46
+ * @returns {string} - path to config file
47
+ */
48
+ async function findConfigPath (configFiles, currentDir = '.') {
49
+ let configDir = path.resolve(currentDir)
50
+ while (configDir && configDir !== path.dirname(configDir)) {
51
+ for (const configFile of configFiles) {
52
+ const configPath = path.join(configDir, configFile)
53
+ const config = await tryRequire(configPath)
54
+ if (configFile !== 'package.json' || config?.eslintConfig) {
55
+ if (exists(configPath) && isfile(configPath)) { // TODO exists neccessary ?
56
+ return configPath
57
+ }
58
+ }
59
+ }
60
+ configDir = path.dirname(configDir);
61
+ }
62
+ return ''
63
+ }
64
+
65
+ /**
66
+ * This class and its subclasses are an attempt to smoothen out some of the
67
+ * workflow in the Linter class. It gets rid of some of the isESLintLegacy ? ... : ...
68
+ * assignments, although not all. The remaining branching is too closely coupled
69
+ * to some properties of the Linter class to be easily abstracted away.
70
+ * @abstract
71
+ */
72
+ class ESLintLoader {
73
+ /**
74
+ * Searches for ESLint config file types (in order or precedence)
75
+ * and returns corresponding directory (usually project's root dir)
76
+ * https://eslint.org/docs/user-guide/configuring#configuration-file-formats
77
+ * @param {string} currentDir start here and search until root dir
78
+ * @returns {string} dir containing ESLint config file (empty if not exists)
79
+ * @abstract
80
+ */
81
+ // eslint-disable-next-line no-unused-vars
82
+ async getConfigPath (currentDir = '.') { throw new Error("Not implemented") }
83
+ /** @type {import('./io').sanitizeEslintConfig} */
84
+ // eslint-disable-next-line no-unused-vars
85
+ sanitizeEslintConfig (configIn, customRuleExample, logger) { throw new Error("Not implemented") }
86
+ /** @type {import('./io').readEslintConfig} */
87
+ // eslint-disable-next-line no-unused-vars
88
+ readEslintConfig(configPath) { throw new Error("Not implemented") }
89
+ }
90
+
91
+ class ESLint8Loader extends ESLintLoader {
92
+ async getConfigPath(currentDir = '.') {
93
+ return findConfigPath([
94
+ ".eslintrc.js",
95
+ ".eslintrc.cjs",
96
+ ".eslintrc.yaml",
97
+ ".eslintrc.yml",
98
+ ".eslintrc.json",
99
+ ".eslintrc",
100
+ "package.json",
101
+ ], currentDir)
102
+ }
103
+
104
+ sanitizeEslintConfig (configIn, customRuleExample, logger) {
105
+ return io.sanitizeEslintConfigLegacy(configIn, customRuleExample, logger)
106
+ }
107
+
108
+ readEslintConfig(configPath) {
109
+ return io.readEslintConfigLegacy(configPath)
110
+ }
111
+ }
112
+
113
+ class ESLint9Loader extends ESLintLoader {
114
+ async getConfigPath(currentDir = '.') {
115
+ return findConfigPath([
116
+ "eslint.config.js",
117
+ "eslint.config.cjs",
118
+ "eslint.config.mjs",
119
+ // the following 3 formats require additional setup by the user as of today,
120
+ // see https://eslint.org/docs/latest/use/configure/configuration-files#typescript-configuration-files
121
+ "eslint.config.ts",
122
+ "eslint.config.mts",
123
+ "eslint.config.cts",
124
+ ], currentDir)
125
+ }
126
+
127
+ sanitizeEslintConfig (configIn, customRuleExample, logger) {
128
+ return io.sanitizeEslintConfig(configIn, customRuleExample, logger)
38
129
  }
39
130
 
131
+ readEslintConfig(configPath) {
132
+ return io.readEslintConfig(configPath)
133
+ }
134
+ }
135
+
136
+ class Linter {
137
+ /** @type {string | undefined} */
138
+ eslintCmd = "";
139
+ /** @type {string} */
140
+ eslintCmdShort = "eslint";
141
+ /** @type {string | string[]} */
142
+ eslintCmdFileExpr = "";
143
+ /** @type {boolean} */
144
+ isFile = false;
145
+ /** @type {string} */
146
+ help = "";
147
+ /** @type {string} */
148
+ debug = "";
149
+ /** @type {string[]} */
150
+ flags = [];
151
+ /** @type {string} */
152
+ configPath = "";
153
+ /** @type {object} */
154
+ configContents = {};
155
+ /** @type {import("fs").PathLike} */
156
+ cdsdkPath = path.join(__dirname, "../..");
157
+ /** @type {import("fs").PathLike} */
158
+ globalPath = path.join(this.cdsdkPath, "../..");
159
+ /** @type {boolean} */
160
+ extendsPlugin = false;
161
+ /** @type {string[]} */
162
+ fileExtensions = [];
163
+ /** @type {string} */
164
+ pluginPath = "";
165
+ /** @type {object} */
166
+ pluginApi = {};
167
+ /** @type {'global' | 'local'} */
168
+ lintType = "global";
169
+ /** @type {object} */
170
+ ruleOpts = {};
171
+ /** @type {array} */
172
+ customRulesOpts = [];
173
+ /** @type {array} */
174
+ pluginRules = [];
175
+ /** @type {ESLintLoader | undefined} */
176
+ #loader = undefined;
177
+ get loader () {
178
+ return this.#loader ??= this.isESLintLegacy
179
+ ? new ESLint8Loader()
180
+ : new ESLint9Loader()
181
+ };
182
+
40
183
  /**
41
184
  * Initializes `cds lint` call and generates the required content
42
185
  * object for `cds` executable
@@ -59,146 +202,69 @@ class Linter {
59
202
  /**
60
203
  * Runner for 'cds lint' which is a wrapper for eslint cmd calls
61
204
  * that detects and adds the required cmd line arguments
62
- * @param {*} args files/globs for eslint to lint
63
- * @param {*} options options/flag passed by user
205
+ * @param {string[]} args files/globs for eslint to lint
206
+ * @param {{[key: string]: unknown}} options options/flag passed by user
64
207
  */
65
208
  async lint(args, options) {
66
209
  // Check for ESLint (legacy)
67
210
  const currentDir = process.cwd();
68
- const DefaultESLint = await this._loadEslint(currentDir);
211
+ const DefaultESLint = await _loadEslint(currentDir)
69
212
  this.isESLintLegacy = DefaultESLint.configType === "eslintrc";
70
-
71
- this.eslintCmdOpts = this._sanitizeUserOpts(options);
213
+ this.eslintCmdOpts = options; // these should probably be passed on in #runEsLint!
72
214
  this.eslintCmdFileExpr = args.length ? args : ["."];
73
- if (this.eslintCmdOpts.help) {
74
- this._printHelp();
75
- } else {
76
- // Determine if case "local" or "global" and resolve plugin accordingly
77
- await this._getConfigFileAndSetProjectPath(args);
78
-
79
- if (this.pluginPath) {
80
- const overwriteRules = checks.hasEslintConfigContent(this.configContents, "rules");
81
- // Overwrite rules severities
82
- if (overwriteRules) {
83
- await this._overwriteRuleSeverities();
84
- }
85
215
 
86
- // Limit to CDS file extensions
87
- this._addExtensions();
216
+ if (this.eslintCmdOpts.help) {
217
+ this.#printHelp();
218
+ return
219
+ }
220
+ // Determine if case "local" or "global" and resolve plugin accordingly
221
+ await this.#getConfigFileAndSetProjectPath(args);
222
+
223
+ if (this.pluginPath) {
224
+ const overwriteRules = checks.hasEslintConfigContent(this.configContents, "rules");
225
+ // Overwrite rules severities
226
+ if (overwriteRules) {
227
+ await this.#overwriteRuleSeverities();
88
228
  }
89
229
 
90
- // Run ESLint with collected options
91
- try {
92
- await this._runEslint(currentDir, DefaultESLint);
93
- } catch (err) {
94
- term.error(err);
95
- }
230
+ // Limit to CDS file extensions
231
+ this.#addExtensions();
96
232
  }
97
- }
98
-
99
- _printHelp() {
100
- console.log(this.help.replace(/ \*([^*]+)\*/g, ` ${term.codes.bold}$1${term.codes.reset}`));
101
- }
102
233
 
103
- _sanitizeUserOpts(options) {
104
- for (let key in options) {
105
- let value = options[key];
106
- if (value && typeof value === "string") {
107
- value = value.replace(/'/g, "").replace(/"/g, "");
108
- }
234
+ // Run ESLint with collected options
235
+ try {
236
+ await this.#runEslint(currentDir, DefaultESLint);
237
+ } catch (err) {
238
+ term.error(err);
109
239
  }
110
- return options;
111
240
  }
112
241
 
113
242
  /**
114
- * Searches for ESLint config file types (in order or precedence)
115
- * and returns corresponding directory (usually project's root dir)
116
- * https://eslint.org/docs/user-guide/configuring#configuration-file-formats
117
- * @param {string} currentDir start here and search until root dir
118
- * @returns {string} dir containing ESLint config file (empty if not exists)
243
+ * Prints help message
119
244
  */
120
- async _getConfigPathLegacy(currentDir = ".") {
121
- const configFiles = [
122
- ".eslintrc.js",
123
- ".eslintrc.cjs",
124
- ".eslintrc.yaml",
125
- ".eslintrc.yml",
126
- ".eslintrc.json",
127
- ".eslintrc",
128
- "package.json",
129
- ];
130
- let configDir = path.resolve(currentDir);
131
- while (configDir && configDir !== path.dirname(configDir)) {
132
- for (let i = 0; i < configFiles.length; i++) {
133
- const configPath = path.join(configDir, configFiles[i]);
134
- const config = this.safeRequire(configPath)
135
-
136
- if (configFiles[i] !== "package.json" || config?.eslintConfig) {
137
- if (exists(configPath) && await isfile(configPath)) {
138
- return configPath;
139
- }
140
- }
141
- }
142
-
143
- configDir = path.dirname(configDir);
144
- }
145
- return "";
146
- }
147
-
148
- /**
149
- * Searches for ESLint config file types (in order or precedence)
150
- * and returns corresponding directory (usually project's root dir)
151
- * https://eslint.org/docs/user-guide/configuring#configuration-file-formats
152
- * @param {string} currentDir start here and search until root dir
153
- * @returns {string} dir containing ESLint config file (empty if not exists)
154
- */
155
- async _getConfigPath(currentDir = ".") {
156
- const configFiles = [
157
- "eslint.config.js",
158
- "eslint.config.cjs",
159
- "eslint.config.mjs"
160
- ];
161
- let configDir = path.resolve(currentDir);
162
- while (configDir && configDir !== path.dirname(configDir)) {
163
- for (let i = 0; i < configFiles.length; i++) {
164
- const configPath = path.join(configDir, configFiles[i]);
165
- const config = this.safeRequire(configPath)
166
- if (configFiles[i] !== "package.json" || config?.eslintConfig) {
167
- if (exists(configPath) && await isfile(configPath)) { // TODO exists neccessary ?
168
- return configPath;
169
- }
170
- }
171
- }
172
- configDir = path.dirname(configDir);
173
- }
174
- return "";
245
+ #printHelp() {
246
+ console.log(this.help.replace(/ \*([^*]+)\*/g, ` ${term.codes.bold}$1${term.codes.reset}`));
175
247
  }
176
248
 
177
- _addExtensions() {
249
+ #addExtensions() {
178
250
  // Add CDS file extensions to lint
179
- this.fileExtensions = this.pluginApi.getFileExtensions().map((ext) => path.extname(ext));
251
+ this.fileExtensions = this.pluginApi.getFileExtensions()
252
+ .map((ext) => path.extname(ext));
180
253
  // Only lint file extensions prescribed by plugin
181
- this.ignorePatterns = [];
182
- this.fileExtensions.forEach((ext) => {
183
- this.ignorePatterns.push(`!${ext}`);
184
- });
254
+ this.ignorePatterns = this.fileExtensions
255
+ .map(ext => `!${ext}`)
185
256
  }
186
257
 
187
- async _runEslint(currentDir, DefaultESLint) {
258
+ async #runEslint(currentDir, DefaultESLint) {
188
259
  try {
189
-
190
- let eslintOpts = {
260
+ const eslintOpts = this.lintType === "global"
261
+ ? {
262
+ cwd: process.cwd(),
263
+ overrideConfig: {},
264
+ }
265
+ : {
191
266
  cwd: currentDir,
192
- overrideConfig: {
193
- ...this.configContents,
194
- },
195
- };
196
-
197
- if (this.lintType === "global") {
198
- eslintOpts = {
199
- cwd: process.cwd(),
200
- overrideConfig: {},
201
- };
267
+ overrideConfig: {...this.configContents},
202
268
  }
203
269
 
204
270
  if (this.isESLintLegacy) {
@@ -213,7 +279,7 @@ class Linter {
213
279
  } else {
214
280
  LOG?.("Using flat ESLint configuration");
215
281
  const cdsPlugin = require(this.pluginPath)
216
- // Exvlude our own apis from plugin to avoid wrapper errors
282
+ // Exclude our own apis from plugin to avoid wrapper errors
217
283
  delete cdsPlugin.configs.recommended?.plugins?.['@sap/cds']
218
284
  eslintOpts.overrideConfig = [cdsPlugin.configs.recommended]
219
285
  }
@@ -224,17 +290,22 @@ class Linter {
224
290
 
225
291
  LOG_CONFIG?.(eslintOpts);
226
292
 
227
- let lintString = `eslint`;
228
- if (this.fileExtensions) { lintString += ` --ext "${this.fileExtensions.join(",")}"` }
293
+ if (LOG) {
294
+ let lintString = `eslint`;
229
295
 
230
- for (const ruleOpt of Object.entries(this.ruleOpts)) {
231
- lintString += ` --rule ${ruleOpt[0]}:${ruleOpt[1]}`;
232
- }
233
- if (this.customRulesOpts && this.customRulesOpts.length > 0) {
234
- lintString += ` --rulesdir "${this.customRulesOpts}"`
235
- }
296
+ if (this.fileExtensions) {
297
+ lintString += ` --ext "${this.fileExtensions.join(",")}"`
298
+ }
236
299
 
237
- LOG?.(lintString);
300
+ for (const [name, rule] of Object.entries(this.ruleOpts)) {
301
+ lintString += ` --rule ${name}:${rule}`
302
+ }
303
+ if (this.customRulesOpts && this.customRulesOpts.length > 0) {
304
+ lintString += ` --rulesdir "${this.customRulesOpts}"`
305
+ }
306
+
307
+ LOG(lintString);
308
+ }
238
309
 
239
310
  if (!process.env.isTest) {
240
311
  const eslint = new DefaultESLint(eslintOpts);
@@ -252,7 +323,7 @@ class Linter {
252
323
  results = results.concat(resultsModel);
253
324
  }
254
325
 
255
- if (results && results.length > 0) {
326
+ if (results?.length > 0) {
256
327
  console.log(formatter.format(results));
257
328
  }
258
329
  }
@@ -272,31 +343,17 @@ class Linter {
272
343
  }
273
344
  }
274
345
 
275
- async _getConfigFileAndSetProjectPath(args) {
276
- let input;
346
+ async #getConfigFileAndSetProjectPath(args) {
277
347
  // Get config path
278
348
  if (args.length > 0) {
279
349
  const firstArg = args[0];
280
- if (firstArg === "." || (isfile(firstArg) || isdir(firstArg))) {
281
- this.isFile = true;
282
- }
283
- input = args.join(" ");
284
- if (!input) {
285
- input = ".";
286
- }
350
+ this.isFile = firstArg === "." || isfile(firstArg) || isdir(firstArg)
287
351
  }
288
- this.configPath = this.isESLintLegacy
289
- ? await this._getConfigPathLegacy(process.cwd())
290
- : await this._getConfigPath(process.cwd());
352
+ this.configPath = await this.loader.getConfigPath(process.cwd())
291
353
  if (!this.configPath) {
292
- this.lintType === "global";
293
- this.configContents = this.isESLintLegacy
294
- ? await io.sanitizeEslintConfigLegacy({}, false, LOG)
295
- : await io.sanitizeEslintConfig({}, false, LOG);
354
+ this.configContents = this.loader.sanitizeEslintConfig({}, false, LOG)
296
355
  } else {
297
- const configContents = this.isESLintLegacy
298
- ? await io.readEslintConfigLegacy(this.configPath)
299
- : await io.readEslintConfig(this.configPath);
356
+ const configContents = this.loader.readEslintConfig(this.configPath)
300
357
  this.configContents = configContents;
301
358
  if (this.isESLintLegacy) {
302
359
  this.configContents = await io.sanitizeEslintConfig(configContents, false, LOG);
@@ -313,14 +370,16 @@ class Linter {
313
370
  paths: [path.dirname(this.configPath)],
314
371
  });
315
372
  this.pluginApi = require(this.pluginPath.replace("index.js", "api"));
316
- } catch (err) {
373
+ } catch {
317
374
  // CLI will report (no locally installed plugin)
318
375
  }
319
376
  }
320
377
  }
321
378
  }
322
379
  } else {
323
- const cdsConfig = configContents?.filter(c => c.files?.includes("*.cds"))[0] ?? {}
380
+ const cdsConfig = Array.isArray(configContents)
381
+ ? configContents?.find(c => c.files?.includes("*.cds")) ?? {}
382
+ : configContents
324
383
  this.configContents = cdsConfig.files
325
384
  ? {
326
385
  ...cdsConfig.plugins.configs?.recommended,
@@ -337,7 +396,7 @@ class Linter {
337
396
  paths: [this.cdsdkPath],
338
397
  });
339
398
  this.pluginApi = require(this.pluginPath.replace("index.js", "api"));
340
- } catch (err) {
399
+ } catch {
341
400
  // CLI will report (no globally installed plugin)
342
401
  }
343
402
  }
@@ -345,46 +404,28 @@ class Linter {
345
404
  LOG?.(`ESLint CDS plugin resolved: ${this.pluginPath}`);
346
405
  }
347
406
 
348
- async _overwriteRuleSeverities() {
407
+ async #overwriteRuleSeverities() {
349
408
  let configContents = {};
350
409
  if (this.configPath) {
351
410
  configContents = await io.readEslintConfigLegacy(this.configPath);
352
411
  } else {
353
- configContents = this.isESLintLegacy
354
- ? await io.readEslintConfigLegacy(await this._getConfigPath(this.cdsdkPath))
355
- : await io.readEslintConfig(await this._getConfigPath(this.cdsdkPath));
412
+ configContents = this.loader.readEslintConfig(await this.loader.getConfigPath(this.cdsdkPath))
356
413
  }
357
- let rules = configContents?.rules || {};
414
+ const rules = configContents?.rules ?? {};
358
415
  // Allow recommended plugin rules in cds-dk to be overwritten
359
416
  // by user by adding rule to cmd line (because of precedence)
360
417
  const pluginRules = require(this.pluginPath).configs.recommended.rules;
361
418
  if (rules && pluginRules) {
362
- for (const rule of Object.keys(rules)) {
363
- if (typeof rules[rule] !== "undefined" && rules[rule] != pluginRules[rule] ||
364
- isfile(path.relative(".", ".eslint", "rules", rule))) {
365
- pluginRules[rule] = rules[rule];
366
- this.ruleOpts[rule] = rules[rule];
419
+ for (const [name, rule] of Object.entries(rules)) {
420
+ if (typeof rule !== "undefined" && rule != pluginRules[name] ||
421
+ isfile(path.relative(".", ".eslint", "rules", name))) {
422
+ pluginRules[name] = rule;
423
+ this.ruleOpts[name] = rule;
367
424
  this.pluginRules = pluginRules;
368
425
  }
369
426
  }
370
427
  }
371
428
  }
372
-
373
- async _loadEslint(currentDir) {
374
- const { loadESLint, ESLint } = require("eslint");
375
- const DefaultESLint = loadESLint
376
- ? await loadESLint({ cwd: currentDir }) // ESLint 9
377
- : new ESLint({ cwd: currentDir }) // ESLint 8
378
- return DefaultESLint;
379
- }
380
-
381
- safeRequire(module) {
382
- try {
383
- return require(module)
384
- } catch (e) {
385
- return {}
386
- }
387
- }
388
429
  }
389
430
 
390
431
  module.exports = Linter;
package/lib/lint/io.js CHANGED
@@ -8,7 +8,7 @@ module.exports = {
8
8
  /**
9
9
  * Reads ESLint config contents from file
10
10
  * @param {*} configPath config file path to read from
11
- * @returns config file contents
11
+ * @returns {object[] | object} config file contents
12
12
  */
13
13
  async readEslintConfigLegacy(configPath) {
14
14
  let config;
@@ -19,7 +19,7 @@ module.exports = {
19
19
  case ".eslintrc.cjs":
20
20
  try {
21
21
  config = JSONC.parse((configString.replace(/^module\.exports =/, "")));
22
- } catch (err) {
22
+ } catch {
23
23
  config = {};
24
24
  }
25
25
  break;
@@ -102,7 +102,6 @@ module.exports = {
102
102
  * (2) Option "root": false to stop further config merging
103
103
  * (3) Add's custom rule example 'no-entity-moo' if requested
104
104
  * @param {*} configPath ESLint config file
105
- * @returns {void}
106
105
  */
107
106
  async sanitizeEslintConfigLegacy(configIn, customRuleExample = false, logger = console) {
108
107
  let configContents = {};
@@ -121,10 +120,8 @@ module.exports = {
121
120
  if (!configContents["extends"].includes(extendsValue)) {
122
121
  configContents["extends"].push(extendsValue);
123
122
  }
124
- } else {
125
- if (configContents["extends"] !== extendsValue) {
126
- configContents["extends"] = [configContents["extends"], extendsValue];
127
- }
123
+ } else if (configContents["extends"] !== extendsValue) {
124
+ configContents["extends"] = [configContents["extends"], extendsValue];
128
125
  }
129
126
  } else {
130
127
  configContents["extends"] = extendsValue;
package/lib/util/cf.js CHANGED
@@ -44,7 +44,7 @@ module.exports = new class CfUtil {
44
44
  args = args.map(arg => arg.replace(/"/g, '\\"'));
45
45
  const cmdLine = `${CF_COMMAND} "${args.join('" "')}"`;
46
46
  DEBUG?.time(cmdLine);
47
- LOG.debug('>>>', cmdLine);
47
+ DEBUG?.('>>>', cmdLine);
48
48
 
49
49
  try {
50
50
  const result = await execAsync(cmdLine, {
@@ -146,13 +146,21 @@ module.exports = new class CfUtil {
146
146
  }
147
147
  }
148
148
 
149
+ #token
149
150
  async _getCfAuthorization() {
150
- DEBUG?.('getting authorization');
151
- try {
152
- return (await this.cfRun('oauth-token')).stdout;
153
- } catch (err) {
154
- throw err.message;
151
+ if (this.#token) return this.#token
152
+ if (!this._tokenPromise) {
153
+ this._tokenPromise = this.cfRun('oauth-token')
154
+ .then(result => {
155
+ this.#token = result.stdout
156
+ return this.#token
157
+ })
158
+ .catch(err => {
159
+ this._tokenPromise = null
160
+ throw err.message
161
+ })
155
162
  }
163
+ return this._tokenPromise
156
164
  }
157
165
 
158
166
  async getCfTarget() {
@@ -163,7 +171,10 @@ module.exports = new class CfUtil {
163
171
  return target;
164
172
  }
165
173
 
174
+ #checkedVersion = false
166
175
  async _checkCliVersion() {
176
+ if (this.#checkedVersion) return
177
+ this.#checkedVersion = true
167
178
  const result = await this.cfRun('-v');
168
179
  const version = result?.stdout?.match(/version.*(\d+\.\d+\.\d+)/i)
169
180
  if (parseInt(version?.[1]) < CF_CLIENT_MINIMUM_VERSION) {
@@ -358,7 +369,7 @@ Deployment will stop in the near future for Cloud Foundry client versions < ${CF
358
369
  async getOrCreateServiceKey(serviceInstance, serviceKeyName, parameters, { silent = false, create = true } = {}) {
359
370
  const serviceKey = await this._getServiceKey(serviceInstance, serviceKeyName, false);
360
371
  if (serviceKey || !create) {
361
- if (serviceKey && !silent) console.log(`getting service key ${bold(serviceKeyName)}`);
372
+ if (serviceKey && !silent) DEBUG?.(`getting service key ${bold(serviceKeyName)}`);
362
373
  return serviceKey;
363
374
  }
364
375
 
@@ -389,4 +400,4 @@ Deployment will stop in the near future for Cloud Foundry client versions < ${CF
389
400
 
390
401
  throw `Could not create service key ${bold(serviceKeyName)}`;
391
402
  }
392
- }
403
+ }
package/lib/util/write.js CHANGED
@@ -88,7 +88,7 @@ function _write (content, /*to:*/ dest, _many) { // NOSONAR
88
88
  }
89
89
 
90
90
  file = path.resolve (cds.root,file)
91
- // write to the target file, creating parent folders if neccessary
91
+ // write to the target file, creating parent folders if necessary
92
92
  return mkdirp (dirname(file))
93
93
  .then (()=> fs.writeFile (file, typeof content === 'string' ? content : JSON.stringify (content, null, 2)))
94
94
  .then (()=> dest.foreach && dest.foreach(file))