@sap/cds-dk 9.4.2 → 9.5.0
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 +94 -0
- package/bin/compile/index.js +1 -1
- package/bin/data-products/index.js +10 -2
- package/bin/data-products/prepare-csn.js +63 -26
- package/bin/export.js +1 -1
- package/bin/help.js +2 -3
- package/bin/import.js +13 -3
- package/bin/repl.js +7 -11
- package/bin/up.js +164 -13
- package/bin/util/containerize.js +183 -0
- package/bin/version.js +4 -4
- package/lib/bind/k8s.js +1 -1
- package/lib/build/buildTaskEngine.js +5 -2
- package/lib/build/plugin.js +1 -1
- package/lib/build/plugins/helm/files/templates/_deployment_helpers.tpl +1 -1
- package/lib/build/plugins/helm/index.js +7 -7
- package/lib/build/provider/hana/2migration.js +16 -24
- package/lib/build/provider/hana/index.js +11 -3
- package/lib/build/provider/hana/template/.hdiconfig-all +7 -1
- package/lib/build/provider/hana/template/package.json +1 -1
- package/lib/build/provider/internalBuildPlugin.js +15 -1
- package/lib/build/provider/java/index.js +12 -1
- package/lib/build/provider/mtx-sidecar/index.js +2 -1
- package/lib/build/provider/nodejs/index.js +2 -1
- package/lib/build/util.js +3 -3
- package/lib/compile/to_openapi.js +1 -5
- package/lib/import/common.js +5 -4
- package/lib/import/message.js +1 -1
- package/lib/import/odata/ParserContext.js +1 -0
- package/lib/import/odata/v4parser.js +48 -3
- package/lib/import/openapi/importOpenAPI.js +6 -1
- package/lib/init/constants.js +1 -1
- package/lib/init/index.js +15 -6
- package/lib/init/merge.js +16 -0
- package/lib/init/projectReader.js +54 -24
- package/lib/init/registries/mta.js +5 -2
- package/lib/init/registries/xs-app.js +7 -1
- package/lib/init/template/ams/index.js +1 -1
- package/lib/init/template/app-frontend/files/app-package.json.hbs +1 -7
- package/lib/init/template/app-frontend/files/ui5.yaml.hbs +20 -1
- package/lib/init/template/app-frontend/index.js +0 -4
- package/lib/init/template/application-logging/index.js +9 -14
- package/lib/init/template/approuter/files/mta.yaml.hbs +7 -2
- package/lib/init/template/approuter/index.js +21 -16
- package/lib/init/template/audit-logging/index.js +2 -2
- package/lib/init/template/cloud-logging/index.js +2 -3
- package/lib/init/template/containerize/files/containerize.yaml.hbs +17 -3
- package/lib/init/template/containerize/index.js +0 -4
- package/lib/init/template/data/as-json.js +18 -20
- package/lib/init/template/destination/index.js +0 -4
- package/lib/init/template/dynatrace/index.js +0 -2
- package/lib/init/template/github-actions/files/kyma-setup/action.yaml +0 -2
- package/lib/init/template/github-actions/files/workflows/cf.yaml.hbs +16 -1
- package/lib/init/template/github-actions/files/workflows/test.yaml.hbs +20 -0
- package/lib/init/template/github-actions/index.js +5 -8
- package/lib/init/template/hana/files/db/package.json +2 -2
- package/lib/init/template/hana/files/mta.yaml.hbs +0 -4
- package/lib/init/template/hana/index.js +5 -7
- package/lib/init/template/helm/index.js +16 -33
- package/lib/init/template/helm-unified-runtime/index.js +0 -4
- package/lib/init/template/html5-repo/files/app-package.json.hbs +1 -7
- 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/ui5.yaml.hbs +20 -1
- 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 +8 -14
- package/lib/init/template/ias/files/values.yaml.hbs +6 -6
- package/lib/init/template/ias/index.js +14 -23
- package/lib/init/template/kyma/index.js +14 -1
- package/lib/init/template/malware-scanner/files/mta.yaml.hbs +3 -1
- package/lib/init/template/malware-scanner/files/values.yaml +3 -1
- package/lib/init/template/multitenancy/files/mta.yaml.hbs +1 -3
- package/lib/init/template/multitenancy/files/values.yaml.hbs +8 -0
- package/lib/init/template/multitenancy/index.js +10 -5
- package/lib/init/template/nodejs/files/package.json.hbs +1 -1
- package/lib/init/template/pipeline/files/app-package.json.hbs +1 -7
- package/lib/init/template/pipeline/files/ui5.yaml.hbs +20 -1
- package/lib/init/template/pipeline/index.js +6 -3
- package/lib/init/template/portal/files/mta.yaml.hbs +5 -3
- package/lib/init/template/portal/index.js +3 -6
- package/lib/init/template/sample/files/_i18n/i18n_de.properties +12 -0
- package/lib/init/template/sample/files/_i18n/i18n_en.properties +21 -0
- package/lib/init/template/sample/files/_i18n/i18n_fr.properties +20 -0
- package/lib/init/template/sample/files/_i18n/messages_de.properties +34 -0
- package/lib/init/template/sample/files/_i18n/messages_en.properties +34 -0
- package/lib/init/template/sample/files/_i18n/messages_fr.properties +34 -0
- package/lib/init/template/sample/files/app/_i18n/i18n_de.properties +0 -26
- package/lib/init/template/sample/files/app/_i18n/i18n_en.properties +8 -0
- package/lib/init/template/sample/files/app/admin-authors/fiori-service.cds +52 -0
- package/lib/init/template/sample/files/app/admin-authors/webapp/Component.js.hbs +3 -0
- package/lib/init/template/sample/files/app/admin-authors/webapp/i18n/i18n.properties +11 -0
- package/lib/init/template/sample/files/app/admin-authors/webapp/manifest.json +130 -0
- package/lib/init/template/sample/files/app/admin-books/fiori-service.cds +81 -88
- package/lib/init/template/sample/files/app/admin-books/webapp/Component.js.hbs +3 -0
- package/lib/init/template/sample/files/app/admin-books/webapp/i18n/i18n.properties +10 -2
- package/lib/init/template/sample/files/app/admin-books/webapp/manifest.json +24 -48
- package/lib/init/template/sample/files/app/appconfig/fioriSandboxConfig.json +54 -3
- package/lib/init/template/sample/files/app/browse/fiori-service.cds +30 -30
- package/lib/init/template/sample/files/app/browse/webapp/Component.js.hbs +3 -0
- package/lib/init/template/sample/files/app/browse/webapp/i18n/i18n.properties +10 -3
- package/lib/init/template/sample/files/app/browse/webapp/manifest.json +18 -23
- package/lib/init/template/sample/files/app/common.cds +126 -158
- package/lib/init/template/sample/files/app/fiori-apps.html +30 -0
- package/lib/init/template/sample/files/app/genres/fiori-service.cds +33 -0
- package/lib/init/template/sample/files/app/genres/tree-view.cds +42 -0
- package/lib/init/template/sample/files/app/genres/value-help.cds +6 -0
- package/lib/init/template/sample/files/app/genres/webapp/Component.js.hbs +3 -0
- package/lib/init/template/sample/files/app/genres/webapp/i18n/i18n.properties +4 -0
- package/lib/init/template/sample/files/app/genres/webapp/i18n/i18n_de.properties +2 -0
- package/lib/init/template/sample/files/app/genres/webapp/manifest.json +126 -0
- package/lib/init/template/sample/files/app/services.cds +6 -2
- package/lib/init/template/sample/files/db/currencies.cds +13 -0
- package/lib/init/template/sample/files/db/data/sap.capire.bookshop-Books.csv +5 -5
- package/lib/init/template/sample/files/db/data/{sap.capire.bookshop-Books_texts.csv → sap.capire.bookshop-Books.texts.csv} +4 -4
- package/lib/init/template/sample/files/db/data/sap.capire.bookshop-Genres.csv +42 -15
- package/lib/init/template/sample/files/db/data/sap.common-Currencies.csv +12 -0
- package/lib/init/template/sample/files/db/data/sap.common-Currencies.texts.csv +15 -0
- package/lib/init/template/sample/files/db/hana/index.cds +10 -0
- package/lib/init/template/sample/files/db/schema.cds +35 -22
- package/lib/init/template/sample/files/db/sqlite/index.cds +10 -0
- package/lib/init/template/sample/files/package.json.hbs +14 -1
- package/lib/init/template/sample/files/srv/access-control.cds +3 -0
- package/lib/init/template/sample/files/srv/admin-service.cds +3 -2
- package/lib/init/template/sample/files/srv/cat-service.cds +7 -11
- package/lib/init/template/sample/index.js +26 -17
- package/lib/init/template/telemetry/index.js +1 -3
- package/lib/init/template/typescript/files/package.json +1 -1
- package/lib/init/template/workzone-standard/files/mta.yaml.hbs +0 -5
- package/lib/init/template/workzone-standard/index.js +0 -1
- package/lib/init/template/xsuaa/files/mta.yaml.hbs +4 -0
- package/lib/init/template/xsuaa/index.js +5 -7
- package/lib/lint/Linter.js +10 -4
- package/lib/lint/eslintHelp.json +1 -1
- package/lib/mtx/tenant_action.js +12 -4
- package/lib/util/checksum.js +32 -0
- package/lib/util/question.js +60 -7
- package/npm-shrinkwrap.json +61 -37
- package/package.json +2 -2
- package/lib/init/template/app-frontend/files/ui5-deploy.yaml.hbs +0 -25
- package/lib/init/template/github-actions/files/cf-setup/action.yaml +0 -53
- package/lib/init/template/html5-repo/files/ui5-deploy.yaml.hbs +0 -25
- package/lib/init/template/pipeline/files/ui5-deploy.yaml.hbs +0 -25
- package/lib/init/template/sample/files/app/_i18n/i18n.properties +0 -29
- package/lib/init/template/sample/files/app/admin-books/webapp/Component.js +0 -8
- package/lib/init/template/sample/files/app/admin-books/webapp/i18n/i18n_de.properties +0 -3
- package/lib/init/template/sample/files/app/admin-books/webapp/i18n/i18n_en.properties +0 -3
- package/lib/init/template/sample/files/app/browse/webapp/Component.js +0 -7
- package/lib/init/template/sample/files/app/browse/webapp/i18n/i18n_de.properties +0 -3
- package/lib/init/template/sample/files/app/browse/webapp/i18n/i18n_en.properties +0 -4
- package/lib/init/template/sample/files/app/index.html +0 -32
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,64 @@ 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.5.0 - 2025-12-01
|
|
10
|
+
|
|
11
|
+
### Added
|
|
12
|
+
|
|
13
|
+
- `cds import` now accepts `--into-namespace <string>` to override the namespace entities are imported to, when importing OpenAPI definitions.
|
|
14
|
+
|
|
15
|
+
### Changed
|
|
16
|
+
|
|
17
|
+
- `cds add portal` adds the correct service key configuration to the _mta.yaml_.
|
|
18
|
+
- `cds add sample` creates sample files aligned with recent versions of bookshop and bookstore in https://github.com/capire.
|
|
19
|
+
- `cds add sample` uses a matching ID in generated `manifest.json` and UI5 components.
|
|
20
|
+
- `cds build` uses `hdblogicalschema` and `hdblogicalschemaconfig` plugins in its default `.hdiconfig`.
|
|
21
|
+
- `cds add cloud-logging` adds required dependencies to _pom.xml_ for Java projects.
|
|
22
|
+
- `cds add kyma` is now the only suggested Kyma facet in `cds add help`.
|
|
23
|
+
- `cds add helm-unified-runtime` option is now deprecated; use `cds add kyma --unified-runtime` instead.
|
|
24
|
+
- `cds up --to k8s` now saves the checksum of buildpack images to determine if a rebuild is necessary.
|
|
25
|
+
- `cds up --to k8s` will interactively ask for registry credentials if they don't exist on the remote cluster.
|
|
26
|
+
- `cds build` automatically adds `contentLocalizedEdmx=true` to the java build task configuration if OData V2 is set.
|
|
27
|
+
- `cds add github-actions` has out-of-the-box support for Java projects.
|
|
28
|
+
- `cds add html5-repo` and `cds add app-frontend` do not create a separate _ui5-deploy.yaml_ but use the default _ui5.yaml_.
|
|
29
|
+
- `cds add` plugins allow merging into non-existing files with `cds.add.merge(<object>).into(<yaml-path>)`.
|
|
30
|
+
- `cds build` tasks that produce `csn.json` now emit the `compile.for.runtime` event, in alignment with other runtime CSN producers like `cds serve`. Plugins can use such events to hook into CSN creation.
|
|
31
|
+
- `cds add xsuaa` sets `credential-types: [ "binding-secret", "x509" ]` for XSUAA instances.
|
|
32
|
+
- `cds add hana` does not create an explicit `.hdiconfig` file any more as `cds build` provides defaults.
|
|
33
|
+
- `cds import` generates the on condition for a composition when the corresponding OData navigation property targets a collection, is contained (`$ContainsTarget` in OData EDM) and has a backlink (`$Partner` in OData EDM).
|
|
34
|
+
- `cds import` now considers OData contained navigation properties as compositions instead of associations.
|
|
35
|
+
|
|
36
|
+
### Fixed
|
|
37
|
+
|
|
38
|
+
- `cds add ias` will always add a binding to the MTX sidecar or approuter if required.
|
|
39
|
+
- `cds add ias` has improved support for multitenancy scenarios on Kyma.
|
|
40
|
+
- `cds lint` no longer fails with error code 1 and no message when loading the config fails.
|
|
41
|
+
- `cds version` no longer prints an `undefined` value for `@sap/cds-dk`.
|
|
42
|
+
- `cds compile --flavor files` no longer crashes with a `TypeError`.
|
|
43
|
+
- `cds add workzone` does not add superfluous `destinations` configuration to the `srv-api` import.
|
|
44
|
+
|
|
45
|
+
## Version 9.4.3 - 2025-10-29
|
|
46
|
+
|
|
47
|
+
### Added
|
|
48
|
+
|
|
49
|
+
### Changed
|
|
50
|
+
|
|
51
|
+
- `cds add github-actions` will use the `cap-js/cf-setup` action from GitHub marketplace instead of creating a local copy.
|
|
52
|
+
- `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.
|
|
53
|
+
- `cds up --to k8s` now uses built-in containerization, eliminating the hard dependency on the `ctz` library.
|
|
54
|
+
- `cds up --to k8s` will interactively ask for a domain if it can't be determined from the current Kubernetes configuration.
|
|
55
|
+
- `cds build --for hana` no longer requires `.hdbgrants` and `.hdbrevokes` to be specified as a plugin in the `.hdiconfig` file.
|
|
56
|
+
- `cds import` no longer emits usage info when invoked with the dry option.
|
|
57
|
+
|
|
58
|
+
### Fixed
|
|
59
|
+
|
|
60
|
+
- `cds add data` fixes an issue where randomly generated `Decimal` and `Integer` values could exceed their defined precision or scale.
|
|
61
|
+
- `cds add pipeline` does not add UI5 template files in some cases.
|
|
62
|
+
- `cds add ias` does not write to the `mta.yaml` for non-MTA projects.
|
|
63
|
+
- `cds add ias` automatically adds the `authenticationType` to `xs-app.json` if required.
|
|
64
|
+
- `cds add ias` adds a binding to the approuter component if required.
|
|
65
|
+
- `cds import --from ...` yields a better error message for unknown import kinds.
|
|
66
|
+
|
|
9
67
|
## Version 9.4.2 - 2025-10-23
|
|
10
68
|
|
|
11
69
|
### Changed
|
|
@@ -23,6 +81,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/).
|
|
|
23
81
|
- `cds add test` creates correct OData URLs with unqualified entity names in service paths.
|
|
24
82
|
- `cds add http` skips contained composition targets.
|
|
25
83
|
- `cds add sample` no longer comes with an error when creating a Book through Fiori.
|
|
84
|
+
- `cds build` now places the `i18n` folder in the generated output root directory instead of nested locations.
|
|
26
85
|
|
|
27
86
|
## Version 9.4.1 - 2025-10-09
|
|
28
87
|
|
|
@@ -313,6 +372,41 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/).
|
|
|
313
372
|
- `cds add ams` for Java adds a custom builder to the `mta.yaml` to circumvent the missing `srv/src/gen/policies`.
|
|
314
373
|
- `cds add multitenancy` requires the `srv-api` instead of the `mtx-api` for Java projects.
|
|
315
374
|
|
|
375
|
+
## Version 8.9.11 - 2025-11-21
|
|
376
|
+
|
|
377
|
+
### Fixed
|
|
378
|
+
|
|
379
|
+
- Bump dependencies in shrinkwrap.
|
|
380
|
+
|
|
381
|
+
## Version 8.9.10 - 2025-09-19
|
|
382
|
+
|
|
383
|
+
### Fixed
|
|
384
|
+
|
|
385
|
+
- Updated shrinkwrap to include `@sap/cds-compiler` version 5.9.12.
|
|
386
|
+
|
|
387
|
+
## Version 8.9.9 - 2025-09-16
|
|
388
|
+
|
|
389
|
+
### Fixed
|
|
390
|
+
|
|
391
|
+
- CVE-2025-58754: vulnerability with `axios` versions <1.12.0.
|
|
392
|
+
|
|
393
|
+
## Version 8.9.8 - 2025-07-24
|
|
394
|
+
|
|
395
|
+
### Fixed
|
|
396
|
+
|
|
397
|
+
- CVE-2025-7783: vulnerability with `form-data` versions <4.0.4.
|
|
398
|
+
|
|
399
|
+
## Version 8.9.7 - 2025-07-17
|
|
400
|
+
|
|
401
|
+
### Fixed
|
|
402
|
+
|
|
403
|
+
- Bump dependencies in shrinkwrap.
|
|
404
|
+
|
|
405
|
+
## Version 8.9.6 - 2025-06-05
|
|
406
|
+
|
|
407
|
+
### Fixed
|
|
408
|
+
|
|
409
|
+
- Bump dependencies in shrinkwrap.
|
|
316
410
|
|
|
317
411
|
## Version 8.9.5 - 2025-06-05
|
|
318
412
|
|
package/bin/compile/index.js
CHANGED
|
@@ -297,7 +297,7 @@ async function compile (models, options={}) {
|
|
|
297
297
|
throw `You must specify a model to compile.\nRun 'cds c -?' to learn more.`
|
|
298
298
|
}
|
|
299
299
|
|
|
300
|
-
let chain = model.then (m => model=m)
|
|
300
|
+
let chain = model.then?. (m => model=m) ?? Promise.resolve(model) // model could be a sync. value
|
|
301
301
|
|
|
302
302
|
// add processors for compiling
|
|
303
303
|
if (options.for) for (let each of options.for.split(',')) {
|
|
@@ -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/export.js
CHANGED
|
@@ -111,7 +111,7 @@ class Export extends Minify {
|
|
|
111
111
|
async export (minified,o) {
|
|
112
112
|
if (this.options.to === '-') return super.print (minified)
|
|
113
113
|
if (this.options.to.endsWith('/*')) this.options.to = this.export_to()
|
|
114
|
-
console.log(`\n${this.name}ing APIs to`, this.options.to, '...\n')
|
|
114
|
+
console.log(`\n${this.name}ing APIs to`, cds.utils.local(this.options.to), '...\n')
|
|
115
115
|
const exports = []; exports.push(...this.export_models (minified))
|
|
116
116
|
if (o.texts) exports.push (...this.export_texts (minified))
|
|
117
117
|
if (o.data) exports.push (this.export_data (minified))
|
package/bin/help.js
CHANGED
|
@@ -12,7 +12,6 @@ module.exports = exports = Object.assign (help, {formatHelp, handleCompletion, h
|
|
|
12
12
|
|
|
13
13
|
*i* | *init* jump-start cds-based projects
|
|
14
14
|
*a* | *add* add a feature to an existing project
|
|
15
|
-
* * | *gen* generate models/data using a descriptive prompt [beta]
|
|
16
15
|
*m* | *import* add models from external sources
|
|
17
16
|
*c* | *compile* compile cds models to different outputs
|
|
18
17
|
*p* | *parse* parses given cds models
|
|
@@ -32,10 +31,10 @@ module.exports = exports = Object.assign (help, {formatHelp, handleCompletion, h
|
|
|
32
31
|
* * | *logout* logout from extensible multitenant SaaS app
|
|
33
32
|
* * | *pull* pull base model of extensible SaaS app
|
|
34
33
|
* * | *push* push extension to extensible SaaS app
|
|
35
|
-
*
|
|
34
|
+
* * | *lint* run linter for env or model checks
|
|
36
35
|
*v* | *version* get detailed version information
|
|
37
36
|
* * | *completion* add/remove cli completion for cds commands
|
|
38
|
-
|
|
37
|
+
*h* | *help* get detailed usage information
|
|
39
38
|
|
|
40
39
|
Learn more about each command using:
|
|
41
40
|
*cds help* <command> or
|
package/bin/import.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
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' ],
|
|
4
|
-
shortcuts: [ '-s',
|
|
3
|
+
options: [ '--services', '--for', '--out', '--as', '--name', '--include-namespaces', '--dependencies', '--from', '--config', '--destination', '--group', '--into-namespace', '--data-product' ],
|
|
4
|
+
shortcuts: [ '-s', '-4', '-o', '-as', '-n', '-ns', '-d',undefined, '-c', undefined, undefined, '-2', '-', '-f'],
|
|
5
5
|
flags: [ '--dry', '--force', '--no-copy', '--no-save', '--keep-namespace', '--data-product', '--edmx', '--rfc', '--odata', '--openapi', '--asyncapi' ],
|
|
6
6
|
help: `
|
|
7
7
|
# SYNOPSIS
|
|
@@ -55,6 +55,16 @@ 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
|
+
|
|
63
|
+
*-2 | --into-namespace* <string>
|
|
64
|
+
Specifies a custom namespace to be used for the imported service.
|
|
65
|
+
This will override the namespace that was specified via the the
|
|
66
|
+
@title annotation of the original service.
|
|
67
|
+
|
|
58
68
|
*Below options are valid for OData only*
|
|
59
69
|
|
|
60
70
|
*--keep-namespace*
|
|
@@ -153,7 +163,7 @@ async function _import(args, options = {}) {
|
|
|
153
163
|
if (options.from) {
|
|
154
164
|
if (!cds.import.from[options.from]) {
|
|
155
165
|
// the required plugin might not be installed, e.g. rfc importer
|
|
156
|
-
throw messages.UNSUPPORTED_FROM_OPTION;
|
|
166
|
+
throw messages.UNSUPPORTED_FROM_OPTION.replace('{0}', options.from).replace('{1}', Object.keys(cds.import.from).join(', '));
|
|
157
167
|
}
|
|
158
168
|
csn = await cds.import.from[options.from](input, apiOptions);
|
|
159
169
|
} else {
|
package/bin/repl.js
CHANGED
|
@@ -35,7 +35,7 @@ module.exports = Object.assign (repl, {
|
|
|
35
35
|
*cds eval* to evaluate and execute JavaScript.
|
|
36
36
|
`})
|
|
37
37
|
|
|
38
|
-
async function repl (
|
|
38
|
+
async function repl ([project], options={}) {
|
|
39
39
|
|
|
40
40
|
const GREEN = '\x1b[32m', RESET = '\x1b[0m'
|
|
41
41
|
const cds = require('../lib/cds')
|
|
@@ -46,9 +46,9 @@ async function repl (_, options={}) {
|
|
|
46
46
|
inspect.defaultOptions.depth = 11
|
|
47
47
|
|
|
48
48
|
Object.defineProperty (cds,'repl',{value:true})
|
|
49
|
-
await cds.service.bindings
|
|
49
|
+
await cds.service.bindings // REVISIT: this pre-populates many of the cds facade's lazy properties
|
|
50
50
|
let context={}, repl = { context, displayPrompt(){} }
|
|
51
|
-
if (options.run) await _run (options.run)
|
|
51
|
+
if ((options.run ??= project)) await _run (options.run)
|
|
52
52
|
if (options.use) _export (...options.use.split(','))
|
|
53
53
|
|
|
54
54
|
repl = require('repl').start ({
|
|
@@ -94,14 +94,10 @@ async function repl (_, options={}) {
|
|
|
94
94
|
process.on = (event, listener) => event !== 'uncaughtException' && event !== 'unhandledRejection' && process.addListener(event, listener)
|
|
95
95
|
|
|
96
96
|
async function _run (project) {
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
} catch (e) {
|
|
102
|
-
if (e.code !== 'MODULE_NOT_FOUND') throw e
|
|
103
|
-
await cds.exec ('--project', project, '--port', '0', '--in-memory?')
|
|
104
|
-
}
|
|
97
|
+
await require('../bin/serve') ([project], { project, port: 0,
|
|
98
|
+
'in-memory?': true,
|
|
99
|
+
'with-mocks': true
|
|
100
|
+
})
|
|
105
101
|
_export ('entities', 'services')
|
|
106
102
|
}
|
|
107
103
|
|
package/bin/up.js
CHANGED
|
@@ -28,31 +28,88 @@ module.exports = Object.assign(up, {
|
|
|
28
28
|
`})
|
|
29
29
|
|
|
30
30
|
const { execSync } = require('node:child_process')
|
|
31
|
+
const { tmpdir } = require('node:os')
|
|
32
|
+
const crypto = require('node:crypto')
|
|
31
33
|
const cds = require('../lib/cds')
|
|
32
|
-
const { highlight } = require('../lib/util/term')
|
|
33
|
-
const { exists, fs, path, path: { join } } = cds.utils
|
|
34
|
+
const { highlight, warn, dim } = require('../lib/util/term')
|
|
35
|
+
const { exists, fs, path, path: { join }, read, write } = cds.utils
|
|
36
|
+
const { ask4 } = require('../lib/util/question')
|
|
34
37
|
const { readProject } = require('../lib/init/projectReader')
|
|
38
|
+
const { mkdirp, copy } = cds.utils
|
|
39
|
+
const { containerize } = require('../bin/util/containerize')
|
|
40
|
+
const { calcDirChecksum } = require('../lib/util/checksum')
|
|
35
41
|
|
|
36
|
-
const
|
|
37
|
-
|
|
42
|
+
const DEBUG = /\b(y|all|cli)\b/.test(process.env.DEBUG) ? console.debug : undefined
|
|
43
|
+
|
|
44
|
+
const run = (cmd, { continueOnFail = false, silent = false } = {}) => {
|
|
45
|
+
const redactedCmd = cmd.replace(/(--docker-password=)(['"]?)[^'"\s]+(['"]?)/, '$1[REDACTED]$3')
|
|
46
|
+
console.log(highlight(redactedCmd))
|
|
38
47
|
try {
|
|
39
48
|
return execSync(cmd, {
|
|
40
|
-
stdio: 'inherit',
|
|
49
|
+
stdio: silent ? 'pipe' : 'inherit',
|
|
41
50
|
env: { ...process.env, FORCE_COLOR: cds.utils.colors.enabled }
|
|
42
51
|
})
|
|
43
52
|
} catch (error) {
|
|
53
|
+
if (continueOnFail) {
|
|
54
|
+
if (!silent)
|
|
55
|
+
console.error(error.message)
|
|
56
|
+
return
|
|
57
|
+
}
|
|
44
58
|
throw error.message
|
|
45
59
|
}
|
|
46
60
|
}
|
|
47
61
|
|
|
62
|
+
/**
|
|
63
|
+
* Get or create checksum file path in temp directory for UI5 apps
|
|
64
|
+
* @param {string} appName - The name of the app
|
|
65
|
+
* @returns {string} - Path to checksum file in temp directory
|
|
66
|
+
*/
|
|
67
|
+
function getChecksumFilePath(appName) {
|
|
68
|
+
const projectHash = crypto.createHash('md5').update(cds.root).digest('hex').substring(0, 8)
|
|
69
|
+
return join(tmpdir(), `cds-ui5-checksum-${projectHash}-${appName}.txt`)
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
/**
|
|
73
|
+
* Read stored checksum for an app from temp directory
|
|
74
|
+
* @param {string} appName - The name of the app
|
|
75
|
+
* @returns {string|null} - The stored checksum or null if not found
|
|
76
|
+
*/
|
|
77
|
+
function readStoredChecksum(appName) {
|
|
78
|
+
const checksumFile = getChecksumFilePath(appName)
|
|
79
|
+
if (exists(checksumFile)) {
|
|
80
|
+
try {
|
|
81
|
+
return fs.readFileSync(checksumFile, 'utf-8').trim()
|
|
82
|
+
} catch (e) {
|
|
83
|
+
// Ignore read errors and assume no checksum is stored
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
return null
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* Write checksum for an app to temp directory
|
|
91
|
+
* @param {string} appName - The name of the app
|
|
92
|
+
* @param {string} checksum - The checksum to store
|
|
93
|
+
*/
|
|
94
|
+
function writeStoredChecksum(appName, checksum) {
|
|
95
|
+
const checksumFile = getChecksumFilePath(appName)
|
|
96
|
+
fs.writeFileSync(checksumFile, checksum, 'utf-8')
|
|
97
|
+
}
|
|
98
|
+
|
|
48
99
|
async function up() {
|
|
49
100
|
const project = readProject()
|
|
50
|
-
const { apps, appName, hasApprouter, hasXsuaa, isMonorepoMicroservice } = project
|
|
101
|
+
const { apps, appName, hasApprouter, hasXsuaa, isMonorepoMicroservice, k8s } = project
|
|
102
|
+
|
|
103
|
+
const html5DeployerPath = join(cds.root, cds.env.folders.app, 'html5-deployer', 'resources')
|
|
51
104
|
|
|
52
105
|
for (const { app } of apps) {
|
|
53
106
|
const appPath = path.join(cds.root, cds.env.folders.app, app)
|
|
54
|
-
if (exists(path.join(appPath, 'package.json'))
|
|
55
|
-
|
|
107
|
+
if (exists(path.join(appPath, 'package.json'))) {
|
|
108
|
+
if (!exists(path.join(appPath, 'package-lock.json'))) {
|
|
109
|
+
run(`npm i --prefix ${appPath}`)
|
|
110
|
+
} else if (process.env.CI) {
|
|
111
|
+
run(`npm ci --prefix ${appPath}`)
|
|
112
|
+
}
|
|
56
113
|
}
|
|
57
114
|
}
|
|
58
115
|
|
|
@@ -74,8 +131,37 @@ async function up() {
|
|
|
74
131
|
}
|
|
75
132
|
|
|
76
133
|
if (cds.cli.options.to === 'k8s') {
|
|
77
|
-
if (!exists('chart')) run('cds add
|
|
78
|
-
|
|
134
|
+
if (!exists('chart') || !exists('containerize.yaml')) run('cds add kyma')
|
|
135
|
+
|
|
136
|
+
if (project.hasHtml5Repo) {
|
|
137
|
+
if (apps.length > 0) {
|
|
138
|
+
await mkdirp(html5DeployerPath)
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
for (const { app } of apps) {
|
|
142
|
+
const appPath = path.join(cds.root, cds.env.folders.app, app)
|
|
143
|
+
const zipPath = path.join(appPath, `dist/${app}.zip`)
|
|
144
|
+
|
|
145
|
+
// Calculate checksum for the app source folder (excluding node_modules and dist)
|
|
146
|
+
const currentChecksum = await calcDirChecksum(
|
|
147
|
+
appPath,
|
|
148
|
+
(dir) => !dir.includes('node_modules') && !dir.includes('dist')
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
const storedChecksum = readStoredChecksum(app)
|
|
152
|
+
const needsBuild = currentChecksum !== storedChecksum || !exists(zipPath)
|
|
153
|
+
|
|
154
|
+
if (needsBuild) {
|
|
155
|
+
DEBUG?.(dim(`build required for '${app}' – checksums`, { old: storedChecksum, new: currentChecksum }))
|
|
156
|
+
run(`npm run build --prefix ${appPath}`)
|
|
157
|
+
writeStoredChecksum(app, currentChecksum)
|
|
158
|
+
} else {
|
|
159
|
+
console.log(highlight(`Skipping build for UI5 app '${app}' - no changes detected (checksum: ${currentChecksum.substring(0, 8)}...)`))
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
await copy(zipPath).to(html5DeployerPath, `${app}.zip`)
|
|
163
|
+
}
|
|
164
|
+
}
|
|
79
165
|
} else {
|
|
80
166
|
if (!exists('mta.yaml')) {
|
|
81
167
|
run('cds add mta')
|
|
@@ -96,13 +182,78 @@ async function up() {
|
|
|
96
182
|
|
|
97
183
|
if (cds.cli.options.to === 'k8s') {
|
|
98
184
|
const ns = cds.cli.options.namespace
|
|
99
|
-
const nsHelm = ns ? `--namespace ${ns}
|
|
185
|
+
const nsHelm = ns ? `--namespace ${ns}` : ''
|
|
100
186
|
const nsKubectl = ns ? `-n ${ns}` : ''
|
|
101
187
|
|
|
102
188
|
run('npm i --package-lock')
|
|
103
|
-
run('
|
|
189
|
+
run('cds build --production')
|
|
190
|
+
|
|
191
|
+
if (ns) {
|
|
192
|
+
const k8sNamespaceResult = run(`kubectl get namespace ${ns} -o jsonpath="{.metadata.name}"`, { continueOnFail: true, silent: true })
|
|
193
|
+
if (!k8sNamespaceResult) {
|
|
194
|
+
run(`kubectl create namespace ${ns}`)
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
const isValidDomain = str => /^[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$/.test(str)
|
|
199
|
+
const { image, imagePullSecret } = k8s.values.global ?? {}
|
|
200
|
+
let registry = image?.registry
|
|
201
|
+
if (!process.env.CI) {
|
|
202
|
+
if (!registry || !isValidDomain(registry)) {
|
|
203
|
+
const valuesPath = 'chart/values.yaml'
|
|
204
|
+
const [yn] = await ask4([warn(`invalid registry server '${registry}' detected, override in ${valuesPath}? (Y/n)`)])
|
|
205
|
+
if (yn?.toLowerCase() !== 'y' || process.env.CI) {
|
|
206
|
+
throw `registry parameter is invalid, please specify it in ${valuesPath}`
|
|
207
|
+
} else {
|
|
208
|
+
registry = image.registry = await ask4(['registry server (e.g. index.docker.io): '])
|
|
209
|
+
const values = (await read(valuesPath)).replace(/(registry:\s*).*/, `$1${registry}`)
|
|
210
|
+
await write(values).to(valuesPath)
|
|
211
|
+
|
|
212
|
+
const containerize = (await read('containerize.yaml')).replace(/(repository:\s*).*/, `$1${registry}`)
|
|
213
|
+
await write(containerize).to('containerize.yaml')
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
if (imagePullSecret?.name) {
|
|
217
|
+
const imagePullSecretResult = run(`kubectl get secret ${imagePullSecret.name} ${nsKubectl} -o jsonpath="{.metadata.name}"`, { continueOnFail: true, silent: true })
|
|
218
|
+
if (!imagePullSecretResult) {
|
|
219
|
+
const yn = await ask4([`image pull secret '${imagePullSecret.name}' not found in namespace '${ns || 'default'}' – do you want to create it? (Y/n) `])
|
|
220
|
+
if (yn?.toLowerCase() !== 'y' || process.env.CI) {
|
|
221
|
+
throw `image pull secret '${imagePullSecret.name}' is required to pull images from the registry`
|
|
222
|
+
} else {
|
|
223
|
+
const [username, email] = await ask4([
|
|
224
|
+
'registry username: ',
|
|
225
|
+
'registry email: ',
|
|
226
|
+
])
|
|
227
|
+
const password = await ask4(['registry password: '], { redacted: true })
|
|
228
|
+
run(`kubectl create secret docker-registry ${imagePullSecret.name} ${nsKubectl} --docker-server=${registry} --docker-username=${username} --docker-password='${password}' --docker-email=${email}`)
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
const modules = await containerize()
|
|
235
|
+
const helmArgs = []
|
|
236
|
+
modules?.forEach(m => {
|
|
237
|
+
if (m.path === 'gen/srv') {
|
|
238
|
+
helmArgs.push(`--set srv.annotations.deployment.deployment-checksum=${m.checksum}`)
|
|
239
|
+
}
|
|
240
|
+
if (m.path === 'app/router') {
|
|
241
|
+
helmArgs.push(`--set approuter.annotations.deployment.deployment-checksum=${m.checksum}`)
|
|
242
|
+
}
|
|
243
|
+
if (m.path === 'app/html5-deployer') {
|
|
244
|
+
helmArgs.push(`--set html5-apps-deployer.annotations.job.job-checksum=${m.checksum}`)
|
|
245
|
+
}
|
|
246
|
+
if (m.path === 'gen/db') {
|
|
247
|
+
helmArgs.push(`--set hana-deployer.annotations.job.job-checksum=${m.checksum}`)
|
|
248
|
+
}
|
|
249
|
+
if (m.path === 'gen/mtx/sidecar') {
|
|
250
|
+
helmArgs.push(`--set sidecar.annotations.deployment.deployment-checksum=${m.checksum}`)
|
|
251
|
+
}
|
|
252
|
+
})
|
|
253
|
+
|
|
104
254
|
let helmUpgrade = `helm upgrade --install ${appName} ./gen/chart ${nsHelm} --wait --wait-for-jobs --timeout=10m`
|
|
105
|
-
if (hasXsuaa)
|
|
255
|
+
if (hasXsuaa) helmArgs.push(' --set-file xsuaa.jsonParameters=xs-security.json')
|
|
256
|
+
if (helmArgs.length > 0) helmUpgrade += ` ${helmArgs.join(' ')}`
|
|
106
257
|
run(helmUpgrade)
|
|
107
258
|
run(`kubectl rollout status deployment ${appName}-srv ${nsKubectl} --timeout=8m`)
|
|
108
259
|
if (hasApprouter) run(`kubectl rollout status deployment ${appName}-approuter ${nsKubectl} --timeout=8m`)
|