@sap/cds-dk 8.9.5 → 9.0.5

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 (109) hide show
  1. package/CHANGELOG.md +101 -2012
  2. package/LICENSE +15 -21
  3. package/bin/bind.js +1 -1
  4. package/bin/build.js +35 -37
  5. package/bin/cds.js +13 -30
  6. package/bin/compile/index.js +2 -2
  7. package/bin/debug.js +27 -7
  8. package/bin/deploy/index.js +6 -23
  9. package/bin/deploy/to-hana.js +4 -2
  10. package/bin/help.js +5 -3
  11. package/bin/init.js +1 -1
  12. package/bin/up.js +45 -12
  13. package/bin/watch.js +45 -63
  14. package/lib/bind/cf.js +2 -1
  15. package/lib/bind/k8s.js +2 -2
  16. package/lib/bind/plugin.js +1 -1
  17. package/lib/bind/shared.js +16 -25
  18. package/lib/build/buildTaskEngine.js +26 -10
  19. package/lib/build/buildTaskFactory.js +3 -2
  20. package/lib/build/buildTaskProvider.js +18 -7
  21. package/lib/build/buildTaskProviderFactory.js +35 -13
  22. package/lib/build/constants.js +4 -0
  23. package/lib/build/index.js +1 -1
  24. package/lib/build/plugin.js +89 -54
  25. package/lib/build/plugins/helm/files/subcharts/web-application/Chart.yaml +3 -1
  26. package/lib/build/plugins/helm/files/subcharts/web-application/templates/_helpers.tpl +4 -2
  27. package/lib/build/plugins/helm/files/subcharts/web-application/templates/api-rule.yaml +4 -2
  28. package/lib/build/plugins/helm/files/subcharts/web-application/templates/deployment.yaml +18 -1
  29. package/lib/build/plugins/helm/files/subcharts/web-application/templates/service-binding.yaml +0 -1
  30. package/lib/build/plugins/helm/files/subcharts/web-application/values.schema.json +34 -2
  31. package/lib/build/plugins/helm/files/subcharts/web-application/values.yaml +9 -2
  32. package/lib/build/plugins/helm/index.js +2 -1
  33. package/lib/build/plugins.js +24 -10
  34. package/lib/build/provider/edmxBuildPlugin.js +1 -11
  35. package/lib/build/provider/fiori/index.js +4 -4
  36. package/lib/build/provider/hana/index.js +12 -5
  37. package/lib/build/provider/hana/template/package.json +1 -1
  38. package/lib/build/provider/internalBuildPlugin.js +1 -1
  39. package/lib/build/provider/internalBuildTaskProvider.js +3 -24
  40. package/lib/build/provider/java/index.js +1 -1
  41. package/lib/build/provider/mtx-extension/index.js +15 -8
  42. package/lib/build/provider/nodejs/index.js +28 -6
  43. package/lib/build/util.js +186 -125
  44. package/lib/deploy/index.js +5 -0
  45. package/lib/deploy/to-hana/hana.js +27 -33
  46. package/lib/deploy/to-hana/hdiDeployUtil.js +2 -2
  47. package/lib/import/odata/v2parser.js +34 -3
  48. package/lib/import/openapi/importOpenAPI.js +4 -0
  49. package/lib/index.js +2 -11
  50. package/lib/init/constants.js +1 -2
  51. package/lib/init/merge.js +101 -77
  52. package/lib/init/mvn.js +3 -3
  53. package/lib/init/projectReader.js +1 -1
  54. package/lib/init/template/ams/files/mta.yaml.hbs +6 -1
  55. package/lib/init/template/ams/files/package.json.hbs +1 -1
  56. package/lib/init/template/approuter/files/mta.yaml.hbs +5 -0
  57. package/lib/init/template/cf-manifest/files/manifest.yml.hbs +1 -1
  58. package/lib/init/template/cloud-logging/files/package.json.hbs +3 -3
  59. package/lib/init/template/data/as-json.js +3 -3
  60. package/lib/init/template/dynatrace/files/mta.yaml.hbs +1 -1
  61. package/lib/init/template/dynatrace/files/package.json.hbs +2 -2
  62. package/lib/init/template/dynatrace/index.js +2 -1
  63. package/lib/init/template/enterprise-messaging/files/mta.yaml.hbs +6 -0
  64. package/lib/init/template/enterprise-messaging/index.js +5 -1
  65. package/lib/init/template/extensibility/files/package.json.hbs +1 -1
  66. package/lib/init/template/hana/files/db/package.json +1 -1
  67. package/lib/init/template/hana/files/package.json.hbs +2 -4
  68. package/lib/init/template/hana/index.js +2 -2
  69. package/lib/init/template/handler/index.js +16 -12
  70. package/lib/init/template/helm/index.js +13 -22
  71. package/lib/init/template/helm-unified-runtime/index.js +2 -1
  72. package/lib/init/template/html5-repo/files/values.yaml.hbs +12 -1
  73. package/lib/init/template/html5-repo/index.js +10 -5
  74. package/lib/init/template/http/index.js +1 -1
  75. package/lib/init/template/ias/files/mta.yaml.hbs +1 -1
  76. package/lib/init/template/ias/files/sidecar.package.json.hbs +0 -7
  77. package/lib/init/template/ias/index.js +5 -4
  78. package/lib/init/template/lint/files/package.json +6 -0
  79. package/lib/init/template/lint/index.js +61 -204
  80. package/lib/init/template/mta/files/package.json +1 -1
  81. package/lib/init/template/multitenancy/files/mta.yaml.hbs +2 -2
  82. package/lib/init/template/multitenancy/files/package.json.hbs +1 -1
  83. package/lib/init/template/multitenancy/files/package.sidecar.json.hbs +7 -4
  84. package/lib/init/template/multitenancy/index.js +3 -0
  85. package/lib/init/template/nodejs/files/package.json.hbs +6 -3
  86. package/lib/init/template/postgres/files/package.json.hbs +1 -1
  87. package/lib/init/template/redis/files/package.json.hbs +1 -1
  88. package/lib/init/template/sample/files/app/admin-books/fiori-service.cds +6 -0
  89. package/lib/init/template/sample/files/app/admin-books/webapp/manifest.json +1 -0
  90. package/lib/init/template/sample/files/app/browse/fiori-service.cds +3 -2
  91. package/lib/init/template/sample/files/app/common.cds +0 -11
  92. package/lib/init/template/sqlite/files/package.json +1 -1
  93. package/lib/init/template/toggles/files/package.json.hbs +1 -1
  94. package/lib/init/template/typescript/files/package.json +2 -4
  95. package/lib/init/template/workzone-standard/index.js +2 -0
  96. package/lib/init/template/xsuaa/index.js +2 -6
  97. package/lib/lint/Linter.js +203 -331
  98. package/lib/lint/generators.js +52 -46
  99. package/lib/lint/io.js +77 -289
  100. package/lib/util/fs.js +2 -1
  101. package/lib/util/git.js +1 -1
  102. package/lib/util/glob.js +77 -0
  103. package/lib/watch/livereload-server.js +3 -4
  104. package/lib/watch/node-watch.js +3 -4
  105. package/lib/watch/watched.js +1 -1
  106. package/npm-shrinkwrap.json +200 -858
  107. package/package.json +6 -8
  108. package/readme.md +1 -1
  109. package/lib/lint/checks.js +0 -135
