@sap/cds-dk 9.4.2 → 9.4.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 +17 -0
- package/bin/data-products/index.js +10 -2
- package/bin/data-products/prepare-csn.js +63 -26
- package/bin/help.js +2 -2
- package/bin/import.js +7 -2
- package/bin/up.js +24 -3
- package/bin/util/containerize.js +142 -0
- package/lib/build/buildTaskEngine.js +5 -2
- package/lib/build/plugin.js +1 -1
- package/lib/build/plugins/helm/index.js +6 -6
- package/lib/build/provider/hana/index.js +10 -2
- package/lib/import/common.js +3 -3
- package/lib/import/message.js +1 -1
- package/lib/init/projectReader.js +37 -22
- package/lib/init/registries/mta.js +3 -0
- package/lib/init/registries/xs-app.js +7 -1
- package/lib/init/template/approuter/files/mta.yaml.hbs +7 -0
- package/lib/init/template/approuter/index.js +17 -11
- package/lib/init/template/audit-logging/index.js +2 -2
- package/lib/init/template/containerize/files/containerize.yaml.hbs +1 -2
- package/lib/init/template/destination/index.js +0 -4
- package/lib/init/template/github-actions/files/kyma-setup/action.yaml +0 -2
- package/lib/init/template/hana/index.js +3 -3
- package/lib/init/template/helm/index.js +16 -28
- package/lib/init/template/html5-repo/files/containerize.yaml.hbs +0 -9
- package/lib/init/template/html5-repo/files/mta.yaml.hbs +0 -7
- package/lib/init/template/html5-repo/files/values.yaml.hbs +6 -0
- package/lib/init/template/html5-repo/files/xs-app.json.hbs +5 -3
- package/lib/init/template/html5-repo/index.js +2 -2
- package/lib/init/template/ias/index.js +4 -5
- package/lib/init/template/multitenancy/index.js +2 -2
- package/lib/init/template/pipeline/index.js +5 -1
- package/lib/init/template/xsuaa/index.js +3 -3
- package/lib/util/question.js +1 -1
- package/npm-shrinkwrap.json +17 -17
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,23 @@ This project adheres to [Semantic Versioning](https://semver.org/).
|
|
|
6
6
|
|
|
7
7
|
The format is based on [Keep a Changelog](https://keepachangelog.com/).
|
|
8
8
|
|
|
9
|
+
## Version 9.4.3 - 2025-10-29
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- `cds add containerize` omits the Unix-based `before_all` scripts in `containerize.yaml` in favor of a cross-platform compatible `cds up --to k8s` integration.
|
|
14
|
+
- `cds up --to k8s` now uses built-in containerization, eliminating the hard dependency on the `ctz` library.
|
|
15
|
+
- `cds up --to k8s` will interactively ask for a domain if it can't be determined from the current Kubernetes configuration.
|
|
16
|
+
- `cds build --for hana` no longer requires `.hdbgrants` and `.hdbrevokes` to be specified as a plugin in the `.hdiconfig` file.
|
|
17
|
+
|
|
18
|
+
### Fixed
|
|
19
|
+
|
|
20
|
+
- `cds add pipeline` does not add UI5 template files in some cases.
|
|
21
|
+
- `cds add ias` does not write to the `mta.yaml` for non-MTA projects.
|
|
22
|
+
- `cds add ias` automatically adds the `authenticationType` to `xs-app.json` if required.
|
|
23
|
+
- `cds add ias` adds a binding to the approuter component if required.
|
|
24
|
+
- `cds import --from ...` yields a better error message for unknown import kinds.
|
|
25
|
+
|
|
9
26
|
## Version 9.4.2 - 2025-10-23
|
|
10
27
|
|
|
11
28
|
### Changed
|
|
@@ -6,8 +6,16 @@ const path = require ('node:path')
|
|
|
6
6
|
|
|
7
7
|
exports.load_data_product = async function (sources, o) {
|
|
8
8
|
let service = /\/([^/]+)\//.exec(o.services)?.[1].replaceAll('\\.','.')
|
|
9
|
-
if (
|
|
10
|
-
|
|
9
|
+
if (service != 'all') {
|
|
10
|
+
// ORD ID has been provided as parameter "-s", valid example: "sap.s4com:apiResource:Product:v1"
|
|
11
|
+
if (!service.match(/^([^:]+):apiResource:([^:]+):(v[0-9]+)$/))
|
|
12
|
+
throw new Error(`"${service}" is not a valid output port ORD ID`)
|
|
13
|
+
}
|
|
14
|
+
else {
|
|
15
|
+
// try to deduce ORD ID from file name, valid example: "sap-s4com-Product-v1.json"
|
|
16
|
+
const m = /^(.+)-([^-]+)-(v[0-9]+)\.(json|csn)$/ .exec (path.basename(String(sources)))
|
|
17
|
+
if (m?.length != 5) throw new Error(`Output port ORD ID cannot be deduced from file name`)
|
|
18
|
+
const [,namespace,name,version] = m
|
|
11
19
|
service = namespace.replace(/-/g,'.') + ':apiResource:' + name + ':' + version
|
|
12
20
|
}
|
|
13
21
|
|
|
@@ -180,38 +180,75 @@ exports.prepareCsn = function (csn, srvName, ordId) {
|
|
|
180
180
|
//
|
|
181
181
|
// ---------- step 5: wrap everyting into service if not yet done -------------------------------
|
|
182
182
|
//
|
|
183
|
-
//
|
|
184
|
-
|
|
183
|
+
// This function must be called with a service name "srvName" as argument.
|
|
184
|
+
if (!srvName) throw new Error('No service name has been provided.')
|
|
185
185
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
186
|
+
// Assumptions (based on the rules for DP CSNs):
|
|
187
|
+
// 1) there is no or one context or service in the CSN
|
|
188
|
+
// 2) if there is a context or service, all objects in the CSN belong to it
|
|
189
|
+
//
|
|
190
|
+
// If there is no context/service (this is a DP CSN coming from BAH):
|
|
191
|
+
// put all entities into a new service "srvName", i.e.
|
|
192
|
+
// - prefix all object and association target names with "srvName"
|
|
193
|
+
// - add a service "srvName" with CAP specific annotations for DP handling
|
|
194
|
+
// If there is a context/service (DP CSN comes from UCL, context/service name is the schema name in the HDLFS share):
|
|
195
|
+
// move all entities out of the context/service and put them into a new service "srvName", i.e.
|
|
196
|
+
// - remove the context/service
|
|
197
|
+
// - change the prefix for all entity and association target names from
|
|
198
|
+
// the old context/service name to "srvName"
|
|
199
|
+
// - add a service "srvName" with CAP specific annotations for DP handling
|
|
200
|
+
//
|
|
201
|
+
// Note: Currently an existing context/service doesn't contain further info (e.g. annos),
|
|
202
|
+
// so deleting it is ok.
|
|
203
|
+
|
|
204
|
+
let ctxName = null;
|
|
205
|
+
const ctxs = Object.entries(csn.definitions).filter(([,o]) => o.kind === 'context' || o.kind === 'service')
|
|
206
|
+
if (ctxs.length > 0) {
|
|
207
|
+
// check assumption 1
|
|
208
|
+
if (ctxs.length > 1) throw new Error('Data Product CSN is invalid: it has more than one context or service.')
|
|
209
|
+
ctxName = ctxs[0][0]
|
|
210
|
+
delete csn.definitions[ctxName]
|
|
211
|
+
// check assumption 2
|
|
212
|
+
const bad = Object.keys(csn.definitions).filter((n) => !n.startsWith(ctxName + '.'))
|
|
213
|
+
if (bad.length > 0)
|
|
214
|
+
throw new Error(`Unexpected entity name "${bad[0]}" without prefix in Data product CSN with context "${ctxName}"`)
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
function changePrefix(name) {
|
|
218
|
+
if (ctxName) {
|
|
219
|
+
name = name.substring(ctxName.length + 1) // remove prefix incl. '.'
|
|
220
|
+
}
|
|
221
|
+
return srvName + '.' + name
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// loop over all definitions and prefix names with service name
|
|
225
|
+
// if ctxName accidentially equals srvName, do nothing
|
|
226
|
+
if (ctxName != srvName) {
|
|
227
|
+
for (let n in csn.definitions) {
|
|
228
|
+
let def = csn.definitions[n]
|
|
229
|
+
// adapt assoc targets
|
|
230
|
+
for (let en in def.elements || {} ) {
|
|
231
|
+
let element = def.elements[en]
|
|
232
|
+
if (element?.type === 'cds.Association' || element?.type === 'cds.Composition') {
|
|
233
|
+
element.target = changePrefix(element.target)
|
|
203
234
|
}
|
|
204
235
|
}
|
|
236
|
+
csn.definitions[changePrefix(n)] = def
|
|
237
|
+
delete csn.definitions[n]
|
|
205
238
|
}
|
|
206
|
-
// add service annotations
|
|
207
|
-
Object.assign(csn.definitions[srvName], {
|
|
208
|
-
'@cds.dp.ordId': ordId,
|
|
209
|
-
'@cds.external': true,
|
|
210
|
-
'@data.product': true,
|
|
211
|
-
'@protocol' : 'none' // don't serve via OData
|
|
212
|
-
})
|
|
213
239
|
}
|
|
214
240
|
|
|
241
|
+
// add the service
|
|
242
|
+
csn.definitions[srvName] = {
|
|
243
|
+
kind : 'service',
|
|
244
|
+
'@cds.dp.ordId': ordId,
|
|
245
|
+
'@cds.external': true,
|
|
246
|
+
'@data.product': true,
|
|
247
|
+
'@protocol' : 'none' // don't serve via OData
|
|
248
|
+
};
|
|
249
|
+
if (ctxName) csn.definitions[srvName]['@data.product.schema'] = ctxName;
|
|
250
|
+
|
|
251
|
+
|
|
215
252
|
//
|
|
216
253
|
// ---------- step 6: move entity/element annotations into extensions section -------------------
|
|
217
254
|
//
|
package/bin/help.js
CHANGED
|
@@ -32,10 +32,10 @@ module.exports = exports = Object.assign (help, {formatHelp, handleCompletion, h
|
|
|
32
32
|
* * | *logout* logout from extensible multitenant SaaS app
|
|
33
33
|
* * | *pull* pull base model of extensible SaaS app
|
|
34
34
|
* * | *push* push extension to extensible SaaS app
|
|
35
|
-
*
|
|
35
|
+
* * | *lint* run linter for env or model checks
|
|
36
36
|
*v* | *version* get detailed version information
|
|
37
37
|
* * | *completion* add/remove cli completion for cds commands
|
|
38
|
-
|
|
38
|
+
*h* | *help* get detailed usage information
|
|
39
39
|
|
|
40
40
|
Learn more about each command using:
|
|
41
41
|
*cds help* <command> or
|
package/bin/import.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
module.exports = Object.assign(_import, {
|
|
2
2
|
_name: 'import',
|
|
3
|
-
options: [ '--services', '--for', '--out', '--as', '--name', '--include-namespaces', '--dependencies', '--from', '--config', '--destination', '--group' ],
|
|
3
|
+
options: [ '--services', '--for', '--out', '--as', '--name', '--include-namespaces', '--dependencies', '--from', '--config', '--destination', '--group', '--data-product' ],
|
|
4
4
|
shortcuts: [ '-s', '-4', '-o', '-as', '-n', '-ns', '-d', undefined, '-c', undefined, undefined, '-', '-f'],
|
|
5
5
|
flags: [ '--dry', '--force', '--no-copy', '--no-save', '--keep-namespace', '--data-product', '--edmx', '--rfc', '--odata', '--openapi', '--asyncapi' ],
|
|
6
6
|
help: `
|
|
@@ -55,6 +55,11 @@ module.exports = Object.assign(_import, {
|
|
|
55
55
|
Destination name under which the remote service can be reached.
|
|
56
56
|
Not supported by import types "edmx", "openapi" or "asyncapi".
|
|
57
57
|
|
|
58
|
+
*--data-product*
|
|
59
|
+
Tells cds import to import a data product. The source must be a valid
|
|
60
|
+
data product CSN file, e.g. as found on http://api.sap.com/dataproducts.
|
|
61
|
+
(Not released with general availability yet)
|
|
62
|
+
|
|
58
63
|
*Below options are valid for OData only*
|
|
59
64
|
|
|
60
65
|
*--keep-namespace*
|
|
@@ -153,7 +158,7 @@ async function _import(args, options = {}) {
|
|
|
153
158
|
if (options.from) {
|
|
154
159
|
if (!cds.import.from[options.from]) {
|
|
155
160
|
// the required plugin might not be installed, e.g. rfc importer
|
|
156
|
-
throw messages.UNSUPPORTED_FROM_OPTION;
|
|
161
|
+
throw messages.UNSUPPORTED_FROM_OPTION.replace('{0}', options.from).replace('{1}', Object.keys(cds.import.from).join(', '));
|
|
157
162
|
}
|
|
158
163
|
csn = await cds.import.from[options.from](input, apiOptions);
|
|
159
164
|
} else {
|
package/bin/up.js
CHANGED
|
@@ -32,6 +32,8 @@ const cds = require('../lib/cds')
|
|
|
32
32
|
const { highlight } = require('../lib/util/term')
|
|
33
33
|
const { exists, fs, path, path: { join } } = cds.utils
|
|
34
34
|
const { readProject } = require('../lib/init/projectReader')
|
|
35
|
+
const { mkdirp, copy } = cds.utils
|
|
36
|
+
const { containerize } = require('../bin/util/containerize')
|
|
35
37
|
|
|
36
38
|
const run = cmd => {
|
|
37
39
|
console.log(highlight(cmd))
|
|
@@ -49,10 +51,16 @@ async function up() {
|
|
|
49
51
|
const project = readProject()
|
|
50
52
|
const { apps, appName, hasApprouter, hasXsuaa, isMonorepoMicroservice } = project
|
|
51
53
|
|
|
54
|
+
const html5DeployerPath = join(cds.root, cds.env.folders.app, 'html5-deployer', 'resources')
|
|
55
|
+
|
|
52
56
|
for (const { app } of apps) {
|
|
53
57
|
const appPath = path.join(cds.root, cds.env.folders.app, app)
|
|
54
|
-
if (exists(path.join(appPath, 'package.json'))
|
|
55
|
-
|
|
58
|
+
if (exists(path.join(appPath, 'package.json'))) {
|
|
59
|
+
if (!exists(path.join(appPath, 'package-lock.json'))) {
|
|
60
|
+
run(`npm i --prefix ${appPath}`)
|
|
61
|
+
} else if (process.env.CI) {
|
|
62
|
+
run(`npm ci --prefix ${appPath}`)
|
|
63
|
+
}
|
|
56
64
|
}
|
|
57
65
|
}
|
|
58
66
|
|
|
@@ -75,6 +83,16 @@ async function up() {
|
|
|
75
83
|
|
|
76
84
|
if (cds.cli.options.to === 'k8s') {
|
|
77
85
|
if (!exists('chart')) run('cds add helm')
|
|
86
|
+
if (apps.length > 0) {
|
|
87
|
+
await mkdirp(html5DeployerPath)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
for (const { app } of apps) {
|
|
91
|
+
const appPath = path.join(cds.root, cds.env.folders.app, app)
|
|
92
|
+
run(`npm run build --prefix ${appPath}`)
|
|
93
|
+
await copy(path.join(appPath, `dist/${app}.zip`)).to(html5DeployerPath, `${app}.zip`)
|
|
94
|
+
}
|
|
95
|
+
|
|
78
96
|
if (!exists('containerize.yaml')) run('cds add containerize')
|
|
79
97
|
} else {
|
|
80
98
|
if (!exists('mta.yaml')) {
|
|
@@ -100,7 +118,10 @@ async function up() {
|
|
|
100
118
|
const nsKubectl = ns ? `-n ${ns}` : ''
|
|
101
119
|
|
|
102
120
|
run('npm i --package-lock')
|
|
103
|
-
run('
|
|
121
|
+
run('cds build --production')
|
|
122
|
+
await containerize('containerize.yaml', { push: true, clearCache: !!process.env.CLEAR_CACHE })
|
|
123
|
+
console.log('Containerization completed')
|
|
124
|
+
|
|
104
125
|
let helmUpgrade = `helm upgrade --install ${appName} ./gen/chart ${nsHelm} --wait --wait-for-jobs --timeout=10m`
|
|
105
126
|
if (hasXsuaa) helmUpgrade += ' --set-file xsuaa.jsonParameters=xs-security.json'
|
|
106
127
|
run(helmUpgrade)
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
const cds = require('../../lib/cds')
|
|
2
|
+
const { path } = cds.utils
|
|
3
|
+
const { join } = path
|
|
4
|
+
const { execSync } = require('node:child_process')
|
|
5
|
+
const { highlight } = require('../../lib/util/term')
|
|
6
|
+
|
|
7
|
+
const buildpacks = new Set([
|
|
8
|
+
'java', 'nodejs', 'sap-machine', 'executable-jar', 'spring-boot', 'syft'
|
|
9
|
+
])
|
|
10
|
+
const builders = new Set([
|
|
11
|
+
'builder-jammy-base', 'builder-jammy-buildpackless-base', 'builder-jammy-full', 'builder-jammy-buildpackless-full'
|
|
12
|
+
])
|
|
13
|
+
|
|
14
|
+
const keyProcessorMap = {
|
|
15
|
+
dockerfile: processDockerfile,
|
|
16
|
+
commands: processCommands,
|
|
17
|
+
buildpack: processBuildpack
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
const run = cmd => {
|
|
21
|
+
cmd = Array.isArray(cmd) ? cmd.join(' ') : cmd
|
|
22
|
+
console.log(highlight(cmd))
|
|
23
|
+
try {
|
|
24
|
+
return execSync(cmd, {
|
|
25
|
+
stdio: 'inherit',
|
|
26
|
+
env: { ...process.env, FORCE_COLOR: cds.utils.colors.enabled }
|
|
27
|
+
})
|
|
28
|
+
} catch (error) {
|
|
29
|
+
throw error.message
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function appendSubcommand(param, subcommand) {
|
|
34
|
+
if (!param) return []
|
|
35
|
+
if (subcommand === '--env') {
|
|
36
|
+
return Object.entries(param).flatMap(([k, v]) =>
|
|
37
|
+
String(v).split(',').map(env => [subcommand, `${k}=${env.trim() || '""'}`])
|
|
38
|
+
).flat()
|
|
39
|
+
}
|
|
40
|
+
return param.split(',').map(e => [subcommand, e.trim()]).flat()
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function processDockerfile(bp, name, tag) {
|
|
44
|
+
if (!bp.dockerfile) return []
|
|
45
|
+
return [['docker', 'build',
|
|
46
|
+
...appendSubcommand(`${name}:${tag ?? 'latest'}`, '-t'),
|
|
47
|
+
...appendSubcommand(bp.dockerfile, '-f'),
|
|
48
|
+
'.']]
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
function processCommands(bp) {
|
|
52
|
+
return bp.commands ? bp.commands.map(c => c.trim().split(' ')) : []
|
|
53
|
+
}
|
|
54
|
+
function processBuildpack(bp, name, tag, clearCache = false) {
|
|
55
|
+
let { type, path, env, builder } = bp.buildpack
|
|
56
|
+
type = type ? type.split(',').map(t => buildpacks.has(t.trim()) ? `paketo-buildpacks/${t.trim()}` : t.trim()) : []
|
|
57
|
+
if (builders.has(builder)) builder = `paketobuildpacks/${builder}`
|
|
58
|
+
const cacheArgs = clearCache ? ['--clear-cache'] : []
|
|
59
|
+
return [['pack',
|
|
60
|
+
...appendSubcommand(`${name}:${tag}`, 'build'),
|
|
61
|
+
...appendSubcommand(path, '--path'),
|
|
62
|
+
...type.flatMap(t => appendSubcommand(t, '--buildpack')),
|
|
63
|
+
...appendSubcommand(builder, '--builder'),
|
|
64
|
+
...appendSubcommand(env, '--env'),
|
|
65
|
+
...cacheArgs
|
|
66
|
+
]]
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* Parses a YAML configuration file to extract Docker build and push commands for modules.
|
|
71
|
+
*
|
|
72
|
+
* @param {string} filename - The path to the YAML file to parse.
|
|
73
|
+
* @param {string} [repoOpt=''] - Optional repository override. If not provided, uses the repository defined in the YAML file.
|
|
74
|
+
* @param {boolean} [clearCache=false] - Whether to clear cache for buildpack builds.
|
|
75
|
+
* @returns {{
|
|
76
|
+
* commands: Array<{
|
|
77
|
+
* name: string,
|
|
78
|
+
* tag: string,
|
|
79
|
+
* buildCmd: Array<string>,
|
|
80
|
+
* tagCmd: Array<string>,
|
|
81
|
+
* pushCmd: Array<string>
|
|
82
|
+
* }>,
|
|
83
|
+
* before_all: Array<Array<string>>,
|
|
84
|
+
* repository: string
|
|
85
|
+
* }} An object containing the commands for each module, any global pre-commands, and the repository name.
|
|
86
|
+
* @throws {Error} If modules are not defined in the YAML file or if build-parameters are invalid.
|
|
87
|
+
*/
|
|
88
|
+
function parseYAML(filename, repoOpt = '', options) {
|
|
89
|
+
const file = cds.load.yaml(join(cds.root, filename))
|
|
90
|
+
const { repository: repoFile, modules, tag: globalTag = 'latest' } = file
|
|
91
|
+
const repository = repoOpt || repoFile
|
|
92
|
+
const before_all = file['before-all'] ? file['before-all'].map(c => c.trim().split(' ')) : []
|
|
93
|
+
if (!modules) throw new Error('Modules not defined')
|
|
94
|
+
console.log(`Using repository: ${repository}`)
|
|
95
|
+
const commands = modules.map(m => {
|
|
96
|
+
const name = m.name, tag = m.tag || globalTag, bp = m['build-parameters']
|
|
97
|
+
const source = `${name}:${tag}`, repoImg = `${repository}/${source}`
|
|
98
|
+
const key = Object.keys(bp)[0]
|
|
99
|
+
const buildType = keyProcessorMap[key]
|
|
100
|
+
if (!buildType) throw new Error(`build-parameters ${Object.keys(bp)} is invalid.`)
|
|
101
|
+
return {
|
|
102
|
+
name, tag,
|
|
103
|
+
buildCmd: key === 'buildpack'
|
|
104
|
+
? buildType(bp, name, tag, options.clearCache)
|
|
105
|
+
: buildType(bp, name, tag),
|
|
106
|
+
tagCmd: ['docker', 'tag', source, repoImg],
|
|
107
|
+
pushCmd: ['docker', 'push', repoImg]
|
|
108
|
+
}
|
|
109
|
+
})
|
|
110
|
+
return { commands, before_all, repository }
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Executes the containerization commands for each module.
|
|
115
|
+
* @param {*} modules - The list of modules with their respective build, tag, and push commands.
|
|
116
|
+
* @param {*} options - Options object, e.g., { push: true } to push images after building.
|
|
117
|
+
*/
|
|
118
|
+
async function executeModules(modules, options) {
|
|
119
|
+
for (const m of modules) {
|
|
120
|
+
console.log(`Containerising module ${m.name}`)
|
|
121
|
+
for (const cmd of m.buildCmd) run(cmd)
|
|
122
|
+
if (options.push) {
|
|
123
|
+
run(m.tagCmd)
|
|
124
|
+
run(m.pushCmd)
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
/**
|
|
130
|
+
* Main function to containerize modules based on a YAML configuration file.
|
|
131
|
+
* @param {string} filename - The path to the YAML configuration file.
|
|
132
|
+
* @param {{ push: boolean, clearCache: boolean }} options - Options object, e.g., { push: true } to push images after building.
|
|
133
|
+
* @returns {Promise<void>}
|
|
134
|
+
*/
|
|
135
|
+
async function containerize(filename, options = { push: false, clearCache: false }) {
|
|
136
|
+
const { repository, before_all, commands: modules } = parseYAML(filename, '', options)
|
|
137
|
+
if (!repository || !modules) return
|
|
138
|
+
for (const cmd of before_all) await run(cmd)
|
|
139
|
+
await executeModules(modules, options)
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
module.exports = { containerize }
|
|
@@ -179,7 +179,7 @@ class BuildTaskEngine {
|
|
|
179
179
|
|
|
180
180
|
/** @private */
|
|
181
181
|
_resolveHandlerResponse(results, pluginMessages = []) {
|
|
182
|
-
/** @type {BuildError[]}*/
|
|
182
|
+
/** @type {(BuildError | Error)[]}*/
|
|
183
183
|
const errors = []
|
|
184
184
|
const resolvedResults = results.reduce((acc, r) => {
|
|
185
185
|
if (r.status === 'fulfilled') {
|
|
@@ -195,7 +195,10 @@ class BuildTaskEngine {
|
|
|
195
195
|
const error = errors.find(e => e.constructor.name !== COMPILATION_ERROR)
|
|
196
196
|
if (error) {
|
|
197
197
|
// throw original BuildError including existing BuildMessages
|
|
198
|
-
|
|
198
|
+
if (!(error instanceof BuildError)) {
|
|
199
|
+
throw error
|
|
200
|
+
}
|
|
201
|
+
error.messages = error.messages?.map(m => typeof m === 'string' ? new BuildMessage(m, SEVERITY_ERROR) : m)
|
|
199
202
|
error.messages = this._sortMessagesUnique(pluginMessages.filter(m => m instanceof BuildMessage), error.messages)
|
|
200
203
|
throw error
|
|
201
204
|
}
|
package/lib/build/plugin.js
CHANGED
|
@@ -126,7 +126,7 @@ class Plugin {
|
|
|
126
126
|
* @abstract
|
|
127
127
|
* Called by the framework to create the artifacts of this build plugin.
|
|
128
128
|
*/
|
|
129
|
-
async build() { /* abstract */}
|
|
129
|
+
async build() { /* abstract */ }
|
|
130
130
|
|
|
131
131
|
/**
|
|
132
132
|
* Called by the framework immediately before 'build' to delete any output created by this build plugin.
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
const cds = require('../../../cds')
|
|
2
|
-
const yaml =
|
|
3
|
-
const { exists, path, readFileSync } = cds.utils
|
|
2
|
+
const { exists, path, readFileSync, yaml } = cds.utils
|
|
4
3
|
const { Plugin, BuildError } = require('../..')
|
|
5
4
|
const { join } = path
|
|
6
5
|
const { readProject } = require('../../../init/projectReader')
|
|
@@ -11,7 +10,7 @@ module.exports = class HelmBuildPlugin extends Plugin {
|
|
|
11
10
|
static hasTask() {
|
|
12
11
|
if (exists('chart/Chart.yaml')) {
|
|
13
12
|
const chartFile = readFileSync(join(cds.root, 'chart', 'Chart.yaml'), 'utf8')
|
|
14
|
-
const chartYaml = yaml.
|
|
13
|
+
const chartYaml = yaml.parse(chartFile)
|
|
15
14
|
return chartYaml.annotations?.["app.kubernetes.io/managed-by"] === 'cds-dk/helm'
|
|
16
15
|
}
|
|
17
16
|
return false
|
|
@@ -27,15 +26,16 @@ module.exports = class HelmBuildPlugin extends Plugin {
|
|
|
27
26
|
}
|
|
28
27
|
|
|
29
28
|
async build() {
|
|
30
|
-
if(!HelmBuildPlugin.hasTask()) {
|
|
29
|
+
if (!HelmBuildPlugin.hasTask()) {
|
|
31
30
|
this.pushMessage("Helm Chart not found. Execute `cds add helm` to add the helm chart.", Plugin.WARNING)
|
|
32
31
|
return
|
|
33
32
|
}
|
|
34
33
|
|
|
35
|
-
const
|
|
34
|
+
const project = readProject()
|
|
35
|
+
const { hasApprouter, hasConnectivity, hasHtml5Repo } = project
|
|
36
36
|
|
|
37
37
|
const chartFile = readFileSync(join(cds.root, 'chart', 'Chart.yaml'), 'utf8')
|
|
38
|
-
const chartYaml = yaml.
|
|
38
|
+
const chartYaml = yaml.parse(chartFile)
|
|
39
39
|
const isInternal = chartYaml.dependencies?.find(dep => dep.name === 'web-application')?.repository !== undefined
|
|
40
40
|
|
|
41
41
|
if (!isInternal) {
|
|
@@ -21,6 +21,10 @@ const FILE_NAME_UNDEPLOY_JSON = "undeploy.json"
|
|
|
21
21
|
const DEPLOY_FORMAT = "deploy-format"
|
|
22
22
|
const DEBUG = cds.debug('cli|build')
|
|
23
23
|
|
|
24
|
+
// Some HDI artifacts do not require explicit plugin specification in _.hdiconfig_.
|
|
25
|
+
// This list contains exceptions which will be ignored during the consistency check.
|
|
26
|
+
const PLUGINLESS_HDB_FILES = new Set([".hdbgrants", ".hdbrevokes"])
|
|
27
|
+
|
|
24
28
|
class HanaBuildPlugin extends InternalBuildPlugin {
|
|
25
29
|
init() {
|
|
26
30
|
if (this.hasBuildOption(OUTPUT_MODE, OUTPUT_MODE_RESULT)) {
|
|
@@ -473,8 +477,12 @@ class HanaBuildPlugin extends InternalBuildPlugin {
|
|
|
473
477
|
}
|
|
474
478
|
}
|
|
475
479
|
if (Object.keys(content['file_suffixes']).length < hdiPlugins.size) {
|
|
476
|
-
const missingPlugins = Array.from(hdiPlugins)
|
|
477
|
-
|
|
480
|
+
const missingPlugins = Array.from(hdiPlugins)
|
|
481
|
+
.filter(key => !content['file_suffixes'][key.slice(1)])
|
|
482
|
+
.filter(key => !PLUGINLESS_HDB_FILES.has(key))
|
|
483
|
+
if (missingPlugins.length) {
|
|
484
|
+
this.pushMessage(`'HANA database plugin not found for following file suffix [${missingPlugins.join(', ')}]`)
|
|
485
|
+
}
|
|
478
486
|
}
|
|
479
487
|
await this.write(content).to(hdiConfigPath)
|
|
480
488
|
}
|
package/lib/import/common.js
CHANGED
|
@@ -336,12 +336,12 @@ function _printUsageHint(dest, service, cwd) {
|
|
|
336
336
|
dest = dest.replace(extension, '');
|
|
337
337
|
let using
|
|
338
338
|
if (service.length > 1){
|
|
339
|
-
using = `using { ${service.join(', ')}} from './${path.relative(cds.env.folders.srv, dest).replace(/\\/g, '/')}'
|
|
339
|
+
using = `using { ${service.join(', ')}} from './${path.relative(cds.env.folders.srv, dest).replace(/\\/g, '/')}';`;
|
|
340
340
|
} else {
|
|
341
|
-
using = `using { ${service} as external } from './${path.relative(cds.env.folders.srv, dest).replace(/\\/g, '/')}'
|
|
341
|
+
using = `using { ${service} as external } from './${path.relative(cds.env.folders.srv, dest).replace(/\\/g, '/')}';`;
|
|
342
342
|
}
|
|
343
343
|
const message = `\n[cds] - imported API to ${path.relative(cwd, dest)}
|
|
344
|
-
> use it in your CDS models through the
|
|
344
|
+
> use it in your CDS models through the likes of:
|
|
345
345
|
|
|
346
346
|
${require('../util/term').info(using)}
|
|
347
347
|
`;
|
package/lib/import/message.js
CHANGED
|
@@ -12,7 +12,7 @@ const messages = {
|
|
|
12
12
|
FILE_MODIFIED: 'The file with the same name exists and is been modified. Use --force flag to forcefully overwrite the file.',
|
|
13
13
|
OUTPUT_FILE_MISMATCH: 'Specify the file extension that --as option supports.',
|
|
14
14
|
INVALID_AS_OPTION: 'For --as option, please specify one of these output file format [cds, csn, json].',
|
|
15
|
-
UNSUPPORTED_FROM_OPTION:
|
|
15
|
+
UNSUPPORTED_FROM_OPTION: `No importer is installed for type '{0}'. Known import kinds are: {1}. Make sure the required importer plugin is installed.`,
|
|
16
16
|
INCORRECT_EXTENSION: 'If --as option is not provided, then please provide either .json or .csn output file extension.',
|
|
17
17
|
INCORRECT_INPUT_NAMESPACES: 'Invalid value for --include-namespaces option. Specify namespaces as comma-separated list or "*"',
|
|
18
18
|
MISSING_ASSOCIATION: 'Association is missing in the input file provided. Please check the edmx file.',
|
|
@@ -15,7 +15,7 @@ module.exports = new class ProjectReader {
|
|
|
15
15
|
|
|
16
16
|
#setPackageInfo() {
|
|
17
17
|
this.isJava = (exists('pom.xml') || cds.cli.options?.add?.has('java'))
|
|
18
|
-
|
|
18
|
+
&& !cds.env.for('cds').profiles.includes('node') // opt-out for hybrid projects like sflight
|
|
19
19
|
|
|
20
20
|
// 1. Use project name and static default values
|
|
21
21
|
this.appVersion = this.isJava ? '1.0.0-SNAPSHOT' : '1.0.0'
|
|
@@ -32,7 +32,7 @@ module.exports = new class ProjectReader {
|
|
|
32
32
|
const v = version?.[0] === '${revision}' ? properties?.[0]?.revision[0] : version?.[0] ?? '1.0.0-SNAPSHOT'
|
|
33
33
|
if (v && REGEX_JAVA_VERSION.test(v)) this.appVersion = v
|
|
34
34
|
this.jdkVersion = properties?.[0]?.['jdk.version']?.[0]?._text?.[0] ?? JAVA_LTS_VERSIONS[JAVA_LTS_VERSIONS.length - 1]
|
|
35
|
-
if (artifactId?.[0])
|
|
35
|
+
if (artifactId?.[0]) this.appName = this.appId = artifactId?.[0]?.split(/-parent/)[0]
|
|
36
36
|
if (description?.[0]) this.appDescription = description[0]
|
|
37
37
|
}
|
|
38
38
|
else if (exists('package.json')) {
|
|
@@ -73,9 +73,9 @@ module.exports = new class ProjectReader {
|
|
|
73
73
|
TRACE?.({ env })
|
|
74
74
|
|
|
75
75
|
const pkgJson = () => exists('package.json') ? JSON.parse(fs.readFileSync(join(cds.root, 'package.json'))) : {}
|
|
76
|
-
const _isESM
|
|
76
|
+
const _isESM = () => pkgJson().type === 'module'
|
|
77
77
|
const _isTypescript = () => !!pkgJson().dependencies?.typescript || !!pkgJson().devDependencies?.typescript
|
|
78
|
-
const _hasTyper
|
|
78
|
+
const _hasTyper = () => !!pkgJson().dependencies?.['@cap-js/cds-typer'] || !!pkgJson().devDependencies?.['@cap-js/cds-typer']
|
|
79
79
|
const _inProd = plugin => require(`./template/${plugin}`).hasInProduction(env) || cds.cli.argv[0]?.includes(plugin) || cds.cli.options.add?.has(plugin)
|
|
80
80
|
const _uiEmbedded = () => exists(join(env.folders.app, 'xs-app.json'))
|
|
81
81
|
const _uiModule = () => !_uiEmbedded() && isdir(env.folders.app) && fs.readdirSync(resolve(cds.root, env.folders.app)).length > 0
|
|
@@ -121,9 +121,22 @@ module.exports = new class ProjectReader {
|
|
|
121
121
|
const manifestPath = path.resolve(cds.root, cds.env.folders.app, app, 'webapp/manifest.json')
|
|
122
122
|
const manifest = exists(manifestPath) ? JSON.parse(fs.readFileSync(manifestPath, 'utf-8')) : {}
|
|
123
123
|
const inbounds = manifest?.['sap.app']?.crossNavigation?.inbounds
|
|
124
|
-
const [firstInbound] = Object.values(inbounds ?? { intent: { semanticObject: 'Books', action: 'display' }})
|
|
124
|
+
const [firstInbound] = Object.values(inbounds ?? { intent: { semanticObject: 'Books', action: 'display' } })
|
|
125
125
|
const vizId = firstInbound.semanticObject + '-' + firstInbound.action
|
|
126
|
-
|
|
126
|
+
// Try to read archiveName from ui5-deploy.yaml if it exists
|
|
127
|
+
let archiveName = ''
|
|
128
|
+
const ui5DeployPath = path.resolve(cds.root, cds.env.folders.app, app, 'ui5-deploy.yaml')
|
|
129
|
+
if (exists(ui5DeployPath)) {
|
|
130
|
+
const ui5Deploy = cds.load.yaml(ui5DeployPath)
|
|
131
|
+
const customTasks = ui5Deploy?.builder?.customTasks
|
|
132
|
+
if (Array.isArray(customTasks)) {
|
|
133
|
+
const zipperTask = customTasks.find(t => t.name === 'ui5-task-zipper')
|
|
134
|
+
if (zipperTask?.configuration?.archiveName) {
|
|
135
|
+
archiveName = `${zipperTask.configuration.archiveName}.zip`
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
return { app, strippedApp: app.replace(/-/g, ''), vizId, isNotLast: i < apps.length - 1, manifestAppId: manifest?.['sap.app']?.id ?? app, archiveName }
|
|
127
140
|
})
|
|
128
141
|
},
|
|
129
142
|
appUIPaths: () => {
|
|
@@ -133,7 +146,7 @@ module.exports = new class ProjectReader {
|
|
|
133
146
|
isdir(path.join(cds.root, cds.env.folders.app, e)) && isApp(e)
|
|
134
147
|
)
|
|
135
148
|
},
|
|
136
|
-
hasIndexHtml: () => {},
|
|
149
|
+
hasIndexHtml: () => { },
|
|
137
150
|
appPath: () => env.folders.app,
|
|
138
151
|
appVersion: () => appVersion,
|
|
139
152
|
jdkVersion: () => jdkVersion,
|
|
@@ -152,6 +165,7 @@ module.exports = new class ProjectReader {
|
|
|
152
165
|
sap: () => cds.cli.options.sap,
|
|
153
166
|
imageRegistry: () => exists('chart/values.yaml') ? cds.load.yaml(join(cds.root, 'chart/values.yaml')).global?.image?.registry ?? '<your-container-registry-server>' : '<your-container-registry-server>',
|
|
154
167
|
extendsApp: _extends,
|
|
168
|
+
isBas: () => Object.entries(process.env).some(([key, value]) => key === 'WS_BASE_URL' && value.includes('applicationstudio') || /THEIA_(DEFAULT_)?PLUGINS/.test(key)),
|
|
155
169
|
npmWorkspaceRoot: () => {
|
|
156
170
|
if (this.cachedWorkspaceRoot) return this.cachedWorkspaceRoot
|
|
157
171
|
this.cachedWorkspaceRoot = execSync('npm prefix', { encoding: 'utf8', cwd: cds.root, stdio: 'pipe' }).trim()
|
|
@@ -160,7 +174,7 @@ module.exports = new class ProjectReader {
|
|
|
160
174
|
isMonorepoMicroservice: function () {
|
|
161
175
|
return this.npmWorkspaceRoot() !== cds.root
|
|
162
176
|
},
|
|
163
|
-
hasRoles: () => {}, roles: () => {}, // for xs-security generation
|
|
177
|
+
hasRoles: () => { }, roles: () => { }, // for xs-security generation
|
|
164
178
|
hasCustomRoleCollections: () => exists('xs-security.json') && !!JSON.parse(fs.readFileSync(path.join(cds.root, 'xs-security.json')))['role-collections']
|
|
165
179
|
}
|
|
166
180
|
|
|
@@ -169,7 +183,8 @@ module.exports = new class ProjectReader {
|
|
|
169
183
|
// E.g. `cds add hana` can be checked using `hasHana` or `isHana`
|
|
170
184
|
const project = (() => {
|
|
171
185
|
const defined = {}
|
|
172
|
-
const _get = property => {
|
|
186
|
+
const _get = property => {
|
|
187
|
+
const p = property
|
|
173
188
|
if (p in reserved) return reserved[p]()
|
|
174
189
|
for (const prefix of ['has', 'is', 'add']) if (p.startsWith(prefix) && p.length > prefix.length) {
|
|
175
190
|
const facet = p.slice(prefix.length).replace(/([a-z0-9])([A-Z])/g, '$1-$2').toLowerCase()
|
|
@@ -200,23 +215,23 @@ module.exports = new class ProjectReader {
|
|
|
200
215
|
* @param {string[]?} roots array of directories to search for application.yaml, like `srv`
|
|
201
216
|
* @returns {Promise<object[]>} array of objects with `cds` property
|
|
202
217
|
*/
|
|
203
|
-
module.exports._readSpringBootConfig = async function (roots=[cds.env.folders.srv]) {
|
|
218
|
+
module.exports._readSpringBootConfig = async function (roots = [cds.env.folders.srv]) {
|
|
204
219
|
const configNodes = []
|
|
205
220
|
const rootDirs = roots.map(d => join(cds.root, d))
|
|
206
221
|
for (let r of rootDirs) {
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
222
|
+
const file = isfile(join(r, './src/main/resources/application.yaml'))
|
|
223
|
+
if (file) {
|
|
224
|
+
const yaml = cds.load.yaml(file)
|
|
225
|
+
for (let yamlDoc of Array.isArray(yaml) ? yaml : [yaml]) {
|
|
226
|
+
let cds = yamlDoc?.cds;
|
|
227
|
+
if (!cds) continue
|
|
228
|
+
cds = _normalizeSpringBootCfg(cds)
|
|
229
|
+
configNodes.push({ cds })
|
|
230
|
+
}
|
|
215
231
|
}
|
|
216
|
-
}
|
|
217
232
|
}
|
|
218
233
|
return configNodes
|
|
219
|
-
|
|
234
|
+
}
|
|
220
235
|
|
|
221
236
|
/**
|
|
222
237
|
* Normalize SpringBoot's dots in keys to express nested objects
|
|
@@ -226,7 +241,7 @@ module.exports._readSpringBootConfig = async function (roots=[cds.env.folders.sr
|
|
|
226
241
|
* @returns {object}
|
|
227
242
|
*/
|
|
228
243
|
function _normalizeSpringBootCfg(obj) {
|
|
229
|
-
if (typeof obj !== 'object')
|
|
244
|
+
if (typeof obj !== 'object') return obj
|
|
230
245
|
Object.keys(obj).forEach(k => {
|
|
231
246
|
const prop = k.split('.')
|
|
232
247
|
const last = prop.pop()
|
|
@@ -239,7 +254,7 @@ function _normalizeSpringBootCfg(obj) {
|
|
|
239
254
|
// recursively normalize
|
|
240
255
|
_normalizeSpringBootCfg(obj[k])
|
|
241
256
|
// delete the original property from object if it was rewritten
|
|
242
|
-
if (prop.length)
|
|
257
|
+
if (prop.length) delete obj[k]
|
|
243
258
|
})
|
|
244
259
|
return obj
|
|
245
260
|
}
|
|
@@ -15,6 +15,8 @@ const appFront = {
|
|
|
15
15
|
where: { type: 'org.cloudfoundry.managed-service', 'parameters.service': 'app-front' }
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
+
const ias = { in: 'resources', where: { 'parameters.service': 'identity' } }
|
|
19
|
+
|
|
18
20
|
const xsuaa = {
|
|
19
21
|
in: 'resources',
|
|
20
22
|
where: { type: 'org.cloudfoundry.managed-service', 'parameters.service': 'xsuaa', 'parameters.service-plan': 'application' }
|
|
@@ -208,6 +210,7 @@ const auditlog = {
|
|
|
208
210
|
module.exports = {
|
|
209
211
|
srv4,
|
|
210
212
|
approuter,
|
|
213
|
+
ias,
|
|
211
214
|
xsuaa,
|
|
212
215
|
xsuaa4,
|
|
213
216
|
destination,
|
|
@@ -5,7 +5,7 @@ const { readProject } = require('../../projectReader')
|
|
|
5
5
|
const { sort, merge } = require('../../merge')
|
|
6
6
|
const {
|
|
7
7
|
srv4, mtxSidecar4, approuter, // Modules
|
|
8
|
-
xsuaa, // BTP Services
|
|
8
|
+
xsuaa, ias, // BTP Services
|
|
9
9
|
requiredMtxApi, requiredJavaApprouterApi4, providedJavaApprouterApi, // APIs
|
|
10
10
|
requiredAppApi4, providedAppApi,
|
|
11
11
|
approuterMTXRoute,
|
|
@@ -34,13 +34,13 @@ module.exports = class ApprouterTemplate extends require('../../plugin') {
|
|
|
34
34
|
const project = readProject()
|
|
35
35
|
const {
|
|
36
36
|
isNodejs, isJava,
|
|
37
|
-
addXsuaa, hasXsuaa, addMultitenancy, hasMTXRoute, hasMTXRouteJava,
|
|
37
|
+
addIas, addXsuaa, hasXsuaa, hasIas, addMultitenancy, hasMTXRoute, hasMTXRouteJava,
|
|
38
38
|
addMta, addHelm, addHelmUnifiedRuntime, hasMta, hasHelm, hasHelmUnifiedRuntime,
|
|
39
39
|
addContainerize, srvPath, approuterPath
|
|
40
40
|
} = project
|
|
41
41
|
|
|
42
42
|
|
|
43
|
-
if (addMta || (addMultitenancy || addXsuaa) && hasMta) {
|
|
43
|
+
if (addMta || (addMultitenancy || addXsuaa || addIas) && hasMta) {
|
|
44
44
|
const srv = srv4(srvPath)
|
|
45
45
|
const apis = isJava && addMultitenancy ? [requiredJavaApprouterApi4(srv), providedJavaApprouterApi] : []
|
|
46
46
|
const mtxSidecar = mtxSidecar4(isJava ? 'mtx/sidecar' : 'gen/mtx/sidecar')
|
|
@@ -49,15 +49,21 @@ module.exports = class ApprouterTemplate extends require('../../plugin') {
|
|
|
49
49
|
const modules = [srv, approuter]
|
|
50
50
|
if (addMultitenancy) modules.push(mtxSidecar)
|
|
51
51
|
const additions = [...modules, ...apis]
|
|
52
|
-
if (hasXsuaa) additions.push(xsuaa)
|
|
53
52
|
const relationships = []
|
|
54
|
-
if (hasXsuaa)
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
}
|
|
53
|
+
if (hasXsuaa) {
|
|
54
|
+
additions.push(xsuaa)
|
|
55
|
+
relationships.push({
|
|
56
|
+
insert: [xsuaa, 'name'],
|
|
57
|
+
into: [approuter, 'requires', 'name']
|
|
58
|
+
})
|
|
59
|
+
}
|
|
60
|
+
if (hasIas) {
|
|
61
|
+
additions.push(ias)
|
|
62
|
+
relationships.push({
|
|
63
|
+
insert: [ias, 'name'],
|
|
64
|
+
into: [approuter, 'requires', 'name']
|
|
65
|
+
})
|
|
66
|
+
}
|
|
61
67
|
await merge(__dirname, 'files/mta.yaml.hbs').into('mta.yaml', {
|
|
62
68
|
with: project, additions, relationships,
|
|
63
69
|
})
|
|
@@ -33,7 +33,7 @@ module.exports = class AuditLoggingTemplate extends require('../../plugin') {
|
|
|
33
33
|
|
|
34
34
|
async combine() {
|
|
35
35
|
const project = readProject()
|
|
36
|
-
const { addMta, addHelm, addMultitenancy, srvPath } = project
|
|
36
|
+
const { addMta, addHelm, addHelmUnifiedRuntime, addMultitenancy, srvPath } = project
|
|
37
37
|
const { plan = 'premium' } = cds.cli.options
|
|
38
38
|
project.servicePlan = plan
|
|
39
39
|
|
|
@@ -53,7 +53,7 @@ module.exports = class AuditLoggingTemplate extends require('../../plugin') {
|
|
|
53
53
|
]
|
|
54
54
|
})
|
|
55
55
|
}
|
|
56
|
-
if (addHelm) {
|
|
56
|
+
if (addHelm || addHelmUnifiedRuntime) {
|
|
57
57
|
await merge({
|
|
58
58
|
srv: { bindings: { auditlog: { serviceInstanceName: 'auditlog' }}},
|
|
59
59
|
auditlog: {
|
|
@@ -26,10 +26,6 @@ module.exports = class DestinationTemplate extends require('../../plugin') {
|
|
|
26
26
|
const project = readProject()
|
|
27
27
|
const { addHelm, addApprouter, addMultitenancy, addHelmUnifiedRuntime, addMta, hasMta, srvPath, isJava } = project
|
|
28
28
|
|
|
29
|
-
if (addMultitenancy) {
|
|
30
|
-
await merge({ cds: { requires: { destinations:true } } }).into('mtx/sidecar/package.json')
|
|
31
|
-
}
|
|
32
|
-
|
|
33
29
|
if (addMta || (addApprouter || addMultitenancy) && hasMta) {
|
|
34
30
|
const srv = srv4(srvPath)
|
|
35
31
|
const additions = [srv, destination]
|
|
@@ -44,9 +44,9 @@ module.exports = class HanaTemplate extends require('../../plugin') {
|
|
|
44
44
|
|
|
45
45
|
async combine() {
|
|
46
46
|
const project = readProject()
|
|
47
|
-
const { addMta, addHelm, addHelmUnifiedRuntime, addMultitenancy, addContainerize, isJava, hasHelm, hasMultitenancy, srvPath } = project
|
|
47
|
+
const { addMta, hasMta, addHelm, addHelmUnifiedRuntime, addMultitenancy, addContainerize, isJava, hasHelm, hasHelmUnifiedRuntime, hasMultitenancy, srvPath } = project
|
|
48
48
|
|
|
49
|
-
if (addMta || addMultitenancy) {
|
|
49
|
+
if (addMta || (addMultitenancy && hasMta)) {
|
|
50
50
|
const srv = srv4(srvPath)
|
|
51
51
|
const db = hasMultitenancy ? serviceManager : hdiContainer
|
|
52
52
|
const deletions = hasMultitenancy ? [{
|
|
@@ -91,7 +91,7 @@ module.exports = class HanaTemplate extends require('../../plugin') {
|
|
|
91
91
|
|
|
92
92
|
}
|
|
93
93
|
|
|
94
|
-
if (addMultitenancy && hasHelm) {
|
|
94
|
+
if (addMultitenancy && (hasHelm || hasHelmUnifiedRuntime)) {
|
|
95
95
|
await merge('chart/values.yaml')
|
|
96
96
|
.remove(['hana-deployer', 'srv.bindings.db', 'hana'])
|
|
97
97
|
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
const { join } = require('path')
|
|
2
|
-
const yaml = require('yaml')
|
|
3
2
|
const cds = require('../../../cds')
|
|
4
|
-
const { exists, rimraf, copy,
|
|
3
|
+
const { readFileSync, exists, rimraf, copy, yaml } = cds.utils
|
|
5
4
|
const { readProject } = require('../../projectReader')
|
|
6
5
|
const { merge } = require('../../merge')
|
|
7
6
|
const { ask4 } = require('../../../util/question')
|
|
@@ -37,47 +36,36 @@ module.exports = class HelmTemplate extends require('../../plugin') {
|
|
|
37
36
|
// Won't need this any more with plugin approach: package not there -> plugin not pulled
|
|
38
37
|
static hasInProduction() {
|
|
39
38
|
if (exists('chart/Chart.yaml')) {
|
|
40
|
-
const
|
|
41
|
-
const chartYaml = yaml.parseDocument(chartFile).toJS()
|
|
39
|
+
const chartYaml = yaml.parse(readFileSync(join(cds.root, 'chart', 'Chart.yaml'), 'utf8'))
|
|
42
40
|
return chartYaml.annotations?.["app.kubernetes.io/managed-by"] === 'cds-dk/helm'
|
|
43
|
-
|
|
44
|
-
|
|
41
|
+
}
|
|
42
|
+
return false
|
|
45
43
|
}
|
|
46
44
|
|
|
47
45
|
async run() {
|
|
48
46
|
const project = readProject()
|
|
49
|
-
|
|
50
|
-
project.
|
|
51
|
-
project.imagePullSecret = 'docker-registry'
|
|
52
|
-
project.tag = 'latest'
|
|
53
|
-
|
|
54
|
-
if (cds.cli.command === 'add' && !HelmTemplate.hasInProduction()) {
|
|
55
|
-
let registry = 'registry-server', tag = 'latest', domain = 'abc.com'
|
|
56
|
-
|
|
47
|
+
let imagePullSecret, registry, domain, tag
|
|
48
|
+
if (!HelmTemplate.hasInProduction() && !project.isBas /* don't run this for the BAS Yeoman generator */) {
|
|
57
49
|
try {
|
|
58
50
|
const domainGuess = execSync(`kubectl config view --minify --output jsonpath="{.clusters[*].cluster.server}"`, { stdio: "pipe" }).toString()
|
|
59
51
|
const domainStartIndex = domainGuess.indexOf('api.')
|
|
60
52
|
if (domainStartIndex !== -1) domain = domainGuess.substring(domainStartIndex + 4)
|
|
61
53
|
} catch (error) { /* ignore */ }
|
|
62
54
|
|
|
63
|
-
// read project.registry
|
|
64
55
|
if (exists('containerize.yaml')) {
|
|
65
|
-
const
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
tag = containerizeYaml.tag
|
|
56
|
+
const { repository: r, tag: t } = yaml.parse(join(cds.root, 'containerize.yaml'))
|
|
57
|
+
if (r) registry = r
|
|
58
|
+
if (t) tag = t
|
|
69
59
|
}
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
registry = registryAns || registry
|
|
60
|
+
if (!cds.cli.options.y && !cds.cli.options.force) {
|
|
61
|
+
registry ??= await ask4(`registry server: `)
|
|
62
|
+
domain ??= await ask4(`cluster domain: `)
|
|
74
63
|
}
|
|
75
|
-
|
|
76
|
-
project.domain = domain
|
|
77
|
-
project.registry = registry
|
|
78
|
-
project.tag = tag
|
|
79
64
|
}
|
|
80
|
-
|
|
65
|
+
project.registry = registry ?? '<registry-server>'
|
|
66
|
+
project.domain = domain ?? '<cluster-domain>'
|
|
67
|
+
project.imagePullSecret = imagePullSecret ?? 'docker-registry'
|
|
68
|
+
project.tag = tag ?? 'latest'
|
|
81
69
|
await copy(join(__dirname, 'files', 'values.schema.json')).to('chart/values.schema.json')
|
|
82
70
|
await merge(__dirname, 'files/Chart.yaml.hbs').into('chart/Chart.yaml', { with: project })
|
|
83
71
|
await merge(__dirname, 'files/values.yaml.hbs').into('chart/values.yaml', { with: project })
|
|
@@ -1,13 +1,4 @@
|
|
|
1
1
|
---
|
|
2
|
-
before-all:
|
|
3
|
-
{{#hasUI5}}
|
|
4
|
-
- mkdir -p app/html5-deployer/resources
|
|
5
|
-
{{#apps}}
|
|
6
|
-
- npm run build --prefix app/{{app}}
|
|
7
|
-
# zip filename should match the archiveName specified in ui5-deploy.yaml
|
|
8
|
-
- cp -f app/{{app}}/dist/{{app}}.zip app/html5-deployer/resources/{{app}}.zip
|
|
9
|
-
{{/apps}}
|
|
10
|
-
{{/hasUI5}}
|
|
11
2
|
modules:
|
|
12
3
|
- name: {{appName}}-html5-deployer
|
|
13
4
|
build-parameters:
|
|
@@ -28,13 +28,6 @@ modules:
|
|
|
28
28
|
{{#hasXsuaa}}
|
|
29
29
|
- name: {{appName}}-auth
|
|
30
30
|
{{/hasXsuaa}}
|
|
31
|
-
{{#hasIas}}
|
|
32
|
-
- name: {{appName}}-ias
|
|
33
|
-
parameters:
|
|
34
|
-
config:
|
|
35
|
-
credential-type: X509_GENERATED
|
|
36
|
-
app-identifier: app-deployer
|
|
37
|
-
{{/hasIas}}
|
|
38
31
|
- name: srv-api
|
|
39
32
|
- name: {{appName}}-html5-repo-host
|
|
40
33
|
parameters:
|
|
@@ -7,13 +7,15 @@
|
|
|
7
7
|
"target": "/odata/$1",
|
|
8
8
|
"destination": "srv-api",
|
|
9
9
|
{{#hasXsuaa}}"authenticationType": "xsuaa",{{/hasXsuaa}}
|
|
10
|
+
{{#hasIas}}"authenticationType": "ias",{{/hasIas}}
|
|
10
11
|
"csrfProtection": true
|
|
11
12
|
},
|
|
12
13
|
{
|
|
13
14
|
"source": "^(.*)$",
|
|
14
|
-
"
|
|
15
|
-
"
|
|
16
|
-
"authenticationType": "
|
|
15
|
+
"service": "html5-apps-repo-rt",
|
|
16
|
+
{{#hasXsuaa}}"authenticationType": "xsuaa",{{/hasXsuaa}}
|
|
17
|
+
{{#hasIas}}"authenticationType": "ias",{{/hasIas}}
|
|
18
|
+
"target": "$1"
|
|
17
19
|
}
|
|
18
20
|
]
|
|
19
21
|
}
|
|
@@ -2,7 +2,7 @@ const { join } = require('path')
|
|
|
2
2
|
const { readProject } = require('../../projectReader')
|
|
3
3
|
const { merge } = require('../../merge')
|
|
4
4
|
const { srv4, html5RepoHost, html5Runtime, xsuaa, appDeployer, approuter, mtxSidecar4 } = require('../../registries/mta')
|
|
5
|
-
const { odataApi } = require('../../registries/xs-app')
|
|
5
|
+
const { odataApi, html5RepoApi } = require('../../registries/xs-app')
|
|
6
6
|
const { exists } = require('../../../cds').utils
|
|
7
7
|
const { renderAndCopy } = require('../../template')
|
|
8
8
|
|
|
@@ -44,7 +44,7 @@ module.exports = class Html5RepoTemplate extends require('../../plugin') {
|
|
|
44
44
|
await Promise.all(apps.map(async ({ app }) => {
|
|
45
45
|
project.app = app
|
|
46
46
|
project.hasIndexHtml = exists(join(appPath, app, 'webapp/index.html'))
|
|
47
|
-
const additions = [odataApi]
|
|
47
|
+
const additions = [odataApi, html5RepoApi]
|
|
48
48
|
await merge(__dirname, 'files/xs-app.json.hbs').into(join(appPath, app, 'xs-app.json'), { with: project, additions })
|
|
49
49
|
await merge(__dirname, 'files/app-package.json.hbs').into(join(appPath, app, 'package.json'), { project })
|
|
50
50
|
}))
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const { readProject } = require('../../projectReader')
|
|
2
2
|
const { merge, sort } = require('../../merge')
|
|
3
|
-
const { srv4, approuter, mtxSidecar4 } = require('../../registries/mta')
|
|
3
|
+
const { srv4, approuter, ias, mtxSidecar4 } = require('../../registries/mta')
|
|
4
4
|
const cds = require('../../../cds')
|
|
5
5
|
const { exists, read } = cds.utils
|
|
6
6
|
|
|
@@ -28,7 +28,7 @@ module.exports = class IasTemplate extends require('../../plugin') {
|
|
|
28
28
|
|
|
29
29
|
async combine() {
|
|
30
30
|
const project = readProject()
|
|
31
|
-
const { isJava, addMta, addCfManifest, addHelm, addHelmUnifiedRuntime, addApprouter, addMultitenancy, srvPath } = project
|
|
31
|
+
const { isJava, addMta, hasMta, addCfManifest, addHelm, addHelmUnifiedRuntime, addApprouter, addMultitenancy, srvPath } = project
|
|
32
32
|
|
|
33
33
|
if (addMultitenancy) {
|
|
34
34
|
const conf = exists('mtx/sidecar/package.json') ? await read('mtx/sidecar/package.json') : {}
|
|
@@ -43,12 +43,11 @@ module.exports = class IasTemplate extends require('../../plugin') {
|
|
|
43
43
|
await merge(__dirname, 'files/sidecar.package.json.hbs').into('mtx/sidecar/package.json')
|
|
44
44
|
}
|
|
45
45
|
|
|
46
|
-
if (addMta || addApprouter || addMultitenancy) {
|
|
46
|
+
if (addMta || (addApprouter || addMultitenancy) && hasMta) {
|
|
47
47
|
const srv = srv4(srvPath)
|
|
48
|
-
const identity = { in: 'resources', where: { 'parameters.service': 'identity' } }
|
|
49
48
|
const srvIdentityBinding = { in: [srv, 'requires'], where: { 'parameters.config.app-identifier': 'srv' } }
|
|
50
49
|
const srvCertUrlApi = { in: [srv, 'provides'], where: { 'name': 'srv-api' } }
|
|
51
|
-
const additions = [srv,
|
|
50
|
+
const additions = [srv, ias, srvIdentityBinding, srvCertUrlApi]
|
|
52
51
|
const overwrites = []
|
|
53
52
|
|
|
54
53
|
if (addApprouter) {
|
|
@@ -42,12 +42,12 @@ module.exports = class MultitenancyTemplate extends require('../../plugin') {
|
|
|
42
42
|
async combine() {
|
|
43
43
|
const project = readProject()
|
|
44
44
|
const {
|
|
45
|
-
isNodejs, isJava, addMta, addHelm, addHelmUnifiedRuntime,
|
|
45
|
+
isNodejs, isJava, addMta, hasMta, addHelm, addHelmUnifiedRuntime,
|
|
46
46
|
addXsuaa, hasXsuaa, addIas, hasIas, hasHana, addContainerize, hasApprouter,
|
|
47
47
|
srvPath, appName
|
|
48
48
|
} = project
|
|
49
49
|
|
|
50
|
-
if (addMta || addIas || addXsuaa) {
|
|
50
|
+
if (addMta || (addIas || addXsuaa) && hasMta) {
|
|
51
51
|
const srv = srv4(srvPath)
|
|
52
52
|
const mtxSidecar = mtxSidecar4(isJava ? 'mtx/sidecar' : 'gen/mtx/sidecar')
|
|
53
53
|
const modules = [srv, mtxSidecar]
|
|
@@ -24,12 +24,16 @@ module.exports = class PipelineTemplate extends require('../../plugin') {
|
|
|
24
24
|
}
|
|
25
25
|
|
|
26
26
|
async run() {
|
|
27
|
-
await
|
|
27
|
+
await Promise.all([
|
|
28
|
+
copy(join(__dirname, 'files', '.pipeline')).to(join(cds.root, '.pipeline')),
|
|
29
|
+
copy(join(__dirname, 'files', 'Jenkinsfile')).to(join(cds.root, 'Jenkinsfile'))
|
|
30
|
+
])
|
|
28
31
|
|
|
29
32
|
const project = readProject()
|
|
30
33
|
const { apps, appPath, hasUI5 } = project
|
|
31
34
|
|
|
32
35
|
if (hasUI5) { // UI5 integration might be needed even without HTML5 repo
|
|
36
|
+
console.log('Adding UI5 integration to the pipeline for the detected UI5 apps...')
|
|
33
37
|
for (const { app } of apps) {
|
|
34
38
|
project.app = app
|
|
35
39
|
await Promise.all([
|
|
@@ -33,14 +33,14 @@ module.exports = class XsuaaTemplate extends require('../../plugin') {
|
|
|
33
33
|
|
|
34
34
|
async combine() {
|
|
35
35
|
const project = readProject()
|
|
36
|
-
const { addMultitenancy, addMta, hasMta, addHelm, hasHelm, addHelmUnifiedRuntime, isJava, srvPath } = project
|
|
36
|
+
const { addMultitenancy, addMta, hasMta, addHelm, hasHelm, hasHelmUnifiedRuntime, addHelmUnifiedRuntime, isJava, srvPath } = project
|
|
37
37
|
|
|
38
38
|
const cds = require('../../../../lib')
|
|
39
39
|
const xsSecurity = await (async () => {
|
|
40
40
|
try {
|
|
41
41
|
const models = await cds.load(cds.env.folders.srv)
|
|
42
42
|
return cds.compile.to.xsuaa(models)
|
|
43
|
-
} catch
|
|
43
|
+
} catch { /* ignore */ }
|
|
44
44
|
})() ?? { scopes: [], attributes: [], 'role-templates': [], 'authorities-inheritance': false }
|
|
45
45
|
|
|
46
46
|
const additions = xsSecurity.scopes.map(scope => ({ in: 'scopes', where: { name: scope.name } }))
|
|
@@ -84,7 +84,7 @@ module.exports = class XsuaaTemplate extends require('../../plugin') {
|
|
|
84
84
|
await merge(__dirname, 'files/mta.yaml.hbs').into('mta.yaml', { project, additions, overwrites, relationships })
|
|
85
85
|
}
|
|
86
86
|
|
|
87
|
-
if (addHelm || addHelmUnifiedRuntime || addMultitenancy && hasHelm) {
|
|
87
|
+
if (addHelm || addHelmUnifiedRuntime || addMultitenancy && (hasHelm || hasHelmUnifiedRuntime)) {
|
|
88
88
|
const overwrites = addMultitenancy ? [{ item: 'xsuaa.parameters.tenant-mode', withValue: 'shared' }] : []
|
|
89
89
|
await merge(__dirname, 'files/values.yaml.hbs').into('chart/values.yaml', { with: project, overwrites })
|
|
90
90
|
await merge({
|
package/lib/util/question.js
CHANGED
package/npm-shrinkwrap.json
CHANGED
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sap/cds-dk",
|
|
3
|
-
"version": "9.4.
|
|
3
|
+
"version": "9.4.3",
|
|
4
4
|
"lockfileVersion": 3,
|
|
5
5
|
"requires": true,
|
|
6
6
|
"packages": {
|
|
7
7
|
"": {
|
|
8
8
|
"name": "@sap/cds-dk",
|
|
9
|
-
"version": "9.4.
|
|
9
|
+
"version": "9.4.3",
|
|
10
10
|
"license": "SEE LICENSE IN LICENSE",
|
|
11
11
|
"dependencies": {
|
|
12
12
|
"@cap-js/asyncapi": "^1.0.0",
|
|
@@ -42,8 +42,8 @@
|
|
|
42
42
|
}
|
|
43
43
|
},
|
|
44
44
|
"node_modules/@cap-js/db-service": {
|
|
45
|
-
"version": "2.
|
|
46
|
-
"integrity": "sha512-
|
|
45
|
+
"version": "2.6.0",
|
|
46
|
+
"integrity": "sha512-t72/FcAYFbPdx+5iV+lVKcwF2MLOx8II3jJdlC1dX/KXQORoS3wDFwWbakP0f/eharE5hfa7KMFJqrSMtDigbQ==",
|
|
47
47
|
"devOptional": true,
|
|
48
48
|
"license": "Apache-2.0",
|
|
49
49
|
"dependencies": {
|
|
@@ -65,12 +65,12 @@
|
|
|
65
65
|
}
|
|
66
66
|
},
|
|
67
67
|
"node_modules/@cap-js/sqlite": {
|
|
68
|
-
"version": "2.0.
|
|
69
|
-
"integrity": "sha512-
|
|
68
|
+
"version": "2.0.4",
|
|
69
|
+
"integrity": "sha512-QPVkycLJG6EubtjrPeiK4dTI1zPH/nabvhiYnTeg2AbeQ8mbazm5pjmcLrzOOKF/5bGS8KQo2J+49fU5LPRR3A==",
|
|
70
70
|
"license": "Apache-2.0",
|
|
71
71
|
"optional": true,
|
|
72
72
|
"dependencies": {
|
|
73
|
-
"@cap-js/db-service": "^2",
|
|
73
|
+
"@cap-js/db-service": "^2.6.0",
|
|
74
74
|
"better-sqlite3": "^12.0.0"
|
|
75
75
|
},
|
|
76
76
|
"peerDependencies": {
|
|
@@ -120,8 +120,8 @@
|
|
|
120
120
|
}
|
|
121
121
|
},
|
|
122
122
|
"node_modules/@sap/cds-compiler": {
|
|
123
|
-
"version": "6.4.
|
|
124
|
-
"integrity": "sha512-
|
|
123
|
+
"version": "6.4.6",
|
|
124
|
+
"integrity": "sha512-auAjRh9t0KKj4LiGAr/fxikZRIngx9YXVHTJWf0LeaGv0ZpYOi6iWbSnU1XRB2e6hsf+Ou1w5oTOHooC5sZfog==",
|
|
125
125
|
"license": "SEE LICENSE IN LICENSE",
|
|
126
126
|
"bin": {
|
|
127
127
|
"cdsc": "bin/cdsc.js",
|
|
@@ -133,8 +133,8 @@
|
|
|
133
133
|
}
|
|
134
134
|
},
|
|
135
135
|
"node_modules/@sap/cds-fiori": {
|
|
136
|
-
"version": "2.1.
|
|
137
|
-
"integrity": "sha512-
|
|
136
|
+
"version": "2.1.1",
|
|
137
|
+
"integrity": "sha512-X+4v4LBAT8HIt0zr28/kJNS15nlNlcM97vAMW+agLrmK134nyBiMwUMcp8BMhxlG9B2PykrnAKH56D9O3tfoBg==",
|
|
138
138
|
"license": "SEE LICENSE IN LICENSE",
|
|
139
139
|
"peerDependencies": {
|
|
140
140
|
"@sap/cds": ">=8",
|
|
@@ -142,8 +142,8 @@
|
|
|
142
142
|
}
|
|
143
143
|
},
|
|
144
144
|
"node_modules/@sap/cds-mtxs": {
|
|
145
|
-
"version": "3.4.
|
|
146
|
-
"integrity": "sha512-
|
|
145
|
+
"version": "3.4.3",
|
|
146
|
+
"integrity": "sha512-vgABFr7huaKWGx2fWHeGom5bVgsQKD7/gqkC7aQ/7yC9hdZdrx0mz4iZ0ASHUZ5PZWp2FWLD+eaJ9sXKUGHgpA==",
|
|
147
147
|
"license": "SEE LICENSE IN LICENSE",
|
|
148
148
|
"dependencies": {
|
|
149
149
|
"@sap/hdi-deploy": "^5"
|
|
@@ -261,8 +261,8 @@
|
|
|
261
261
|
"license": "MIT"
|
|
262
262
|
},
|
|
263
263
|
"node_modules/axios": {
|
|
264
|
-
"version": "1.
|
|
265
|
-
"integrity": "sha512-
|
|
264
|
+
"version": "1.13.1",
|
|
265
|
+
"integrity": "sha512-hU4EGxxt+j7TQijx1oYdAjw4xuIp1wRQSsbMFwSthCWeBQur1eF+qJ5iQ5sN3Tw8YRzQNKb8jszgBdMDVqwJcw==",
|
|
266
266
|
"license": "MIT",
|
|
267
267
|
"dependencies": {
|
|
268
268
|
"follow-redirects": "^1.15.6",
|
|
@@ -1213,8 +1213,8 @@
|
|
|
1213
1213
|
"license": "MIT"
|
|
1214
1214
|
},
|
|
1215
1215
|
"node_modules/node-abi": {
|
|
1216
|
-
"version": "3.
|
|
1217
|
-
"integrity": "sha512-
|
|
1216
|
+
"version": "3.79.0",
|
|
1217
|
+
"integrity": "sha512-Pr/5KdBQGG8TirdkS0qN3B+f3eo8zTOfZQWAxHoJqopMz2/uvRnG+S4fWu/6AZxKei2CP2p/psdQ5HFC2Ap5BA==",
|
|
1218
1218
|
"license": "MIT",
|
|
1219
1219
|
"optional": true,
|
|
1220
1220
|
"dependencies": {
|