@sap/cds-dk 7.5.0 → 7.6.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.
Files changed (67) hide show
  1. package/bin/add.js +28 -24
  2. package/bin/bind.js +59 -32
  3. package/bin/build.js +1 -1
  4. package/bin/cds.js +1 -1
  5. package/bin/env.js +1 -6
  6. package/bin/init.js +7 -3
  7. package/bin/logout.js +2 -2
  8. package/bin/migrate.js +1 -1
  9. package/bin/version.js +1 -0
  10. package/bin/watch.js +8 -1
  11. package/lib/bind/bindingUtil.js +14 -20
  12. package/lib/bind/cfKeyProvider.js +9 -11
  13. package/lib/bind/index.js +4 -2
  14. package/lib/build/buildTaskFactory.js +31 -20
  15. package/lib/build/constants.js +9 -8
  16. package/lib/build/provider/featureToggleBuildPlugin.js +1 -7
  17. package/lib/build/provider/hana/index.js +44 -9
  18. package/lib/build/provider/internalBuildTaskProvider.js +7 -5
  19. package/lib/build/provider/java/index.js +0 -6
  20. package/lib/build/provider/mtx-extension/index.js +2 -1
  21. package/lib/build/provider/mtx-sidecar/index.js +5 -0
  22. package/lib/build/provider/nodejs/index.js +32 -47
  23. package/lib/build/util.js +134 -33
  24. package/lib/compile/openapi/csdl2openapi.js +1 -1
  25. package/lib/deploy/to-hana/hana.js +1 -1
  26. package/lib/env/schema.js +157 -17
  27. package/lib/env/schemas/cds-rc.js +118 -0
  28. package/lib/init/constants.js +1 -1
  29. package/lib/init/merge.js +2 -1
  30. package/lib/init/template/application-logging/index.js +1 -2
  31. package/lib/init/template/approuter/files/mta.yaml.hbs +2 -0
  32. package/lib/init/template/approuter/files/package.json +1 -1
  33. package/lib/init/template/approuter/files/values.yaml.hbs +8 -1
  34. package/lib/init/template/hana/files/undeploy.json +3 -2
  35. package/lib/init/template/helm/files/chart/templates/NOTES.txt +2 -0
  36. package/lib/init/template/helm/files/chart/templates/_deployment_helpers.tpl +10 -2
  37. package/lib/init/template/helm/files/content-deployment/Chart.yaml +1 -1
  38. package/lib/init/template/helm/files/content-deployment/templates/_helpers.tpl +6 -5
  39. package/lib/init/template/helm/files/content-deployment/values.yaml +1 -1
  40. package/lib/init/template/helm/files/service-instance/Chart.yaml +1 -1
  41. package/lib/init/template/helm/files/service-instance/templates/_helpers.tpl +6 -5
  42. package/lib/init/template/helm/files/service-instance/values.yaml +1 -1
  43. package/lib/init/template/helm/files/web-application/Chart.yaml +1 -1
  44. package/lib/init/template/helm/files/web-application/templates/_api.tpl +6 -5
  45. package/lib/init/template/helm/files/web-application/values.yaml +1 -1
  46. package/lib/init/template/helm/index.js +1 -12
  47. package/lib/init/template/helm-unified-runtime/index.js +3 -2
  48. package/lib/init/template/html5-repo/files/values.yaml.hbs +9 -0
  49. package/lib/init/template/multitenancy/files/package.sidecar.json.hbs +1 -2
  50. package/lib/init/template/multitenancy/index.js +3 -2
  51. package/lib/init/template/xsuaa/files/package.json.hbs +1 -2
  52. package/lib/mtx/activate.js +1 -1
  53. package/lib/mtx/auth_manager.js +4 -2
  54. package/lib/mtx/download_legacy.js +1 -1
  55. package/lib/mtx/extend.js +2 -2
  56. package/lib/mtx/pull.js +1 -1
  57. package/lib/mtx/push.js +1 -1
  58. package/lib/mtx/settings_manager.js +77 -26
  59. package/lib/mtx/tenant_action.js +1 -1
  60. package/lib/mtx/util/legacy_ext.js +0 -1
  61. package/lib/util/axios.js +66 -0
  62. package/lib/util/cf.js +15 -10
  63. package/npm-shrinkwrap.json +361 -167
  64. package/package.json +1 -1
  65. package/lib/env/schemas/cds-rc.json +0 -101
  66. package/lib/init/template/helm-unified-runtime/lib/build.js +0 -0
  67. package/lib/util/pruneAxiosErrors.js +0 -56