@@ -1,15 +1,25 @@
1
- const term = require("../util/term");
2
- const io = require("./io");
3
- const checks = require("./checks");
4
- const generators = require("./generators");
5
- const cds = require("../../lib/cds");
6
- const { path, exists, isdir, isfile, readdir } = cds.utils
7
- const { exit } = require("process");
1
+ const term = require('../util/term')
2
+ const io = require('./io')
3
+ const generators = require('./generators')
4
+ const cds = require('../../lib/cds')
5
+ const { path, isfile, readdir } = cds.utils
6
+ const { exit } = require('node:process')
7
+ const { pathToFileURL } = require('node:url')
8
8
 
9
- const LOG = cds.debug("lint");
10
- const LOG_CONFIG = cds.debug("lint:config");
11
- const GENERAL_ERROR = 1;
12
- const FATAL_ERROR = 2;
9
+
10
+ const LOG = cds.debug('lint')
11
+ const LOG_CONFIG = cds.debug('lint:config')
12
+ const GENERAL_ERROR = 1
13
+ const FATAL_ERROR = 2
14
+
15
+ /**
16
+ * @param {{errorCount: number, fatalErrorCount: number}[]} results
17
+ */
18
+ const countErrors = results => results.reduce((acc, result) => {
19
+ acc.errorCount += result.errorCount
20
+ acc.fatalErrorCount += result.fatalErrorCount
21
+ return acc
22
+ }, { errorCount: 0, fatalErrorCount: 0 })
13
23
 
