@sap/cds-dk 7.8.2 → 7.9.1

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.
Files changed (78) hide show
  1. package/bin/add.js +28 -7
  2. package/bin/bind.js +64 -21
  3. package/bin/build.js +35 -3
  4. package/bin/cds.js +31 -13
  5. package/bin/compile/index.js +44 -3
  6. package/bin/completion/completionFs.js +61 -0
  7. package/bin/completion/index.js +212 -0
  8. package/bin/completion/scripts/cds-bash.sh +17 -0
  9. package/bin/completion/scripts/cds-ps.ps1 +18 -0
  10. package/bin/completion/scripts/cds-zsh.sh +12 -0
  11. package/bin/help.js +8 -2
  12. package/bin/init.js +34 -4
  13. package/bin/subscribe.js +5 -1
  14. package/bin/watch.js +4 -2
  15. package/lib/bind/KubernetesApiAccessor.js +3 -2
  16. package/lib/bind/bindingResolver.js +3 -2
  17. package/lib/bind/bindingUtil.js +10 -8
  18. package/lib/bind/cfKeyProvider.js +58 -153
  19. package/lib/bind/index.js +46 -36
  20. package/lib/bind/kubernetesCredentialsProvider.js +6 -5
  21. package/lib/bind/userCdsrcJsonStorageProvider.js +25 -3
  22. package/lib/build/buildTaskEngine.js +1 -1
  23. package/lib/build/provider/hana/index.js +1 -1
  24. package/lib/build/provider/hana/template/undeploy.json +3 -1
  25. package/lib/build/provider/nodejs/index.js +1 -1
  26. package/lib/compile/openapi/index.js +5 -0
  27. package/lib/deploy/to-hana/hana.js +25 -7
  28. package/lib/env/schemas/cds-rc.js +29 -2
  29. package/lib/import/odata/v4parser.js +78 -25
  30. package/lib/init/constants.js +2 -1
  31. package/lib/init/index.js +18 -13
  32. package/lib/init/merge.js +1 -0
  33. package/lib/init/mvn.js +8 -47
  34. package/lib/init/plugin.js +0 -4
  35. package/lib/init/projectReader.js +17 -10
  36. package/lib/init/template/approuter/files/values.yaml.hbs +0 -12
  37. package/lib/init/template/completion/completionSetup.js +184 -0
  38. package/lib/init/template/completion/index.js +22 -0
  39. package/lib/init/template/data/as-csv.js +25 -17
  40. package/lib/init/template/data/as-json.js +115 -30
  41. package/lib/init/template/data/index.js +28 -15
  42. package/lib/init/template/hana/files/undeploy.json +2 -1
  43. package/lib/init/template/helm/files/chart/values.schema.json +1 -1
  44. package/lib/init/template/helm/files/content-deployment/Chart.yaml +0 -2
  45. package/lib/init/template/helm/files/content-deployment/values.schema.json +0 -1
  46. package/lib/init/template/helm/files/service-instance/Chart.yaml +0 -2
  47. package/lib/init/template/helm/files/service-instance/values.schema.json +0 -1
  48. package/lib/init/template/helm/files/web-application/Chart.yaml +0 -2
  49. package/lib/init/template/helm/files/web-application/values.schema.json +0 -1
  50. package/lib/init/template/helm-unified-runtime/files/values.schema.json +1 -1
  51. package/lib/init/template/http/index.js +150 -104
  52. package/lib/init/template/kafka/index.js +6 -0
  53. package/lib/init/template/lint/index.js +12 -13
  54. package/lib/init/template/mta/files/mta.yaml.hbs +1 -1
  55. package/lib/init/template/multitenancy/files/values.yaml.hbs +0 -5
  56. package/lib/init/template/multitenancy/index.js +13 -5
  57. package/lib/init/template/nodejs/files/.vscode/settings.json +11 -0
  58. package/lib/init/template/nodejs/files/eslint.config.js +13 -0
  59. package/lib/init/template/nodejs/files/package.json.hbs +3 -1
  60. package/lib/init/template/sample/index.js +18 -20
  61. package/lib/init/template/tiny-sample/index.js +1 -2
  62. package/lib/init/template/typer/index.js +2 -2
  63. package/lib/lint/Linter.js +133 -55
  64. package/lib/lint/checks.js +5 -6
  65. package/lib/lint/io.js +119 -10
  66. package/lib/mtx/auth_manager.js +5 -5
  67. package/lib/mtx/params.js +4 -2
  68. package/lib/mtx/settings_manager.js +1 -1
  69. package/lib/mtx/tenant_action.js +12 -2
  70. package/lib/mtx/util/logging.js +10 -4
  71. package/lib/util/axios.js +2 -1
  72. package/lib/util/cf.js +32 -37
  73. package/lib/util/git.js +1 -1
  74. package/lib/util/term.js +1 -1
  75. package/{bin → lib/watch}/watched.js +2 -2
  76. package/npm-shrinkwrap.json +34 -34
  77. package/package.json +2 -2
  78. package/lib/init/template/nodejs/files/.eslintrc +0 -26
