@sap/cds-dk 8.4.2 → 8.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 +27 -1
- package/bin/debug.js +90 -0
- package/bin/watch.js +12 -2
- package/lib/bind/userCdsrcJsonStorageProvider.js +0 -19
- package/lib/build/buildTaskEngine.js +1 -1
- package/lib/build/provider/hana/2migration.js +34 -20
- package/lib/build/provider/hana/index.js +74 -41
- package/lib/build/provider/internalBuildTaskProvider.js +1 -1
- package/lib/build/provider/mtx-extension/index.js +4 -4
- package/lib/build/provider/mtx-sidecar/index.js +2 -2
- package/lib/build/provider/nodejs/index.js +3 -19
- package/lib/build/util.js +41 -17
- package/lib/deploy/to-hana/hdiDeployUtil.js +14 -10
- package/lib/import/message.js +2 -1
- package/lib/import/odata/metadataconverter/MetadataConverter.js +5 -0
- package/lib/import/odata/v4parser.js +32 -43
- package/lib/init/add.js +26 -0
- package/lib/init/constants.js +4 -5
- package/lib/init/index.js +17 -25
- package/lib/init/merge.js +1 -4
- package/lib/init/projectReader.js +75 -9
- package/lib/init/registries/mta.js +0 -1
- package/lib/init/template/approuter/files/package.json +1 -1
- package/lib/init/template/cloud-logging/files/package.json.hbs +1 -12
- package/lib/init/template/cloud-logging/files/values.yaml.hbs +16 -0
- package/lib/init/template/cloud-logging/index.js +14 -10
- package/lib/init/template/data/as-json.js +1 -1
- package/lib/init/template/data/index.js +1 -26
- package/lib/init/template/esm/files/package.json.hbs +3 -0
- package/lib/init/template/esm/index.js +16 -0
- package/lib/init/template/handler/files/handler.xs.hbs +56 -0
- package/lib/init/template/handler/index.js +84 -9
- package/lib/init/template/helm/index.js +4 -1
- package/lib/init/template/http/index.js +102 -35
- package/lib/init/template/sample/files/nodejs/{admin-service.js → admin-service.js.hbs} +8 -1
- package/lib/init/template/sample/files/nodejs/{cat-service.js → cat-service.js.hbs} +8 -3
- package/lib/init/template/sample/index.js +4 -2
- package/lib/init/template/telemetry/files/package.json.hbs +18 -0
- package/lib/init/template/telemetry/index.js +8 -2
- package/lib/init/template/typescript/files/package.json +1 -1
- package/lib/init/template/workzone-standard/index.js +1 -1
- package/lib/init/types.d.ts +0 -6
- package/lib/watch/node-watch.js +25 -9
- package/npm-shrinkwrap.json +26 -31
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -7,6 +7,33 @@ This project adheres to [Semantic Versioning](http://semver.org/).
|
|
|
7
7
|
|
|
8
8
|
The format is based on [Keep a Changelog](http://keepachangelog.com/).
|
|
9
9
|
|
|
10
|
+
## Version 8.5.0 - 2024-11-26
|
|
11
|
+
|
|
12
|
+
### Changed
|
|
13
|
+
|
|
14
|
+
- `cds import` now generates generic action/function in case of bound action/function collision and unbound function collision with @cds.validate = false.
|
|
15
|
+
- `cds add typescript` creates projects with `cds-tsx` to run apps instead of former `cds-ts`.
|
|
16
|
+
- `cds deploy` writes error output to `stderr`.
|
|
17
|
+
- `cds add http` now evaluates mocks users when creating `Authorization` headers
|
|
18
|
+
|
|
19
|
+
### Added
|
|
20
|
+
|
|
21
|
+
- `cds debug` lets you debug Node.js applications running locally or in Cloud Foundry.
|
|
22
|
+
- `cds watch --inspect` and `cds watch --inspect-brk` activate the debugger in the same way as the standard Node.js CLI options, i.e. they accept a `host:port` combination and `0` as values.
|
|
23
|
+
- The existing `cds watch --debug` is now an alias to `cds watch --inspect`.
|
|
24
|
+
- `cds add cloud-logging` adds cloud-logging as an alternative for application logging in Kyma.
|
|
25
|
+
- `cds add cloud-logging --with-telemetry` or `cds add telemetry` adds cloud-logging as well as support for `cap-js/telemetry` plugin in Kyma.
|
|
26
|
+
- `cds add handler` now also works for Node.js projects. It creates an implementation file for each service with event handlers for all entites and actions.
|
|
27
|
+
- `cds add esm` creates ESM-compatible Node.js projects. Sample code added by `add sample` and `add handler` will respect this setting if added aftwards. Existing code will not be adjusted by `cds add esm`, though.
|
|
28
|
+
|
|
29
|
+
### Fixed
|
|
30
|
+
|
|
31
|
+
- `cds import` throws proper error message if Annotation element doesn't have Term attribute.
|
|
32
|
+
- `cds add approuter` no longer enforces Node.js 22, which is not supporter by current `@sap/approuter` version 17. The previous Node.js version 20 is used again.
|
|
33
|
+
- `cds add http` works for actions without parameters.
|
|
34
|
+
- `cds add workzone` includes the `updateManifestJson` task on initial generation.
|
|
35
|
+
- `cds build --ws` no longer creates migration tables in shared database or wrong workspace.
|
|
36
|
+
|
|
10
37
|
## Version 8.4.2 - 2024-11-19
|
|
11
38
|
|
|
12
39
|
### Changed
|
|
@@ -23,7 +50,6 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/).
|
|
|
23
50
|
- `cds add hana` adds the `requires.db` entry to `.cdsrc.json` for Java projects.
|
|
24
51
|
- `cds add data` creates decimal values with correct scale `0` if only precision is given, like in `Decimal(15)`.
|
|
25
52
|
|
|
26
|
-
|
|
27
53
|
## Version 8.4.1 - 2024-11-08
|
|
28
54
|
|
|
29
55
|
### Added
|
package/bin/debug.js
ADDED
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
module.exports = Object.assign(debug, {
|
|
2
|
+
options: ['--port', '--host'],
|
|
3
|
+
shortcuts: [],
|
|
4
|
+
flags: [/*'--vscode', '--k8s'*/],
|
|
5
|
+
help: `
|
|
6
|
+
# SYNOPSIS
|
|
7
|
+
|
|
8
|
+
*cds debug* [<app>]
|
|
9
|
+
|
|
10
|
+
Debugs a CAP-based application in the Chrome Developer Tools.
|
|
11
|
+
|
|
12
|
+
If <app> is given, it's assumed to be running on the currently logged-in Cloud Foundry space.
|
|
13
|
+
|
|
14
|
+
If no <app> is given, the app in the current working is started with 'cds watch --debug'.
|
|
15
|
+
|
|
16
|
+
# OPTIONS
|
|
17
|
+
|
|
18
|
+
*--host* the debug host (default: '127.0.0.1')
|
|
19
|
+
|
|
20
|
+
*--port* the debug port (default: 9229)
|
|
21
|
+
|
|
22
|
+
# EXAMPLES
|
|
23
|
+
|
|
24
|
+
*cds debug*
|
|
25
|
+
*cds debug* bookshop-srv
|
|
26
|
+
`})
|
|
27
|
+
|
|
28
|
+
async function debug() {
|
|
29
|
+
const cds = require('../lib/cds')
|
|
30
|
+
const { fork, spawn, execSync, exec } = require('node:child_process')
|
|
31
|
+
const os = require('node:os').platform()
|
|
32
|
+
|
|
33
|
+
const [appName] = cds.cli.argv
|
|
34
|
+
const { port = 9229, host = '127.0.0.1', vscode } = cds.cli.options
|
|
35
|
+
const ide = vscode ? 'vscode' : 'chrome'
|
|
36
|
+
|
|
37
|
+
if (!appName) { // local app
|
|
38
|
+
const script = process.argv[1] // `.../cds` or `.../cds-tsx`
|
|
39
|
+
console.log(`Starting '${cds.utils.path.basename(script)} watch --debug'`)
|
|
40
|
+
fork(script, ['watch', '--debug']).on('error', e => { throw 'Failed to start debug process:' + e.message })
|
|
41
|
+
} else if (cds.cli.options.k8s) { // Kyma/Kubernetes
|
|
42
|
+
// to be implemented
|
|
43
|
+
} else { // Cloud Foundry
|
|
44
|
+
const script = `ps aux | grep '[n]ode' | awk '{print \\$2}' | head -n 1`
|
|
45
|
+
const pid = execSync(`cf ssh ${appName} -c "${script}"`, { encoding: 'utf-8', shell: true }).trim()
|
|
46
|
+
if (!pid) throw `Node.js process for app '${appName}' not found`
|
|
47
|
+
execSync(`cf ssh ${appName} -c "kill -usr1 ${pid}"`, { stdio: 'inherit' })
|
|
48
|
+
console.log(`Opening SSH tunnel for CF app '${appName}'`)
|
|
49
|
+
spawn('cf', ['ssh', '-N', '-L', `${port}:${host}:${port}`, appName], { stdio: 'inherit' })
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
const maxAttempts = 15
|
|
53
|
+
let attempts = 0
|
|
54
|
+
|
|
55
|
+
const openDevTools = async () => {
|
|
56
|
+
attempts++
|
|
57
|
+
try {
|
|
58
|
+
switch (ide) {
|
|
59
|
+
case 'chrome': {
|
|
60
|
+
const res = await fetch(`http://localhost:${port}/json/list`)
|
|
61
|
+
const data = await res.json()
|
|
62
|
+
if (!data?.length) throw 'No debugger found on port ' + port
|
|
63
|
+
const wsUrl = data[0].webSocketDebuggerUrl
|
|
64
|
+
let command
|
|
65
|
+
const url = `devtools://devtools/bundled/inspector.html?ws=${wsUrl.split('ws://')[1]}`
|
|
66
|
+
console.log(`Opening Chrome DevTools at ${url}`)
|
|
67
|
+
if (os === 'darwin') {
|
|
68
|
+
command = `open -a "Google Chrome" '${url}'`
|
|
69
|
+
} else if (os === 'win32') {
|
|
70
|
+
command = `start chrome "${url}"`
|
|
71
|
+
} else {
|
|
72
|
+
command = `google-chrome '${url}' || chromium-browser '${url}' || chromium '${url}'`
|
|
73
|
+
}
|
|
74
|
+
exec(command, (error) => { if (error) throw 'Failed to open DevTools: ' + error.message })
|
|
75
|
+
break
|
|
76
|
+
}
|
|
77
|
+
case 'vscode': {
|
|
78
|
+
// to be implemented
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
} catch (err) {
|
|
82
|
+
if (attempts < maxAttempts) {
|
|
83
|
+
setTimeout(openDevTools, 1000) // connection fails sometimes, retry
|
|
84
|
+
} else {
|
|
85
|
+
throw `Failed to connect to debugger after ${maxAttempts} attempts: ${err}`
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
openDevTools()
|
|
90
|
+
}
|
package/bin/watch.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
const watchOnlyOptions = ['--ext', '--livereload', '--open']
|
|
1
|
+
const watchOnlyOptions = ['--ext', '--livereload', '--open', '--debug', '--inspect', '--inspect-brk']
|
|
2
2
|
module.exports = Object.assign ( watch, {
|
|
3
|
-
flags: [ '--
|
|
3
|
+
flags: [ '--in-memory', '--in-memory?', '--with-mocks' ],
|
|
4
4
|
options: [ '--port', ...watchOnlyOptions ],
|
|
5
5
|
help: `
|
|
6
6
|
# SYNOPSIS
|
|
@@ -42,6 +42,16 @@ module.exports = Object.assign ( watch, {
|
|
|
42
42
|
Specify from which profile(s) the binding information is taken.
|
|
43
43
|
*Example:* cds w --profile hybrid,production
|
|
44
44
|
|
|
45
|
+
*--debug* / *--inspect* <host:port | 127.0.0.1:9229>
|
|
46
|
+
|
|
47
|
+
Activate debugger on the given host:port.
|
|
48
|
+
If port 0 is specified, a random available port will be used.
|
|
49
|
+
|
|
50
|
+
*--inspect-brk* <host:port | 127.0.0.1:9229>
|
|
51
|
+
|
|
52
|
+
Activate debugger on the given host:port and break at start of user script.
|
|
53
|
+
If port 0 is specified, a random available port will be used.
|
|
54
|
+
|
|
45
55
|
# SEE ALSO
|
|
46
56
|
|
|
47
57
|
*cds serve --help* for the different start options.
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
const os = require('os');
|
|
2
2
|
const path = require('path');
|
|
3
|
-
const readline = require('readline');
|
|
4
3
|
|
|
5
4
|
const fs = require('fs').promises;
|
|
6
5
|
const { bold } = require('../util/term');
|
|
@@ -11,24 +10,6 @@ const gitUtil = require('../util/git');
|
|
|
11
10
|
const CDSRC_FILENAME = '.cdsrc-private.json';
|
|
12
11
|
|
|
13
12
|
class UserCdsrcJsonStorageProvider {
|
|
14
|
-
async askBooleanQuestion(question) {
|
|
15
|
-
const rl = readline.createInterface({
|
|
16
|
-
input: process.stdin,
|
|
17
|
-
output: process.stdout
|
|
18
|
-
});
|
|
19
|
-
|
|
20
|
-
question = question.trim() + ' ';
|
|
21
|
-
return new Promise(resolve => {
|
|
22
|
-
rl.question(question, answer => {
|
|
23
|
-
if (answer === 'y' || answer === 'Y') {
|
|
24
|
-
resolve(true);
|
|
25
|
-
} else {
|
|
26
|
-
resolve(false);
|
|
27
|
-
}
|
|
28
|
-
rl.close();
|
|
29
|
-
});
|
|
30
|
-
});
|
|
31
|
-
}
|
|
32
13
|
|
|
33
14
|
async storeServices(services, options = {}) {
|
|
34
15
|
let jsonPath = options.out || CDSRC_FILENAME;
|
|
@@ -39,7 +39,7 @@ class BuildTaskEngine {
|
|
|
39
39
|
// validate required @sap namespace models - log only
|
|
40
40
|
const unresolved = BuildTaskEngine._resolveRequiredSapServices(tasks)
|
|
41
41
|
if (unresolved.length > 0) {
|
|
42
|
-
throw new BuildError(`Required CDS models [${unresolved.join(', ')}] cannot be resolved.
|
|
42
|
+
throw new BuildError(`Required CDS models [${unresolved.join(', ')}] cannot be resolved. Run the 'npm install' command to install up-to-date versions of the missing packages.`)
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
// create build plugins
|
|
@@ -7,15 +7,15 @@ const cdscVersion = `-- generated by cds-compiler version ${cdsc.version()}`
|
|
|
7
7
|
const { getArtifactCdsPersistenceName } = cdsc
|
|
8
8
|
const LOG = cds.log('cli|build'), DEBUG = cds.debug('cli|build')
|
|
9
9
|
|
|
10
|
-
|
|
11
|
-
const { definitions, migrations, afterImage,
|
|
10
|
+
async function compileToHdbMigration(model, beforeImage, srcPath, options = {}) {
|
|
11
|
+
const { definitions, migrations, afterImage, migrationTableNames } = _compileToHana(model, options, beforeImage)
|
|
12
12
|
const definitionResult = []
|
|
13
13
|
|
|
14
14
|
for (const { name, suffix, sql } of definitions) {
|
|
15
15
|
let definitionEntry = { name, suffix, content: sql }
|
|
16
16
|
|
|
17
17
|
if (suffix === '.hdbtable') {
|
|
18
|
-
if (
|
|
18
|
+
if (migrationTableNames.has(name)) {
|
|
19
19
|
const migration = migrations.find(migration => migration.name === name)
|
|
20
20
|
definitionEntry = await _2migrationtable(srcPath, migration || _emptyMigration(name), sql)
|
|
21
21
|
}
|
|
@@ -27,6 +27,22 @@ module.exports = async (model, beforeImage, srcPath, options = {}) => {
|
|
|
27
27
|
return { definitions: definitionResult, afterImage }
|
|
28
28
|
}
|
|
29
29
|
|
|
30
|
+
function getJournalEntities(model, options = {}) {
|
|
31
|
+
return cds.reflect(model).all(item => {
|
|
32
|
+
if (item.kind === 'entity' && item['@cds.persistence.journal'] === true) {
|
|
33
|
+
if (item['@cds.persistence.skip'] === true || item['@cds.persistence.exists'] === true) {
|
|
34
|
+
options.messages?.push(new BuildMessage(`[hdbmigrationtable] annotation @cds.persistence.journal skipped for entity '${item.name}' as persistence exists`, SEVERITY_WARNING))
|
|
35
|
+
}
|
|
36
|
+
return true
|
|
37
|
+
}
|
|
38
|
+
return false
|
|
39
|
+
})
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function getMigrationTableName(entity, model) {
|
|
43
|
+
return getArtifactCdsPersistenceName(entity.name, 'quoted', model, 'hana')
|
|
44
|
+
}
|
|
45
|
+
|
|
30
46
|
function _compileToHana(model, options, beforeImage) {
|
|
31
47
|
let definitions = []
|
|
32
48
|
let migrations = []
|
|
@@ -58,20 +74,20 @@ function _compileToHana(model, options, beforeImage) {
|
|
|
58
74
|
migrations = result.migrations
|
|
59
75
|
afterImage = result.afterImage
|
|
60
76
|
}
|
|
61
|
-
let
|
|
77
|
+
let migrationTableNames = new Set()
|
|
62
78
|
if (afterImage) {
|
|
63
79
|
// 1. determine journal file names before _filterJournalArtifacts is called as getArtifactCdsPersistenceName
|
|
64
80
|
// might no longer return the proper file name for the reduced CSN, e.g. for draft entities
|
|
65
|
-
|
|
81
|
+
migrationTableNames = _getMigrationTableNames(afterImage, options)
|
|
66
82
|
|
|
67
83
|
// 2. leave only persisted journal-related entities in afterImage
|
|
68
84
|
afterImage = _filterJournalArtifacts(afterImage)
|
|
69
85
|
}
|
|
70
86
|
|
|
71
|
-
_debug(definitions, migrations,
|
|
87
|
+
_debug(definitions, migrations, migrationTableNames)
|
|
72
88
|
|
|
73
89
|
return {
|
|
74
|
-
definitions, migrations, afterImage,
|
|
90
|
+
definitions, migrations, afterImage, migrationTableNames
|
|
75
91
|
}
|
|
76
92
|
}
|
|
77
93
|
|
|
@@ -158,21 +174,13 @@ function _emptyMigration(name) {
|
|
|
158
174
|
return { name, suffix: ".hdbmigrationtable", changeset: [] }
|
|
159
175
|
}
|
|
160
176
|
|
|
161
|
-
function
|
|
162
|
-
const
|
|
163
|
-
if (item.kind === 'entity' && item['@cds.persistence.journal'] === true) {
|
|
164
|
-
if (item['@cds.persistence.skip'] === true || item['@cds.persistence.exists'] === true) {
|
|
165
|
-
options.messages?.push(new BuildMessage(`[hdbmigrationtable] annotation @cds.persistence.journal skipped for entity '${item.name}' as persistence exists`, SEVERITY_WARNING))
|
|
166
|
-
}
|
|
167
|
-
return true
|
|
168
|
-
}
|
|
169
|
-
return false
|
|
170
|
-
}).map(entity => getArtifactCdsPersistenceName(entity.name, 'quoted', model, 'hana')))
|
|
177
|
+
function _getMigrationTableNames(model, options) {
|
|
178
|
+
const migrationTableNames = getJournalEntities(model, options).map(entity => getMigrationTableName(entity, model))
|
|
171
179
|
|
|
172
|
-
DEBUG?.(`\n[hdbmigrationtable] found ${
|
|
173
|
-
DEBUG?.(`[hdbmigrationtable] ${
|
|
180
|
+
DEBUG?.(`\n[hdbmigrationtable] found ${migrationTableNames.length} model entities annotated with '@cds.persistence.journal`)
|
|
181
|
+
DEBUG?.(`[hdbmigrationtable] ${migrationTableNames.join(', ')}\n`)
|
|
174
182
|
|
|
175
|
-
return
|
|
183
|
+
return new Set(migrationTableNames)
|
|
176
184
|
}
|
|
177
185
|
|
|
178
186
|
// only keep journal related entities
|
|
@@ -217,3 +225,9 @@ ${migration ? migration.changeset.map(change => change.sql).join('\n') : 'Empty
|
|
|
217
225
|
}
|
|
218
226
|
}
|
|
219
227
|
}
|
|
228
|
+
|
|
229
|
+
module.exports = {
|
|
230
|
+
compileToHdbMigration,
|
|
231
|
+
getJournalEntities,
|
|
232
|
+
getMigrationTableName
|
|
233
|
+
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
const fs = require('fs')
|
|
2
2
|
const path = require('path')
|
|
3
3
|
const cds = require('../../../cds')
|
|
4
|
-
const
|
|
4
|
+
const { compileToHdbMigration, getJournalEntities, getMigrationTableName } = require('./2migration')
|
|
5
5
|
const InternalBuildPlugin = require('../internalBuildPlugin')
|
|
6
|
-
const { BuildError, relativePaths, BuildMessage,
|
|
6
|
+
const { BuildError, relativePaths, BuildMessage, hasOptionValue, getFiles, getWorkspacePaths } = require('../../util')
|
|
7
7
|
const { OUTPUT_MODE_NONE, OUTPUT_MODE, CONTENT_PACKAGE_JSON, CONTENT_HDBTABLEDATA, CSV_FILE_DETECTION,
|
|
8
8
|
CONTENT_ENV, CONTENT_DEFAULT_ENV_JSON, CONTENT_NODE_MODULES, OUTPUT_MODE_RESULT, CONTINUE_UNRESOLVED_SCHEMA_CHANGES,
|
|
9
9
|
CSV_FILE_TARGET, OPTION_WS } = require('../../constants')
|
|
@@ -38,14 +38,15 @@ class HanaBuildPlugin extends InternalBuildPlugin {
|
|
|
38
38
|
if (!model) {
|
|
39
39
|
return
|
|
40
40
|
}
|
|
41
|
-
let
|
|
42
|
-
if
|
|
43
|
-
|
|
41
|
+
let wsPaths = []
|
|
42
|
+
if(getJournalEntities(model).length || cds.cli.options?.[OPTION_WS] || hasOptionValue(this.task.options[OPTION_WS], true)) {
|
|
43
|
+
// workspace paths don't include the current project root
|
|
44
|
+
wsPaths = await getWorkspacePaths()
|
|
44
45
|
}
|
|
45
46
|
|
|
46
47
|
// the order of 1 and 2 is important
|
|
47
48
|
// 1. compile
|
|
48
|
-
const hdiPlugins = await this._compileToHana(model,
|
|
49
|
+
const hdiPlugins = await this._compileToHana(model, wsPaths)
|
|
49
50
|
|
|
50
51
|
if (!this.hasBuildOption(OUTPUT_MODE, OUTPUT_MODE_NONE)) {
|
|
51
52
|
// 2. copy resources
|
|
@@ -54,16 +55,6 @@ class HanaBuildPlugin extends InternalBuildPlugin {
|
|
|
54
55
|
} else {
|
|
55
56
|
await this._copyResourcesExt(src, dest, model)
|
|
56
57
|
}
|
|
57
|
-
// copy native hana artifacts from workspaces
|
|
58
|
-
if (workspaceFiles) {
|
|
59
|
-
const dest = path.join(this.task.dest, this.isStagingBuild() ? 'src' : 'src/gen')
|
|
60
|
-
await Promise.all(
|
|
61
|
-
workspaceFiles.map((src) => {
|
|
62
|
-
const { dir, filePath } = src
|
|
63
|
-
return this.copy(path.join(dir, filePath)).to(path.join(dest, filePath))
|
|
64
|
-
})
|
|
65
|
-
)
|
|
66
|
-
}
|
|
67
58
|
|
|
68
59
|
// create additional stuff in dest
|
|
69
60
|
await this._writeHdiConfig(hdiPlugins)
|
|
@@ -78,6 +69,28 @@ class HanaBuildPlugin extends InternalBuildPlugin {
|
|
|
78
69
|
if (!this.hasBuildOption(CONTENT_PACKAGE_JSON, false)) {
|
|
79
70
|
await this._writePackageJson()
|
|
80
71
|
}
|
|
72
|
+
|
|
73
|
+
// copy native hana artifacts from other workspaces including .hdbmigrationtable files defined for
|
|
74
|
+
// common model artifacts that are not part of the current project
|
|
75
|
+
if (wsPaths.length || cds.cli.options?.[OPTION_WS] || hasOptionValue(this.task.options[OPTION_WS], true)) {
|
|
76
|
+
// first is winning, e.g. a .hdiconfig file might exist in multiple workspaces
|
|
77
|
+
const wsFiles = (await this._getWorkspaceFiles(wsPaths)).reduce((acc, src) => {
|
|
78
|
+
const { filePath } = src;
|
|
79
|
+
if (!acc.some(file => file.filePath === filePath)) {
|
|
80
|
+
acc.push(src);
|
|
81
|
+
}
|
|
82
|
+
return acc;
|
|
83
|
+
}, []);
|
|
84
|
+
|
|
85
|
+
// copy artifacts to the src/gen folder in inplace mode
|
|
86
|
+
const dest = path.join(this.task.dest, this.isStagingBuild() ? 'src' : 'src/gen')
|
|
87
|
+
await Promise.all(
|
|
88
|
+
wsFiles.map((src) => {
|
|
89
|
+
const { dir, filePath } = src
|
|
90
|
+
return this.copy(path.join(dir, filePath)).to(path.join(dest, filePath))
|
|
91
|
+
})
|
|
92
|
+
)
|
|
93
|
+
}
|
|
81
94
|
}
|
|
82
95
|
return this._result
|
|
83
96
|
}
|
|
@@ -264,20 +277,18 @@ class HanaBuildPlugin extends InternalBuildPlugin {
|
|
|
264
277
|
*
|
|
265
278
|
* @param {object} model The compiled csn model
|
|
266
279
|
*/
|
|
267
|
-
async _compileToHana(model,
|
|
280
|
+
async _compileToHana(model, wsPaths) {
|
|
268
281
|
// compile to old format (.hdbcds) or new format (.hdbtable / .hdbview)
|
|
269
282
|
const format = this.getBuildOption(DEPLOY_FORMAT) || cds.env.requires.db?.[DEPLOY_FORMAT] || cds.env.hana?.[DEPLOY_FORMAT]
|
|
270
283
|
if (!cds.compile.to[format]) {
|
|
271
284
|
return Promise.reject(new Error(`Invalid deploy-format defined: ${format}`))
|
|
272
285
|
}
|
|
273
286
|
|
|
274
|
-
if (cds.env.features.journal === false
|
|
275
|
-
|| format === 'hdbcds'
|
|
276
|
-
|| (!cds.compile.to.hana && Object.values(model.definitions).every(def => !def['@cds.persistence.journal']))) {
|
|
287
|
+
if (cds.env.features.journal === false || format === 'hdbcds') {
|
|
277
288
|
// compatibility with existing cds.compile.to.hdbtable plugins cds < 8.0.0
|
|
278
289
|
return await this._compileToHdb(model, format)
|
|
279
290
|
} else {
|
|
280
|
-
return await this._compileToHdbmigration(model, format,
|
|
291
|
+
return await this._compileToHdbmigration(model, format, wsPaths)
|
|
281
292
|
}
|
|
282
293
|
}
|
|
283
294
|
|
|
@@ -313,7 +324,7 @@ class HanaBuildPlugin extends InternalBuildPlugin {
|
|
|
313
324
|
return hdiPlugins
|
|
314
325
|
}
|
|
315
326
|
|
|
316
|
-
async _compileToHdbmigration(model, format,
|
|
327
|
+
async _compileToHdbmigration(model, format, wsPaths) {
|
|
317
328
|
const hdiPlugins = new Set()
|
|
318
329
|
const relDestDir = path.relative(this.task.dest, this.task.options.compileDest)
|
|
319
330
|
const relDbDestDir = path.relative(cds.root, this.task.options.compileDest)
|
|
@@ -328,19 +339,30 @@ class HanaBuildPlugin extends InternalBuildPlugin {
|
|
|
328
339
|
if (fs.existsSync(lastDevCsnDir)) {
|
|
329
340
|
lastDev = JSON.parse((await fs.promises.readFile(lastDevCsnDir, 'utf-8')).toString())
|
|
330
341
|
}
|
|
342
|
+
|
|
331
343
|
// enforces sqlNames option for compiler in tests, pass options from cds env, ensures that the correct format is taken
|
|
332
344
|
const options = { ...this.options(), messages: this.messages, sql_mapping: cds.env.sql.names, "deploy-format": format }
|
|
333
|
-
|
|
334
|
-
const { definitions, afterImage } = await to_hdbmigration(model, lastDev, dbSrcDir, options)
|
|
345
|
+
const { definitions, afterImage } = await compileToHdbMigration(model, lastDev, dbSrcDir, options)
|
|
335
346
|
let validationError
|
|
336
347
|
|
|
348
|
+
// determine the migration tables originating from a different workspaces
|
|
349
|
+
const journalEntities = getJournalEntities(model)
|
|
350
|
+
const wsMigrationTables = new Set()
|
|
351
|
+
if (journalEntities.length) {
|
|
352
|
+
for (const entity of journalEntities) {
|
|
353
|
+
if (entity['$location']?.file && wsPaths.some(wsPath => path.resolve(cds.root, entity['$location'].file).startsWith(wsPath))) {
|
|
354
|
+
wsMigrationTables.add(getMigrationTableName(entity, model))
|
|
355
|
+
}
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
337
359
|
for (const { name, suffix, content, changed } of definitions) {
|
|
338
360
|
hdiPlugins.add(suffix)
|
|
339
361
|
const fileName = name + suffix
|
|
340
362
|
if (suffix === FILE_EXT_HDBMIGRATIONTABLE) {
|
|
341
|
-
//
|
|
342
|
-
//
|
|
343
|
-
if (!
|
|
363
|
+
// do not create .hdbmigrationtable files originating from a different workspace
|
|
364
|
+
// the --ws build option will copy these artifacts to either gen/db/src or db/src/gen
|
|
365
|
+
if (!wsMigrationTables.has(name)) {
|
|
344
366
|
migrationTableFiles.push(path.join(relDbSrcDir, fileName))
|
|
345
367
|
if (changed) {
|
|
346
368
|
if (this.hasBuildOption(OUTPUT_MODE, OUTPUT_MODE_RESULT)) {
|
|
@@ -350,6 +372,8 @@ class HanaBuildPlugin extends InternalBuildPlugin {
|
|
|
350
372
|
} else {
|
|
351
373
|
DEBUG?.(`no change, keep existing ${fileName}`)
|
|
352
374
|
}
|
|
375
|
+
} else {
|
|
376
|
+
DEBUG?.(`skip generation of migration table [${fileName}] as it originates from a different workspace`)
|
|
353
377
|
}
|
|
354
378
|
} else {
|
|
355
379
|
if (this.hasBuildOption(OUTPUT_MODE, OUTPUT_MODE_RESULT)) {
|
|
@@ -547,21 +571,26 @@ class HanaBuildPlugin extends InternalBuildPlugin {
|
|
|
547
571
|
|
|
548
572
|
/**
|
|
549
573
|
* Returns all hana native artifacts located in db/src/** workspace folders.
|
|
574
|
+
* @param {Array} workspacePaths Array of workspace paths, 'cds.root' is excluded from the list
|
|
550
575
|
* @returns {Array} Array of workspace files, each entry contains the db src directory,
|
|
551
576
|
* the relative file path and the file name.
|
|
552
577
|
*/
|
|
553
|
-
async _getWorkspaceFiles() {
|
|
578
|
+
async _getWorkspaceFiles(workspacePaths) {
|
|
579
|
+
if (!workspacePaths || !workspacePaths.length) {
|
|
580
|
+
return []
|
|
581
|
+
}
|
|
554
582
|
const dbPaths = new Set()
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
583
|
+
workspacePaths.forEach(workspacePath => {
|
|
584
|
+
// skip workspace matching the current project if cds build is executed in a workspace folder
|
|
585
|
+
if (cds.root !== workspacePath) {
|
|
586
|
+
const dbSrc = path.join(workspacePath, 'db/src')
|
|
587
|
+
const dbCfg = path.join(workspacePath, 'db/cfg')
|
|
588
|
+
if (fs.existsSync(dbSrc)) {
|
|
589
|
+
dbPaths.add(dbSrc)
|
|
590
|
+
}
|
|
591
|
+
if (fs.existsSync(dbCfg)) {
|
|
592
|
+
dbPaths.add(dbCfg)
|
|
593
|
+
}
|
|
565
594
|
}
|
|
566
595
|
})
|
|
567
596
|
const workspaceFiles = [...dbPaths].reduce((acc, src) => {
|
|
@@ -572,11 +601,15 @@ class HanaBuildPlugin extends InternalBuildPlugin {
|
|
|
572
601
|
}
|
|
573
602
|
return true
|
|
574
603
|
})
|
|
575
|
-
files.forEach(file => acc.
|
|
604
|
+
files.forEach(file => acc.push({ dir: src, filePath: path.relative(src, file), fileName: path.basename(file) }))
|
|
576
605
|
return acc
|
|
577
|
-
},
|
|
606
|
+
}, [])
|
|
607
|
+
|
|
608
|
+
return workspaceFiles
|
|
609
|
+
}
|
|
578
610
|
|
|
579
|
-
|
|
611
|
+
_hasJournalDefinitions(model) {
|
|
612
|
+
return Object.values(model.definitions).some(def => def['@cds.persistence.journal'])
|
|
580
613
|
}
|
|
581
614
|
|
|
582
615
|
static async _readTemplateAsJson(template) {
|
|
@@ -167,7 +167,7 @@ class InternalBuildTaskProvider extends BuildTaskProvider {
|
|
|
167
167
|
if (!sidecarEnv.requires["cds.xt.ModelProviderService"]?._in_sidecar) {
|
|
168
168
|
if (sidecarEnv.profiles?.includes('mtx-sidecar')) {
|
|
169
169
|
// profile has been set, but cannot be resolved
|
|
170
|
-
throw new BuildError("MTX configuration cannot be resolved.
|
|
170
|
+
throw new BuildError("MTX configuration cannot be resolved. Run the 'npm install' command to install up-to-date versions of @sap/cds-mtxs and @sap/cds.")
|
|
171
171
|
}
|
|
172
172
|
throw new BuildError(`Invalid MTX sidecar configuration - profile 'mtx-sidecar' not set.`)
|
|
173
173
|
}
|
|
@@ -85,13 +85,13 @@ class MtxExtensionBuildPlugin extends InternalBuildPlugin {
|
|
|
85
85
|
// existence already checked
|
|
86
86
|
await this.copy(path.join(cds.root, 'package.json')).to(path.join(destExt, 'package.json'))
|
|
87
87
|
|
|
88
|
-
// copy handlers
|
|
89
|
-
const
|
|
88
|
+
// copy handlers from srv folder
|
|
89
|
+
const folder = path.join(cds.root, cds.env.folders.srv).replace(/[/\\]$/, '')
|
|
90
90
|
await this.copyNativeContent(cds.root, destExt, res => {
|
|
91
91
|
if (fs.statSync(res).isDirectory()) {
|
|
92
|
-
return
|
|
92
|
+
return res.startsWith(folder)
|
|
93
93
|
}
|
|
94
|
-
if (
|
|
94
|
+
if (path.dirname(res).startsWith(folder) && /\.js$/.test(res)) {
|
|
95
95
|
return true
|
|
96
96
|
}
|
|
97
97
|
})
|
|
@@ -118,7 +118,7 @@ class MtxSidecarBuildPlugin extends NodejsBuildPlugin {
|
|
|
118
118
|
const modelFilePaths = cds.resolve(modelPaths)
|
|
119
119
|
|
|
120
120
|
if (!modelFilePaths || modelFilePaths.length === 0) {
|
|
121
|
-
throw new BuildError("No CDS models found in MTX sidecar.
|
|
121
|
+
throw new BuildError("No CDS models found in MTX sidecar. Run the 'npm install' command to install up-to-date versions of the required packages.")
|
|
122
122
|
}
|
|
123
123
|
DEBUG?.(`sidecar model: ${relativePaths(cds.root, modelFilePaths).join(", ")}`)
|
|
124
124
|
|
|
@@ -126,7 +126,7 @@ class MtxSidecarBuildPlugin extends NodejsBuildPlugin {
|
|
|
126
126
|
const unresolved = resolveRequiredSapModels(modelPaths)
|
|
127
127
|
if (unresolved.length > 0) {
|
|
128
128
|
// log error, but don't fail
|
|
129
|
-
this.pushMessage(`CDS models [${unresolved.join(', ')}] required by MTX sidecar cannot be resolved.
|
|
129
|
+
this.pushMessage(`CDS models [${unresolved.join(', ')}] required by MTX sidecar cannot be resolved. Run the 'npm install' command to install up-to-date versions of the required packages.`, ERROR)
|
|
130
130
|
}
|
|
131
131
|
|
|
132
132
|
// using synchronous compilation as cds.root and cds.env have been switched to mtx/sidecar
|
|
@@ -3,7 +3,7 @@ const path = require('path')
|
|
|
3
3
|
const cds = require('../../../cds')
|
|
4
4
|
const cmd = require('../../../util/command');
|
|
5
5
|
const EdmxBuildPlugin = require('../edmxBuildPlugin')
|
|
6
|
-
const { BuildError, getWorkspaces } = require('../../util')
|
|
6
|
+
const { BuildError, getWorkspaces, findWorkspaceRoot } = require('../../util')
|
|
7
7
|
const { OUTPUT_MODE, OUTPUT_MODE_FILESYSTEM, ODATA_VERSION_V2, FOLDER_GEN, CONTENT_EDMX, CONTENT_PACKAGELOCK_JSON,
|
|
8
8
|
CONTENT_NPMRC, CONTENT_CDSRC_JSON, CONTENT_ENV, CONTENT_DEFAULT_ENV_JSON, FLAVOR_LOCALIZED_EDMX,
|
|
9
9
|
OPTION_WS_PACK} = require('../../constants')
|
|
@@ -160,14 +160,14 @@ class NodejsBuildPlugin extends EdmxBuildPlugin {
|
|
|
160
160
|
}
|
|
161
161
|
|
|
162
162
|
async _packWorkspaceDependencies(dest) {
|
|
163
|
-
const wsRoot =
|
|
163
|
+
const wsRoot = findWorkspaceRoot(cds.root)
|
|
164
164
|
const appPkg = require(path.join(dest, 'package.json'))
|
|
165
165
|
let workspaces
|
|
166
166
|
if (this.task.options.workspaces) {
|
|
167
167
|
workspaces = Array.isArray(this.task.options.workspaces) ? this.task.options.workspaces : [this.task.options.workspaces]
|
|
168
168
|
} else {
|
|
169
169
|
// devDependencies are not considered
|
|
170
|
-
workspaces = await getWorkspaces(true, false
|
|
170
|
+
workspaces = await getWorkspaces(wsRoot, true, false);
|
|
171
171
|
}
|
|
172
172
|
if (workspaces.length) {
|
|
173
173
|
let pkgDescriptors
|
|
@@ -197,22 +197,6 @@ class NodejsBuildPlugin extends EdmxBuildPlugin {
|
|
|
197
197
|
}
|
|
198
198
|
}
|
|
199
199
|
|
|
200
|
-
static _findWorkspaceRoot(dir) {
|
|
201
|
-
try {
|
|
202
|
-
if (require(path.join(dir, 'package.json')).workspaces) {
|
|
203
|
-
return dir
|
|
204
|
-
}
|
|
205
|
-
} catch (err) {
|
|
206
|
-
// ignore
|
|
207
|
-
}
|
|
208
|
-
if (fs.existsSync(path.join(dir, '.gitmodules'))) {
|
|
209
|
-
return // project root reached
|
|
210
|
-
}
|
|
211
|
-
if (path.dirname(dir) !== dir) {
|
|
212
|
-
return this._findWorkspaceRoot(path.dirname(dir))
|
|
213
|
-
}
|
|
214
|
-
}
|
|
215
|
-
|
|
216
200
|
static async _execNpmPack(dest, wsRoot, workspaces) {
|
|
217
201
|
const args = ['pack', '-ws', '--json', `--pack-destination=${dest}`]
|
|
218
202
|
workspaces.forEach(w => args.push(`-w=${w}`))
|