@sap/cds-dk 9.2.1 → 9.3.1
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 +35 -0
- package/bin/bind.js +5 -8
- package/bin/up.js +19 -13
- package/lib/bind/cf.js +4 -0
- package/lib/bind/k8s.js +21 -0
- package/lib/bind/plugin.js +9 -0
- package/lib/build/buildTaskProviderFactory.js +8 -14
- package/lib/build/constants.js +1 -0
- package/lib/build/index.js +17 -2
- package/lib/build/plugin.js +5 -7
- package/lib/build/provider/hana/index.js +4 -4
- package/lib/env/schemas/cds-rc.js +2 -2
- package/lib/import/odata/metadataconverter/MetadataConverter.js +5 -0
- package/lib/init/constants.js +3 -2
- package/lib/init/projectReader.js +17 -5
- package/lib/init/template/ams/index.js +1 -2
- package/lib/init/template/approuter/files/containerize.yaml.hbs +1 -1
- package/lib/init/template/approuter/index.js +1 -1
- package/lib/init/template/data/as-csv.js +10 -7
- package/lib/init/template/data/as-json.js +7 -4
- package/lib/init/template/enterprise-messaging/index.js +3 -2
- package/lib/init/template/enterprise-messaging-shared/index.js +3 -2
- package/lib/init/template/extension/files/.base/index.cds +6 -6
- package/lib/init/template/extension/index.js +5 -5
- package/lib/init/template/github-actions/files/kyma-info/action.yaml +1 -1
- package/lib/init/template/github-actions/files/kyma-setup/action.yaml +1 -9
- package/lib/init/template/github-actions/files/workflows/cf.yaml.hbs +25 -12
- package/lib/init/template/github-actions/files/workflows/kyma.yaml.hbs +8 -5
- package/lib/init/template/github-actions/files/workflows/release.yaml.hbs +30 -0
- package/lib/init/template/github-actions/files/workflows/test.yaml.hbs +6 -3
- package/lib/init/template/github-actions/index.js +28 -14
- package/lib/init/template/hana/index.js +2 -1
- package/lib/init/template/html5-repo/files/mta.yaml.hbs +5 -1
- package/lib/init/template/html5-repo/files/package.json.hbs +1 -1
- package/lib/init/template/html5-repo/index.js +27 -19
- package/lib/init/template/ias/files/mta.yaml.hbs +5 -4
- package/lib/init/template/ias/files/values.yaml.hbs +41 -40
- package/lib/init/template/mta/files/mta.yaml.hbs +1 -1
- package/lib/init/template/mta/files/package.json +1 -1
- package/lib/init/template/multitenancy/files/mta.yaml.hbs +1 -1
- package/lib/init/template/multitenancy/files/values.yaml.hbs +1 -1
- package/lib/init/template/nodejs/files/.vscode/extensions.json +0 -1
- package/lib/init/template/nodejs/files/package.json.hbs +3 -9
- package/lib/init/template/portal/files/mta.yaml.hbs +5 -1
- package/lib/init/template/portal/index.js +0 -1
- package/lib/init/template/sample/files/app/_i18n/i18n.properties +15 -0
- package/lib/init/template/sample/files/app/_i18n/i18n_de.properties +15 -0
- package/lib/init/template/sample/files/app/admin-books/fiori-service.cds +14 -15
- package/lib/init/template/sample/files/app/browse/fiori-service.cds +44 -44
- package/lib/init/template/sample/files/app/common.cds +99 -84
- package/lib/init/template/sample/files/db/schema.cds +22 -17
- package/lib/init/template/sample/files/package.json.hbs +1 -3
- package/lib/init/template/sample/files/srv/admin-service.cds +4 -3
- package/lib/init/template/sample/files/srv/cat-service.cds +28 -11
- package/lib/init/template/tiny-sample/files/nodejs/db/schema.cds +3 -3
- package/lib/init/template/typescript/files/package.json +1 -1
- package/lib/init/template/ui5/index.js +22 -0
- package/lib/init/template/xsuaa/files/mta.yaml.hbs +2 -1
- package/lib/init/template/xsuaa/index.js +7 -5
- package/lib/lint/eslintHelp.json +5 -3
- package/npm-shrinkwrap.json +15 -15
- package/package.json +1 -1
- package/lib/init/template/github-actions/files/cf-github-deploy/action.yaml +0 -48
- package/lib/init/template/sample/files/javascript/admin-service.js.hbs +0 -24
- package/lib/init/template/sample/files/javascript/cat-service.js.hbs +0 -43
- package/lib/init/template/sample/files/typescript/admin-service.ts.hbs +0 -16
- package/lib/init/template/sample/files/typescript/cat-service.ts.hbs +0 -38
package/CHANGELOG.md
CHANGED
|
@@ -6,6 +6,41 @@ 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.3.1 - 2025-09-08
|
|
10
|
+
|
|
11
|
+
### Fixed
|
|
12
|
+
|
|
13
|
+
- `cds add ias` correctly sets the `subscription-manager` dependencies endpoint for Node.js.
|
|
14
|
+
- `cds add data --records` respects the max length of a string field when it is annotated with `@Communication.IsEmailAddress`
|
|
15
|
+
- `cds add data --records --format csv` correctly escaping complex properties nested within structs.
|
|
16
|
+
- `cds add data --records` correctly generates values for structs in cases when the entity and the struct, used by the entity, have both a property with the same name.
|
|
17
|
+
- `cds add html5-repo` also binds the `html5-repo-host` service to the approuter.
|
|
18
|
+
- `cds up` has improved support for setups with multiple microservices.
|
|
19
|
+
- `cds add mta` will add no `role-collections` parameters if there are some specified in `xs-security.json`.
|
|
20
|
+
- `cds add github-actions` correctly generates a release workflow.
|
|
21
|
+
- `cds add github-actions` uses a simplified and more resilient Kyma setup script.
|
|
22
|
+
- `cds import` now correctly imports EDMX files with empty NavigationPropertyPath tags.
|
|
23
|
+
|
|
24
|
+
## Version 9.3.0 - 2025-09-01
|
|
25
|
+
|
|
26
|
+
### Added
|
|
27
|
+
|
|
28
|
+
- `cds bind -a` is now also supported for Kyma, where the app prefix can be passed for `-a`, e.g. `bookshop-srv`.
|
|
29
|
+
- `cds build --for hana` now trims leading and trailing whitespaces in csv-Files if build option `trimCsvWhitespaces` is set.
|
|
30
|
+
- `cds add github-actions` sets required `permissions`.
|
|
31
|
+
- `cds up` supports a deployment layout where approuter or portal service configuration is in a top-level `.deploy` folder.
|
|
32
|
+
- `cds add ias` sets the `access-token-format` to `jwt` by default.
|
|
33
|
+
|
|
34
|
+
### Changed
|
|
35
|
+
|
|
36
|
+
- `cds add sample` provides more i18n translations.
|
|
37
|
+
- `cds add ias` sets the `xsuaa-cross-consumption` field to `true` by default.
|
|
38
|
+
|
|
39
|
+
### Fixed
|
|
40
|
+
|
|
41
|
+
- `cds add workzone` with missing `sap.app` config in `manifest.json` does not throw a `TypeError`.
|
|
42
|
+
- `cds add ias` adds a subdomain-less application URL in `redirect_uris`.
|
|
43
|
+
|
|
9
44
|
## Version 9.2.1 - 2025-08-22
|
|
10
45
|
|
|
11
46
|
### Changed
|
package/bin/bind.js
CHANGED
|
@@ -46,7 +46,7 @@ module.exports = Object.assign(cds_bind, {
|
|
|
46
46
|
|
|
47
47
|
*-a | --to-app-services* <app>
|
|
48
48
|
|
|
49
|
-
Bind services of a given application
|
|
49
|
+
Bind services of a given application.
|
|
50
50
|
|
|
51
51
|
*--no-create-service-key*
|
|
52
52
|
|
|
@@ -83,7 +83,7 @@ const { bind, storeBindings } = require('../lib/bind/shared');
|
|
|
83
83
|
|
|
84
84
|
|
|
85
85
|
async function cds_bind(args, options) {
|
|
86
|
-
const { on, 'to-app-services': app, to, out, 'output-file': outputFile, exec, credentials } = options
|
|
86
|
+
const { on = 'cf', 'to-app-services': app, to, out, 'output-file': outputFile, exec, credentials } = options
|
|
87
87
|
|
|
88
88
|
if (exec) {
|
|
89
89
|
process.env.CDS_ENV ??= 'hybrid'
|
|
@@ -103,10 +103,6 @@ async function cds_bind(args, options) {
|
|
|
103
103
|
options.out ??= outputFile
|
|
104
104
|
}
|
|
105
105
|
|
|
106
|
-
if (on === 'k8s' && app) {
|
|
107
|
-
throw `Option ${term.bold('--to-app-services')} is only supported for Cloud Foundry.`;
|
|
108
|
-
}
|
|
109
|
-
|
|
110
106
|
if (credentials === true) {
|
|
111
107
|
throw `Option ${term.bold('--credentials')} requires a JSON object or a file path.`;
|
|
112
108
|
}
|
|
@@ -155,9 +151,10 @@ async function cds_bind(args, options) {
|
|
|
155
151
|
options.for ??= 'hybrid';
|
|
156
152
|
|
|
157
153
|
if (app) {
|
|
158
|
-
const cds = require('../lib/cds')
|
|
154
|
+
const cds = require('../lib/cds')
|
|
159
155
|
const resolvedServices = [], unresolvedServices = {}
|
|
160
|
-
const
|
|
156
|
+
const plugin = require(`../lib/bind/${on}`)
|
|
157
|
+
const services = await plugin.services4(app)
|
|
161
158
|
|
|
162
159
|
if (!services) console.warn(`no services bound to app ${app} - nothing to bind`)
|
|
163
160
|
|
package/bin/up.js
CHANGED
|
@@ -30,7 +30,7 @@ module.exports = Object.assign(up, {
|
|
|
30
30
|
const { execSync, spawn } = require('node:child_process')
|
|
31
31
|
const cds = require('../lib/cds')
|
|
32
32
|
const { highlight, bold } = require('../lib/util/term')
|
|
33
|
-
const { exists, fs, path } = cds.utils
|
|
33
|
+
const { exists, fs, path, path: { join } } = cds.utils
|
|
34
34
|
const { readProject } = require('../lib/init/projectReader')
|
|
35
35
|
|
|
36
36
|
const run = cmd => {
|
|
@@ -67,7 +67,8 @@ const runDetectingError = async (cmd, errorRegex) => {
|
|
|
67
67
|
}
|
|
68
68
|
|
|
69
69
|
async function up() {
|
|
70
|
-
const
|
|
70
|
+
const project = readProject()
|
|
71
|
+
const { apps, appName, hasApprouter, hasXsuaa, isMonorepoMicroservice } = project
|
|
71
72
|
|
|
72
73
|
for (const { app } of apps) {
|
|
73
74
|
const appPath = path.join(cds.root, cds.env.folders.app, app)
|
|
@@ -79,14 +80,18 @@ async function up() {
|
|
|
79
80
|
if (exists('mtx/sidecar') && !exists('mtx/sidecar/package-lock.json')) {
|
|
80
81
|
run('npm i --package-lock-only --prefix mtx/sidecar')
|
|
81
82
|
}
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
83
|
+
|
|
84
|
+
const app = cds.env.folders.app
|
|
85
|
+
const legacyToPreferred = {
|
|
86
|
+
[join(app, 'router')]: '.deploy/app-router',
|
|
87
|
+
[join(app, 'portal')]: '.deploy/portal',
|
|
88
|
+
[join(app, 'html5-deployer')]: '.deploy/html5-deployer'
|
|
87
89
|
}
|
|
88
|
-
|
|
89
|
-
|
|
90
|
+
for (const [legacy, preferred] of Object.entries(legacyToPreferred)) {
|
|
91
|
+
const root = exists(preferred) ? preferred : legacy
|
|
92
|
+
if (exists(join(root, 'package.json')) && !exists(join(root, 'package-lock.json'))) {
|
|
93
|
+
run(`npm i --package-lock-only --prefix ${root}`)
|
|
94
|
+
}
|
|
90
95
|
}
|
|
91
96
|
|
|
92
97
|
if (cds.cli.options.to === 'k8s') {
|
|
@@ -100,11 +105,12 @@ async function up() {
|
|
|
100
105
|
run('npm i --package-lock-only')
|
|
101
106
|
}
|
|
102
107
|
}
|
|
103
|
-
|
|
104
|
-
if (
|
|
105
|
-
const
|
|
108
|
+
|
|
109
|
+
if (isMonorepoMicroservice) {
|
|
110
|
+
const { npmWorkspaceRoot } = project
|
|
111
|
+
const source = path.join(npmWorkspaceRoot, 'package-lock.json')
|
|
106
112
|
const target = path.join(process.cwd(), 'package-lock.json')
|
|
107
|
-
if (exists(target)
|
|
113
|
+
if (exists(target) && fs.lstatSync(target).isSymbolicLink()) fs.unlinkSync(target)
|
|
108
114
|
const relative = path.relative(process.cwd(), source)
|
|
109
115
|
fs.symlinkSync(relative, target, 'junction')
|
|
110
116
|
}
|
package/lib/bind/cf.js
CHANGED
|
@@ -33,6 +33,10 @@ module.exports = new class CFBind extends require('./plugin') {
|
|
|
33
33
|
return service
|
|
34
34
|
}
|
|
35
35
|
|
|
36
|
+
async services4(app) {
|
|
37
|
+
return (await cf.services(app)).filter(s => Object.keys(s.credentials).length > 0)
|
|
38
|
+
}
|
|
39
|
+
|
|
36
40
|
async resolve(name, binding) {
|
|
37
41
|
this.target ??= await cf.target()
|
|
38
42
|
this.#checkApiEndpoint(name, binding)
|
package/lib/bind/k8s.js
CHANGED
|
@@ -30,6 +30,27 @@ module.exports = new class K8SBind extends require('./plugin') {
|
|
|
30
30
|
return service
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
+
services4(app) {
|
|
34
|
+
const { execSync } = require('node:child_process')
|
|
35
|
+
const json = cmd => JSON.parse(execSync(cmd, { encoding: 'utf8' }))
|
|
36
|
+
|
|
37
|
+
const bindings = json('kubectl get servicebindings -o json').items
|
|
38
|
+
.filter(b => b.metadata.name.startsWith(app))
|
|
39
|
+
if (!bindings.length) throw `cannot find services with metadata name starting with '${app}'`
|
|
40
|
+
|
|
41
|
+
const offerings = Object.fromEntries(
|
|
42
|
+
json('kubectl get serviceinstances -o json').items
|
|
43
|
+
.map(i => [i.metadata.name, i.spec.serviceOfferingName])
|
|
44
|
+
)
|
|
45
|
+
|
|
46
|
+
for (const b of bindings) {
|
|
47
|
+
const offering = offerings[b.spec.serviceInstanceName]
|
|
48
|
+
b.label = b.type = offering
|
|
49
|
+
b.name = b.metadata.name
|
|
50
|
+
}
|
|
51
|
+
return bindings
|
|
52
|
+
}
|
|
53
|
+
|
|
33
54
|
async resolve(name, binding) {
|
|
34
55
|
const { context, namespace, cluster } = await this.#context4(binding)
|
|
35
56
|
|
package/lib/bind/plugin.js
CHANGED
|
@@ -36,6 +36,15 @@ module.exports = class BindPlugin {
|
|
|
36
36
|
throw new Error('credentials4 not implemented')
|
|
37
37
|
}
|
|
38
38
|
|
|
39
|
+
/**
|
|
40
|
+
* Returns the services for the given app
|
|
41
|
+
* @param {string} app - The name of the app
|
|
42
|
+
* @returns {Promise<Array>} A promise that resolves to an array of bound service names
|
|
43
|
+
*/
|
|
44
|
+
services4(_app) {
|
|
45
|
+
throw new Error('services4 not implemented')
|
|
46
|
+
}
|
|
47
|
+
|
|
39
48
|
/**
|
|
40
49
|
* Resolves the binding configuration for the given service name
|
|
41
50
|
* @param {string} name - The name of the service
|
|
@@ -8,24 +8,13 @@ const BuildTaskProvider = require('./buildTaskProvider')
|
|
|
8
8
|
const { BuildError, normalizePath } = require('./util')
|
|
9
9
|
const DEBUG = cds.debug('cli|build')
|
|
10
10
|
|
|
11
|
-
/**
|
|
12
|
-
* @typedef {{
|
|
13
|
-
* clean?: boolean,
|
|
14
|
-
* outputMode?: number,
|
|
15
|
-
* for?: string,
|
|
16
|
-
* src?: string,
|
|
17
|
-
* dest?: string,
|
|
18
|
-
* } & {[key:string]:unknown}} BuildTaskProviderFactoryOptions
|
|
19
|
-
*/
|
|
20
|
-
|
|
21
11
|
/**
|
|
22
|
-
*
|
|
23
|
-
* @typedef {object} Task
|
|
12
|
+
* @typedef {import('./plugin').Task} Task
|
|
24
13
|
*/
|
|
25
14
|
|
|
26
15
|
class BuildTaskProviderFactory {
|
|
27
16
|
/**
|
|
28
|
-
* @param {
|
|
17
|
+
* @param {import('./index').BuildOptions} options - build options
|
|
29
18
|
*/
|
|
30
19
|
constructor(options = {}) {
|
|
31
20
|
options.outputMode = options.outputMode || OUTPUT_MODE_FILESYSTEM
|
|
@@ -43,7 +32,7 @@ class BuildTaskProviderFactory {
|
|
|
43
32
|
}
|
|
44
33
|
}
|
|
45
34
|
})
|
|
46
|
-
/** @type {{ options:
|
|
35
|
+
/** @type {{ options: import('./index').BuildOptions, tasks: Task[]}} */
|
|
47
36
|
this._context = { options, tasks: [] } // resolved tasks
|
|
48
37
|
}
|
|
49
38
|
get options() {
|
|
@@ -112,6 +101,7 @@ class BuildTaskProviderFactory {
|
|
|
112
101
|
* 'for' defines an alias for built-in plugins like 'hana', 'java', 'node', 'fiori' or 'mtx'.
|
|
113
102
|
* 'use' defines the fully qualified module name of custom build plugins implementations.
|
|
114
103
|
* @param {Task} task
|
|
104
|
+
* @returns {typeof import('./plugin')}
|
|
115
105
|
*/
|
|
116
106
|
getPlugin(task) {
|
|
117
107
|
const provider = this._getProvider(task)
|
|
@@ -227,6 +217,10 @@ class PluginBuildTaskProvider extends BuildTaskProvider {
|
|
|
227
217
|
})
|
|
228
218
|
}
|
|
229
219
|
|
|
220
|
+
/**
|
|
221
|
+
* @param {{for: string}} key
|
|
222
|
+
* @returns {Task}
|
|
223
|
+
*/
|
|
230
224
|
getTask(key) {
|
|
231
225
|
// plugin entry is already validated, a taskDefaults default member exists
|
|
232
226
|
const task = JSON.parse(JSON.stringify(this.plugins.get(key.for)?.taskDefaults ?? {}))
|
package/lib/build/constants.js
CHANGED
|
@@ -38,6 +38,7 @@ exports.OPTION_WS = "ws" // run a workspa
|
|
|
38
38
|
exports.OPTION_WS_PACK = "ws-pack" // enable tarball based packaging of workspace dependencies
|
|
39
39
|
exports.CSV_FILE_TARGET = "csvFileTarget" // target folder when copying CSV files to the deployment target folder ./db/src/gen/*, default is 'data'
|
|
40
40
|
exports.CSV_FILE_DETECTION = "csvFileDetection" // detect CSV files based on CDS model location - ENABLED by default
|
|
41
|
+
exports.CSV_TRIM_WHITESPACES = "trimCsvWhitespaces" // trim whitespaces from CSV column names and values - DISABLED by default
|
|
41
42
|
exports.EXTENSION_POINT_VALIDATION = "extensionPointValidation" // validate extension point restrictions defined by the extended SaaS app - ENABLED by default
|
|
42
43
|
exports.MTX_SIDECAR_DB_VALIDATION = "mtxSidecarDbValidation" // validate consistency of app and sidecar db configuration - ENABLED by default
|
|
43
44
|
exports.CONTINUE_UNRESOLVED_SCHEMA_CHANGES = "continueUnresolvedSchemaChanges" // build doesn't fail if unresolved HANA schema changes are detected
|
package/lib/build/index.js
CHANGED
|
@@ -6,10 +6,25 @@ const { BuildError } = require('./util')
|
|
|
6
6
|
|
|
7
7
|
module.exports = { build, register, Plugin, BuildError }
|
|
8
8
|
|
|
9
|
+
// reuse schema definition from cds-rc.js used for package.json code completion
|
|
10
|
+
/** @typedef{import('../env/schemas/cds-rc')['$defs']['buildTaskType']['enum'][number]} BuildFor */
|
|
11
|
+
|
|
12
|
+
// using (string & {}) prevents widening of `'x' | string` to just `string`
|
|
13
|
+
/**
|
|
14
|
+
* @typedef {{
|
|
15
|
+
* clean?: boolean,
|
|
16
|
+
* outputMode?: number,
|
|
17
|
+
* for?: BuildFor | (string & {}),
|
|
18
|
+
* src?: string,
|
|
19
|
+
* dest?: string,
|
|
20
|
+
* model?: string[]
|
|
21
|
+
* } & {[key:string]:unknown}} BuildOptions
|
|
22
|
+
*/
|
|
23
|
+
|
|
9
24
|
/**
|
|
10
25
|
* Executes cds build in the directory defined by cds.root.
|
|
11
26
|
*
|
|
12
|
-
* @param {
|
|
27
|
+
* @param {BuildOptions} options - command options as defined by build command.
|
|
13
28
|
*/
|
|
14
29
|
async function build(options = {}) {
|
|
15
30
|
if (!fs.existsSync(cds.root) || !cds.utils.isdir(cds.root)) {
|
|
@@ -31,4 +46,4 @@ async function build(options = {}) {
|
|
|
31
46
|
|
|
32
47
|
function register(id, plugin) {
|
|
33
48
|
return require('./plugins').register(id, plugin)
|
|
34
|
-
}
|
|
49
|
+
}
|
package/lib/build/plugin.js
CHANGED
|
@@ -21,7 +21,7 @@ const DEBUG = cds.debug('cli|build')
|
|
|
21
21
|
|
|
22
22
|
/**
|
|
23
23
|
* The build plugin creates the build output for a dedicated build task. It is uniquely identified
|
|
24
|
-
* by the build task's `for<` or `use` property. The `use` property represents
|
|
24
|
+
* by the build task's `for<` or `use` property. The `use` property represents
|
|
25
25
|
* the fully qualified node module path of the build plugin implementation.
|
|
26
26
|
* The build task engine defines the following protocol. The methods are invoked in descending order:
|
|
27
27
|
* - `init()` - optional
|
|
@@ -40,13 +40,11 @@ class Plugin {
|
|
|
40
40
|
|
|
41
41
|
/**
|
|
42
42
|
* @type {Task | undefined}
|
|
43
|
-
* @private
|
|
44
43
|
*/
|
|
45
44
|
_task
|
|
46
45
|
|
|
47
46
|
/**
|
|
48
47
|
* @type {Context | undefined}
|
|
49
|
-
* @private
|
|
50
48
|
*/
|
|
51
49
|
_context
|
|
52
50
|
|
|
@@ -69,7 +67,7 @@ class Plugin {
|
|
|
69
67
|
*/
|
|
70
68
|
_messages = []
|
|
71
69
|
/**
|
|
72
|
-
* Returns a list of build and compiler messages created by this build plugin.
|
|
70
|
+
* Returns a list of build and compiler messages created by this build plugin.
|
|
73
71
|
* Supported message severities are 'Info', 'Warning', and 'Error'.
|
|
74
72
|
*/
|
|
75
73
|
get messages() {
|
|
@@ -176,9 +174,9 @@ class Plugin {
|
|
|
176
174
|
|
|
177
175
|
/**
|
|
178
176
|
* Asynchronously copies a single file or the entire directory structure from 'src' to 'dest', including subdirectories and files.
|
|
179
|
-
*
|
|
177
|
+
*
|
|
180
178
|
* Note: The file names are stored in the list of files written by this build plugin.
|
|
181
|
-
*
|
|
179
|
+
*
|
|
182
180
|
* @param {string} src - The absolute or relative source path of the file or directory to copy.
|
|
183
181
|
* Relative paths will be resolved to this task's source path.
|
|
184
182
|
* @returns {FileWriter}
|
|
@@ -216,7 +214,7 @@ class Plugin {
|
|
|
216
214
|
|
|
217
215
|
/**
|
|
218
216
|
* Adds the given user message and severity to the list of messages issued by this build task.
|
|
219
|
-
*
|
|
217
|
+
*
|
|
220
218
|
* User messages will be logged after CDS build has been finished based on the log-level that has been set.
|
|
221
219
|
* By default messages with severity <em>warning</em> and <em>error</em> will be logged.
|
|
222
220
|
* @param {string} message - the message text
|
|
@@ -74,7 +74,7 @@ class HanaBuildPlugin extends InternalBuildPlugin {
|
|
|
74
74
|
await this._writePackageJson()
|
|
75
75
|
}
|
|
76
76
|
|
|
77
|
-
// copy native hana artifacts from other workspaces including .hdbmigrationtable files defined for
|
|
77
|
+
// copy native hana artifacts from other workspaces including .hdbmigrationtable files defined for
|
|
78
78
|
// common model artifacts that are not part of the current project
|
|
79
79
|
if (wsPaths.length || cds.cli.options?.[OPTION_WS] || hasOptionValue(this.task.options[OPTION_WS], true)) {
|
|
80
80
|
// first is winning, e.g. a .hdiconfig file might exist in multiple workspaces
|
|
@@ -272,7 +272,7 @@ class HanaBuildPlugin extends InternalBuildPlugin {
|
|
|
272
272
|
|
|
273
273
|
for (const file of csvFiles) {
|
|
274
274
|
if (this.isStagingBuild() || file.startsWith(this.task.options.compileDest)) {
|
|
275
|
-
await stripComments(file)
|
|
275
|
+
await stripComments(file, null, this.task.options.trimCsvWhitespaces)
|
|
276
276
|
}
|
|
277
277
|
}
|
|
278
278
|
}
|
|
@@ -394,7 +394,7 @@ class HanaBuildPlugin extends InternalBuildPlugin {
|
|
|
394
394
|
}
|
|
395
395
|
}
|
|
396
396
|
// The last-dev CSN shall only be updated, if all .hdbmigrationtable files could be successfully updated.
|
|
397
|
-
// Of course, .hdbmigrationtable files already written would need to be manually reverted before the
|
|
397
|
+
// Of course, .hdbmigrationtable files already written would need to be manually reverted before the
|
|
398
398
|
// cds build command is again executed.
|
|
399
399
|
await Promise.all(promises)
|
|
400
400
|
|
|
@@ -578,7 +578,7 @@ class HanaBuildPlugin extends InternalBuildPlugin {
|
|
|
578
578
|
/**
|
|
579
579
|
* Returns all hana native artifacts located in db/src/** workspace folders.
|
|
580
580
|
* @param {Array} workspacePaths Array of workspace paths, 'cds.root' is excluded from the list
|
|
581
|
-
* @returns {Array} Array of workspace files, each entry contains the db src directory,
|
|
581
|
+
* @returns {Array} Array of workspace files, each entry contains the db src directory,
|
|
582
582
|
* the relative file path and the file name.
|
|
583
583
|
*/
|
|
584
584
|
async _getWorkspaceFiles(workspacePaths) {
|
|
@@ -278,7 +278,7 @@ module.exports = {
|
|
|
278
278
|
},
|
|
279
279
|
|
|
280
280
|
buildTaskType: {
|
|
281
|
-
enum: [
|
|
281
|
+
enum: /**@type { const } */ ([
|
|
282
282
|
"nodejs",
|
|
283
283
|
"java",
|
|
284
284
|
"hana",
|
|
@@ -288,7 +288,7 @@ module.exports = {
|
|
|
288
288
|
"node-cf",
|
|
289
289
|
"java-cf",
|
|
290
290
|
"helm"
|
|
291
|
-
],
|
|
291
|
+
]),
|
|
292
292
|
enumDescriptions: [
|
|
293
293
|
"Node.js build plugin",
|
|
294
294
|
"Java build plugin",
|
|
@@ -1620,6 +1620,11 @@ class PropertyPathExpression extends Expression {
|
|
|
1620
1620
|
const targetOption = this.getContext().getConverterOptions().target;
|
|
1621
1621
|
if (targetOption === MetadataConverter.TARGETS.LIBRARY) {
|
|
1622
1622
|
target = { [this._targetName]: this.getElement().elements[0].text };
|
|
1623
|
+
} else if (!this.getElement().elements && this._targetName === '$NavigationPropertyPath') {
|
|
1624
|
+
//elements are not given, when <NavigationPropertyPath/> is in EDMX, which is used to refresh the current context
|
|
1625
|
+
return {
|
|
1626
|
+
"=": "($self)"
|
|
1627
|
+
};
|
|
1623
1628
|
} else {
|
|
1624
1629
|
target = this.getElement().elements[0].text;
|
|
1625
1630
|
}
|
package/lib/init/constants.js
CHANGED
|
@@ -5,7 +5,7 @@ module.exports = {
|
|
|
5
5
|
COMMAND_ADD: 'add',
|
|
6
6
|
|
|
7
7
|
JAVA_LTS_VERSIONS: [17, 21],
|
|
8
|
-
MAVEN_ARCHETYPE_VERSION: '4.
|
|
8
|
+
MAVEN_ARCHETYPE_VERSION: '4.3.0',
|
|
9
9
|
|
|
10
10
|
OPTIONS: Object.freeze({
|
|
11
11
|
COMPLETION: 'completion',
|
|
@@ -60,6 +60,7 @@ module.exports = {
|
|
|
60
60
|
NOTIFICATIONS: 'notifications',
|
|
61
61
|
WORKZONE: 'workzone',
|
|
62
62
|
WORKZONE_STANDARD: 'workzone-standard',
|
|
63
|
+
UI5: 'ui5',
|
|
63
64
|
DATA: 'data',
|
|
64
65
|
HTTP: 'http',
|
|
65
66
|
LINT: 'lint',
|
|
@@ -74,7 +75,7 @@ module.exports = {
|
|
|
74
75
|
|
|
75
76
|
URLS: Object.freeze({
|
|
76
77
|
PIPELINE_HELP: 'https://www.project-piper.io/',
|
|
77
|
-
SAMPLES: 'https://github.com/
|
|
78
|
+
SAMPLES: 'https://github.com/capire/samples',
|
|
78
79
|
CAPIRE: 'https://cap.cloud.sap',
|
|
79
80
|
MAVEN_ARCHETYPE_HELP: `https://cap.cloud.sap/docs/java/developing-applications/building#the-maven-archetype`,
|
|
80
81
|
MAVEN_INSTALL_HELP: `https://maven.apache.org/install.html`,
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
const cds = require('../cds')
|
|
2
|
-
const { join, resolve, basename } = require('path')
|
|
2
|
+
const { join, resolve, basename } = require('node:path')
|
|
3
|
+
const { execSync } = require('node:child_process')
|
|
3
4
|
const { fs, exists, isfile, isdir, path } = cds.utils
|
|
4
5
|
const { parseXml } = require('./xml')
|
|
5
6
|
const { REGEX_JAVA_VERSION, JAVA_LTS_VERSIONS } = require('./constants')
|
|
@@ -9,6 +10,7 @@ module.exports = new class ProjectReader {
|
|
|
9
10
|
|
|
10
11
|
constructor() {
|
|
11
12
|
this.readProject = this.readProject.bind(this)
|
|
13
|
+
this.cachedWorkspaceRoot = null
|
|
12
14
|
}
|
|
13
15
|
|
|
14
16
|
#setPackageInfo() {
|
|
@@ -105,6 +107,9 @@ module.exports = new class ProjectReader {
|
|
|
105
107
|
* @property {boolean} hasDynatrace Indicates if the project uses Dynatrace.
|
|
106
108
|
* @property {boolean} hasCloudLogging Indicates if the project uses Cloud Logging.
|
|
107
109
|
* @property {String} extendsApp Indicates which project is extended if any.
|
|
110
|
+
* @property {String} githubSlug GitHub org/repo.
|
|
111
|
+
* @property {boolean} isMonorepoMicroservice Indicates if the project is a monorepo microservice.
|
|
112
|
+
* @property {String} npmWorkspaceRoot The root path of the monorepo workspace.
|
|
108
113
|
*/
|
|
109
114
|
|
|
110
115
|
/**
|
|
@@ -124,7 +129,6 @@ module.exports = new class ProjectReader {
|
|
|
124
129
|
const _isTypescript = () => !!pkgJson().dependencies?.typescript || !!pkgJson().devDependencies?.typescript
|
|
125
130
|
const _hasTyper = () => !!pkgJson().dependencies?.['@cap-js/cds-typer'] || !!pkgJson().devDependencies?.['@cap-js/cds-typer']
|
|
126
131
|
const _inProd = plugin => require(`./template/${plugin}`).hasInProduction(env) || cds.cli.argv.includes(plugin) || cds.cli.options.add?.has(plugin)
|
|
127
|
-
const _ui5 = () => pkgJson().sapux
|
|
128
132
|
const _uiEmbedded = () => exists(join(env.folders.app, 'xs-app.json'))
|
|
129
133
|
const _uiModule = () => !_uiEmbedded() && isdir(env.folders.app) && fs.readdirSync(resolve(cds.root, env.folders.app)).length > 0
|
|
130
134
|
const _isJava = () => this.isJava
|
|
@@ -134,6 +138,7 @@ module.exports = new class ProjectReader {
|
|
|
134
138
|
}
|
|
135
139
|
const reserved = {
|
|
136
140
|
dependencies: () => pkgJson().dependencies,
|
|
141
|
+
devDependencies: () => pkgJson().devDependencies,
|
|
137
142
|
has: x => _inProd(x),
|
|
138
143
|
profile: () => cds.cli.options?.for,
|
|
139
144
|
language: () => _isJava() ? 'java' : 'nodejs',
|
|
@@ -183,7 +188,7 @@ module.exports = new class ProjectReader {
|
|
|
183
188
|
appPath: () => env.folders.app,
|
|
184
189
|
appVersion: () => appVersion,
|
|
185
190
|
jdkVersion: () => jdkVersion,
|
|
186
|
-
approuterPath: () => (_uiEmbedded() ? join(env.folders.app) : join(env.folders.app, 'router')).replace(/\\/g, '/'),
|
|
191
|
+
approuterPath: () => (_uiEmbedded() ? join(env.folders.app) : exists('.deploy/app-router') ? join('.deploy', 'app-router') : join(env.folders.app, 'router')).replace(/\\/g, '/'),
|
|
187
192
|
appName: () => appName,
|
|
188
193
|
cleanedAppName: () => appName.replaceAll('_', '-'),
|
|
189
194
|
strippedAppName: () => appName.replace(/-/g, ''),
|
|
@@ -192,15 +197,22 @@ module.exports = new class ProjectReader {
|
|
|
192
197
|
hasUIEmbedded: _uiEmbedded,
|
|
193
198
|
hasUIModule: _uiModule,
|
|
194
199
|
hasUI: () => _uiEmbedded() || _uiModule(),
|
|
195
|
-
isUI5: _ui5,
|
|
196
|
-
hasUI5: _ui5,
|
|
197
200
|
literal: () => () => text => text,
|
|
198
201
|
hasMTXRoute: () => _inProd('extensibility') || (_inProd('multitenancy') && !_isJava()),
|
|
199
202
|
hasMTXRouteJava: () => (_inProd('helm-unified-runtime') || _inProd('helm')) && _isJava() && _inProd('multitenancy'),
|
|
200
203
|
sap: () => cds.cli.options.sap,
|
|
201
204
|
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>',
|
|
202
205
|
extendsApp: _extends,
|
|
206
|
+
npmWorkspaceRoot: () => {
|
|
207
|
+
if (this.cachedWorkspaceRoot) return this.cachedWorkspaceRoot
|
|
208
|
+
this.cachedWorkspaceRoot = execSync('npm prefix', { encoding: 'utf8', cwd: cds.root, stdio: 'pipe' }).trim()
|
|
209
|
+
return this.cachedWorkspaceRoot
|
|
210
|
+
},
|
|
211
|
+
isMonorepoMicroservice: function () {
|
|
212
|
+
return this.npmWorkspaceRoot() !== cds.root
|
|
213
|
+
},
|
|
203
214
|
hasRoles: () => {}, roles: () => {}, // for xs-security generation
|
|
215
|
+
hasCustomRoleCollections: () => exists('xs-security.json') && !!JSON.parse(fs.readFileSync(path.join(cds.root, 'xs-security.json')))['role-collections']
|
|
204
216
|
}
|
|
205
217
|
|
|
206
218
|
// Automatically creates availability checks for `cds add` commands.
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
const cds = require('../../../cds')
|
|
2
1
|
const { readProject } = require('../../projectReader')
|
|
3
2
|
const { merge, sort } = require('../../merge')
|
|
4
3
|
const { srv4 } = require('../../registries/mta')
|
|
@@ -15,7 +14,7 @@ module.exports = class AmsTemplate extends require('../../plugin') {
|
|
|
15
14
|
}
|
|
16
15
|
|
|
17
16
|
static hasInProduction(env) {
|
|
18
|
-
return env.requires?.auth?.ams ||
|
|
17
|
+
return env.requires?.auth?.ams || readProject().devDependencies?.['@sap/ams']
|
|
19
18
|
}
|
|
20
19
|
|
|
21
20
|
async run() {
|
|
@@ -19,7 +19,7 @@ module.exports = class ApprouterTemplate extends require('../../plugin') {
|
|
|
19
19
|
}
|
|
20
20
|
|
|
21
21
|
static hasInProduction(env) {
|
|
22
|
-
return exists(join(env.folders.app, 'xs-app.json')) || exists(join(env.folders.app, 'router', 'xs-app.json'))
|
|
22
|
+
return exists(join('.deploy', 'app-router')) || exists(join(env.folders.app, 'xs-app.json')) || exists(join(env.folders.app, 'router', 'xs-app.json'))
|
|
23
23
|
}
|
|
24
24
|
|
|
25
25
|
async run() {
|
|
@@ -48,11 +48,7 @@ module.exports = async (data={}, csn, { headerOnly=false, separator=',' }={}) =>
|
|
|
48
48
|
}
|
|
49
49
|
else {
|
|
50
50
|
if (i === 0) header.push(name)
|
|
51
|
-
|
|
52
|
-
value = JSON.stringify(value)
|
|
53
|
-
}
|
|
54
|
-
value = escape(value)
|
|
55
|
-
record.push(value)
|
|
51
|
+
record.push(processValue(value))
|
|
56
52
|
}
|
|
57
53
|
}
|
|
58
54
|
records.push(record.join(separator))
|
|
@@ -71,16 +67,23 @@ module.exports = async (data={}, csn, { headerOnly=false, separator=',' }={}) =>
|
|
|
71
67
|
return value
|
|
72
68
|
}
|
|
73
69
|
|
|
70
|
+
function processValue(v) {
|
|
71
|
+
if (Array.isArray(v) || typeof v === 'object') { // `array of`/`many` and `cds.Map`
|
|
72
|
+
return escape(JSON.stringify(v))
|
|
73
|
+
}
|
|
74
|
+
return escape(v)
|
|
75
|
+
}
|
|
76
|
+
|
|
74
77
|
function resolveStruct(v, prefix) {
|
|
75
78
|
return Object.entries(v).reduce((acc, [key, val]) => {
|
|
76
79
|
//structs can be within structs
|
|
77
|
-
if (typeof val === 'object') {
|
|
80
|
+
if (typeof val === 'object' && !Array.isArray(val)) {
|
|
78
81
|
const {headers, records} = resolveStruct(val, prefix + '_' + key);
|
|
79
82
|
acc.headers = acc.headers.concat(headers);
|
|
80
83
|
acc.records = acc.records.concat(records);
|
|
81
84
|
} else {
|
|
82
85
|
acc.headers.push(prefix + '_' + key);
|
|
83
|
-
acc.records.push(val);
|
|
86
|
+
acc.records.push(processValue(val));
|
|
84
87
|
}
|
|
85
88
|
return acc;
|
|
86
89
|
}, {headers: [], records: []});
|
|
@@ -125,8 +125,9 @@ function dataForField(element, artifact, csn, record, data, parent, depth, refer
|
|
|
125
125
|
|
|
126
126
|
if (element.is_struct) {
|
|
127
127
|
let struct = {}
|
|
128
|
+
Object.defineProperty(struct, '$id', {value: record['$id'], enumerable: false})
|
|
128
129
|
for (const el of element.elements) {
|
|
129
|
-
struct[el.name] = dataForField(el, artifact, csn,
|
|
130
|
+
struct[el.name] = dataForField(el, artifact, csn, struct, data, null, depth+1, referenceData)
|
|
130
131
|
}
|
|
131
132
|
return struct
|
|
132
133
|
}
|
|
@@ -176,7 +177,7 @@ function dataForField(element, artifact, csn, record, data, parent, depth, refer
|
|
|
176
177
|
|| element['@Communication.IsEmailAddress']
|
|
177
178
|
|| element['@cds.on.insert']?.['='] === '$user'
|
|
178
179
|
|| element['@cds.on.update']?.['='] === '$user') {
|
|
179
|
-
return randomEmailAddress(element
|
|
180
|
+
return randomEmailAddress(element, record['$id'], csn)
|
|
180
181
|
}
|
|
181
182
|
|
|
182
183
|
let str = `${element.name}-${record['$id']}`
|
|
@@ -450,11 +451,13 @@ function randomFromList(list) {
|
|
|
450
451
|
return list[randomNumber() % list.length]
|
|
451
452
|
}
|
|
452
453
|
|
|
453
|
-
function randomEmailAddress(
|
|
454
|
+
function randomEmailAddress(element, num, csn) {
|
|
454
455
|
let addr = fakerValue('faker.internet.email', csn, false)
|
|
455
456
|
if (addr) return addr
|
|
456
457
|
const provider = randomFromList(['example.com', 'example.org', 'example.net'])
|
|
457
|
-
|
|
458
|
+
let localPart = `${element.name}.${num.toString(36)}`
|
|
459
|
+
// -1 in substring for @
|
|
460
|
+
return (element.length ? localPart.substring(0, element.length - 1 - provider.length) : localPart) + `@${provider}`
|
|
458
461
|
}
|
|
459
462
|
|
|
460
463
|
function randomStrForRegex(regexStr, csn) {
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
const cds = require('../../../cds')
|
|
2
|
-
const { read, yaml: YAML } = cds.utils
|
|
2
|
+
const { read, exists, yaml: YAML } = cds.utils
|
|
3
3
|
const { readProject } = require('../../projectReader')
|
|
4
4
|
const { merge } = require('../../merge')
|
|
5
5
|
const { srv4, enterpriseMessaging, xsuaa } = require('../../registries/mta')
|
|
@@ -37,7 +37,6 @@ module.exports = class EnterpriseMessagingTemplate extends require('../../plugin
|
|
|
37
37
|
project.cloudevents = cds.cli.options.cloudevents
|
|
38
38
|
await merge(__dirname, 'files/package.json.hbs').into(configFile, { with: project })
|
|
39
39
|
}
|
|
40
|
-
await merge(__dirname, 'files/event-mesh.json.hbs').into('event-mesh.json', { project })
|
|
41
40
|
}
|
|
42
41
|
|
|
43
42
|
async combine() {
|
|
@@ -76,5 +75,7 @@ module.exports = class EnterpriseMessagingTemplate extends require('../../plugin
|
|
|
76
75
|
if (hasHelm || hasHelmUnifiedRuntime) {
|
|
77
76
|
await merge(__dirname, 'files', 'values.yaml').into('chart/values.yaml')
|
|
78
77
|
}
|
|
78
|
+
|
|
79
|
+
if (!exists('event-mesh.json')) await merge(__dirname, 'files/event-mesh.json.hbs').into('event-mesh.json', { project })
|
|
79
80
|
}
|
|
80
81
|
}
|