@sap/cds-dk 7.3.2 → 7.4.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/bin/build.js CHANGED
@@ -1,7 +1,7 @@
1
1
  module.exports = Object.assign(build, {
2
2
  options: ['--project', '--src', '--dest', '--for', '--use', '--opts', '--log-level', '--options-model'],
3
3
  shortcuts: ['-in', '-s', '-o', '-4'],
4
- flags: ['--ws', '--clean'],
4
+ flags: ['--clean', '--no-clean', '--ws'], // REVISIT: --clean is deprecated, backward compatibility
5
5
  help: `
6
6
  # SYNOPSIS
7
7
 
@@ -16,7 +16,6 @@ module.exports = Object.assign(build, {
16
16
  # OPTIONS
17
17
 
18
18
  *-4 | --for* hana | java | nodejs | mtx | mtx-sidecar | mtx-extension
19
-
20
19
  Name of the build task that is applied to the source folder, either
21
20
  explicitly configured in the build task list or generated out of the
22
21
  project setup
@@ -25,7 +24,6 @@ module.exports = Object.assign(build, {
25
24
  otherwise.
26
25
 
27
26
  *-s | --src* <folder>
28
-
29
27
  The module folder to build using the build task defined by option
30
28
  '--for'.
31
29
  This option is only supported when building an individual module of the
@@ -36,7 +34,6 @@ module.exports = Object.assign(build, {
36
34
  *hana* build task, 'srv' for *nodejs* or *java* build task.
37
35
 
38
36
  *-o | --dest* <folder>
39
-
40
37
  Output folder of the build task. The type of build task is defined by
41
38
  option '--for'.
42
39
  The folder is resolved based on '<project>/<cds.env.build.target>'.
@@ -49,14 +46,12 @@ module.exports = Object.assign(build, {
49
46
  '<project>/*<dest>*/*'.
50
47
 
51
48
  *--opts* <build task specific options>
52
-
53
49
  Add a comma-separated list of build task specific options in the format
54
50
  <opt1>=[value1,value2],<opt2>=value.
55
51
  For example, --opts model=[db,srv,schema.cds] loads and compiles the CDS
56
52
  model based on the given sources.
57
53
 
58
54
  *--profile* <profile,...>
59
-
60
55
  Loads the configuration data for the specified profile(s) and executes
61
56
  'cds build' based on this profile settings.
62
57
  E.g. you may want to create the HANA deployment artefacts only if the
@@ -64,18 +59,28 @@ module.exports = Object.assign(build, {
64
59
  The *development* profile is used by default.
65
60
 
66
61
  *--production*
67
-
68
62
  Shortcut for '--profile production'
69
63
 
70
64
  *--log-level* <debug | info | warn | error>
71
-
72
65
  Choose which level of cds build messages to log, default is *warn*.
73
66
 
74
67
  *-in | --project* [<project>]
75
-
76
68
  Runs 'cds build' for the specified project, default is current working
77
69
  directory.
78
-
70
+
71
+ *--no-clean* (beta)
72
+ Skips cleaning the build output folder before running the actual build
73
+ operation. Clients need to clean the output folder upfront to ensure
74
+ consistent build results.
75
+
76
+ *--ws* (beta)
77
+ Enable tarball based packaging of npm workspace dependencies for Node.js apps.
78
+ This feature can be used to package npm workspace dependencies as part of your
79
+ mono or multi-repo deployment.
80
+ Tarball files are created for all 'dependencies' and 'devDependencies'
81
+ of your project for which a npm workspace exists. The '*' dependency is replaced
82
+ by the correct tarball file URL.
83
+ See 'npm workspaces' for further details.
79
84
 
80
85
  # EXAMPLES
81
86
 
@@ -105,13 +110,17 @@ async function build([project], options = {}) {
105
110
  if (Number(major) < 6) {
106
111
  throw "This application uses @sap/cds version < 6, which is not compatible with the installed @sap/cds-dk version >= 7. Update @sap/cds to version >= 6."
107
112
  }
108
- if (options.clean === "false") {
109
- throw "cds build option '--clean' is deprecated - build output folders are always cleaned before build"
110
- }
111
113
  if (typeof options["options-model"] === "string") {
112
114
  options.opts = "model=[" + options["options-model"].split(",").map((entry) => entry.trim()) + "]"
113
115
  delete options["options-model"]
114
116
  }
117
+ if (options["no-clean"]) {
118
+ // mapping to the existing 'clean' option
119
+ if (options["no-clean"] === "true" || options["no-clean"] === true) {
120
+ options.clean = false
121
+ }
122
+ delete options["no-clean"]
123
+ }
115
124
 
116
125
  await build(options)
117
126
  }
package/bin/cds.js CHANGED
@@ -93,8 +93,8 @@ const cli = { //NOSONAR
93
93
 
94
94
  function add (k,v) { options[k.slice(2)] = v || true }
95
95
  function add_global (k,v='') {
96
- if (k === '--production') return process.env.CDS_ENV = [...process.env.CDS_ENV?.split(',') ?? [], 'production']
97
- if (k === '--profile') return process.env.CDS_ENV = [...process.env.CDS_ENV?.split(',') ?? [], ...v.split(',')]
96
+ if (k === '--production') return process.env.CDS_ENV = Array.from(new Set([...process.env.CDS_ENV?.split(',') ?? [], 'production']))
97
+ if (k === '--profile') return process.env.CDS_ENV = Array.from(new Set([...process.env.CDS_ENV?.split(',') ?? [], ...v.split(',')]))
98
98
  if (k === '--odata') v = { flavor:v }
99
99
  let e=env || (env={}), path = k.slice(2).split('-')
100
100
  while (path.length > 1) { let p = path.shift(); e = e[p]||(e[p]={}) }
@@ -103,7 +103,7 @@ const cli = { //NOSONAR
103
103
 
104
104
  // if (env) require('../lib/cds').env.add (env) //> avoid loading env
105
105
  // unless it has already explicitly been set by the user
106
- process.env.CDS_CONFIG ??= JSON.stringify(env)
106
+ if (env) process.env.CDS_CONFIG ??= JSON.stringify(env)
107
107
  return [ args, options ]
108
108
  },
109
109
 
package/bin/migrate.js CHANGED
@@ -43,11 +43,11 @@ module.exports = Object.assign(migrate, {
43
43
  Skip verification step. Does not check for potential deployment
44
44
  differences.
45
45
 
46
- *--cleanup* (beta)
46
+ *--cleanup*
47
47
 
48
48
  Removes all metadata containers created by @sap/cds-mtx.
49
49
 
50
- *--resolve-bindings* (beta)
50
+ *--resolve-bindings*
51
51
 
52
52
  Resolve remote service bindings configured via *cds bind*.
53
53
 
package/bin/repl.js CHANGED
@@ -17,8 +17,6 @@ async function repl (_, options={}) {
17
17
  const cds = require('../lib/cds')
18
18
  console.log('\x1b[32m\x1b[1m'+'Welcome to cds repl v', cds.version, '\x1b[0m')
19
19
 
20
- await cds.plugins
21
-
22
20
  const { inspect } = require('util')
23
21
  inspect.defaultOptions.colors = true
24
22
  inspect.defaultOptions.depth = 11
package/bin/subscribe.js CHANGED
@@ -1,8 +1,8 @@
1
1
  module.exports = Object.assign(subscribe, {
2
2
  init: cmd => new (require('../lib/client/tenant_action'))(cmd),
3
3
  options: ['--to', '--user'],
4
- flags: ['--local'],
5
- shortcuts: ['-2', '-u', '-l'],
4
+ flags: ['--local', '--resolve-bindings'],
5
+ shortcuts: ['-2', '-u', '-l', '-b'],
6
6
  help: `
7
7
  # SYNOPSIS
8
8
 
@@ -24,10 +24,10 @@ module.exports = Object.assign(subscribe, {
24
24
 
25
25
  Username and optionally password for authentication with Basic Auth in
26
26
  test scenarios.
27
-
27
+
28
28
  Only relevant if <url> is given. May be omitted in case of dummy
29
29
  authentication.
30
-
30
+
31
31
  Tokens saved with *cds login* are not used for this command.
32
32
 
33
33
  *-l* | *--local*
@@ -35,11 +35,15 @@ module.exports = Object.assign(subscribe, {
35
35
  Run a locally-installed @sap/cds-mtxs and connect to it instead of a
36
36
  URL. Ignore settings saved with *cds login*.
37
37
 
38
+ *-b* | *--resolve-bindings*
39
+
40
+ Resolve remote service bindings configured via *cds bind*.
41
+
38
42
 
39
43
  # SEE ALSO
40
44
 
41
45
  *cds login* for more information on authentication options.
42
-
46
+
43
47
  *cds login* to save project settings and Basic-Auth data, simplifying multiple runs of this command.
44
48
 
45
49
  `});
@@ -63,6 +67,7 @@ async function subscribe([tenant], options = {}) {
63
67
  }
64
68
  const [username, password] = options.user?.split(':') ?? [];
65
69
  delete options.user;
70
+ delete options['resolve-bindings'];
66
71
 
67
72
  await subscribe.init('subscribe').run({
68
73
  ...options,
@@ -1,8 +1,8 @@
1
1
  module.exports = Object.assign(unsubscribe, {
2
2
  init: cmd => new (require('../lib/client/tenant_action'))(cmd),
3
3
  options: ['--from', '--user'],
4
- flags: ['--local'],
5
- shortcuts: ['-f', '-u', '-l'],
4
+ flags: ['--local', '--resolve-bindings'],
5
+ shortcuts: ['-f', '-u', '-l', '-b'],
6
6
  help: `
7
7
  # SYNOPSIS
8
8
 
@@ -25,10 +25,10 @@ module.exports = Object.assign(unsubscribe, {
25
25
 
26
26
  Username and optionally password for authentication with Basic Auth in
27
27
  test scenarios.
28
-
28
+
29
29
  Only relevant if <url> is given. May be omitted in case of dummy
30
30
  authentication.
31
-
31
+
32
32
  Tokens saved with *cds login* are not used for this command.
33
33
 
34
34
  *-l* | *--local*
@@ -36,11 +36,15 @@ module.exports = Object.assign(unsubscribe, {
36
36
  Run a locally-installed @sap/cds-mtxs and connect to it instead of a
37
37
  URL. Ignore settings saved with *cds login*.
38
38
 
39
+ *-b* | *--resolve-bindings*
40
+
41
+ Resolve remote service bindings configured via *cds bind*.
42
+
39
43
 
40
44
  # SEE ALSO
41
45
 
42
46
  *cds login* for more information on authentication options.
43
-
47
+
44
48
  *cds login* to save project settings and Basic-Auth data, simplifying multiple runs of this command.
45
49
 
46
50
  `});
@@ -64,6 +68,7 @@ async function unsubscribe([tenant], options = {}) {
64
68
  }
65
69
  const [username, password] = options.user?.split(':') ?? [];
66
70
  delete options.user;
71
+ delete options['resolve-bindings'];
67
72
 
68
73
  await unsubscribe.init('unsubscribe').run({
69
74
  ...options,
package/bin/upgrade.js CHANGED
@@ -1,8 +1,8 @@
1
1
  module.exports = Object.assign(upgrade, {
2
2
  init: cmd => new (require('../lib/client/tenant_action'))(cmd),
3
3
  options: ['--at', '--user'],
4
- flags: ['--local'],
5
- shortcuts: ['-a', '-u', '-l'],
4
+ flags: ['--local', '--resolve-bindings'],
5
+ shortcuts: ['-a', '-u', '-l', '-b'],
6
6
  help: `
7
7
  # SYNOPSIS
8
8
 
@@ -25,10 +25,10 @@ module.exports = Object.assign(upgrade, {
25
25
 
26
26
  Username and optionally password for authentication with Basic Auth in
27
27
  test scenarios.
28
-
28
+
29
29
  Only relevant if <url> is given. May be omitted in case of dummy
30
30
  authentication.
31
-
31
+
32
32
  Tokens saved with *cds login* are not used for this command.
33
33
 
34
34
  *-l* | *--local*
@@ -36,11 +36,15 @@ module.exports = Object.assign(upgrade, {
36
36
  Run a locally-installed @sap/cds-mtxs and connect to it instead of a
37
37
  URL. Ignore settings saved with *cds login*.
38
38
 
39
+ *-b* | *--resolve-bindings*
40
+
41
+ Resolve remote service bindings configured via *cds bind*.
42
+
39
43
 
40
44
  # SEE ALSO
41
45
 
42
46
  *cds login* for more information on authentication options.
43
-
47
+
44
48
  *cds login* to save project settings and Basic-Auth data, simplifying multiple runs of this command.
45
49
 
46
50
  `});
@@ -64,6 +68,7 @@ async function upgrade([tenant], options = {}) {
64
68
  }
65
69
  const [username, password] = options.user?.split(':') ?? [];
66
70
  delete options.user;
71
+ delete options['resolve-bindings'];
67
72
 
68
73
  await upgrade.init('upgrade').run({
69
74
  ...options,
package/bin/watch.js CHANGED
@@ -37,6 +37,11 @@ module.exports = Object.assign ( watch, {
37
37
  Open the given URL (suffix) in the browser after starting.
38
38
  If none is given, the default application URL will be opened.
39
39
 
40
+ *--profile* <profile,...>
41
+
42
+ Specify from which profile(s) the binding information is taken.
43
+ *Example:* cds w --profile hybrid,production
44
+
40
45
  # SEE ALSO
41
46
 
42
47
  *cds serve --help* for the different start options.
package/bin/watched.js CHANGED
@@ -7,8 +7,8 @@
7
7
 
8
8
  // run through cli in order to use its error handling
9
9
  const args = JSON.parse(process.env._args)
10
+ const cds = require('../lib/cds'); cds.watched = true
10
11
  const bootstrap = require('./cds')(...args)
11
- const {cds} = global; cds.watched = true
12
12
 
13
13
  cds.once('listening', ({server}) => {
14
14
  process.send ({ code:'listening', port: server.address().port })
@@ -230,7 +230,7 @@ class BuildTaskFactory {
230
230
 
231
231
  static _scanTaskOptionParams(optsParams) {
232
232
  // need to create new regex every call since a constant would keep the match state
233
- const quoteRegex = /([\w-]+)=([\w/.]+|\[([\w/,.]+)\])/g
233
+ const quoteRegex = /([\w-]+)=([\w/.-]+|\[([\w/,.-]+)\])/g
234
234
 
235
235
  // captures a=1 => a:1
236
236
  // a=[x,y,z] => a:[x,y,z]
@@ -34,6 +34,7 @@ exports.CONTENT_NODE_MODULES = "contentNodeModules" // copy node_mod
34
34
  exports.CONTENT_ENV = "contentEnv" // copy .env file if existing into deployment folder - DISABLED by default
35
35
  exports.CONTENT_DEFAULT_ENV_JSON = "contentDefaultEnvJson" // copy default-env.json file if existing into deployment folder - DISABLED by default
36
36
 
37
+ exports.CSV_FILE_TARGET = "csvFileTarget" // target folder when copying CSV files to the deployment target folder ./db/src/gen/*, default is 'data'
37
38
  exports.CSV_FILE_DETECTION = "csvFileDetection" // detect CSV files based on CDS model location - ENABLED by default
38
39
  exports.EXTENSION_POINT_VALIDATION = "extensionPointValidation" // validate extension point restrictions defined by the extended SaaS app - ENABLED by default
39
40
  exports.MTX_SIDECAR_DB_VALIDATION = "mtxSidecarDbValidation" // validate consistency of app and sidecar db configuration - ENABLED by default
@@ -13,8 +13,7 @@ module.exports = async (model, lastDevVersion, srcPath, options = {}) => {
13
13
  logger = options.logger
14
14
  }
15
15
 
16
- const journalFileNames = _getJournalFileNames(model)
17
- const { definitions, deletions, migrations, afterImage } = _toHdiMigration(model, lastDevVersion, journalFileNames, options)
16
+ const { definitions, deletions, migrations, afterImage, journalFileNames } = _toHdiMigration(model, lastDevVersion, options)
18
17
  const definitionResult = []
19
18
 
20
19
  for (const { name, suffix, sql } of definitions) {
@@ -33,8 +32,9 @@ module.exports = async (model, lastDevVersion, srcPath, options = {}) => {
33
32
  return { definitions: definitionResult, deletions, afterImage: _filterJournalArtifacts(afterImage) }
34
33
  }
35
34
 
36
- function _toHdiMigration(model, lastDevVersion, journalFileNames, options) {
35
+ function _toHdiMigration(model, lastDevVersion, options) {
37
36
  const result = cdsc.to.hdi.migration(cds.minify(model), options, lastDevVersion);
37
+ const journalFileNames = _getJournalFileNames(result.afterImage);
38
38
  if (logger._debug) {
39
39
  logger.debug('cdsc.to.hdi.migration returned')
40
40
  for (const { name, suffix, sql } of result.definitions) {
@@ -50,7 +50,7 @@ ${migration ? migration.changeset.map(change => change.sql).join('\n') : 'Empty
50
50
  }
51
51
  }
52
52
  }
53
- return result
53
+ return { ...result, journalFileNames };
54
54
  }
55
55
 
56
56
  /**
@@ -6,7 +6,7 @@ const InternalBuildPlugin = require('../internalBuildPlugin')
6
6
  const { BuildError, relativePaths, BuildMessage } = require('../../util')
7
7
 
8
8
  const { OUTPUT_MODE_NONE, OUTPUT_MODE, CONTENT_PACKAGE_JSON, CONTENT_HDBTABLEDATA, CSV_FILE_DETECTION,
9
- CONTENT_ENV, CONTENT_DEFAULT_ENV_JSON, CONTENT_NODE_MODULES, OUTPUT_MODE_RESULT, CONTINUE_UNRESOLVED_SCHEMA_CHANGES } = require('../../constants')
9
+ CONTENT_ENV, CONTENT_DEFAULT_ENV_JSON, CONTENT_NODE_MODULES, OUTPUT_MODE_RESULT, CONTINUE_UNRESOLVED_SCHEMA_CHANGES, CSV_FILE_TARGET } = require('../../constants')
10
10
  const { WARNING } = InternalBuildPlugin
11
11
 
12
12
  const DEFAULT_COMPILE_DEST_FOLDER = path.normalize("src/gen")
@@ -90,18 +90,28 @@ class HanaBuildPlugin extends InternalBuildPlugin {
90
90
  */
91
91
  async _copyResourcesExt(src, dest, model) {
92
92
  const resources = Object.keys(await cds.deploy.resources(model)).reverse() // reverse to get reuse resources first and app resources last
93
- const csvPath = path.join(src, "data")
94
- // determine subfolder name used by the application for backward compatibility
95
- const csvFolder = resources.some(res => res && res.startsWith(csvPath)) ? "data" : "csv"
93
+ const dbSrc = path.join(src, 'src')
96
94
 
97
95
  // 1. copy csv files into 'src/gen/data' or 'src/gen/csv' subfolder
98
- const dbSrc = path.join(src, 'src')
96
+ if (resources.length) {
97
+ // determine the CSV folder from the location of the CSVs defined in this build task db folder, use subfolder data by default
98
+ const dbCSVs = resources.filter(res => res.startsWith(src) && !res.startsWith(dbSrc))
99
+ let csvFolder = this.getBuildOption(CSV_FILE_TARGET)
100
+ if (csvFolder && !["data", "csv"].includes(csvFolder)) {
101
+ throw new BuildError("Invalid value for option 'csvFileTarget'. Valid values are 'data' or 'csv'.")
102
+ }
103
+ if (!csvFolder) {
104
+ const csvPath = path.join(src, "data")
105
+ // determine subfolder name used by the application for backward compatibility
106
+ csvFolder = !dbCSVs.length || dbCSVs.some(res => res && res.startsWith(csvPath)) ? "data" : "csv"
107
+ }
99
108
 
100
- // await each copy as order is important - reuse resources first and app resources last
101
- for (const res of resources) {
102
- // do not duplicate resources that are already contained in db/src/**
103
- if (res && /\.csv$/.test(res) && !res.startsWith(dbSrc)) {
104
- await this.copy(res).to(path.join(this.task.options.compileDest, csvFolder, path.basename(res)))
109
+ // await each copy as order is important - reuse resources first and app resources last
110
+ for (const res of resources) {
111
+ // do not duplicate resources that are already contained in db/src/**
112
+ if (res && /\.csv$/.test(res) && !res.startsWith(dbSrc)) {
113
+ await this.copy(res).to(path.join(this.task.options.compileDest, csvFolder, path.basename(res)))
114
+ }
105
115
  }
106
116
  }
107
117
 
@@ -24,12 +24,12 @@ class JavaBuildPlugin extends EdmxBuildPlugin {
24
24
 
25
25
  if (!this.hasBuildOption(CONTENT_EDMX, false)) {
26
26
  // generate edmx files containing all features
27
- // localized edmx for backward compatibility
27
+ // REVISIT: localized edmx for backward compatibility
28
28
  if (!this.hasBuildOption(CONTENT_LOCALIZED_EDMX, false)) {
29
29
  await this.compileToEdmx(odata, compileDest, { [FLAVOR_LOCALIZED_EDMX]: true })
30
30
  }
31
31
  // non-localized edmx
32
- await this.compileToEdmx(odata, path.join(compileDest, 'odata', cds.env.odata.version), { [FLAVOR_LOCALIZED_EDMX]: false })
32
+ await this.compileToEdmx(odata, path.join(compileDest, 'odata', cds.env.odata.version), { [FLAVOR_LOCALIZED_EDMX]: this.hasBuildOption(FLAVOR_LOCALIZED_EDMX, true) })
33
33
  }
34
34
 
35
35
  if (!this.hasBuildOption(CONTENT_LANGUAGE_BUNDLES, false)) {
@@ -1,8 +1,9 @@
1
1
  const fs = require('fs')
2
2
  const path = require('path')
3
3
  const cds = require('../../../cds')
4
+ const cmd = require('../../../util/command');
4
5
  const EdmxBuildPlugin = require('../edmxBuildPlugin')
5
- const { BuildError } = require('../../util')
6
+ const { BuildError, pathExists } = require('../../util')
6
7
  const { OUTPUT_MODE, OUTPUT_MODE_FILESYSTEM, ODATA_VERSION_V2, FOLDER_GEN, CONTENT_EDMX, CONTENT_PACKAGELOCK_JSON,
7
8
  CONTENT_NPMRC, CONTENT_CDSRC_JSON, CONTENT_ENV, CONTENT_DEFAULT_ENV_JSON, FLAVOR_LOCALIZED_EDMX } = require('../../constants')
8
9
  const { WARNING } = EdmxBuildPlugin
@@ -61,59 +62,6 @@ class NodejsBuildPlugin extends EdmxBuildPlugin {
61
62
  return this._result
62
63
  }
63
64
 
64
- // REVISIT: fix for simple mono repos using npm workspaces
65
- // Support for HANA native artifacts defined in sub-modules required
66
- async _addWorkspaceDependencies(dest) {
67
- try {
68
- const workspaceRoot = path.resolve(cds.root, this.getBuildOption("workspaceRoot") ?? '.')
69
- if (workspaceRoot !== cds.root && !fs.existsSync(workspaceRoot)) {
70
- throw new BuildError(`The given workspaceRoot path ${workspaceRoot} does not exist`)
71
- }
72
- const packageJson = require(path.join(dest, 'package.json'))
73
- const workspaces = workspaceRoot === cds.root ? packageJson.workspaces : require(path.join(workspaceRoot, 'package.json')).workspaces
74
- if (workspaces?.length) {
75
- this.logger._debug && this.logger.debug(`Existing npm workspace configuration '${JSON.stringify(workspaces, ' ')}'`)
76
- const { tar } = require('../../../../lib').utils
77
- const { dependencies } = packageJson
78
- const folders = fs.readdirSync(workspaceRoot).filter(res => {
79
- try {
80
- return fs.lstatSync(path.join(workspaceRoot, res)).isDirectory()
81
- } catch (e) {
82
- return false
83
- }
84
- })
85
- this.logger._debug && this.logger.debug(`Existing workspace subfolders '${JSON.stringify(folders, ' ')}'`)
86
-
87
- const workspaceNames = new Set()
88
- let changed
89
- for (const workspace of workspaces) {
90
- //REVISIT: workspaces must only use simple glob like 'foo*', nested folders are not supported
91
- const regex = new RegExp('^' + workspace.split('/').pop().replace(/\*/g, '.*') + '$')
92
- for (const folder of folders) {
93
- if (!workspaceNames.has(folder) && folder.match(regex)) {
94
- workspaceNames.add(folder)
95
- const plugin = require(require.resolve(path.join(workspaceRoot, folder, 'package.json')))
96
- const tarFile = `${plugin.name.replace(/\//g, '-').replace(/@/, '')}-${plugin.version}.tgz`
97
- if (dependencies[plugin.name] === '*') {
98
- const tarFilePath = path.join(dest, tarFile)
99
- dependencies[plugin.name] = `file:${tarFile}`
100
- await tar.czfd(tarFilePath, path.join(workspaceRoot, folder))
101
- this.pushFile(tarFilePath)
102
- changed = true
103
- }
104
- }
105
- }
106
- }
107
- if (changed) {
108
- return this.write(JSON.stringify(packageJson, null, ' ')).to(path.join(dest, 'package.json'))
109
- }
110
- }
111
- } catch (e) {
112
- this.pushMessage(`Failed to add npm workspace dependencies: ${e.message}`)
113
- this.logger.debug(e)
114
- }
115
- }
116
-
117
65
  async clean() {
118
66
  // staging build content is deleted by BuildTaskEngine
119
67
  if (cds.env.build.target === '.') {
@@ -209,6 +157,78 @@ class NodejsBuildPlugin extends EdmxBuildPlugin {
209
157
  return srvAllowList.test(entry) && (!filter || filter.call(this, entry))
210
158
  })
211
159
  }
160
+
161
+ async _addWorkspaceDependencies(dest) {
162
+ let packageDescriptors
163
+ try {
164
+ packageDescriptors = await this._execNpmPack(dest)
165
+ } catch (e) {
166
+ this.logger.debug(e)
167
+ throw new BuildError(`Failed to package npm workspace dependencies\n${e.message}`)
168
+ }
169
+ if (packageDescriptors?.length) {
170
+ const packageJson = require(path.join(dest, 'package.json'))
171
+ const dependencies = packageJson.dependencies
172
+ const devDependencies = packageJson.devDependencies
173
+ let changed
174
+ const promises = []
175
+ for (const packageDescriptor of packageDescriptors) {
176
+ const {name, filename: fileName} = packageDescriptor
177
+ const filePath = path.join(dest, fileName)
178
+ if (dependencies?.[name] === '*' || devDependencies?.[name] === '*') {
179
+ if (dependencies?.[name] === '*') {
180
+ dependencies[name] = `file:${fileName}`
181
+ } else {
182
+ devDependencies[name] = `file:${fileName}`
183
+ }
184
+ this.pushFile(filePath)
185
+ changed |= true
186
+ } else {
187
+ if (await pathExists(filePath)) {
188
+ promises.push(fs.promises.rm(filePath))
189
+ }
190
+ }
191
+ }
192
+ if (changed) {
193
+ promises.push(this.write(JSON.stringify(packageJson, 0, 2)).to(path.join(dest, 'package.json')))
194
+ } else {
195
+ throw new BuildError('No matching workspaces found!')
196
+ }
197
+ await Promise.all(promises)
198
+ }
199
+ }
200
+
201
+ async _execNpmPack(dest) {
202
+ function findWorkspaceRoot(dir) {
203
+ if(fs.existsSync(path.join(dir, 'package.json'))) {
204
+ if(require(path.join(dir, 'package.json')).workspaces) {
205
+ return dir
206
+ }
207
+ }
208
+ if(fs.existsSync(path.join(dir, '.gitmodules')) || fs.existsSync(path.join(dir, 'node_modules'))) {
209
+ return // project root reached
210
+ }
211
+ if (path.dirname(dir) !== dir) {
212
+ return findWorkspaceRoot(path.dirname(dir))
213
+ }
214
+ }
215
+ const wsRoot = findWorkspaceRoot(cds.root)
216
+ if (!wsRoot) {
217
+ throw new BuildError('No workspace configuration found!')
218
+ }
219
+ const args = ['pack', '-ws', '--json', `--pack-destination=${dest}`]
220
+ if (this.task.options?.workspaces) {
221
+ let {workspaces} = this.task.options
222
+ workspaces = Array.isArray(workspaces) ? workspaces : [workspaces]
223
+ workspaces.forEach(w => args.push(`-w=${w}`))
224
+ }
225
+ this.logger._debug && this.logger.debug(`execute command: npm ${args}`)
226
+
227
+ const result = await cmd.spawnCommand( 'npm', args, { cwd: wsRoot || cds.root }, true, !!this.logger._debug)
228
+
229
+ this.logger._debug && this.logger.debug(result)
230
+ return JSON.parse(result)
231
+ }
212
232
  }
213
233
 
214
234
  module.exports = NodejsBuildPlugin
@@ -66,7 +66,7 @@ async function retrieveTokenOrPasscodeUrl(params) {
66
66
  let response;
67
67
  for (const { method, url, data } of reqParams(params)) {
68
68
  try {
69
- DEBUG?.(`Getting authentication token or passcode URL from ${method.toUpperCase()} ${(params.obfuscateQueryParams(url))}`);
69
+ DEBUG?.(`Getting authentication token or passcode URL from ${method.toUpperCase()} ${params.obfuscateQueryParams(url)}`);
70
70
  response = await axios[method](url, data);
71
71
  } catch (error) {
72
72
  if (error.status === 404) { // may represent 405 in case of wrong method
@@ -49,6 +49,14 @@ module.exports = class Pull extends BaseCommand {
49
49
  .catch(error => this.handleHttpError(error, params, { url: pullUrl }));
50
50
  }
51
51
 
52
+ static getPackageJsonIndentation(packageContents) {
53
+ return /^ +/m.exec(packageContents)?.[0].length ?? 2;
54
+ }
55
+
56
+ static formatPackageJson(newObj, oldContents) {
57
+ return JSON.stringify(newObj, undefined, this.getPackageJsonIndentation(oldContents));
58
+ }
59
+
52
60
  static amendPackageJson(projectFolder, env) {
53
61
  if ('extends' in env) {
54
62
  return;
@@ -61,7 +69,7 @@ module.exports = class Pull extends BaseCommand {
61
69
  } catch (error) {
62
70
  throw getMessage('package.json missing or unreadable', { error });
63
71
  }
64
- fs.writeFileSync(packageJson, JSON.stringify({
72
+ fs.writeFileSync(packageJson, this.formatPackageJson({
65
73
  ...packageContents,
66
74
  cds: {
67
75
  ...(packageContents.cds ?? {}),
@@ -1,3 +1,17 @@
1
+
2
+ const odmAnnotationsMapping = Object.freeze({
3
+ '@ODM.entityName': 'x-sap-odm-entity-name',
4
+ '@ODM.oid': 'x-sap-odm-oid'
5
+ });
6
+
7
+ function addOdmExtensions(definition, object) {
8
+ for (const [annotation, asyncApiExtension] of Object.entries(odmAnnotationsMapping)) {
9
+ if (definition[annotation]) {
10
+ object[asyncApiExtension] = definition[annotation];
11
+ }
12
+ }
13
+ }
14
+
1
15
  module.exports = function addAnnotations(definition, object) {
2
16
  if (typeof (definition['@title']) === 'string') {
3
17
  object.title = definition['@title'];
@@ -9,5 +23,11 @@ module.exports = function addAnnotations(definition, object) {
9
23
  object.description = definition.doc.replace(/\n/g, ' ');
10
24
  }
11
25
 
26
+ if (definition['@ODM.root']) {
27
+ object['x-sap-root-entity'] = definition['@ODM.root']
28
+ }
29
+
30
+ addOdmExtensions(definition, object);
31
+
12
32
  return object;
13
33
  }