14
24
  /**
15
25
  * Safely require a module, return empty object if module not found
@@ -19,185 +29,141 @@ const FATAL_ERROR = 2;
19
29
  */
20
30
  async function tryRequire(module, esm = false) {
21
31
  try {
22
- return esm ? import(module) : require(module)
32
+ const modulePath = require.resolve(module, { paths: [process.cwd()] })
33
+ const result = esm
34
+ // pathToFileURL is used to convert the path to a file URL on Windows
35
+ ? (await import(pathToFileURL(modulePath))).default
36
+ : require(modulePath)
37
+ return result
23
38
  } catch (e) {
24
- if (e.code === 'MODULE_NOT_FOUND' && e.message.includes(module)) return {}
39
+ if (e.code === 'MODULE_NOT_FOUND' && e.message.includes(module))
40
+ return {}
25
41
  // target module is either .mjs (etc) or user project is ESM
26
- if (['ERR_REQUIRE_ASYNC_MODULE', 'ERR_REQUIRE_ESM'].includes(e.code)) return tryRequire(module, true)
42
+ if (['ERR_REQUIRE_ASYNC_MODULE', 'ERR_REQUIRE_ESM'].includes(e.code))
43
+ return tryRequire(module, true)
27
44
  throw e
28
45
  }
29
46
  }
30
47
 
31
48
  /**
32
- * Load ESLint module
33
- * @param {string} currentDir - current directory to start search
34
- * @returns {Promise<ReturnType<NodeRequire>>} - ESLint module
35
- */
36
- async function _loadEslint(currentDir) {
37
- const { loadESLint, ESLint } = require('eslint')
38
- const DefaultESLint = loadESLint
39
- ? await loadESLint({ cwd: currentDir }) // ESLint 9
40
- : new ESLint({ cwd: currentDir }) // ESLint 8
41
- return DefaultESLint;
42
- }
43
-
44
- /**
45
- * @param {string[]} configFiles - possible config file paths
49
+ * Ascends through directory tree to find the first config file.
46
50
  * @param {string} currentDir - current directory to start search
47
51
  * @returns {string} - path to config file
48
52
  */