@@ -1,4 +1,3 @@
1
- const fs = require('fs')
2
1
  const path = require('path')
3
2
  const cds = require('../../cds')
4
3
  const { DEFAULT_CSN_FILE_NAME, OUTPUT_MODE, OUTPUT_MODE_RESULT } = require('../constants')
@@ -65,7 +64,7 @@ class FeatureToggleBuildPlugin extends InternalBuildPlugin {
65
64
  }
66
65
 
67
66
  async _resolveSourcePaths(csn) {
68
- const regex = new RegExp(path.resolve(cds.root, this.ftsName).replace(/\\/g, '\\\\') + '[/|\\\\](?<ftName>[^/|\\\\]*)')
67
+ const regex = new RegExp(this.ftsName + '[/|\\\\](?<ftName>[^/|\\\\]*)')
69
68
  let paths = { base: [] }
70
69
 
71
70
  // add ROOT source file paths for the base model
@@ -77,11 +76,6 @@ class FeatureToggleBuildPlugin extends InternalBuildPlugin {
77
76
  return acc
78
77
  }, [])
79
78
 
80
- // keep existing behavior and return paths returned by cds.resolve if features are not supported by this project
81
- if (!fs.existsSync(path.join(cds.root, this.ftsName))) {
82
- return paths
83
- }
84
-
85
79
  // add source file paths for the features
