@sap/cds-dk 8.4.2 → 8.5.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 +41 -3
- package/bin/cds.js +49 -28
- package/bin/debug.js +90 -0
- package/bin/repl.js +50 -4
- package/bin/watch.js +13 -3
- package/lib/bind/userCdsrcJsonStorageProvider.js +0 -19
- package/lib/build/buildTaskEngine.js +4 -4
- 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/cds.js +10 -2
- 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 +5 -6
- package/lib/init/index.js +17 -25
- package/lib/init/merge.js +2 -5
- package/lib/init/projectReader.js +76 -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/html5-repo/files/xs-app.json.hbs +1 -0
- package/lib/init/template/html5-repo/index.js +2 -0
- package/lib/init/template/http/index.js +102 -35
- package/lib/init/template/nodejs/files/package.json.hbs +1 -1
- 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 +2 -2
- 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 +63 -48
- package/package.json +1 -1
|
@@ -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}`))
|
package/lib/build/util.js
CHANGED
|
@@ -7,6 +7,7 @@ const IS_WIN = require('os').platform() === 'win32'
|
|
|
7
7
|
const { SEVERITY_ERROR, FILE_EXT_CDS } = require('./constants');
|
|
8
8
|
const DEBUG = cds.debug('cli|build')
|
|
9
9
|
const SECRETS = /(passw)|(cert)|(ca)|(secret)|(key)/i
|
|
10
|
+
const WS_MAX_DEPTH = 2
|
|
10
11
|
|
|
11
12
|
function getProperty(src, segments) {
|
|
12
13
|
segments = Array.isArray(segments) ? segments : segments.split('.')
|
|
@@ -98,13 +99,13 @@ function _redacted(cred) {
|
|
|
98
99
|
if (!cred) return cred
|
|
99
100
|
if (Array.isArray(cred)) return cred.map(c => typeof c === 'string' ? '...' : _redacted(c))
|
|
100
101
|
if (typeof cred === 'object') {
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
102
|
+
const newCred = Object.assign({}, cred)
|
|
103
|
+
Object.keys(newCred).forEach(k => (typeof newCred[k] === 'string' && SECRETS.test(k)) ? (newCred[k] = '...') : (newCred[k] = _redacted(newCred[k])))
|
|
104
|
+
return newCred
|
|
104
105
|
}
|
|
105
106
|
return cred
|
|
106
107
|
}
|
|
107
|
-
|
|
108
|
+
|
|
108
109
|
/**
|
|
109
110
|
* Returns a list of fully qualified model names belonging to the '@sap' namespace that cannot be resolved.
|
|
110
111
|
* E.g. the module might NOT have been installed.
|
|
@@ -173,28 +174,30 @@ async function pathExists(path) {
|
|
|
173
174
|
return fs.promises.access(path).then(() => true).catch(() => false);
|
|
174
175
|
}
|
|
175
176
|
|
|
177
|
+
async function getWorkspacePaths() {
|
|
178
|
+
const wsRoot = findWorkspaceRoot(cds.root)
|
|
179
|
+
if (!wsRoot) {
|
|
180
|
+
return []
|
|
181
|
+
}
|
|
182
|
+
return (await getWorkspaces(wsRoot)).filter(ws => cds.root !== path.join(wsRoot, ws)).map(ws => path.join(wsRoot, ws))
|
|
183
|
+
}
|
|
184
|
+
|
|
176
185
|
/**
|
|
177
186
|
* Returns the list of workspaces defined for the current project. A workspace dependency
|
|
178
187
|
* needs to be defined resulting in a corresponding sym-link.
|
|
179
|
-
* @returns the paths are relative to '
|
|
188
|
+
* @returns the paths are relative to 'wsRoot'.
|
|
180
189
|
*/
|
|
181
|
-
async function getWorkspaces(resolve = false, devDependencies = true
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
} catch (e) {
|
|
186
|
-
if (e.code === 'MODULE_NOT_FOUND') {
|
|
187
|
-
DEBUG?.(`No package.json found in project '${wsRoot}', skipping workspace resolution.`)
|
|
188
|
-
return []
|
|
189
|
-
}
|
|
190
|
-
throw e
|
|
190
|
+
async function getWorkspaces(wsRoot, resolve = false, devDependencies = true) {
|
|
191
|
+
wsRoot ??= findWorkspaceRoot(cds.root)
|
|
192
|
+
if (!wsRoot) {
|
|
193
|
+
return []
|
|
191
194
|
}
|
|
192
|
-
|
|
193
|
-
const workspaces = []
|
|
195
|
+
const pgkJsonWsRoot = require(path.join(wsRoot, 'package.json'))
|
|
194
196
|
let pgkJson = pgkJsonWsRoot
|
|
195
197
|
if (wsRoot !== cds.root) {
|
|
196
198
|
pgkJson = require(path.join(cds.root, 'package.json'))
|
|
197
199
|
}
|
|
200
|
+
const workspaces = []
|
|
198
201
|
if (pgkJsonWsRoot.workspaces) {
|
|
199
202
|
if (resolve) {
|
|
200
203
|
_validateWsDependencies(wsRoot, pgkJson.dependencies, devDependencies ? pgkJson.devDependencies : {})
|
|
@@ -215,6 +218,25 @@ async function getWorkspaces(resolve = false, devDependencies = true, wsRoot = c
|
|
|
215
218
|
return workspaces
|
|
216
219
|
}
|
|
217
220
|
|
|
221
|
+
function findWorkspaceRoot(currentPath, depth = 0) {
|
|
222
|
+
if (depth > WS_MAX_DEPTH) {
|
|
223
|
+
DEBUG?.(`No workspaces exist, max depth of ${WS_MAX_DEPTH} reached`)
|
|
224
|
+
return
|
|
225
|
+
}
|
|
226
|
+
const packageJson = path.join(currentPath, 'package.json')
|
|
227
|
+
if (fs.existsSync(packageJson)) {
|
|
228
|
+
if (require(path.join(currentPath, 'package.json')).workspaces) {
|
|
229
|
+
return currentPath
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
if (fs.existsSync(path.join(currentPath, '.gitmodules'))) {
|
|
233
|
+
return // project root reached
|
|
234
|
+
}
|
|
235
|
+
if (path.dirname(currentPath) !== currentPath) {
|
|
236
|
+
return findWorkspaceRoot(path.dirname(currentPath), depth + 1)
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
218
240
|
/**
|
|
219
241
|
* This JavaScript function filterFeaturePaths is used to filter and categorize file paths
|
|
220
242
|
* from a given model into two categories: base and features.
|
|
@@ -452,7 +474,9 @@ module.exports = {
|
|
|
452
474
|
getI18nDefaultFolder,
|
|
453
475
|
normalizePath,
|
|
454
476
|
pathExists,
|
|
477
|
+
getWorkspacePaths,
|
|
455
478
|
getWorkspaces,
|
|
479
|
+
findWorkspaceRoot,
|
|
456
480
|
filterFeaturePaths,
|
|
457
481
|
getFiles,
|
|
458
482
|
BuildMessage,
|
package/lib/cds.js
CHANGED
|
@@ -6,11 +6,19 @@ module.exports = Object.defineProperties (cds, {
|
|
|
6
6
|
// for internal use in cds-dk only
|
|
7
7
|
_log: { value: _local ('../bin/util/log'), configurable: true },
|
|
8
8
|
_local: { value: _local, configurable: true },
|
|
9
|
+
_localOrGlobal: { value: id => _local(id, _npmRoot()), configurable: true },
|
|
9
10
|
})
|
|
10
11
|
|
|
11
|
-
function _local (id) {
|
|
12
|
+
function _local (id, ...morePaths) {
|
|
12
13
|
return require(require.resolve (id, {paths:[
|
|
13
14
|
process.cwd(), // project-local module is preferred
|
|
14
|
-
__dirname // otherwise from our own dependencies
|
|
15
|
+
__dirname, // otherwise from our own dependencies
|
|
16
|
+
...morePaths
|
|
15
17
|
]}))
|
|
16
18
|
}
|
|
19
|
+
|
|
20
|
+
function _npmRoot() {
|
|
21
|
+
try {
|
|
22
|
+
return require ('child_process').execSync('npm root -g').toString().trim()
|
|
23
|
+
} catch { return '' }
|
|
24
|
+
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
const path = require('path');
|
|
2
|
-
const util = require('util');
|
|
3
2
|
const { bold } = require('../../util/term');
|
|
4
3
|
|
|
5
4
|
const HDI_DEPLOY_LIB = '@sap/hdi-deploy';
|
|
@@ -40,15 +39,20 @@ module.exports = new class HdiDeployUtil {
|
|
|
40
39
|
if (options.use_hdb === false) options.use_hdb = undefined
|
|
41
40
|
deployerEnv.HDI_DEPLOY_OPTIONS = JSON.stringify(options)
|
|
42
41
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
42
|
+
return await new Promise((resolve, reject) => {
|
|
43
|
+
deploy(dbDir, deployerEnv, (error, response) => {
|
|
44
|
+
if (error) return reject(error)
|
|
45
|
+
if (response?.exitCode) {
|
|
46
|
+
let message = `HDI deployment failed with exit code ${response.exitCode}.`
|
|
47
|
+
if (response.signal) message += `. ${response.signal}`
|
|
48
|
+
return reject(new Error(message))
|
|
49
|
+
}
|
|
50
|
+
return resolve()
|
|
51
|
+
}, {
|
|
52
|
+
stderrCB: buffer => console.error(buffer.toString()),
|
|
53
|
+
stdoutCB: buffer => console.log(buffer.toString())
|
|
54
|
+
})
|
|
55
|
+
})
|
|
52
56
|
}
|
|
53
57
|
|
|
54
58
|
_loadHdiDeployLib(cwd) {
|
package/lib/import/message.js
CHANGED
|
@@ -28,7 +28,7 @@ const messages = {
|
|
|
28
28
|
BOUND_ACTION_COLLISION: 'The combination of Action name and binding parameter type must be unique for Bound Action ',
|
|
29
29
|
STREAM_NO_COLLECTION: `'Edm.Stream' cannot be of Collection type.`,
|
|
30
30
|
UNSUPPORTED_PROPERTY_TYPE: 'Property cannot be of EntityType. Check the type: ',
|
|
31
|
-
|
|
31
|
+
UNRESOLVED_SCHEMA_NAMESPACE: `Could not resolve the schema `,
|
|
32
32
|
INVALID_DATATYPE: `Invalid Data Type `,
|
|
33
33
|
ELEMENT_NAME_COLLISION: 'Collision between EntityContainer Element Name and Schema Element Name is currently not supported. Please check the element name: ',
|
|
34
34
|
NO_DEFAULT_FOR_COLLECTIONS: 'WARNING: Collection type cannot have a Default value.',
|
|
@@ -38,6 +38,7 @@ const messages = {
|
|
|
38
38
|
NULLABLE_KEY: 'WARNING: Key element cannot be Nullable.',
|
|
39
39
|
COLLISION_DETECTED: `INFO: Element name collision detected. Adding suffix to affected type category.`,
|
|
40
40
|
SPECIFY_CONFIG: 'For --config option, please specify correct JSON Object.',
|
|
41
|
+
INVALID_ANNOTATION: 'Annotation element must have a Term attribute',
|
|
41
42
|
};
|
|
42
43
|
|
|
43
44
|
function getMessages() {
|
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
|
+
|
|
3
|
+
const message = require("../../message").getMessages();
|
|
2
4
|
|
|
3
5
|
const vocabulariesMap = new Map([
|
|
4
6
|
['org.odata.core.v1', () => require('../vocabularies/Org.OData.Core.V1.json')],
|
|
@@ -1754,6 +1756,9 @@ class AnnotationExpression extends Expression {
|
|
|
1754
1756
|
annotate(result) { super.annotate(this.getTargetPropertyName(), this.getTarget(), result); }
|
|
1755
1757
|
interpret() {
|
|
1756
1758
|
const element = this.getElement();
|
|
1759
|
+
if(!Object.hasOwn(element, "attributes") || !Object.hasOwn(element.attributes, "Term")) {
|
|
1760
|
+
throw new Error(message.INVALID_ANNOTATION);
|
|
1761
|
+
}
|
|
1757
1762
|
let name = element.attributes.Term;
|
|
1758
1763
|
if (element.attributes.Qualifier) name += '#' + element.attributes.Qualifier;
|
|
1759
1764
|
name = '@' + name;
|
|
@@ -4,10 +4,10 @@
|
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
6
|
"use strict";
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
const messages = require("../message").getMessages();
|
|
8
|
+
const common = require("../common");
|
|
9
|
+
const versionInfo = require("../../../package.json").version;
|
|
10
|
+
const Multimap = require("./util/MultiMap");
|
|
11
11
|
const ignoreObjects = ["$Reference"]; // add all the objects that needs to be ignored for parsing.
|
|
12
12
|
const { warn, info } = require("../../util/term");
|
|
13
13
|
const MetadataConverterFactory = require("./metadataconverter/MetadataConverterFactory");
|
|
@@ -428,6 +428,11 @@ function _parseAnnotationTerms(annotationTerm, annotationTermObject, csnDefsTarg
|
|
|
428
428
|
function _validateAnnotationNamespace(annotationTerm, vocabNamespaceToAlias) {
|
|
429
429
|
let aliasOrNamespace = annotationTerm.substring(1, annotationTerm.lastIndexOf('.'));
|
|
430
430
|
|
|
431
|
+
// This check is for ignoring the @cds annotations given for special cases, e.g. @cds.validate = false for collided action/function
|
|
432
|
+
if (aliasOrNamespace === "cds") {
|
|
433
|
+
return
|
|
434
|
+
}
|
|
435
|
+
|
|
431
436
|
for (let alias of Object.values(known_vocabularies)) {
|
|
432
437
|
if (alias === aliasOrNamespace) {
|
|
433
438
|
return;
|
|
@@ -1358,29 +1363,6 @@ function _parseEntityContainer(csnDefs, entityContainerElements, parserContext)
|
|
|
1358
1363
|
return csnDefs;
|
|
1359
1364
|
}
|
|
1360
1365
|
|
|
1361
|
-
/**
|
|
1362
|
-
* @param {List<Object>} dataObjList, action/function object list
|
|
1363
|
-
* @returns unique parameter list
|
|
1364
|
-
*/
|
|
1365
|
-
function _collectAllParameters(dataObjList) {
|
|
1366
|
-
let uniqueParams = [], paramNames = [], params;
|
|
1367
|
-
|
|
1368
|
-
for (let dataObj of dataObjList) {
|
|
1369
|
-
params = dataObj.$Parameter;
|
|
1370
|
-
|
|
1371
|
-
if (params) {
|
|
1372
|
-
for (let i = 0; i < params.length; i++) {
|
|
1373
|
-
if (!paramNames.includes(params[i].$Name)) {
|
|
1374
|
-
paramNames.push(params[i].$Name);
|
|
1375
|
-
uniqueParams.push(params[i]);
|
|
1376
|
-
}
|
|
1377
|
-
}
|
|
1378
|
-
}
|
|
1379
|
-
}
|
|
1380
|
-
|
|
1381
|
-
return uniqueParams;
|
|
1382
|
-
}
|
|
1383
|
-
|
|
1384
1366
|
/**
|
|
1385
1367
|
* @param {Boolean} isAction, to identify action/function
|
|
1386
1368
|
* @param {String} fqName, fully qualified name
|
|
@@ -1389,7 +1371,7 @@ function _collectAllParameters(dataObjList) {
|
|
|
1389
1371
|
*
|
|
1390
1372
|
* Scenarios:
|
|
1391
1373
|
* 1. Unbound Actions can't have collision. Action name must be unique.
|
|
1392
|
-
* 2. Unbound Functions can have collision;
|
|
1374
|
+
* 2. Unbound Functions can have collision; we will parse only one function with the same name having no paramter and a generic String return type.
|
|
1393
1375
|
*/
|
|
1394
1376
|
function _populateUnboundActionFunction(isAction, fqName, schemaDataList, parserContext) {
|
|
1395
1377
|
let [schemaName, actFunName] = _extractSchemaNameAndElementName(fqName);
|
|
@@ -1409,8 +1391,12 @@ function _populateUnboundActionFunction(isAction, fqName, schemaDataList, parser
|
|
|
1409
1391
|
}
|
|
1410
1392
|
else {
|
|
1411
1393
|
if (dataList.length > 1) {
|
|
1412
|
-
actionFunctionObject['$Parameter'] =
|
|
1394
|
+
actionFunctionObject['$Parameter'] = [];
|
|
1413
1395
|
parserContext.unboundFunctions[fqName] = actionFunctionObject;
|
|
1396
|
+
parserContext.unboundFunctions[fqName]["@cds.validate"] = false;
|
|
1397
|
+
if(parserContext.unboundFunctions[fqName].$ReturnType){
|
|
1398
|
+
parserContext.unboundFunctions[fqName].$ReturnType.$Type = "Edm.String";
|
|
1399
|
+
}
|
|
1414
1400
|
}
|
|
1415
1401
|
else {
|
|
1416
1402
|
parserContext.unboundFunctions[fqName] = actionFunctionObject;
|
|
@@ -1439,12 +1425,11 @@ function _collectUnboundActionFunction(schemaDataList, parserContext) {
|
|
|
1439
1425
|
* @param {Object} parserContext
|
|
1440
1426
|
*
|
|
1441
1427
|
* Scenarios for bound actions, functions:
|
|
1442
|
-
* 1. Actions with same name and binding parameter type can
|
|
1443
|
-
* 2. Functions can have collision and we will
|
|
1428
|
+
* 1. Actions with same name and binding parameter type can have collision and we will parse only one action with that name having no paramter and a genric String return type.
|
|
1429
|
+
* 2. Functions can have collision and we will parse only one function with that name having no paramter and a genric String return type.
|
|
1444
1430
|
* Special scenario is when same function name is bound to different entity as well.
|
|
1445
1431
|
*/
|
|
1446
1432
|
function _collectBoundActionFunction(isAction, currNamespace, elementName, elementDataList, parserContext) {
|
|
1447
|
-
let functionObjList; // list to collect all the collision obj for function
|
|
1448
1433
|
|
|
1449
1434
|
for (let i = 0; i < elementDataList.length; i++) {
|
|
1450
1435
|
let dataObj = elementDataList[i];
|
|
@@ -1480,7 +1465,12 @@ function _collectBoundActionFunction(isAction, currNamespace, elementName, eleme
|
|
|
1480
1465
|
parserContext.entityToBoundActions[boundEntityName][elementName] = dataObj;
|
|
1481
1466
|
}
|
|
1482
1467
|
else {
|
|
1483
|
-
|
|
1468
|
+
parserContext.entityToBoundActions[boundEntityName][elementName]["@cds.validate"] = false;
|
|
1469
|
+
parserContext.entityToBoundActions[boundEntityName][elementName]["$Parameter"] = [];
|
|
1470
|
+
if (!parserContext.entityToBoundActions[boundEntityName][elementName].$ReturnType) {
|
|
1471
|
+
parserContext.entityToBoundActions[boundEntityName][elementName].$ReturnType = {};
|
|
1472
|
+
}
|
|
1473
|
+
parserContext.entityToBoundActions[boundEntityName][elementName].$ReturnType.$Type = "Edm.String";
|
|
1484
1474
|
}
|
|
1485
1475
|
}
|
|
1486
1476
|
else {
|
|
@@ -1488,18 +1478,12 @@ function _collectBoundActionFunction(isAction, currNamespace, elementName, eleme
|
|
|
1488
1478
|
if (!parserContext.entityToBoundFunctions[boundEntityName]) {
|
|
1489
1479
|
parserContext.entityToBoundFunctions[boundEntityName] = {};
|
|
1490
1480
|
}
|
|
1491
|
-
if (!functionObjList || !parserContext.entityToBoundFunctions[boundEntityName][elementName]) {
|
|
1492
|
-
functionObjList = [];
|
|
1493
|
-
}
|
|
1494
1481
|
parserContext.entityToBoundFunctions[boundEntityName][elementName] = dataObj;
|
|
1495
|
-
functionObjList.push(dataObj);
|
|
1496
1482
|
}
|
|
1497
1483
|
else {
|
|
1498
|
-
|
|
1499
|
-
|
|
1500
|
-
|
|
1501
|
-
functionObjList = [functionObject]; // reset the function object list with recently formed object
|
|
1502
|
-
parserContext.entityToBoundFunctions[boundEntityName][elementName] = functionObject;
|
|
1484
|
+
parserContext.entityToBoundFunctions[boundEntityName][elementName]["@cds.validate"] = false;
|
|
1485
|
+
parserContext.entityToBoundFunctions[boundEntityName][elementName]["$Parameter"] = [];
|
|
1486
|
+
parserContext.entityToBoundFunctions[boundEntityName][elementName].$ReturnType.$Type = "Edm.String";
|
|
1503
1487
|
}
|
|
1504
1488
|
// throw error if the return type is missing
|
|
1505
1489
|
if (!parserContext.entityToBoundFunctions[boundEntityName][elementName].$ReturnType) {
|
|
@@ -1688,7 +1672,12 @@ function _generateEDMX2JSON(edmx, name_space) {
|
|
|
1688
1672
|
if (missingReferencedDocuments?.length > 0) {
|
|
1689
1673
|
console.log(missingReferencedDocuments);
|
|
1690
1674
|
}
|
|
1691
|
-
|
|
1675
|
+
|
|
1676
|
+
if (error) {
|
|
1677
|
+
reject(error);
|
|
1678
|
+
} else {
|
|
1679
|
+
resolve(json);
|
|
1680
|
+
}
|
|
1692
1681
|
});
|
|
1693
1682
|
} catch (err) {
|
|
1694
1683
|
reject(err);
|