49
- async function findConfigPath (configFiles, currentDir = '.') {
53
+ async function findConfigPath (currentDir = '.') {
54
+ // possible config file paths
55
+ const configFiles = [
56
+ 'eslint.config.js',
57
+ 'eslint.config.cjs',
58
+ 'eslint.config.mjs',
59
+ // the following 3 formats require additional setup by the user as of today,
60
+ // see https://eslint.org/docs/latest/use/configure/configuration-files#typescript-configuration-files
61
+ 'eslint.config.ts',
62
+ 'eslint.config.mts',
63
+ 'eslint.config.cts',
64
+ ]
65
+
50
66
  let configDir = path.resolve(currentDir)
51
67
  while (configDir && configDir !== path.dirname(configDir)) {
52
68
  for (const configFile of configFiles) {
53
69
  const configPath = path.join(configDir, configFile)
54
70
  const config = await tryRequire(configPath)
55
- if (configFile !== 'package.json' || config?.eslintConfig) {
56
- if (exists(configPath) && isfile(configPath)) { // TODO exists neccessary ?
57
- return configPath
58
- }
71
+ if ((configFile !== 'package.json' || config?.eslintConfig) && isfile(configPath)) {
72
+ return configPath
59
73
  }
60
74
  }
61
- configDir = path.dirname(configDir);
75
+ configDir = path.dirname(configDir)
62
76
  }
63
77
  return ''
64
78
  }
65
79
 
66
- /**
67
- * This class and its subclasses are an attempt to smoothen out some of the
68
- * workflow in the Linter class. It gets rid of some of the isESLintLegacy ? ... : ...
69
- * assignments, although not all. The remaining branching is too closely coupled
70
- * to some properties of the Linter class to be easily abstracted away.
71
- * @abstract
72
- */
73
- class ESLintLoader {
74
- /**
75
- * Searches for ESLint config file types (in order or precedence)
76
- * and returns corresponding directory (usually project's root dir)
77
- * https://eslint.org/docs/user-guide/configuring#configuration-file-formats
78
- * @param {string} currentDir start here and search until root dir
79
- * @returns {string} dir containing ESLint config file (empty if not exists)
80
- * @abstract
81
- */
82
- // eslint-disable-next-line no-unused-vars
83
- async getConfigPath (currentDir = '.') { throw new Error("Not implemented") }
84
- /** @type {import('./io').sanitizeEslintConfig} */
85
- // eslint-disable-next-line no-unused-vars
86
- sanitizeEslintConfig (configIn, customRuleExample, logger) { throw new Error("Not implemented") }
87
- /** @type {import('./io').readEslintConfig} */
88
- // eslint-disable-next-line no-unused-vars
89
- readEslintConfig(configPath) { throw new Error("Not implemented") }
90
- }
91
-
92
- class ESLint8Loader extends ESLintLoader {
93
- async getConfigPath(currentDir = '.') {
94
- return findConfigPath([
95
- ".eslintrc.js",
96
- ".eslintrc.cjs",
97
- ".eslintrc.yaml",
98
- ".eslintrc.yml",
99
- ".eslintrc.json",
100
- ".eslintrc",
101
- "package.json",
102
- ], currentDir)
103
- }
104
-
105
- sanitizeEslintConfig (configIn, customRuleExample, logger) {
106
- return io.sanitizeEslintConfigLegacy(configIn, customRuleExample, logger)
107
- }
108
-
109
- readEslintConfig(configPath) {
110
- return io.readEslintConfigLegacy(configPath)
111
- }
112
- }
113
-
114
- class ESLint9Loader extends ESLintLoader {
115
- async getConfigPath(currentDir = '.') {
116
- return findConfigPath([
117
- "eslint.config.js",
118
- "eslint.config.cjs",
119
- "eslint.config.mjs",
120
- // the following 3 formats require additional setup by the user as of today,
121
- // see https://eslint.org/docs/latest/use/configure/configuration-files#typescript-configuration-files
122
- "eslint.config.ts",
123
- "eslint.config.mts",
124
- "eslint.config.cts",
125
- ], currentDir)
126
- }
127
-
128
- sanitizeEslintConfig (configIn, customRuleExample, logger) {
129
- return io.sanitizeEslintConfig(configIn, customRuleExample, logger)
130
- }
131
-
132
- readEslintConfig(configPath) {
133
- return io.readEslintConfig(configPath)
134
- }
135
- }
136
-
137
80
  class Linter {
138
- /** @type {string | undefined} */
139
- eslintCmd = "";
140
81
  /** @type {string} */
141
- eslintCmdShort = "eslint";
82
+ eslintCmd = 'npx eslint'
142
83
  /** @type {string | string[]} */
143
- eslintCmdFileExpr = "";
144
- /** @type {boolean} */
145
- isFile = false;
146
- /** @type {string} */
147
- help = "";
84
+ eslintCmdFileExpr = ''
148
85
  /** @type {string} */
149
- debug = "";
150
- /** @type {string[]} */
151
- flags = [];
86
+ help = ''
152
87
  /** @type {string} */
153
- configPath = "";
88
+ debug = ''
89
+ /** @type {string[]} */
90
+ flags = []
91
+ /** @type {import('fs').PathLike} */
92
+ #configPath
93
+ async #getConfigPath () {
94
+ return this.#configPath ??= await findConfigPath(process.cwd())
95
+ }
154
96
  /** @type {object} */
155
- configContents = {};
156
- /** @type {import("fs").PathLike} */
157
- cdsdkPath = path.join(__dirname, "../..");
158
- /** @type {import("fs").PathLike} */
159
- globalPath = path.join(this.cdsdkPath, "../..");
160
- /** @type {boolean} */
161
- extendsPlugin = false;
97
+ #configContents
98
+ async #getConfigContents () {
99
+ if (!this.#configContents) {
100
+ if (!(await this.#getConfigPath())) {
101
+ this.#configContents = io.sanitizeEslintConfig({}, LOG)
102
+ } else {
103
+ const cc = await io.readEslintConfig(await this.#getConfigPath())
104
+ this.#configContents = cc
105
+ const cdsConfig = Array.isArray(cc)
106
+ ? cc?.find(c => c.files?.some(f => f.includes('*.cds'))) ?? {}
107
+ : cc
108
+ this.#configContents = cdsConfig.files
109
+ ? {
110
+ ...cdsConfig.plugins.configs?.recommended,
111
+ languageOptions: cdsConfig.languageOptions,
112
+ files: cdsConfig.files,
113
+ rules: cdsConfig.rules,
114
+ }
115
+ : {}
116
+ }
117
+ }
118
+ return this.#configContents
119
+ }
162
120
  /** @type {string[]} */
163
- fileExtensions = [];
121
+ fileExtensions = []
164
122
  /** @type {string} */
165
- pluginPath = "";
166
- /** @type {object} */
167
- pluginApi = {};
168
- /** @type {'global' | 'local'} */
169
- lintType = "global";
123
+ #pluginPath
124
+ async #getPluginPath () {
125
+ if (this.#pluginPath === undefined) {
126
+ try {
127
+ this.#pluginPath = require.resolve('@sap/eslint-plugin-cds', {
128
+ paths: [path.dirname(await this.#getConfigPath())],
129
+ })
130
+ } catch {
131
+ // CLI will report (plugin not installed)
132
+ this.#pluginPath = null
133
+ }
134
+ }
135
+ return this.#pluginPath
136
+ }
137
+
138
+ /** @type {NodeJS.Module} */
139
+ #pluginApi
140
+ async #getPluginApi () {
141
+ return this.#pluginApi ??= require((await this.#getPluginPath()).replace('index.js', 'api'))
142
+ }
170
143
  /** @type {object} */
171
- ruleOpts = {};
144
+ ruleOpts = {}
172
145
  /** @type {array} */
173
- customRulesOpts = [];
146
+ customRulesOpts = []
174
147
  /** @type {array} */
175
- pluginRules = [];
176
- /** @type {ESLintLoader | undefined} */
177
- #loader = undefined;
178
- get loader () {
179
- return this.#loader ??= this.isESLintLegacy
180
- ? new ESLint8Loader()
181
- : new ESLint9Loader()
182
- };
148
+ pluginRules = []
183
149
 
184
150
  /**
185
151
  * Initializes `cds lint` call and generates the required content
186
152
  * object for `cds` executable
187
- * @returns object containing help, options, flags and shortcuts
153
+ * @returns {{
154
+ * help: string,
155
+ * options: string[],
156
+ * flags: unknown[],
157
+ * shortcuts: unknown[],
158
+ * }} object containing help, options, flags and shortcuts
188
159
  */
189
- init(help = "", options = [], flags = [], shortcuts = []) {
190
- this.eslintCmd = checks.resolveEslint(this.cdsdkPath, this.globalPath);
191
- if (this.eslintCmd) {
192
- help = generators.genEslintHelp(this.eslintCmd);
193
- flags = generators.genEslintFlags(help);
194
- [help, options, shortcuts, flags] = generators.genEslintShortcutsAndOpts(help, flags);
195
- this.help = help;
196
- this.flags = flags;
197
- } else {
198
- console.log(`${term.error("Cannot call 'eslint -h', install and try again.")}\n`);
199
- }
200
- return { help, options, flags, shortcuts };
160
+ init() {
161
+ const eslintHelp = generators.genEslintHelp(this.eslintCmd)
162
+ const eslintFlags = generators.genEslintFlags(eslintHelp)
163
+ const [help, options, shortcuts, flags] = generators.genEslintShortcutsAndOpts(eslintHelp, eslintFlags)
164
+ this.help = help
165
+ this.flags = flags
166
+ return { help, flags, options, shortcuts }
201
167
  }
202
168
 
203
169
  /**
@@ -207,36 +173,33 @@ class Linter {
207
173
  * @param {{[key: string]: unknown}} options options/flag passed by user
208
174
  */
209
175
  async lint(args, options) {
210
- // Check for ESLint (legacy)
211
- const currentDir = process.cwd();
212
- const DefaultESLint = await _loadEslint(currentDir)
213
- this.isESLintLegacy = DefaultESLint.configType === "eslintrc";
214
- this.eslintCmdOpts = options; // these should probably be passed on in #runEsLint!
215
- this.eslintCmdFileExpr = args.length ? args : ["."];
216
-
217
- if (this.eslintCmdOpts.help) {
218
- this.#printHelp();
219
- return
176
+ // user should install eslint locally
177
+ const { loadESLint } = await tryRequire('eslint')
178
+ if (!loadESLint)
179
+ throw 'ESLint is not installed. Please install ESLint in your project using \'npm i -D eslint\'.'
180
+
181
+ const DefaultESLint = await loadESLint()
182
+ if (options?.version) {
183
+ return this.#printVersion(DefaultESLint.version)
220
184
  }
221
- // Determine if case "local" or "global" and resolve plugin accordingly
222
- await this.#getConfigFileAndSetProjectPath(args);
223
185
 
224
- if (this.pluginPath) {
225
- const overwriteRules = checks.hasEslintConfigContent(this.configContents, "rules");
226
- // Overwrite rules severities
227
- if (overwriteRules) {
228
- await this.#overwriteRuleSeverities();
229
- }
186
+ this.eslintCmdOpts = options
187
+ this.eslintCmdFileExpr = args.length ? args : ['.']
188
+
189
+ if (this.eslintCmdOpts.help)
190
+ return this.#printHelp()
230
191
 
192
+ if (await this.#getPluginPath()) {
193
+ await this.#overwriteRuleSeverities()
231
194
  // Limit to CDS file extensions
232
- this.#addExtensions();
195
+ await this.#addExtensions()
233
196
  }
234
197
 
235
198
  // Run ESLint with collected options
236
199
  try {
237
- await this.#runEslint(currentDir, DefaultESLint);
200
+ await this.#runEslint(DefaultESLint)
238
201
  } catch (err) {
239
- term.error(err);
202
+ term.error(err)
240
203
  }
241
204
  }
242
205
 
@@ -244,207 +207,116 @@ class Linter {
244
207
  * Prints help message
245
208
  */
246
209
  #printHelp() {
247
- console.log(this.help.replace(/ \*([^*]+)\*/g, ` ${term.codes.bold}$1${term.codes.reset}`));
210
+ console.log(this.help.replace(/ \*([^*]+)\*/g, ` ${term.codes.bold}$1${term.codes.reset}`))
211
+ }
212
+
213
+ #printVersion(version) {
214
+ console.log(`Using ESLint version ${term.codes.bold}${version}${term.codes.reset}`)
248
215
  }
249
216
 
250
- #addExtensions() {
217
+ async #addExtensions() {
251
218
  // Add CDS file extensions to lint
252
- this.fileExtensions = this.pluginApi.getFileExtensions()
253
- .map((ext) => path.extname(ext));
219
+ this.fileExtensions = (await this.#getPluginApi()).getFileExtensions()
220
+ .map(ext => path.extname(ext))
254
221
  // Only lint file extensions prescribed by plugin
255
222
  this.ignorePatterns = this.fileExtensions
256
223
  .map(ext => `!${ext}`)
257
224
  }
258
225
 
259
- async #runEslint(currentDir, DefaultESLint) {
226
+ async #runEslint(DefaultESLint) {
260
227
  try {
261
- const eslintOpts = this.lintType === "global"
262
- ? {
228
+ const eslintOpts = {
263
229
  cwd: process.cwd(),
264
- overrideConfig: {},
265
- }
266
- : {
267
- cwd: currentDir,
268
- overrideConfig: {...this.configContents},
230
+ overrideConfig: await this.#getConfigContents(),
269
231
  }
270
232
 
271
- if (this.isESLintLegacy) {
272
- LOG?.("Using legacy ESLint configuration");
273
- eslintOpts.extensions = this.fileExtensions;
274
- eslintOpts.overrideConfig.extends = "plugin:@sap/cds/recommended-legacy"
275
- eslintOpts.overrideConfig.plugins = ["@sap/eslint-plugin-cds"]
276
- eslintOpts.useEslintrc = false
277
- if (this.lintType === "global") {
278
- eslintOpts.resolvePluginsRelativeTo = this.cdsdkPath
279
- }
280
- } else {
281
- LOG?.("Using flat ESLint configuration");
282
- const cdsPlugin = require(this.pluginPath)
283
- // Exclude our own apis from plugin to avoid wrapper errors
284
- delete cdsPlugin.configs.recommended?.plugins?.['@sap/cds']
285
- eslintOpts.overrideConfig = [cdsPlugin.configs.recommended]
286
- }
233
+ const cdsPlugin = require(await this.#getPluginPath())
234
+ // Exclude our own apis from plugin to avoid wrapper errors
235
+ //delete cdsPlugin.configs.recommended?.plugins?.['@sap/cds']
236
+ eslintOpts.overrideConfig = [cdsPlugin.configs.recommended]
287
237
 
288
- if (this.customRulesOpts && this.customRulesOpts.length > 0) {
238
+ if (this.customRulesOpts?.length) {
289
239
  eslintOpts.rulePaths = [this.customRulesOpts]
290
240
  }
291
241
 
292
- LOG_CONFIG?.(eslintOpts);
242
+ LOG_CONFIG?.(eslintOpts)
293
243
 
294
244
  if (LOG) {
295
- let lintString = `eslint`;
296
-
245
+ const lintString = ['npx eslint']
297
246
  if (this.fileExtensions) {
298
- lintString += ` --ext "${this.fileExtensions.join(",")}"`
247
+ lintString.push(`--ext "${this.fileExtensions.join(',')}"`)
299
248
  }
300
-
301
249
  for (const [name, rule] of Object.entries(this.ruleOpts)) {
302
- lintString += ` --rule ${name}:${rule}`
250
+ lintString.push(`--rule ${name}:${rule}`)
303
251
  }
304
- if (this.customRulesOpts && this.customRulesOpts.length > 0) {
305
- lintString += ` --rulesdir "${this.customRulesOpts}"`
252
+ if (this.customRulesOpts?.length) {
253
+ lintString.push(`--rulesdir "${this.customRulesOpts}"`)
306
254
  }
307
255
 
308
- LOG(lintString);
256
+ LOG(lintString.join(' '))
309
257
  }
310
258
 
311
- if (!process.env.isTest) {
312
- const eslint = new DefaultESLint(eslintOpts);
313
- const formatter = await eslint.loadFormatter("stylish");
314
-
315
- let results = (await eslint.lintText("")).map((result) => {
316
- result.filePath = path.resolve(currentDir);
317
- return result;
318
- }).filter(result => result.messages.length > 0);
259
+ if (process.env.isTest)
260
+ // only log config and hypothetical lintString, do not actually run
261
+ return
262
+
263
+ /** @type {import('eslint').ESLint} */
264
+ const eslint = new DefaultESLint(eslintOpts)
265
+ const formatter = await eslint.loadFormatter('stylish')
266
+
267
+ // environment rules
268
+ const results = (await eslint.lintText(''))
269
+ .filter(result => result.messages.length)
270
+ .map((result) => {
271
+ result.filePath = path.resolve(process.cwd())
272
+ return result
273
+ })
274
+
275
+ // lint files in current dir
276
+ const files = await readdir(process.cwd())
277
+ if (files.some(file => isfile(file))) {
278
+ results.push(...await eslint.lintFiles(this.eslintCmdFileExpr))
279
+ }
319
280
 
320
- const files = await readdir(currentDir);
321
- const hasFiles = files.some(file => isfile(file));
322
- if (hasFiles) {
323
- const resultsModel = await eslint.lintFiles(this.eslintCmdFileExpr);
324
- results = results.concat(resultsModel);
281
+ if (results?.length) {
282
+ console.log(formatter.format(results))
283
+ const { errorCount, fatalErrorCount } = countErrors(results)
284
+ if (fatalErrorCount) {
285
+ exit(FATAL_ERROR)
325
286
  }
326
-
327
- if (results?.length > 0) {
328
- console.log(formatter.format(results));
329
- const { errorCount, fatalErrorCount } = this.#countErrors(results);
330
- if (fatalErrorCount > 0) {
331
- exit(FATAL_ERROR);
332
- } else if (errorCount > 0) {
333
- exit(GENERAL_ERROR);
334
- }
287
+ if (errorCount) {
288
+ exit(GENERAL_ERROR)
335
289
  }
336
290
  }
337
291
  } catch (err) {
338
292
  // Report identically to ESLint CLI
339
- if (typeof err.messageTemplate === "string") {
293
+ if (typeof err.messageTemplate === 'string') {
340
294
  try {
341
- const eslintBase = `${require.resolve('eslint').split('eslint')[0]}/eslint`;
342
- const template = require(path.join(eslintBase, `messages/${err.messageTemplate}.js`));
343
- console.log(template(err.messageData || {}));
295
+ const eslintBase = `${require.resolve('eslint').split('eslint')[0]}/eslint`
296
+ const template = require(path.join(eslintBase, `messages/${err.messageTemplate}.js`))
297
+ console.log(template(err.messageData || {}))
344
298
  } catch {
345
299
  // Ignore template error then fallback to use `error.stack`.
346
- console.log(err.stack);
300
+ console.log(err.stack)
347
301
  }
348
302
  }
349
- exit(GENERAL_ERROR); // TODO use error constant
303
+ exit(GENERAL_ERROR)
350
304
  }
351
305
  }
352
306
 
353
- #countErrors(results) {
354
- let errorCount = 0;
355
- let fatalErrorCount = 0;
356
-
357
- for (const result of results) {
358
- errorCount += result.errorCount;
359
- fatalErrorCount += result.fatalErrorCount;
360
- }
361
-
362
- return { errorCount, fatalErrorCount };
363
- }
364
-
365
- async #getConfigFileAndSetProjectPath(args) {
366
- // Get config path
367
- if (args.length > 0) {
368
- const firstArg = args[0];
369
- this.isFile = firstArg === "." || isfile(firstArg) || isdir(firstArg)
370
- }
371
- this.configPath = await this.loader.getConfigPath(process.cwd())
372
- if (!this.configPath) {
373
- this.configContents = this.loader.sanitizeEslintConfig({}, false, LOG)
374
- } else {
375
- const configContents = this.loader.readEslintConfig(this.configPath)
376
- this.configContents = configContents;
377
- if (this.isESLintLegacy) {
378
- this.configContents = await io.sanitizeEslintConfig(configContents, false, LOG);
379
- if (this.configContents) {
380
- if ("extends" in this.configContents) {
381
- this.extendsPlugin = checks.hasEslintConfigContent(
382
- this.configContents,
383
- "extends"
384
- );
385
- if (this.extendsPlugin) {
386
- this.lintType = "local";
387
- try {
388
- this.pluginPath = require.resolve("@sap/eslint-plugin-cds", {
389
- paths: [path.dirname(this.configPath)],
390
- });
391
- this.pluginApi = require(this.pluginPath.replace("index.js", "api"));
392
- } catch {
393
- // CLI will report (no locally installed plugin)
394
- }
395
- }
396
- }
397
- }
398
- } else {
399
- const cdsConfig = Array.isArray(configContents)
400
- ? configContents?.find(c => c.files?.includes("*.cds")) ?? {}
401
- : configContents
402
- this.configContents = cdsConfig.files
403
- ? {
404
- ...cdsConfig.plugins.configs?.recommended,
405
- languageOptions: cdsConfig.languageOptions,
406
- files: cdsConfig.files,
407
- rules: cdsConfig.rules,
408
- }
409
- : {};
410
- }
411
- }
412
- if (!this.pluginPath) {
413
- try {
414
- this.pluginPath = require.resolve("@sap/eslint-plugin-cds", {
415
- paths: [this.cdsdkPath],
416
- });
417
- this.pluginApi = require(this.pluginPath.replace("index.js", "api"));
418
- } catch {
419
- // CLI will report (no globally installed plugin)
420
- }
421
- }
422
- LOG?.(`Lint type: ${this.lintType}`);
423
- LOG?.(`ESLint CDS plugin resolved: ${this.pluginPath}`);
424
- }
425
-
426
307
  async #overwriteRuleSeverities() {
427
- let configContents = {};
428
- if (this.configPath) {
429
- configContents = await io.readEslintConfigLegacy(this.configPath);
430
- } else {
431
- configContents = this.loader.readEslintConfig(await this.loader.getConfigPath(this.cdsdkPath))
432
- }
433
- const rules = configContents?.rules ?? {};
434
- // Allow recommended plugin rules in cds-dk to be overwritten
308
+ const rules = this.#getConfigContents().rules ?? {}
309
+ // Allow recommended plugin rules to be overwritten
435
310
  // by user by adding rule to cmd line (because of precedence)
436
- const pluginRules = require(this.pluginPath).configs.recommended.rules;
437
- if (rules && pluginRules) {
438
- for (const [name, rule] of Object.entries(rules)) {
439
- if (typeof rule !== "undefined" && rule != pluginRules[name] ||
440
- isfile(path.relative(".", ".eslint", "rules", name))) {
441
- pluginRules[name] = rule;
442
- this.ruleOpts[name] = rule;
443
- this.pluginRules = pluginRules;
444
- }
311
+ const pluginRules = require(await this.#getPluginPath()).configs.recommended.rules ?? {}
312
+ for (const [name, rule] of Object.entries(rules)) {
313
+ if (rule && rule !== pluginRules[name] || isfile(path.relative('.', '.eslint', 'rules', name))) {
314
+ pluginRules[name] = rule
315
+ this.ruleOpts[name] = rule
316
+ this.pluginRules = pluginRules
445
317
  }
446
318
  }
447
319
  }
448
320
  }
449
321
 
450
- module.exports = Linter;
322
+ module.exports = Linter