86
80
  paths.features = csn['$sources'].reduce((acc, file) => {
87
81
  const match = file.match(regex)
@@ -3,9 +3,10 @@ const path = require('path')
3
3
  const cds = require('../../../cds')
4
4
  const to_hdbmigration = require('./2migration')
5
5
  const InternalBuildPlugin = require('../internalBuildPlugin')
6
- const { BuildError, relativePaths, BuildMessage } = require('../../util')
6
+ const { BuildError, relativePaths, BuildMessage, getWorkspaces, hasOptionValue } = require('../../util')
7
7
  const { OUTPUT_MODE_NONE, OUTPUT_MODE, CONTENT_PACKAGE_JSON, CONTENT_HDBTABLEDATA, CSV_FILE_DETECTION,
8
- CONTENT_ENV, CONTENT_DEFAULT_ENV_JSON, CONTENT_NODE_MODULES, OUTPUT_MODE_RESULT, CONTINUE_UNRESOLVED_SCHEMA_CHANGES, CSV_FILE_TARGET } = require('../../constants')
8
+ CONTENT_ENV, CONTENT_DEFAULT_ENV_JSON, CONTENT_NODE_MODULES, OUTPUT_MODE_RESULT, CONTINUE_UNRESOLVED_SCHEMA_CHANGES,
9
+ CSV_FILE_TARGET, CONTENT_WS } = require('../../constants')
9
10
  const { WARNING } = InternalBuildPlugin
10
11
  const DEFAULT_COMPILE_DEST_FOLDER = path.normalize("src/gen")
11
12
  const FILE_EXT_CSV = ".csv"
@@ -37,7 +38,7 @@ class HanaBuildPlugin extends InternalBuildPlugin {
37
38
  if (!model) {
38
39
  return
39
40
  }
40
- // the order is important
41
+ // the order of 1 and 2 is important
41
42
  // 1. compile
42
43
  const hdiPlugins = await this._compileToHana(model)
43
44
 
@@ -48,8 +49,12 @@ class HanaBuildPlugin extends InternalBuildPlugin {
48
49
  } else {
49
50
  await this._copyResourcesExt(src, dest, model)
50
51
  }
52
+ // copy native hana artifacts from workspace dependencies
53
+ if (hasOptionValue(this.task.options?.[CONTENT_WS], true)) {
54
+ await this._addWorkspaceContent()
55
+ }
51
56
 
52
- // 3. create additional stuff in dest
57
+ // create additional stuff in dest
53
58
  await this._writeHdiConfig(hdiPlugins)
54
59
  await this._writeHdiNamespace()
55
60
 
@@ -113,16 +118,15 @@ class HanaBuildPlugin extends InternalBuildPlugin {
113
118
  }
114
119
  }
115
120
 
116
- // 2. staging build: copy files except *.cds, .env, default-env.json, ./node_modules/**
117
121
  if (this.isStagingBuild()) {
118
122
  let blockList = "\\.cds$|\\.csv$|\\.hdbtabledata$"
119
123
  blockList += !this.hasBuildOption(CONTENT_ENV, true) ? "|\\.env($|\\..*$)" : ""
120
124
  blockList += !this.hasBuildOption(CONTENT_DEFAULT_ENV_JSON, true) ? "|default-env\\.json$" : ""
121
125
  blockList = new RegExp(blockList)
122
126
 
127
+ // 2. staging build: copy files except *.cds, .env, default-env.json, ./node_modules/**
123
128
  await this.copyNativeContent(src, dest, (entry) => {
124
129
  if (entry.startsWith(dbSrc)) {
125
- // entire native content
126
130
  return true
127
131
  }
128
132
  if (fs.statSync(entry).isDirectory()) {
@@ -378,11 +382,11 @@ class HanaBuildPlugin extends InternalBuildPlugin {
378
382
  }
379
383
 
380
384
  async _writePackageJson() {
381
- const packageJson = path.join(this.task.src, "package.json")
382
- const exists = fs.existsSync(packageJson)
385
+ const pkgJson = path.join(this.task.src, "package.json")
386
+ const exists = fs.existsSync(pkgJson)
383
387
 
384
388
  if (exists) {
385
- DEBUG?.(`skip create [${relativePaths(cds.root, packageJson)}], already existing`)
389
+ DEBUG?.(`skip create [${relativePaths(cds.root, pkgJson)}], already existing`)
386
390
  }
387
391
  if (this.isStagingBuild() && !exists) {
388
392
  const content = await HanaBuildPlugin._readTemplateAsJson(FILE_NAME_PACKAGE_JSON)
@@ -532,6 +536,37 @@ class HanaBuildPlugin extends InternalBuildPlugin {
532
536
  }
533
537
  }
534
538
 
539
+
540
+ /**
541
+ * Copy native hana content from workspaces.
542
+ */
543
+ async _addWorkspaceContent() {
544
+ const srcPaths = new Set()
545
+ const workspaces = await getWorkspaces()
546
+
547
+ workspaces.forEach(workspace => {
548
+ const dbSrc = path.join(cds.root, workspace, 'db/src')
549
+ const dbCfg = path.join(cds.root, workspace, 'db/cfg')
550
+ if (fs.existsSync(dbSrc)) {
551
+ srcPaths.add(dbSrc)
552
+ }
553
+ if (fs.existsSync(dbCfg)) {
554
+ srcPaths.add(dbCfg)
555
+ }
556
+ })
557
+ DEBUG?.(`Copying hana native artifacts from folders '${[...srcPaths].map(f => path.relative(cds.root, f)).join(', ')}'`)
558
+ const dest = path.join(this.task.dest, 'src/gen')
559
+ for (const src of srcPaths) {
560
+ await this.copyNativeContent(src, dest, (entry) => {
561
+ if (entry.match(/(\/|\\)gen(\/|\\)?$/)) {
562
+ // skip gen folder content
563
+ return false
564
+ }
565
+ return true
566
+ })
567
+ }
568
+ }
569
+
535
570
  static async _readTemplateAsJson(template) {
536
571
  const content = await fs.promises.readFile(path.join(__dirname, 'template', template), 'utf-8')
537
572
  return JSON.parse(content.toString())
@@ -5,7 +5,7 @@ const { hasJavaNature, getDefaultModelOptions, BuildError, hasOptionValue, isCla
5
5
  const BuildTaskProvider = require('../buildTaskProvider')
6
6
  const { FILE_EXT_CDS, BUILD_TASK_HANA, BUILD_TASK_FIORI, BUILD_TASK_JAVA, BUILD_TASK_JAVA_CF, BUILD_TASK_NODEJS, BUILD_TASK_NODE_CF, BUILD_TASK_MTX,
7
7
  BUILD_TASK_PREFIX, BUILD_TASKS, BUILD_TASK_MTX_SIDECAR, MTX_SIDECAR_FOLDER, BUILD_TASK_MTX_EXTENSION, NODEJS_MODEL_EXCLUDE_LIST,
8
- IGNORE_DEFAULT_MODELS } = require("../constants")
8
+ IGNORE_DEFAULT_MODELS, CONTENT_WS } = require("../constants")
9
9
  const DEBUG = cds.debug('cli|build')
10
10
 
11
11
  class InternalBuildTaskProvider extends BuildTaskProvider {
@@ -36,7 +36,7 @@ Upgrade to @sap/cds-mtxs following the migration guide https://cap.cloud.sap/doc
36
36
  // src folder needs to be initialized first
37
37
  InternalBuildTaskProvider._setDefaultSrcFolder(task)
38
38
 
39
- InternalBuildTaskProvider._setDefaultModel(task)
39
+ await InternalBuildTaskProvider._setDefaultModel(task)
40
40
  }
41
41
 
42
42
  getPlugin(task) {
@@ -290,7 +290,8 @@ Upgrade to @sap/cds-mtxs following the migration guide https://cap.cloud.sap/doc
290
290
  }
291
291
  }
292
292
 
293
- static _setDefaultModel(task) {
293
+ static async _setDefaultModel(task) {
294
+ const ws = hasOptionValue(task.options?.[CONTENT_WS], true)
294
295
  let taskModelPaths = task.options?.model
295
296
  if (taskModelPaths && !Array.isArray(taskModelPaths)) {
296
297
  taskModelPaths = [taskModelPaths]
@@ -301,10 +302,11 @@ Upgrade to @sap/cds-mtxs following the migration guide https://cap.cloud.sap/doc
301
302
  let defaultModelPaths = []
302
303
  if (taskModelPaths?.length) {
303
304
  if (!hasOptionValue(task.options?.[IGNORE_DEFAULT_MODELS], true)) {
304
- defaultModelPaths = getDefaultModelOptions().filter(p => p.match(allowList))
305
+ defaultModelPaths = await getDefaultModelOptions(ws)
306
+ defaultModelPaths = defaultModelPaths.filter(p => p.match(allowList))
305
307
  }
306
308
  } else {
307
- defaultModelPaths = getDefaultModelOptions()
309
+ defaultModelPaths = await getDefaultModelOptions(ws)
308
310
  defaultModelPaths.push(task.src)
309
311
  if (hasOptionValue(task.options?.[IGNORE_DEFAULT_MODELS], true)) {
310
312
  // all default models except the built-in models
@@ -90,11 +90,5 @@ class JavaBuildPlugin extends EdmxBuildPlugin {
90
90
  }
91
91
  return m
92
92
  }
93
-
94
- _isCompilerV1() {
95
- const version = cds.compiler.version()
96
- const match = version.match(/(\d+)\.?(\d*)\.?(\d*)/)
97
- return match && match[1] === 1
98
- }
99
93
  }
100
94
  module.exports = JavaBuildPlugin
@@ -43,7 +43,8 @@ class MtxExtensionBuildPlugin extends InternalBuildPlugin {
43
43
  await this.collectLanguageBundles(extModel, path.join(destExt, 'i18n'))
44
44
 
45
45
  // perform SQL backend validation to prevent extension deployment problems later on
46
- cds.compile.to.sql(model)
46
+ // REVISIT: disabled - causing stack overflow errors in LinkedCSN for some scenarios
47
+ // cds.compile.to.sql(model)
47
48
 
48
49
  const files = Object.keys(await cds.deploy.resources(model))
49
50
  if (files.length > 0) {
@@ -50,6 +50,11 @@ class MtxSidecarBuildPlugin extends NodejsBuildPlugin {
50
50
  await this.compileToJson(model, path.join(destSidecarSrc, DEFAULT_CSN_FILE_NAME))
51
51
  await this.collectLanguageBundles(model, path.join(destSidecarSrc, getI18nDefaultFolder()))
52
52
  await this.copySrvContent(this.task.src, destSidecar, destSidecar)
53
+
54
+ // copy .npmrc from project root, if it exists and no .npmrc exists in the sidecar
55
+ if (!fs.existsSync(path.join(destSidecar, '.npmrc')) && fs.existsSync(path.join(cds.root, '.npmrc'))) {
56
+ await this.copy(path.join(cds.root, '.npmrc')).to(path.join(destSidecar, '.npmrc'))
57
+ }
53
58
  }
54
59
 
55
60
  /**
@@ -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 } = require('../../util')
6
+ const { BuildError, getWorkspaces } = 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 } = require('../../constants')
9
9
  const { WARNING } = EdmxBuildPlugin
@@ -36,27 +36,26 @@ class NodejsBuildPlugin extends EdmxBuildPlugin {
36
36
  }
37
37
  // by default model contains all features
38
38
  const model = await this.model()
39
- if (!model) {
39
+ if (!model && !this.context.options['ws']) {
40
40
  return
41
41
  }
42
-
43
- const { dictionary, sources } = await this.compileAll(model, destSrv, destRoot)
44
-
45
- // collect and write language bundles into single i18n.json file
46
- await this.collectAllLanguageBundles(dictionary, sources, destSrv, destRoot)
47
-
48
- if (!this.hasBuildOption(CONTENT_EDMX, false)) {
49
- const compileOptions = { [FLAVOR_LOCALIZED_EDMX]: this.hasBuildOption(FLAVOR_LOCALIZED_EDMX, true) }
50
- // inferred flavor is required by edmx compiler backend
51
- // using cds.compile instead of cds.compiler.compileSources ensures that cds.env options are correctly read
52
- const baseModel = dictionary.base.meta.flavor !== 'inferred' ? await cds.compile(sources.base, super.options(), 'inferred') : dictionary.base
53
- await this.compileToEdmx(baseModel, path.join(this.destSrv, 'odata', cds.env.odata.version), compileOptions)
42
+ if (model) {
43
+ const { dictionary, sources } = await this.compileAll(model, destSrv, destRoot)
44
+ // collect and write language bundles into single i18n.json file
45
+ await this.collectAllLanguageBundles(dictionary, sources, destSrv, destRoot)
46
+
47
+ if (!this.hasBuildOption(CONTENT_EDMX, false)) {
48
+ const compileOptions = { [FLAVOR_LOCALIZED_EDMX]: this.hasBuildOption(FLAVOR_LOCALIZED_EDMX, true) }
49
+ // inferred flavor is required by edmx compiler backend
50
+ // using cds.compile instead of cds.compiler.compileSources ensures that cds.env options are correctly read
51
+ const baseModel = dictionary.base.meta.flavor !== 'inferred' ? await cds.compile(sources.base, super.options(), 'inferred') : dictionary.base
52
+ await this.compileToEdmx(baseModel, path.join(this.destSrv, 'odata', cds.env.odata.version), compileOptions)
53
+ }
54
54
  }
55
-
56
55
  if (this.isStagingBuild() && this.hasBuildOption(OUTPUT_MODE, OUTPUT_MODE_FILESYSTEM)) {
57
56
  const srcSrv = this.task.src === cds.root ? path.resolve(this.task.src, cds.env.folders.srv) : this.task.src
58
57
  await this._copyNativeContent(cds.root, srcSrv, destRoot, destSrv)
59
- if (this.context.options.ws) {
58
+ if (this.context.options['ws']) {
60
59
  await this._addWorkspaceDependencies(destRoot)
61
60
  }
62
61
  }
@@ -160,32 +159,18 @@ class NodejsBuildPlugin extends EdmxBuildPlugin {
160
159
  }
161
160
 
162
161
  async _addWorkspaceDependencies(dest) {
163
- const wsRoot = this._findWorkspaceRoot(cds.root)
162
+ const wsRoot = NodejsBuildPlugin._findWorkspaceRoot(cds.root)
164
163
  const appPkg = require(path.join(dest, 'package.json'))
165
164
  let workspaces
166
- if (this.task.options?.workspaces) {
165
+ if (this.task.options.workspaces) {
167
166
  workspaces = Array.isArray(this.task.options.workspaces) ? this.task.options.workspaces : [this.task.options.workspaces]
168
- }
169
- if (!workspaces) {
170
- workspaces = []
171
- for (const name in appPkg.dependencies) {
172
- if (appPkg.dependencies[name] === '*') {
173
- try {
174
- const pkg = require.resolve(path.join(name, 'package.json'), { paths: [wsRoot] })
175
- workspaces.push(path.dirname(path.relative(wsRoot, pkg)))
176
- } catch(e) {
177
- if (e.code === 'MODULE_NOT_FOUND') {
178
- throw new BuildError(`Workspace packaging failed - module '${name}' not found. Make sure that configured npm workspaces are installed.`)
179
- }
180
- throw e
181
- }
182
- }
183
- }
167
+ } else {
168
+ workspaces = await getWorkspaces(true, false, wsRoot);
184
169
  }
185
170
  if (workspaces.length) {
186
171
  let pkgDescriptors
187
172
  try {
188
- pkgDescriptors = await this._execNpmPack(dest, wsRoot, workspaces)
173
+ pkgDescriptors = await NodejsBuildPlugin._execNpmPack(dest, wsRoot, workspaces)
189
174
  } catch (e) {
190
175
  DEBUG?.(e)
191
176
  throw new BuildError(`Failed to package npm workspace dependencies\n${e.message}`)
@@ -193,9 +178,9 @@ class NodejsBuildPlugin extends EdmxBuildPlugin {
193
178
  if (pkgDescriptors?.length) {
194
179
  let changed
195
180
  for (const pkgDescriptor of pkgDescriptors) {
196
- const {name, filename: fileName} = pkgDescriptor
197
- const filePath = path.join(dest, fileName)
198
- if (appPkg.dependencies?.[name] === '*') {
181
+ const { name, filename: fileName } = pkgDescriptor
182
+ const filePath = path.join(dest, fileName)
183
+ if (appPkg.dependencies?.[name]) {
199
184
  appPkg.dependencies[name] = `file:${fileName}`
200
185
  this.pushFile(filePath)
201
186
  changed |= true
@@ -210,28 +195,28 @@ class NodejsBuildPlugin extends EdmxBuildPlugin {
210
195
  }
211
196
  }
212
197
 
213
- _findWorkspaceRoot(dir) {
198
+ static _findWorkspaceRoot(dir) {
214
199
  try {
215
- if(require(path.join(dir, 'package.json')).workspaces) {
216
- return dir
200
+ if (require(path.join(dir, 'package.json')).workspaces) {
201
+ return dir
217
202
  }
218
- } catch(err) {
203
+ } catch (err) {
219
204
  // ignore
220
205
  }
221
- if(fs.existsSync(path.join(dir, '.gitmodules')) || fs.existsSync(path.join(dir, 'node_modules'))) {
206
+ if (fs.existsSync(path.join(dir, '.gitmodules')) || fs.existsSync(path.join(dir, 'node_modules'))) {
222
207
  return // project root reached
223
208
  }
224
209
  if (path.dirname(dir) !== dir) {
225
210
  return this._findWorkspaceRoot(path.dirname(dir))
226
211
  }
227
212
  }
228
-
229
- async _execNpmPack(dest, wsRoot, workspaces) {
213
+
214
+ static async _execNpmPack(dest, wsRoot, workspaces) {
230
215
  const args = ['pack', '-ws', '--json', `--pack-destination=${dest}`]
231
216
  workspaces.forEach(w => args.push(`-w=${w}`))
232
217
 
233
- DEBUG?.(`execute command: npm ${args}`)
234
- const result = await cmd.spawnCommand( 'npm', args, { cwd: wsRoot }, true, !!DEBUG)
218
+ DEBUG?.(`execute command: npm ${args.join(', ')}`)
219
+ const result = await cmd.spawnCommand('npm', args, { cwd: wsRoot }, true, !!DEBUG)
235
220
  DEBUG?.(result)
236
221
  return JSON.parse(result)
237
222
  }
package/lib/build/util.js CHANGED
@@ -1,7 +1,11 @@
1
1
  const fs = require('fs')
2
2
  const path = require('path')
3
3
  const cds = require('../cds')
4
- const { SEVERITY_ERROR, FILE_EXT_CDS } = require('./constants')
4
+ const cp = require('child_process');
5
+ const execAsync = require('util').promisify(cp.exec)
6
+ const IS_WIN = require('os').platform() === 'win32'
7
+ const { SEVERITY_ERROR, FILE_EXT_CDS } = require('./constants');
8
+ const DEBUG = cds.debug('cli|build')
5
9
 
6
10
  function getProperty(src, segments) {
7
11
  segments = Array.isArray(segments) ? segments : segments.split('.')
@@ -113,10 +117,26 @@ function resolveRequiredSapModels(modelPaths) {
113
117
  })
114
118
  }
115
119
 
116
- function getDefaultModelOptions() {
117
- const fts = cds.env.features.folders
118
- const modelPaths = cds.resolve(!fts ? '*' : ['*', fts], false)
119
- return _pushModelPaths(modelPaths)
120
+ async function getDefaultModelOptions(ws = false) {
121
+ const wildcards = cds.env.requires.toggles && cds.env.features.folders ? ['*', cds.env.features.folders] : '*'
122
+ let paths = _pushModelPaths(cds.resolve(wildcards, false))
123
+
124
+ if (ws) {
125
+ const workspaces = await getWorkspaces()
126
+ const env = cds.env
127
+ const root = cds.root
128
+ try {
129
+ workspaces.forEach(workspace => {
130
+ cds.root = path.join(root, workspace)
131
+ cds.env = cds.env.for('cds', cds.root)
132
+ paths = paths.concat(_pushModelPaths(cds.resolve(cds.env.requires.toggles ? wildcards : '*', false)).map(p => path.join(workspace, p)))
133
+ })
134
+ } finally {
135
+ cds.root = root
136
+ cds.env = env
137
+ }
138
+ }
139
+ return paths
120
140
  }
121
141
 
122
142
  /**
@@ -127,30 +147,6 @@ function normalizePath(dir) {
127
147
  return typeof dir === "string" ? dir.replace(/\\/g, '/').replace(/\/$/, '') : dir
128
148
  }
129
149
 
130
- function _pushModelPaths(...modelPaths) {
131
- const model = new Set()
132
- // may contain nested arrays
133
- modelPaths = flatten(modelPaths)
134
- const { roots } = cds.env
135
- modelPaths.forEach(m => {
136
- if (m && !model.has(m) && !model.has(m + "/")) {
137
- // filter root model paths that do not exist
138
- // other entries are added as is, e.g. reuse model entries
139
- if (roots.includes(m)) {
140
- const dir = path.resolve(cds.root, m)
141
- if (fs.existsSync(dir)) {
142
- model.add(m)
143
- } else if (fs.existsSync(dir + FILE_EXT_CDS)) { //might be cds file name, compatibility to old build configs
144
- model.add(m)
145
- }
146
- } else {
147
- model.add(m)
148
- }
149
- }
150
- })
151
- return [...model].map(m => normalizePath(m))
152
- }
153
-
154
150
  function flatten(modelPaths) {
155
151
  return modelPaths.reduce((acc, m) => {
156
152
  if (Array.isArray(m)) {
@@ -180,6 +176,114 @@ function isClassicMtx() {
180
176
  return /true/i.test(process.env.OLD_MTX) || _hasMtxDependency()
181
177
  }
182
178
 
179
+ async function pathExists(path) {
180
+ return fs.promises.access(path).then(() => true).catch(() => false);
181
+ }
182
+
183
+ /**
184
+ * Returns the list of workspaces defined for the current project. A workspace dependency
185
+ * needs to be defined resulting in a corresponding sym-link.
186
+ * @returns the paths are relative to 'cds.root'.
187
+ */
188
+ async function getWorkspaces(resolve = false, devDependencies = true, wsRoot = cds.root) {
189
+ const workspaces = []
190
+ const pgkJsonWsRoot = require(path.join(wsRoot, 'package.json'))
191
+ let pgkJson = pgkJsonWsRoot
192
+ if (wsRoot !== cds.root) {
193
+ pgkJson = require(path.join(cds.root, 'package.json'))
194
+ }
195
+ if (pgkJsonWsRoot.workspaces) {
196
+ if (resolve) {
197
+ _validateWsDependencies(wsRoot, pgkJson.dependencies, devDependencies ? pgkJson.devDependencies : {})
198
+ }
199
+ // read npm workspaces
200
+ const pkgs = await _execNpmWs(wsRoot)
201
+ pkgs.forEach(pkg => {
202
+ if (!resolve || devDependencies && pgkJson.devDependencies?.[pkg] || pgkJson.dependencies?.[pkg]) {
203
+ const workspace = _getWorkspace(wsRoot, pkg)
204
+ if (workspace) {
205
+ workspaces.push(workspace)
206
+ }
207
+ }
208
+ })
209
+ }
210
+ DEBUG?.(`Found ${workspaces.length} workspaces ${workspaces.join(', ')}`)
211
+ return workspaces
212
+ }
213
+
214
+ /**
215
+ * Read installed npm workspaces using 'npm ls -ws --json'.
216
+ * @returns {Array} list of package paths, or an empty array if either
217
+ * no workspaces are defined or have not been installed.
218
+ */
219
+ async function _execNpmWs(wsRoot) {
220
+ try {
221
+ const cmdLine = 'npm ls -ws --json'
222
+ DEBUG?.(`execute ${cmdLine}`)
223
+ let result = await execAsync(cmdLine, { shell: IS_WIN, stdio: ['inherit', 'pipe', 'inherit'], cwd: wsRoot })
224
+ // DEBUG?.(result.stdout)
225
+ result = JSON.parse(result.stdout)
226
+ if (result.dependencies) {
227
+ return Object.keys(result.dependencies)
228
+ } else if (result.name) {
229
+ return [result.name]
230
+ }
231
+ } catch (e) {
232
+ e.stderr = e.stderr?.replace(/npm ERR! /g, '')
233
+ throw e
234
+ }
235
+ return []
236
+ }
237
+
238
+ function _getWorkspace(wsRoot, pkg) {
239
+ try {
240
+ const pkgPath = require.resolve(path.join(pkg, 'package.json'), { paths: [wsRoot] })
241
+ if (!pkgPath.match(/(\/|\\)node_modules(\/|\\)?/) && pkgPath.startsWith(wsRoot)) {
242
+ return path.relative(wsRoot, path.dirname(pkgPath))
243
+ }
244
+ } catch (e) {
245
+ // ignore - workspace dependencies have already been validated
246
+ }
247
+ }
248
+
249
+ function _validateWsDependencies(wsRoot, dependencies, devDependencies) {
250
+ const allDependencies = {...dependencies, ...devDependencies}
251
+ for (const name in allDependencies) {
252
+ if (allDependencies[name] === '*') {
253
+ try {
254
+ require.resolve(path.join(name, 'package.json'), { paths: [wsRoot] })
255
+ } catch (e) {
256
+ if (e.code === 'MODULE_NOT_FOUND') {
257
+ throw new BuildError(`npm packaging failed, module '${name}' not found. Make sure configured npm workspaces are installed.`)
258
+ }
259
+ throw e
260
+ }
261
+ }
262
+ }
263
+ }
264
+
265
+ function _pushModelPaths(...modelPaths) {
266
+ const model = new Set()
267
+ // may contain nested arrays
268
+ modelPaths = flatten(modelPaths)
269
+ const { roots } = cds.env
270
+ modelPaths.forEach(m => {
271
+ if (m && !model.has(m) && !model.has(m + "/")) {
272
+ // filter roots and absolute model paths that do not exist
273
+ if (roots.includes(m)) {
274
+ const dir = path.resolve(cds.root, m)
275
+ if (fs.existsSync(dir)) {
276
+ model.add(m)
277
+ } else if (fs.existsSync(dir + FILE_EXT_CDS)) { //might be cds file name, compatibility to old build configs
278
+ model.add(m)
279
+ }
280
+ } else {
281
+ model.add(m)
282
+ }
283
+ }
284
+ })
285
+ return [...model].map(m => normalizePath(m))
286
+ }
183
287
 
184
288
  /**
185
289
  * Return gnu-style error string for location `loc`:
@@ -208,10 +312,6 @@ function _locationString(loc) {
208
312
  : `${loc.file}:${loc.line}.${loc.col}-${loc.endLine}.${loc.endCol}`;
209
313
  }
210
314
 
211
- async function pathExists(path) {
212
- return fs.promises.access(path).then(() => true).catch(() => false);
213
- }
214
-
215
315
  /**
216
316
  * Class for individual build message.
217
317
  *
@@ -276,6 +376,7 @@ module.exports = {
276
376
  isClassicMtx,
277
377
  normalizePath,
278
378
  pathExists,
379
+ getWorkspaces,
279
380
  BuildMessage,
280
381
  BuildError
281
382
  }
@@ -1843,7 +1843,7 @@ see [Expand](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-prot
1843
1843
  if (suffix === SUFFIX.create)
1844
1844
  schemaProperties[iName] = getSchema(property, SUFFIX.create);
1845
1845
  if (suffix === SUFFIX.update)
1846
- schemaProperties[iName] = getSchema(property, SUFFIX.update);
1846
+ schemaProperties[iName] = getSchema(property, SUFFIX.create);
1847
1847
  }
1848
1848
  } else {
1849
1849
  if (property[voc.Core.Permissions] === "Read" || property[voc.Core.Computed]) {
@@ -159,7 +159,7 @@ Therefore, specifying the CDS model is not required when calling 'cds deploy'.`;
159
159
  const serviceInstance = await this.createHanaService(cfServiceInstanceName, cfConfig);
160
160
 
161
161
  const cfServiceInstanceKeyName = `${cfServiceInstanceName}-key`;
162
- let serviceKey = await cfUtil.getOrCreateServiceKey(serviceInstance, cfServiceInstanceKeyName);
162
+ let serviceKey = await cfUtil.getOrCreateServiceKey(serviceInstance, cfServiceInstanceKeyName, { permissions: 'development' });
163
163
  this._validateServiceKey(serviceKey, cfServiceInstanceKeyName);
164
164
 
165
165
  if (tunnelAddress) {