@sap/cds-dk 8.7.2 → 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.
package/CHANGELOG.md CHANGED
@@ -7,7 +7,17 @@ This project adheres to [Semantic Versioning](https://semver.org/).
7
7
 
8
8
  The format is based on [Keep a Changelog](https://keepachangelog.com/).
9
9
 
10
- ## Version 8.7.2 - 2024-02-14
10
+ ## Version 8.7.3 - 2025-02-19
11
+
12
+ ### Fixed
13
+
14
+ - `cds compile -o` fixes the output file name in `-o <service>.json`.
15
+ - `cds lint` won't stumble over scalar config objects anymore.
16
+ - `cds init` uses latest Maven Java archetype version 3.7.2 for creating Java projects.
17
+ - `cds deploy --to hana --dry` doesn't exit with a `TypeError` if there are no models.
18
+ - `cds add ias` fixes a few scenarios in combination with multitenancy.
19
+
20
+ ## Version 8.7.2 - 2025-02-14
11
21
 
12
22
  ### Fixed
13
23
 
@@ -21,7 +31,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/).
21
31
  - `cds bind -a` doesn't concurrently try to check the Cloud Foundry version or OAuth token.
22
32
  - `cds import` now resolves target of association/composition correctly for multiple schema files.
23
33
 
24
- ## Version 8.7.1 - 2024-02-04
34
+ ## Version 8.7.1 - 2025-02-04
25
35
 
26
36
  ### Fixed
27
37
 
@@ -294,7 +294,7 @@ async function compile (models, options={}) {
294
294
  const write = require ('../../lib/util/write')
295
295
  chain = chain.then (write.to ({
296
296
  folder: options.dest,
297
- file: options.file || options.service === 'all' ? path.basename(src) : options.service,
297
+ file: options.file || (options.service === 'all' ? path.basename(src) : options.service),
298
298
  suffix: options.suffix || suffix4(_suffix),
299
299
  [options.dest ? 'foreach' : 'log']: options.log || consoleLog
300
300
  }))
package/bin/lint.js CHANGED
@@ -1,7 +1,13 @@
1
1
  const Linter = require('../lib/lint/Linter');
2
2
  const linter = new Linter();
3
3
 
4
- module.exports = Object.assign(
5
- linter.lint,
6
- linter.init()
7
- );
4
+ // cds cli exec expects an executable object with various additional properties.
5
+ // As most of the linting functionality is bound to a class instance, we need to wrap the instance
6
+ // to not lose the this-context and attach the additional properties to the function.
7
+ const lint = (...args) => linter.lint(...args)
8
+ const additionalProperties = Object.entries(linter.init())
9
+ .map(([key, value]) => [key, { value }])
10
+
11
+ Object.defineProperties(lint, Object.fromEntries(additionalProperties))
12
+
13
+ module.exports = lint
@@ -79,6 +79,7 @@ Therefore, specifying the CDS model is not required when calling 'cds deploy'.`;
79
79
  }
80
80
 
81
81
  if (dry) {
82
+ if (!buildResult) return
82
83
  const sortedResult = Array.from(buildResult.hana).sort();
83
84
  for (const file of sortedResult) {
84
85
  const srcPath = path.join(buildResult.dest, file);
@@ -87,7 +88,7 @@ Therefore, specifying the CDS model is not required when calling 'cds deploy'.`;
87
88
  console.log();
88
89
  }
89
90
  } else {
90
- // the primary database is always built and deployed
91
+ // the primary database is always built and deployed
91
92
  const dest = path.join(cds.env.build.target, cds.env.folders.db);
92
93
 
93
94
  let serviceName = pServiceName;
@@ -211,8 +212,7 @@ Therefore, specifying the CDS model is not required when calling 'cds deploy'.`;
211
212
  src: cds.env.folders.db,
212
213
  [OUTPUT_MODE]: OUTPUT_MODE_FILESYSTEM | OUTPUT_MODE_RESULT
213
214
  }
214
- console.log(`Running build`);
215
-
215
+
216
216
  // cds build throws an error if the src folder does not exist
217
217
  const buildResult = await build(buildTaskOptions);
218
218
  return buildResult[0].result;
@@ -5,7 +5,7 @@ module.exports = {
5
5
  COMMAND_ADD: 'add',
6
6
 
7
7
  JAVA_LTS_VERSIONS: [17, 21],
8
- MAVEN_ARCHETYPE_VERSION: '3.7.1',
8
+ MAVEN_ARCHETYPE_VERSION: '3.7.2',
9
9
 
10
10
  OPTIONS: Object.freeze({
11
11
  COMPLETION: 'completion',
package/lib/init/merge.js CHANGED
@@ -330,7 +330,7 @@ async function _mergeYAML(into, from, project, semantics = {}) {
330
330
  })
331
331
  .filter(item => !entryMap.get(item))
332
332
  .forEach(item => {
333
- if (!templateEntryMap.get(item)) throw 'Error: did not find entry in template for ' + inspect(item, { colors }) + ' in ' + from
333
+ if (!templateEntryMap.get(item)) throw 'Error: did not find entry in template for ' + inspect(item, { colors, depth:11 }) + ' in ' + from
334
334
  const templateNode = templateEntryMap.get(item).node
335
335
  item.at !== undefined ? targetNode.items.splice(item.at, 0, templateNode) : targetNode.add(templateNode)
336
336
  })
@@ -140,7 +140,7 @@ const requiredMtxSidecarApi4 = srv => ({
140
140
  where: { 'properties.CDS_MULTITENANCY_SIDECAR_URL': '~{mtx-url}' }
141
141
  })
142
142
 
143
- const providedJavaApprouterApi4 = srv => ({
143
+ const requiredJavaApprouterApi4 = srv => ({
144
144
  in: [srv, 'requires'],
145
145
  where: { 'properties.CDS_MULTITENANCY_APPUI_URL': '~{url}' }
146
146
  })
@@ -216,7 +216,7 @@ module.exports = {
216
216
  requiredMtxSidecarApi4,
217
217
  providedAppApi,
218
218
  requiredAppApi4,
219
- providedJavaApprouterApi4,
219
+ requiredJavaApprouterApi4,
220
220
  providedJavaApprouterApi,
221
221
  applicationLogging,
222
222
  approuterMTXRoute,
@@ -12,9 +12,9 @@ modules:
12
12
  requires:
13
13
  - name: {{appName}}-auth
14
14
  {{#isJava}}
15
- - name: app-api
15
+ - name: app-api{{#hasMultitenancy}}
16
16
  properties:
17
- CDS_MULTITENANCY_APPUI_URL: ~{url}
17
+ CDS_MULTITENANCY_APPUI_URL: ~{url}{{/hasMultitenancy}}
18
18
  {{/isJava}}
19
19
 
20
20
  - name: {{appName}}
@@ -6,7 +6,7 @@ const { sort, merge } = require('../../merge')
6
6
  const {
7
7
  srv4, mtxSidecar4, approuter, // Modules
8
8
  xsuaa, // BTP Services
9
- requiredMtxApi, providedJavaApprouterApi4, providedJavaApprouterApi, // APIs
9
+ requiredMtxApi, requiredJavaApprouterApi4, providedJavaApprouterApi, // APIs
10
10
  requiredAppApi4, providedAppApi,
11
11
  approuterMTXRoute,
12
12
  approuterMTXRouteJava // xs-app.json config
@@ -36,7 +36,7 @@ module.exports = class ApprouterTemplate extends require('../../plugin') {
36
36
 
37
37
  if (hasMta) {
38
38
  const srv = srv4(srvPath)
39
- const apis = isJava ? [providedJavaApprouterApi4(srv), providedJavaApprouterApi] : []
39
+ const apis = isJava && hasMultitenancy ? [requiredJavaApprouterApi4(srv), providedJavaApprouterApi] : []
40
40
  const mtxSidecar = mtxSidecar4(isJava ? 'mtx/sidecar' : 'gen/mtx/sidecar')
41
41
  if (isNodejs && hasMultitenancy) apis.push(requiredMtxApi, requiredAppApi4(mtxSidecar), providedAppApi)
42
42
  if (isJava && hasMultitenancy) apis.push(requiredMtxApi)
@@ -36,7 +36,7 @@ modules:
36
36
 
37
37
  - name: {{appName}}-mtx
38
38
  type: nodejs
39
- path: gen/mtx/sidecar
39
+ path: {{#isNodejs}}gen/{{/isNodejs}}mtx/sidecar
40
40
  requires:
41
41
  - name: {{appName}}-auth
42
42
  parameters:
@@ -0,0 +1,12 @@
1
+ {
2
+ "dependencies": {
3
+ "@sap/xssec": "^4"
4
+ },
5
+ "cds": {
6
+ "requires": {
7
+ "auth": {
8
+ "kind": "ias"
9
+ }
10
+ }
11
+ }
12
+ }
@@ -23,9 +23,6 @@ module.exports = class IasTemplate extends require('../../plugin') {
23
23
  const { configFile, isNodejs } = project
24
24
  await merge(__dirname, 'files/package.json.hbs').into(configFile, { project, forceOverwrite: true })
25
25
  if (isNodejs) await sort('package.json', 'dependencies')
26
- if (project.hasMultitenancy) {
27
- await merge(__dirname, 'files/package.json.hbs').into('mtx/sidecar/package.json', { project, forceOverwrite: true })
28
- }
29
26
  // if (project.isJava) await mvn.add('ias')
30
27
  }
31
28
 
@@ -43,6 +40,7 @@ module.exports = class IasTemplate extends require('../../plugin') {
43
40
  conf.cds.profiles.push('subscription-manager')
44
41
  }
45
42
  await write('mtx/sidecar/package.json', conf, { spaces: 2 })
43
+ await merge(__dirname, 'files/sidecar.package.json.hbs').into('mtx/sidecar/package.json')
46
44
  }
47
45
 
48
46
  if (hasMta) {
@@ -92,14 +92,14 @@ resources:
92
92
  applicationType: application
93
93
  appName: {{appName}}-${org}-${space}
94
94
  appCallbacks:
95
- dependenciesCallbacks:
96
- url: ~{mtx-api/mtx-cert-url}/-/cds/sms-provisioning/dependencies/{app_tid}
97
- subscriptionCallbacks:
98
- url: ~{mtx-api/mtx-cert-url}/-/cds/sms-provisioning/tenant/{app_tid}
99
- async:
100
- subscribeEnable: true
101
- unSubscribeEnable: true
102
- timeoutInMillis: 300000 # Increase if your deployments are taking longer than that
95
+ dependenciesCallbacks:
96
+ url: {{#isJava}}~{srv-api/srv-cert-url}/mt/sms/subscriptions/tenants/{app_tid}/dependencies{{/isJava}}{{#isNodejs}}~{mtx-api/mtx-cert-url}/-/cds/sms-provisioning/dependencies/{app_tid}{{/isNodejs}}
97
+ subscriptionCallbacks:
98
+ url: {{#isJava}}~{srv-api/srv-cert-url}/mt/sms/subscriptions/tenants/{app_tid}{{/isJava}}{{#isNodejs}}~{mtx-api/mtx-cert-url}/-/cds/sms-provisioning/tenant/{app_tid}{{/isNodejs}}
99
+ async:
100
+ subscribeEnable: true
101
+ unSubscribeEnable: true
102
+ timeoutInMillis: 300000 # Increase if your deployments are taking longer than that
103
103
  displayName: {{appName}}
104
104
  description: Description for {{appName}}
105
105
  category: Category
@@ -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;
@@ -1,12 +1,12 @@
1
1
  {
2
2
  "name": "@sap/cds-dk",
3
- "version": "8.7.2",
3
+ "version": "8.7.3",
4
4
  "lockfileVersion": 3,
5
5
  "requires": true,
6
6
  "packages": {
7
7
  "": {
8
8
  "name": "@sap/cds-dk",
9
- "version": "8.7.2",
9
+ "version": "8.7.3",
10
10
  "license": "SEE LICENSE IN LICENSE",
11
11
  "dependencies": {
12
12
  "@cap-js/asyncapi": "^1.0.0",
@@ -166,26 +166,16 @@
166
166
  }
167
167
  },
168
168
  "node_modules/@eslint/plugin-kit": {
169
- "version": "0.2.5",
169
+ "version": "0.2.6",
170
170
  "license": "Apache-2.0",
171
171
  "dependencies": {
172
- "@eslint/core": "^0.10.0",
172
+ "@eslint/core": "^0.11.0",
173
173
  "levn": "^0.4.1"
174
174
  },
175
175
  "engines": {
176
176
  "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
177
177
  }
178
178
  },
179
- "node_modules/@eslint/plugin-kit/node_modules/@eslint/core": {
180
- "version": "0.10.0",
181
- "license": "Apache-2.0",
182
- "dependencies": {
183
- "@types/json-schema": "^7.0.15"
184
- },
185
- "engines": {
186
- "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
187
- }
188
- },
189
179
  "node_modules/@humanfs/core": {
190
180
  "version": "0.19.1",
191
181
  "license": "Apache-2.0",
@@ -901,6 +891,19 @@
901
891
  "node": ">= 0.4"
902
892
  }
903
893
  },
894
+ "node_modules/es-set-tostringtag": {
895
+ "version": "2.1.0",
896
+ "license": "MIT",
897
+ "dependencies": {
898
+ "es-errors": "^1.3.0",
899
+ "get-intrinsic": "^1.2.6",
900
+ "has-tostringtag": "^1.0.2",
901
+ "hasown": "^2.0.2"
902
+ },
903
+ "engines": {
904
+ "node": ">= 0.4"
905
+ }
906
+ },
904
907
  "node_modules/escape-html": {
905
908
  "version": "1.0.3",
906
909
  "license": "MIT"
@@ -1212,7 +1215,7 @@
1212
1215
  }
1213
1216
  },
1214
1217
  "node_modules/flatted": {
1215
- "version": "3.3.2",
1218
+ "version": "3.3.3",
1216
1219
  "license": "ISC"
1217
1220
  },
1218
1221
  "node_modules/follow-redirects": {
@@ -1234,11 +1237,12 @@
1234
1237
  }
1235
1238
  },
1236
1239
  "node_modules/form-data": {
1237
- "version": "4.0.1",
1240
+ "version": "4.0.2",
1238
1241
  "license": "MIT",
1239
1242
  "dependencies": {
1240
1243
  "asynckit": "^0.4.0",
1241
1244
  "combined-stream": "^1.0.8",
1245
+ "es-set-tostringtag": "^2.1.0",
1242
1246
  "mime-types": "^2.1.12"
1243
1247
  },
1244
1248
  "engines": {
@@ -1382,6 +1386,19 @@
1382
1386
  "url": "https://github.com/sponsors/ljharb"
1383
1387
  }
1384
1388
  },
1389
+ "node_modules/has-tostringtag": {
1390
+ "version": "1.0.2",
1391
+ "license": "MIT",
1392
+ "dependencies": {
1393
+ "has-symbols": "^1.0.3"
1394
+ },
1395
+ "engines": {
1396
+ "node": ">= 0.4"
1397
+ },
1398
+ "funding": {
1399
+ "url": "https://github.com/sponsors/ljharb"
1400
+ }
1401
+ },
1385
1402
  "node_modules/hasown": {
1386
1403
  "version": "2.0.2",
1387
1404
  "license": "MIT",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sap/cds-dk",
3
- "version": "8.7.2",
3
+ "version": "8.7.3",
4
4
  "description": "Command line client and development toolkit for the SAP Cloud Application Programming Model",
5
5
  "homepage": "https://cap.cloud.sap/",
6
6
  "keywords": [