@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 +12 -2
- package/bin/compile/index.js +1 -1
- package/bin/lint.js +10 -4
- package/lib/deploy/to-hana/hana.js +3 -3
- package/lib/init/constants.js +1 -1
- package/lib/init/merge.js +1 -1
- package/lib/init/registries/mta.js +2 -2
- package/lib/init/template/approuter/files/mta.yaml.hbs +2 -2
- package/lib/init/template/approuter/index.js +2 -2
- package/lib/init/template/ias/files/mta.yaml.hbs +1 -1
- package/lib/init/template/ias/files/sidecar.package.json.hbs +12 -0
- package/lib/init/template/ias/index.js +1 -3
- package/lib/init/template/multitenancy/files/mta.yaml.hbs +8 -8
- package/lib/lint/Linter.js +242 -201
- package/lib/lint/io.js +4 -7
- package/npm-shrinkwrap.json +33 -16
- package/package.json +1 -1
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.
|
|
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 -
|
|
34
|
+
## Version 8.7.1 - 2025-02-04
|
|
25
35
|
|
|
26
36
|
### Fixed
|
|
27
37
|
|
package/bin/compile/index.js
CHANGED
|
@@ -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
|
-
|
|
5
|
-
|
|
6
|
-
|
|
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
|
-
|
|
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;
|
package/lib/init/constants.js
CHANGED
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
|
|
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
|
-
|
|
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,
|
|
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 ? [
|
|
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)
|
|
@@ -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
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
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
|
package/lib/lint/Linter.js
CHANGED
|
@@ -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
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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 {
|
|
63
|
-
* @param {
|
|
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
|
|
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
|
-
|
|
87
|
-
|
|
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
|
-
//
|
|
91
|
-
|
|
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
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
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
|
-
*
|
|
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
|
-
|
|
121
|
-
|
|
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
|
-
|
|
249
|
+
#addExtensions() {
|
|
178
250
|
// Add CDS file extensions to lint
|
|
179
|
-
this.fileExtensions = this.pluginApi.getFileExtensions()
|
|
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
|
-
|
|
183
|
-
this.ignorePatterns.push(`!${ext}`);
|
|
184
|
-
});
|
|
254
|
+
this.ignorePatterns = this.fileExtensions
|
|
255
|
+
.map(ext => `!${ext}`)
|
|
185
256
|
}
|
|
186
257
|
|
|
187
|
-
async
|
|
258
|
+
async #runEslint(currentDir, DefaultESLint) {
|
|
188
259
|
try {
|
|
189
|
-
|
|
190
|
-
|
|
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
|
-
//
|
|
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
|
-
|
|
228
|
-
|
|
293
|
+
if (LOG) {
|
|
294
|
+
let lintString = `eslint`;
|
|
229
295
|
|
|
230
|
-
|
|
231
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
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.
|
|
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.
|
|
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.
|
|
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
|
|
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 =
|
|
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
|
|
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
|
|
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.
|
|
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
|
-
|
|
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.
|
|
363
|
-
if (typeof
|
|
364
|
-
isfile(path.relative(".", ".eslint", "rules",
|
|
365
|
-
pluginRules[
|
|
366
|
-
this.ruleOpts[
|
|
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
|
|
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
|
-
|
|
126
|
-
configContents["extends"] = [configContents["extends"], extendsValue];
|
|
127
|
-
}
|
|
123
|
+
} else if (configContents["extends"] !== extendsValue) {
|
|
124
|
+
configContents["extends"] = [configContents["extends"], extendsValue];
|
|
128
125
|
}
|
|
129
126
|
} else {
|
|
130
127
|
configContents["extends"] = extendsValue;
|
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sap/cds-dk",
|
|
3
|
-
"version": "8.7.
|
|
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.
|
|
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.
|
|
169
|
+
"version": "0.2.6",
|
|
170
170
|
"license": "Apache-2.0",
|
|
171
171
|
"dependencies": {
|
|
172
|
-
"@eslint/core": "^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.
|
|
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.
|
|
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",
|