@@ -1,7 +1,7 @@
1
1
  const { join } = require('path')
2
2
  const { readProject } = require('../../projectReader')
3
3
  const { merge, mergeGitignore } = require('../../merge')
4
- const { warn, secondary } = require('../../../util/term')
4
+ const { warn, dim } = require('../../../util/term')
5
5
  const { TYPER } = require('../../constants').OPTIONS
6
6
  const { exists, read } = require('../../../cds').utils
7
7
 
@@ -25,7 +25,7 @@ module.exports = class TyperTemplate extends require('../../plugin') {
25
25
  }
26
26
  await merge(template('package.json')).into('package.json')
27
27
  await mergeGitignore('.gitignore', template('gitignore'))
28
- console.log(secondary(` > Adding '@cds-models' to .gitignore to ignore generated types`)) // REVISIT: other facets are silent wrt. what they change
28
+ console.log(dim(` Adding '@cds-models' to .gitignore to ignore generated types`)) // REVISIT: other facets are silent wrt. what they change
29
29
  try {
30
30
  await merge(source).into(target)
31
31
  } catch (e) {
@@ -10,6 +10,8 @@ const { exit } = require("process");
10
10
  const LOG = cds.debug("lint");
11
11
  const LOG_CONFIG = cds.debug("lint:config");
12
12
 
13
+ const GENERAL_ERROR = 1;
14
+
13
15
  class Linter {
14
16
  constructor() {
15
17
  this.eslintCmd = "";
@@ -61,6 +63,11 @@ class Linter {
61
63
  * @param {*} options options/flag passed by user
62
64
  */
63
65
  async lint(args, options) {
66
+ // Check for ESLint (legacy)
67
+ const currentDir = process.cwd();
68
+ const DefaultESLint = await this._loadEslint(currentDir);
69
+ this.isESLintLegacy = DefaultESLint.configType === "eslintrc";
70
+
64
71
  this.eslintCmdOpts = this._sanitizeUserOpts(options);
65
72
  this.eslintCmdFileExpr = args.length ? args : ["."];
66
73
  if (this.eslintCmdOpts.help) {
@@ -82,7 +89,7 @@ class Linter {
82
89
 
83
90
  // Run ESLint with collected options
84
91
  try {
85
- await this._runEslint();
92
+ await this._runEslint(currentDir, DefaultESLint);
86
93
  } catch (err) {
87
94
  term.error(err);
88
95
  }
@@ -91,7 +98,6 @@ class Linter {
91
98
 
92
99
  _printHelp() {
93
100
  console.log(this.help.replace(/ \*([^*]+)\*/g, ` ${term.codes.bold}$1${term.codes.reset}`));
94
- return;
95
101
  }
96
102
 
97
103
  _sanitizeUserOpts(options) {
@@ -111,7 +117,7 @@ class Linter {
111
117
  * @param {string} currentDir start here and search until root dir
112
118
  * @returns {string} dir containing ESLint config file (empty if not exists)
113
119
  */
114
- async _getConfigPath(currentDir = ".") {
120
+ async _getConfigPathLegacy(currentDir = ".") {
115
121
  const configFiles = [
116
122
  ".eslintrc.js",
117
123
  ".eslintrc.cjs",
@@ -122,18 +128,48 @@ class Linter {
122
128
  "package.json",
123
129
  ];
124
130
  let configDir = path.resolve(currentDir);
125
- while (configDir !== path.resolve(configDir, "..")) {
131
+ while (configDir && configDir !== path.dirname(configDir)) {
126
132
  for (let i = 0; i < configFiles.length; i++) {
127
133
  const configPath = path.join(configDir, configFiles[i]);
128
- if (configFiles[i] !== "package.json" || (
129
- configFiles[i] === "package.json" && require(configPath).eslintConfig
130
- )) {
134
+ const config = this.safeRequire(configPath)
135
+
136
+ if (configFiles[i] !== "package.json" || config?.eslintConfig) {
131
137
  if (exists(configPath) && await isfile(configPath)) {
132
138
  return configPath;
133
139
  }
134
140
  }
135
141
  }
136
- configDir = path.join(configDir, "..");
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);
137
173
  }
138
174
  return "";
139
175
  }
@@ -146,45 +182,51 @@ class Linter {
146
182
  this.fileExtensions.forEach((ext) => {
147
183
  this.ignorePatterns.push(`!${ext}`);
148
184
  });
149
- return;
150
185
  }
151
186
 
152
- async _runEslint() {
187
+ async _runEslint(currentDir, DefaultESLint) {
153
188
  try {
154
- const currentDir = process.cwd();
155
-
156
- const { ESLint } = require("eslint");
157
189
 
158
190
  let eslintOpts = {
159
191
  cwd: currentDir,
160
- extensions: this.fileExtensions,
161
192
  overrideConfig: {
162
193
  ...this.configContents,
163
194
  },
164
- useEslintrc: false,
165
195
  };
166
196
 
167
197
  if (this.lintType === "global") {
168
198
  eslintOpts = {
169
199
  cwd: process.cwd(),
170
- extensions: this.fileExtensions,
171
- overrideConfig: {
172
- plugins: ["@sap/eslint-plugin-cds"],
173
- extends: "plugin:@sap/cds/recommended"
174
- },
175
- useEslintrc: false,
176
- resolvePluginsRelativeTo: this.cdsdkPath
200
+ overrideConfig: {},
177
201
  };
178
202
  }
179
203
 
204
+ if (this.isESLintLegacy) {
205
+ LOG?.("Using legacy ESLint configuration");
206
+ eslintOpts.extensions = this.fileExtensions;
207
+ eslintOpts.overrideConfig.extends = "plugin:@sap/cds/recommended-legacy"
208
+ eslintOpts.overrideConfig.plugins = ["@sap/eslint-plugin-cds"]
209
+ eslintOpts.useEslintrc = false
210
+ if (this.lintType === "global") {
211
+ eslintOpts.resolvePluginsRelativeTo = this.cdsdkPath
212
+ }
213
+ } else {
214
+ LOG?.("Using flat ESLint configuration");
215
+ const cdsPlugin = require(this.pluginPath)
216
+ // Exvlude our own apis from plugin to avoid wrapper errors
217
+ delete cdsPlugin.configs.recommended?.plugins?.['@sap/cds']
218
+ eslintOpts.overrideConfig = [cdsPlugin.configs.recommended]
219
+ }
220
+
180
221
  if (this.customRulesOpts && this.customRulesOpts.length > 0) {
181
222
  eslintOpts.rulePaths = [this.customRulesOpts]
182
223
  }
183
224
 
184
- LOG_CONFIG && LOG_CONFIG(eslintOpts);
225
+ LOG_CONFIG?.(eslintOpts);
185
226
 
186
227
  let lintString = `eslint`;
187
- if (this.fileExtensions) { lintString += ` --ext "${this.fileExtensions.join(",")}"`}
228
+ if (this.fileExtensions) { lintString += ` --ext "${this.fileExtensions.join(",")}"` }
229
+
188
230
  for (const ruleOpt of Object.entries(this.ruleOpts)) {
189
231
  lintString += ` --rule ${ruleOpt[0]}:${ruleOpt[1]}`;
190
232
  }
@@ -192,10 +234,10 @@ class Linter {
192
234
  lintString += ` --rulesdir "${this.customRulesOpts}"`
193
235
  }
194
236
 
195
- LOG && LOG(lintString);
237
+ LOG?.(lintString);
196
238
 
197
239
  if (!process.env.isTest) {
198
- const eslint = new ESLint(eslintOpts);
240
+ const eslint = new DefaultESLint(eslintOpts);
199
241
  const formatter = await eslint.loadFormatter("stylish");
200
242
 
201
243
  let results = (await eslint.lintText("")).map((result) => {
@@ -221,13 +263,12 @@ class Linter {
221
263
  const eslintBase = `${require.resolve('eslint').split('eslint')[0]}/eslint`;
222
264
  const template = require(path.join(eslintBase, `messages/${err.messageTemplate}.js`));
223
265
  console.log(template(err.messageData || {}));
224
- exit(1)
225
266
  } catch {
226
- // Ignore template error then fallback to use `error.stack`.
267
+ // Ignore template error then fallback to use `error.stack`.
268
+ console.log(err.stack);
227
269
  }
228
- console.log(err.stack);
229
- }
230
- exit(1)
270
+ }
271
+ exit(GENERAL_ERROR); // TODO use error constant
231
272
  }
232
273
  }
233
274
 
@@ -244,31 +285,50 @@ class Linter {
244
285
  input = ".";
245
286
  }
246
287
  }
247
- this.configPath = await this._getConfigPath(process.cwd());
288
+ this.configPath = this.isESLintLegacy
289
+ ? await this._getConfigPathLegacy(process.cwd())
290
+ : await this._getConfigPath(process.cwd());
248
291
  if (!this.configPath) {
249
292
  this.lintType === "global";
250
- this.configContents = await io.sanitizeEslintConfig({}, false, LOG);
293
+ this.configContents = this.isESLintLegacy
294
+ ? await io.sanitizeEslintConfigLegacy({}, false, LOG)
295
+ : await io.sanitizeEslintConfig({}, false, LOG);
251
296
  } else {
252
- const configContents = await io.readEslintConfig(this.configPath);
253
- this.configContents = await io.sanitizeEslintConfig(configContents, false, LOG);
254
- if (this.configContents) {
255
- if ("extends" in this.configContents) {
256
- this.extendsPlugin = checks.hasEslintConfigContent(
257
- this.configContents,
258
- "extends"
259
- );
260
- if (this.extendsPlugin) {
261
- this.lintType = "local";
262
- try {
263
- this.pluginPath = require.resolve("@sap/eslint-plugin-cds", {
264
- paths: [path.dirname(this.configPath)],
265
- });
266
- this.pluginApi = require(this.pluginPath.replace("index.js", "api"));
267
- } catch (err) {
268
- // CLI will report (no locally installed plugin)
297
+ const configContents = this.isESLintLegacy
298
+ ? await io.readEslintConfigLegacy(this.configPath)
299
+ : await io.readEslintConfig(this.configPath);
300
+ this.configContents = configContents;
301
+ if (this.isESLintLegacy) {
302
+ this.configContents = await io.sanitizeEslintConfig(configContents, false, LOG);
303
+ if (this.configContents) {
304
+ if ("extends" in this.configContents) {
305
+ this.extendsPlugin = checks.hasEslintConfigContent(
306
+ this.configContents,
307
+ "extends"
308
+ );
309
+ if (this.extendsPlugin) {
310
+ this.lintType = "local";
311
+ try {
312
+ this.pluginPath = require.resolve("@sap/eslint-plugin-cds", {
313
+ paths: [path.dirname(this.configPath)],
314
+ });
315
+ this.pluginApi = require(this.pluginPath.replace("index.js", "api"));
316
+ } catch (err) {
317
+ // CLI will report (no locally installed plugin)
318
+ }
269
319
  }
270
320
  }
271
321
  }
322
+ } else {
323
+ const cdsConfig = configContents?.filter(c => c.files?.includes("*.cds"))[0] ?? {}
324
+ this.configContents = cdsConfig.files
325
+ ? {
326
+ ...cdsConfig.plugins.configs?.recommended,
327
+ languageOptions: cdsConfig.languageOptions,
328
+ files: cdsConfig.files,
329
+ rules: cdsConfig.rules,
330
+ }
331
+ : {};
272
332
  }
273
333
  }
274
334
  if (!this.pluginPath) {
@@ -281,17 +341,20 @@ class Linter {
281
341
  // CLI will report (no globally installed plugin)
282
342
  }
283
343
  }
284
- return;
344
+ LOG?.(`Lint type: ${this.lintType}`);
345
+ LOG?.(`ESLint CDS plugin resolved: ${this.pluginPath}`);
285
346
  }
286
347
 
287
348
  async _overwriteRuleSeverities() {
288
349
  let configContents = {};
289
350
  if (this.configPath) {
290
- configContents = await io.readEslintConfig(this.configPath);
351
+ configContents = await io.readEslintConfigLegacy(this.configPath);
291
352
  } else {
292
- configContents = await io.readEslintConfig(await this._getConfigPath(this.cdsdkPath));
353
+ configContents = this.isESLintLegacy
354
+ ? await io.readEslintConfigLegacy(await this._getConfigPath(this.cdsdkPath))
355
+ : await io.readEslintConfig(await this._getConfigPath(this.cdsdkPath));
293
356
  }
294
- let rules = configContents.rules;
357
+ let rules = configContents?.rules || {};
295
358
  // Allow recommended plugin rules in cds-dk to be overwritten
296
359
  // by user by adding rule to cmd line (because of precedence)
297
360
  const pluginRules = require(this.pluginPath).configs.recommended.rules;
@@ -305,7 +368,22 @@ class Linter {
305
368
  }
306
369
  }
307
370
  }
308
- return;
371
+ }
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
+ }
309
387
  }
310
388
  }
311
389
 
@@ -7,7 +7,7 @@ const IS_WIN = os.platform() === "win32";
7
7
 
8
8
  module.exports = {
9
9
  /**
10
- * Checks whether ESLint v>=7 is installed by trying to resolve
10
+ * Checks whether ESLint v>=8 is installed by trying to resolve
11
11
  * 'eslint' module from (1) <inputPath> or (2) global npm path
12
12
  * @param {*} inputPath where to resolve 'eslint' module from
13
13
  * @param {*} globalPath resolves 'eslint' module from cds-dk
@@ -23,14 +23,14 @@ module.exports = {
23
23
  paths: [inputPath, globalPath],
24
24
  });
25
25
  const packageJson = require(eslintPath);
26
- if (packageJson.version > "7") {
26
+ if (packageJson.version >= "8") {
27
27
  version = packageJson.version;
28
28
  } else {
29
29
  errMessage =
30
- `ESLint v>=6.0.0 required!\n\n` +
30
+ `ESLint v>=8.0.0 required!\n\n` +
31
31
  ` Make sure to update and check for any breaking changes:\n` +
32
32
  ` ${term.warn(
33
- "https://eslint.org/docs/user-guide/migrating-to-6.0.0"
33
+ "https://eslint.org/docs/user-guide/migrating-to-8.0.0"
34
34
  )}\n`;
35
35
  }
36
36
  const bin = packageJson.bin.eslint;
@@ -53,7 +53,6 @@ module.exports = {
53
53
  if (logger) {
54
54
  logger.error(errMessage);
55
55
  }
56
- return;
57
56
  },
58
57
 
59
58
  /**
@@ -132,5 +131,5 @@ module.exports = {
132
131
  // Do nothing
133
132
  }
134
133
  return false;
135
- },
134
+ }
136
135
  };
package/lib/lint/io.js CHANGED
@@ -11,7 +11,7 @@ module.exports = {
11
11
  * @param {*} configPath config file path to read from
12
12
  * @returns config file contents
13
13
  */
14
- async readEslintConfig(configPath) {
14
+ async readEslintConfigLegacy(configPath) {
15
15
  let config;
16
16
  const configFile = path.basename(configPath);
17
17
  const configString = await read(configPath, "utf8");
@@ -33,7 +33,7 @@ module.exports = {
33
33
  config = await readJSONC(configPath);
34
34
  break;
35
35
  case "package.json":
36
- config = JSON.parse(await read(configPath, "utf8"));
36
+ config = await read(configPath, "json");
37
37
  if ("eslintConfig" in config) {
38
38
  config = config["eslintConfig"];
39
39
  } else {
@@ -46,16 +46,68 @@ module.exports = {
46
46
  return config;
47
47
  },
48
48
 
49
+ /**
50
+ * Reads ESLint config contents from file
51
+ * @param {*} configPath config file path to read from
52
+ * @returns config file contents
53
+ */
54
+ async readEslintConfig(configPath) {
55
+ let config;
56
+ const configFile = path.basename(configPath);
57
+ switch (configFile) {
58
+ case "eslint.config.js":
59
+ case "eslint.config.cjs":
60
+ case "eslint.config.mjs":
61
+ config = this.safeRequire(configPath)
62
+ break;
63
+ default:
64
+ break;
65
+ }
66
+ return config;
67
+ },
68
+
49
69
  /**
50
70
  * Checks ESLint config file contents for locally installed
51
71
  * plugin with:
52
72
  * (1) All CDS recommended rules "on"
53
- * (2) Option "root": false to stop further config merging
54
- * (3) Add's custom rule example 'no-entity-moo' if requested
73
+ * (2) Add's custom rule example 'no-entity-moo' if requested
55
74
  * @param {*} configPath ESLint config file
56
75
  * @returns {void}
57
76
  */
58
77
  async sanitizeEslintConfig(configIn, customRuleExample = false, logger = console) {
78
+ let configContents;
79
+ const isFilePath = typeof configIn === "string";
80
+ if (isFilePath) {
81
+ configContents = this.safeRequire(configIn); // TODO error handling invalid json
82
+ }
83
+
84
+ configContents ??= { ...configIn };
85
+
86
+ if (customRuleExample) {
87
+ const ruleName = "no-entity-moo";
88
+ const ruleValue = 2;
89
+ if ("rules" in configContents) {
90
+ configContents["rules"][ruleName] = ruleValue;
91
+ } else {
92
+ configContents["rules"] = { [ruleName]: ruleValue };
93
+ }
94
+ }
95
+ if (isFilePath) {
96
+ await this.writeEslintConfig(configIn, logger);
97
+ }
98
+ return configContents;
99
+ },
100
+
101
+ /**
102
+ * Checks ESLint config file contents for locally installed
103
+ * plugin with:
104
+ * (1) All CDS recommended rules "on"
105
+ * (2) Option "root": false to stop further config merging
106
+ * (3) Add's custom rule example 'no-entity-moo' if requested
107
+ * @param {*} configPath ESLint config file
108
+ * @returns {void}
109
+ */
110
+ async sanitizeEslintConfigLegacy(configIn, customRuleExample = false, logger = console) {
59
111
  let configContents = {};
60
112
  const isFilePath = typeof configIn === "string";
61
113
  if (isFilePath) {
@@ -93,19 +145,20 @@ module.exports = {
93
145
  }
94
146
  }
95
147
  if (isFilePath) {
96
- await this.writeEslintConfig(configIn, configContents, logger);
148
+ await this.writeEslintConfigLegacy(configIn, configContents, logger);
97
149
  }
98
150
  return configContents;
99
151
  },
100
152
 
153
+
101
154
  /**
102
155
  * Writes ESLint config contents to file
103
156
  * @param {*} configContents config file contents
104
157
  * @param {*} configPath config file path to write to
105
158
  * @returns {Promise<void>}
106
159
  */
107
- async writeEslintConfig(configPath, configContents, logger=console) {
108
- const configType = "recommended";
160
+ async writeEslintConfigLegacy(configPath, configContents, logger = console) {
161
+ const configType = "recommended-legacy";
109
162
  const extendsValue = `plugin:@sap/cds/${configType}`;
110
163
  let contents = {};
111
164
  let configFile = path.basename(configPath);
@@ -114,7 +167,7 @@ module.exports = {
114
167
  case ".eslintrc.js":
115
168
  case ".eslintrc.cjs":
116
169
  try {
117
- configContents = await this.readEslintConfig(configPath);
170
+ configContents = await this.readEslintConfigLegacy(configPath);
118
171
  } catch (err) {
119
172
  configContents = {};
120
173
  }
@@ -146,7 +199,7 @@ module.exports = {
146
199
  break;
147
200
  case "package.json":
148
201
  if (exists(configPath)) {
149
- contents = JSON.parse(await read(configPath, "utf8"));
202
+ contents = await read(configPath, "json");
150
203
  } else {
151
204
  contents = {};
152
205
  }
@@ -162,6 +215,54 @@ module.exports = {
162
215
  }
163
216
  },
164
217
 
218
+ /**
219
+ * Suggest ESLint config contents to file
220
+ * @param {*} configContents config file contents
221
+ * @param {*} configPath config file path to write to
222
+ * @returns {Promise<void>}
223
+ */
224
+ async writeEslintConfig(configPath, logger = console) {
225
+ let configFile = path.basename(configPath);
226
+ switch (configFile) {
227
+ case "eslint.config.js":
228
+ case "eslint.config.cjs":
229
+ case "eslint.config.mjs":
230
+ if (exists(configPath)) {
231
+ logger.log(
232
+ `${term.warn(
233
+ `\n\nPlease add the following to your "${configFile}":` +
234
+ '\n\n 1. At the top of your file, import the following:')}` +
235
+ `\n\n ${term.bold('const cds = require(\'@sap/eslint-plugin-cds\')')}` +
236
+ `${term.warn(
237
+ '\n\n 2. In your export, add the following to the top of the array:')}` +
238
+ `\n\n ${term.bold('cds.configs.recommended')}`
239
+ );
240
+ } else {
241
+ await write(configPath, `
242
+ const cds = require('@sap/eslint-plugin-cds')
243
+
244
+ module.exports = [
245
+ cds.configs.recommended,
246
+ {
247
+ plugins: {
248
+ "@sap/cds": cds
249
+ },
250
+ "files": [
251
+ ...cds.configs.recommended.files
252
+ ],
253
+ "rules": {
254
+ ...cds.configs.recommended.rules
255
+ }
256
+ }
257
+ ]
258
+ `)
259
+ }
260
+ break;
261
+ default:
262
+ break;
263
+ }
264
+ },
265
+
165
266
  /**
166
267
  * Read VS Code settings from file
167
268
  * @param {*} settingsPath settings file path to read from
@@ -204,7 +305,7 @@ module.exports = {
204
305
  * (3) "Global" case adds configFile and removes rulePaths
205
306
  * @param {*} settingsPath VS Code settings file
206
307
  */
207
- async sanitizeVscodeSettings(projectPath, settingsPath, lintType, lintFileTypes, customRuleExample = false) {
308
+ async sanitizeVscodeSettings(settingsPath, lintType, lintFileTypes, customRuleExample = false) {
208
309
  let settings = (await this.readVscodeSettings(settingsPath)) || {};
209
310
  const rulePaths = [path.join(".eslint/rules")];
210
311
 
@@ -253,4 +354,12 @@ module.exports = {
253
354
  }
254
355
  return userOpts;
255
356
  },
357
+
358
+ safeRequire(module) {
359
+ try {
360
+ return require(module)
361
+ } catch (e) {
362
+ return null
363
+ }
364
+ }
256
365
  };
@@ -65,26 +65,26 @@ async function retrieveTokenOrPasscodeUrl(params) {
65
65
  DEBUG?.(`Getting authentication token or passcode URL from ${method.toUpperCase()} ${params.obfuscateQueryParams(url)}`);
66
66
  response = await axios[method](url, data);
67
67
  } catch (e) {
68
- error = e;
69
- if (error.status === 404) { // may represent 405 in case of wrong method
68
+ if (e.status === 404) { // may represent 405 in case of wrong method
70
69
  continue reqParams;
71
70
  } else if (params.has('refreshToken')) {
72
71
  DEBUG?.('Discarding invalid refresh token');
73
72
  params.delete('refreshToken');
74
73
  } else {
74
+ error = e;
75
75
  break reqParams;
76
76
  }
77
77
  }
78
- } while (!(response || error));
78
+ } while (!response);
79
79
  }
80
80
  const data = response?.data ?? error?.auth;
81
81
  assign(params, data);
82
- if (!response) {
82
+ if (error) {
83
83
  throw new Error(REASONS.invalid_scope.test(error.message)
84
84
  ? 'Token has invalid scope. Check if your user has the required roles'
85
85
  : `No valid passcode or token provided. Get a passcode${
86
86
  params.get('passcodeUrl') ? ' from ' + params.get('passcodeUrl') : ''
87
- } and re-run the command with '-p <passcode>'`);
87
+ } and re-run the command with '-p <passcode>'`, { cause: error });
88
88
  }
89
89
  }
90
90
 
package/lib/mtx/params.js CHANGED
@@ -213,6 +213,7 @@ class AllParams extends BasicParams {
213
213
  // `extend` PARAMS
214
214
  this.force = new Param('force', false);
215
215
  this.templates = new Param('templates', false);
216
+ this["download-migrated-projects"] = new Param('download-migrated-projects', false)
216
217
 
217
218
  // `activate` PARAMS
218
219
  this.undeploy = new Param('undeploy', false);
@@ -220,6 +221,9 @@ class AllParams extends BasicParams {
220
221
  this.sync = new Param('sync', false);
221
222
  this.async = new Param('async', false);
222
223
 
224
+ // `subscribe` PARAMS
225
+ this.body = new Param('body', '', { persist: Persistence.none });
226
+
223
227
  // `login` PARAMS
224
228
  this.skipToken = new Param('skipToken', false, { internal: true });
225
229
 
@@ -229,8 +233,6 @@ class AllParams extends BasicParams {
229
233
  this.clearInvalid = new Param('clearInvalid', false);
230
234
  this["clear-invalid"] = this.clearInvalid.newAlias('clear-invalid');
231
235
 
232
- // download for mxts
233
- this["download-migrated-projects"] = new Param('download-migrated-projects', false)
234
236
  }
235
237
  }
236
238
 
@@ -252,7 +252,7 @@ class SettingsManager {
252
252
  async function updateTokenUrl() {
253
253
  const tokenUrl = params.get('tokenUrl');
254
254
  const renewUrl = !logout && !params.get('skipToken')
255
- && (!tokenUrl || tokenUrl.includes('//-/cds') || params.get('appUrl') !== loadedSettings.get('appUrl'));
255
+ && (!tokenUrl || tokenUrl.includes('//-/cds') || tokenUrl.includes(OAUTH_PATH_OLD_MTX) || params.get('appUrl') !== loadedSettings.get('appUrl'));
256
256
  if (renewUrl) {
257
257
  const tokenUrl = await getTokenBaseUrl(params, renewUrl);
258
258
  params.set('tokenUrl', tokenUrl);
@@ -48,8 +48,17 @@ module.exports = class TenantAction {
48
48
  console.log(capitalize(this.gerund), { tenant }, this.preposition, { url: params.get('appUrl') });
49
49
  const url = `${params.get('appUrl')}/-/cds/deployment/${this.command}`;
50
50
  DEBUG?.(`Sending request to ${url}`);
51
+ const body = params.get('body');
52
+ let parsedBody;
53
+ if (body) {
54
+ try {
55
+ parsedBody = JSON.parse(body)
56
+ } catch (error) {
57
+ throw getMessage('Invalid subscription body', { error, command: this.command });
58
+ }
59
+ }
51
60
  try {
52
- await require('axios').post(url, { tenant }, params.get('reqAuth'));
61
+ await require('axios').post(url, { tenant, metadata: parsedBody, options: parsedBody }, params.get('reqAuth'));
53
62
  } catch (error) {
54
63
  handleHttpError(error, params, { url });
55
64
  }
@@ -74,6 +83,7 @@ module.exports = class TenantAction {
74
83
  if (!cds_mtx) {
75
84
  throw '@sap/cds-mtxs outdated. Please upgrade to @sap/cds-mtxs@>=1.7.0 and re-run this command.';
76
85
  }
77
- return cds_mtx(this.command, tenant);
86
+ const body = params.get('body')
87
+ return cds_mtx(this.command, tenant, body);
78
88
  }
79
89
  }