@sap/cds-dk 8.9.5 → 9.0.5

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 (109) hide show
  1. package/CHANGELOG.md +101 -2012
  2. package/LICENSE +15 -21
  3. package/bin/bind.js +1 -1
  4. package/bin/build.js +35 -37
  5. package/bin/cds.js +13 -30
  6. package/bin/compile/index.js +2 -2
  7. package/bin/debug.js +27 -7
  8. package/bin/deploy/index.js +6 -23
  9. package/bin/deploy/to-hana.js +4 -2
  10. package/bin/help.js +5 -3
  11. package/bin/init.js +1 -1
  12. package/bin/up.js +45 -12
  13. package/bin/watch.js +45 -63
  14. package/lib/bind/cf.js +2 -1
  15. package/lib/bind/k8s.js +2 -2
  16. package/lib/bind/plugin.js +1 -1
  17. package/lib/bind/shared.js +16 -25
  18. package/lib/build/buildTaskEngine.js +26 -10
  19. package/lib/build/buildTaskFactory.js +3 -2
  20. package/lib/build/buildTaskProvider.js +18 -7
  21. package/lib/build/buildTaskProviderFactory.js +35 -13
  22. package/lib/build/constants.js +4 -0
  23. package/lib/build/index.js +1 -1
  24. package/lib/build/plugin.js +89 -54
  25. package/lib/build/plugins/helm/files/subcharts/web-application/Chart.yaml +3 -1
  26. package/lib/build/plugins/helm/files/subcharts/web-application/templates/_helpers.tpl +4 -2
  27. package/lib/build/plugins/helm/files/subcharts/web-application/templates/api-rule.yaml +4 -2
  28. package/lib/build/plugins/helm/files/subcharts/web-application/templates/deployment.yaml +18 -1
  29. package/lib/build/plugins/helm/files/subcharts/web-application/templates/service-binding.yaml +0 -1
  30. package/lib/build/plugins/helm/files/subcharts/web-application/values.schema.json +34 -2
  31. package/lib/build/plugins/helm/files/subcharts/web-application/values.yaml +9 -2
  32. package/lib/build/plugins/helm/index.js +2 -1
  33. package/lib/build/plugins.js +24 -10
  34. package/lib/build/provider/edmxBuildPlugin.js +1 -11
  35. package/lib/build/provider/fiori/index.js +4 -4
  36. package/lib/build/provider/hana/index.js +12 -5
  37. package/lib/build/provider/hana/template/package.json +1 -1
  38. package/lib/build/provider/internalBuildPlugin.js +1 -1
  39. package/lib/build/provider/internalBuildTaskProvider.js +3 -24
  40. package/lib/build/provider/java/index.js +1 -1
  41. package/lib/build/provider/mtx-extension/index.js +15 -8
  42. package/lib/build/provider/nodejs/index.js +28 -6
  43. package/lib/build/util.js +186 -125
  44. package/lib/deploy/index.js +5 -0
  45. package/lib/deploy/to-hana/hana.js +27 -33
  46. package/lib/deploy/to-hana/hdiDeployUtil.js +2 -2
  47. package/lib/import/odata/v2parser.js +34 -3
  48. package/lib/import/openapi/importOpenAPI.js +4 -0
  49. package/lib/index.js +2 -11
  50. package/lib/init/constants.js +1 -2
  51. package/lib/init/merge.js +101 -77
  52. package/lib/init/mvn.js +3 -3
  53. package/lib/init/projectReader.js +1 -1
  54. package/lib/init/template/ams/files/mta.yaml.hbs +6 -1
  55. package/lib/init/template/ams/files/package.json.hbs +1 -1
  56. package/lib/init/template/approuter/files/mta.yaml.hbs +5 -0
  57. package/lib/init/template/cf-manifest/files/manifest.yml.hbs +1 -1
  58. package/lib/init/template/cloud-logging/files/package.json.hbs +3 -3
  59. package/lib/init/template/data/as-json.js +3 -3
  60. package/lib/init/template/dynatrace/files/mta.yaml.hbs +1 -1
  61. package/lib/init/template/dynatrace/files/package.json.hbs +2 -2
  62. package/lib/init/template/dynatrace/index.js +2 -1
  63. package/lib/init/template/enterprise-messaging/files/mta.yaml.hbs +6 -0
  64. package/lib/init/template/enterprise-messaging/index.js +5 -1
  65. package/lib/init/template/extensibility/files/package.json.hbs +1 -1
  66. package/lib/init/template/hana/files/db/package.json +1 -1
  67. package/lib/init/template/hana/files/package.json.hbs +2 -4
  68. package/lib/init/template/hana/index.js +2 -2
  69. package/lib/init/template/handler/index.js +16 -12
  70. package/lib/init/template/helm/index.js +13 -22
  71. package/lib/init/template/helm-unified-runtime/index.js +2 -1
  72. package/lib/init/template/html5-repo/files/values.yaml.hbs +12 -1
  73. package/lib/init/template/html5-repo/index.js +10 -5
  74. package/lib/init/template/http/index.js +1 -1
  75. package/lib/init/template/ias/files/mta.yaml.hbs +1 -1
  76. package/lib/init/template/ias/files/sidecar.package.json.hbs +0 -7
  77. package/lib/init/template/ias/index.js +5 -4
  78. package/lib/init/template/lint/files/package.json +6 -0
  79. package/lib/init/template/lint/index.js +61 -204
  80. package/lib/init/template/mta/files/package.json +1 -1
  81. package/lib/init/template/multitenancy/files/mta.yaml.hbs +2 -2
  82. package/lib/init/template/multitenancy/files/package.json.hbs +1 -1
  83. package/lib/init/template/multitenancy/files/package.sidecar.json.hbs +7 -4
  84. package/lib/init/template/multitenancy/index.js +3 -0
  85. package/lib/init/template/nodejs/files/package.json.hbs +6 -3
  86. package/lib/init/template/postgres/files/package.json.hbs +1 -1
  87. package/lib/init/template/redis/files/package.json.hbs +1 -1
  88. package/lib/init/template/sample/files/app/admin-books/fiori-service.cds +6 -0
  89. package/lib/init/template/sample/files/app/admin-books/webapp/manifest.json +1 -0
  90. package/lib/init/template/sample/files/app/browse/fiori-service.cds +3 -2
  91. package/lib/init/template/sample/files/app/common.cds +0 -11
  92. package/lib/init/template/sqlite/files/package.json +1 -1
  93. package/lib/init/template/toggles/files/package.json.hbs +1 -1
  94. package/lib/init/template/typescript/files/package.json +2 -4
  95. package/lib/init/template/workzone-standard/index.js +2 -0
  96. package/lib/init/template/xsuaa/index.js +2 -6
  97. package/lib/lint/Linter.js +203 -331
  98. package/lib/lint/generators.js +52 -46
  99. package/lib/lint/io.js +77 -289
  100. package/lib/util/fs.js +2 -1
  101. package/lib/util/git.js +1 -1
  102. package/lib/util/glob.js +77 -0
  103. package/lib/watch/livereload-server.js +3 -4
  104. package/lib/watch/node-watch.js +3 -4
  105. package/lib/watch/watched.js +1 -1
  106. package/npm-shrinkwrap.json +200 -858
  107. package/package.json +6 -8
  108. package/readme.md +1 -1
  109. package/lib/lint/checks.js +0 -135
package/lib/build/util.js CHANGED
@@ -1,3 +1,4 @@
1
+ // @ts-check
1
2
  const fs = require('fs')
2
3
  const path = require('path')
3
4
  const cds = require('../cds')
@@ -6,23 +7,33 @@ const execAsync = require('util').promisify(cp.exec)
6
7
  const IS_WIN = require('os').platform() === 'win32'
7
8
  const { SEVERITY_ERROR, FILE_EXT_CDS, NODEJS_MODEL_EXCLUDE_LIST } = require('./constants');
8
9
  const DEBUG = cds.debug('cli|build')
9
- const SECRETS = /(passw)|(cert)|(ca)|(secret)|(key)/i
10
10
  const WS_MAX_DEPTH = 2
11
11
 
12
+ /** @typedef {{col?: number, endCol?: number, line?: number, endLine?: number, file: string}} SourceLocation */
13
+ /** @typedef {object[] | object} Credentials */
14
+ /** @typedef {(string | NestedStringArray)[]} NestedStringArray */
15
+
16
+ /**
17
+ * @param {object} src - object to get property from
18
+ * @param {string | string[]} segments - property path
19
+ */
12
20
  function getProperty(src, segments) {
13
21
  segments = Array.isArray(segments) ? segments : segments.split('.')
14
22
  return segments.reduce((p, n) => p && p[n], src)
15
23
  }
16
24
 
25
+ /**
26
+ * @param {object} src - object to set property on
27
+ * @param {string | string[]} segments - property path
28
+ * @param {unknown} value - value to set
29
+ */
17
30
  function setProperty(src, segments, value) {
18
31
  segments = Array.isArray(segments) ? segments : segments.split('.')
19
32
  segments.reduce((p, n, idx) => {
20
33
  if (segments.length === idx + 1) {
21
34
  p[n] = value
22
- } else {
23
- if (p[n] === undefined) {
24
- p[n] = {}
25
- }
35
+ } else if (p[n] === undefined) {
36
+ p[n] = {}
26
37
  }
27
38
  return p[n]
28
39
  }, src)
@@ -37,8 +48,9 @@ function hasJavaNature() {
37
48
 
38
49
  /**
39
50
  * Determines whether the both values are identical.
40
- * @param {*} actual
41
- * @param {*} expected
51
+ * @template {number | boolean} T
52
+ * @param {T} actual
53
+ * @param {T} expected
42
54
  */
43
55
  function hasOptionValue(actual, expected) {
44
56
  if (typeof expected === 'undefined') {
@@ -52,65 +64,29 @@ function hasOptionValue(actual, expected) {
52
64
  return String(expected) === actual
53
65
  }
54
66
  }
55
- if (typeof expected === 'number') {
67
+ if (typeof expected === 'number' && typeof actual === 'number') {
56
68
  return (actual & expected) !== 0
57
69
  }
58
70
  return actual === expected
59
71
  }
60
72
 
61
- // Returning the project relative path representation of the given path(s),
62
- function relativePaths(root, qualifiedPaths) {
63
- qualifiedPaths = typeof qualifiedPaths === "string" ? [qualifiedPaths] : qualifiedPaths
64
- if (Array.isArray(qualifiedPaths)) {
65
- return qualifiedPaths.map(qualifiedPath => {
66
- const relPath = path.relative(root, qualifiedPath)
67
- return relPath || "."
68
- })
69
- }
70
- return qualifiedPaths
71
- }
72
-
73
- function redactCredentials(config) {
74
- if (typeof config !== 'object' || config === null) {
75
- return config;
76
- }
77
-
78
- if (Array.isArray(config)) {
79
- return config.map(item => redactCredentials(item));
80
- } else {
81
- const newConfig = Object.assign({}, config);
82
- for (let key in newConfig) {
83
- if (key === 'credentials') {
84
- newConfig[key] = _redacted(newConfig[key]);
85
- } else {
86
- newConfig[key] = redactCredentials(newConfig[key]);
87
- }
88
- }
89
- return newConfig;
90
- }
91
- }
92
-
93
73
  /**
94
- * Masks password-like strings, also reducing clutter in output
95
- * @param {any} cred - object or array with credentials
96
- * @returns {any}
74
+ * Returning the project relative path representation of the given path(s),
75
+ * @param {string} root - project root
76
+ * @param {string | string[]} qualifiedPaths - fully qualified path(s)
97
77
  */
98
- function _redacted(cred) {
99
- if (!cred) return cred
100
- if (Array.isArray(cred)) return cred.map(c => typeof c === 'string' ? '...' : _redacted(c))
101
- if (typeof cred === 'object') {
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
105
- }
106
- return cred
78
+ function relativePaths(root, qualifiedPaths) {
79
+ qualifiedPaths = typeof qualifiedPaths === "string" ? [qualifiedPaths] : qualifiedPaths
80
+ return Array.isArray(qualifiedPaths)
81
+ ? qualifiedPaths.map(qualifiedPath => path.relative(root, qualifiedPath) ?? ".")
82
+ : qualifiedPaths
107
83
  }
108
84
 
109
85
  /**
110
86
  * Returns a list of fully qualified model names belonging to the '@sap' namespace that cannot be resolved.
111
87
  * E.g. the module might NOT have been installed.
112
- * @param {Array} modelPaths
113
- * @returns {Array}
88
+ * @param {string[]} modelPaths
89
+ * @returns {string[]}
114
90
  */
115
91
  function resolveRequiredSapModels(modelPaths) {
116
92
  return Array.isArray(modelPaths) && modelPaths.filter(p => {
@@ -121,13 +97,19 @@ function resolveRequiredSapModels(modelPaths) {
121
97
  })
122
98
  }
123
99
 
124
- async function getDefaultModelOptions(ws = false, filter) {
100
+ /**
101
+ * Returns the default model options for the given workspace.
102
+ * @param {boolean} ws - whether to include workspace models
103
+ * @param {Function | undefined} filter - filter function for model paths
104
+ */
105
+ async function getDefaultModelOptions(ws = false, filter = undefined) {
125
106
  // Note: requires.toggles holds the actual kind representation and not the value 'true'
126
107
  const wildcards = cds.env.requires.toggles && cds.env.features.folders ? ['*', cds.env.features.folders] : '*'
127
108
  let paths = _filterModelPaths(cds.resolve(wildcards, false), filter)
128
109
 
129
110
  if (ws) {
130
- const workspaces = await getWorkspaces()
111
+ const workspaces = (await getWorkspaces())
112
+ .map(ws => ws.workspace)
131
113
  const env = cds.env
132
114
  const root = cds.root
133
115
  try {
@@ -141,28 +123,44 @@ async function getDefaultModelOptions(ws = false, filter) {
141
123
  cds.env = env
142
124
  }
143
125
  // exclude redundant built-in models starting with workspace path, e.g. 'books/@sap/cds-mtxs/srv/bootstrap'
144
- paths = paths.filter(p => NODEJS_MODEL_EXCLUDE_LIST.includes(p) || !NODEJS_MODEL_EXCLUDE_LIST.some(ex => p.endsWith(ex)))
126
+ paths = paths
127
+ .filter(p => typeof(p) === 'string' && (NODEJS_MODEL_EXCLUDE_LIST.includes(p) || !NODEJS_MODEL_EXCLUDE_LIST.some(ex => p.endsWith(ex))))
145
128
  }
146
129
  return paths
147
130
  }
148
131
 
132
+ /**
133
+ * @overload
134
+ * @param {string} dir
135
+ * @returns {string}
136
+ */
137
+ /**
138
+ * @overload
139
+ * @param {unknown} dir
140
+ * @returns {unknown}
141
+ */
149
142
  /**
150
143
  * For valid paths replace '\\' with '/' and remove trailing '/'. Otherwise return as is
151
- * @param {*} dir
144
+ * @param {string | unknown} dir
145
+ * @returns {string | unknown}
152
146
  */
153
147
  function normalizePath(dir) {
154
148
  return typeof dir === "string" ? dir.replace(/\\/g, '/').replace(/\/$/, '') : dir
155
149
  }
156
150
 
151
+ /**
152
+ * @param {NestedStringArray} modelPaths
153
+ */
157
154
  function flatten(modelPaths) {
158
- return modelPaths.reduce((acc, m) => {
155
+ // TODO: I guess this could be modelPaths.flat(Infinity) in the future
156
+ return /** @type{string[]}*/(modelPaths.reduce((/** @type {string[]}*/acc, m) => {
159
157
  if (Array.isArray(m)) {
160
158
  acc = acc.concat(flatten(m))
161
159
  } else if (m) {
162
160
  acc.push(m)
163
161
  }
164
162
  return acc
165
- }, [])
163
+ }, []))
166
164
  }
167
165
 
168
166
  function getI18nDefaultFolder() {
@@ -181,43 +179,103 @@ async function getWorkspacePaths() {
181
179
  if (!wsRoot) {
182
180
  return []
183
181
  }
184
- return (await getWorkspaces(wsRoot)).filter(ws => cds.root !== path.join(wsRoot, ws)).map(ws => path.join(wsRoot, ws))
182
+ return (await getWorkspaces(wsRoot))
183
+ .map(ws => ws.workspace)
184
+ .filter(ws => cds.root !== path.join(wsRoot, ws))
185
+ .map(ws => path.join(wsRoot, ws))
186
+ }
187
+
188
+ /**
189
+ * @typedef {{
190
+ * path: string,
191
+ * workspace: string,
192
+ * packageName: string
193
+ * }} Workspace
194
+ *
195
+ * Returns a map of workspaces from a root project, with their paths and workspace names,
196
+ * I.e. for a monorepo setup this command finds all workspaces defined therein.
197
+ * @param {string} root - the root directory to search for workspaces
198
+ * @returns {Promise<Record<string, Workspace>>} - a map of workspaces with their paths and workspace names
199
+ */
200
+ async function getWorkspacesDeclarations (root) {
201
+ const rootPackageJson = path.join(root, 'package.json')
202
+ if (!fs.existsSync(rootPackageJson)) return {}
203
+ const pkg = require(rootPackageJson)
204
+ if (!pkg?.workspaces) return {}
205
+
206
+ const workspaces = []
207
+ for (const workspace of pkg.workspaces) {
208
+ if (/[*?[\]]/.test(workspace)) {
209
+ // contains globs
210
+ const { glob } = require('../util/glob')
211
+ for await (const file of glob(workspace, { cwd: root })) {
212
+ try {
213
+ const projectPath = path.join(root, file)
214
+ const projectName = require(path.join(projectPath, 'package.json')).name
215
+ workspaces.push([projectName, {
216
+ packageName: projectName,
217
+ path: projectPath,
218
+ workspace: file
219
+ }])
220
+ } catch {
221
+ // not a directory or doesn't contain a package.json -> not a workspace -> skip
222
+ }
223
+ }
224
+ } else {
225
+ // no glob, just a path literal
226
+ const projectPath = path.join(root, workspace)
227
+ try {
228
+ const projectName = require(path.join(projectPath, 'package.json')).name
229
+ workspaces.push([projectName, { path: projectPath, workspace }])
230
+ } catch {
231
+ // not a directory or doesn't contain a package.json -> not a workspace -> skip
232
+ }
233
+ }
234
+ }
235
+ return Object.fromEntries(workspaces)
185
236
  }
186
237
 
187
238
  /**
188
239
  * Returns the list of workspaces defined for the current project. A workspace dependency
189
- * needs to be defined resulting in a corresponding sym-link.
190
- * @returns the paths are relative to 'wsRoot'.
240
+ * needs to be defined resulting in a corresponding symlink.
241
+ * @returns {Promise<Workspace[]>} the paths are relative to 'wsRoot'.
191
242
  */
192
- async function getWorkspaces(wsRoot, resolve = false, devDependencies = true) {
243
+ async function getWorkspaces(wsRoot, resolve = false, devDependencies = true, visited = new Set()) {
193
244
  wsRoot ??= findWorkspaceRoot(cds.root)
194
245
  if (!wsRoot) {
195
246
  return []
196
247
  }
197
- const pgkJsonWsRoot = require(path.join(wsRoot, 'package.json'))
198
- let pgkJson = pgkJsonWsRoot
248
+ const pkgJsonWsRoot = require(path.join(wsRoot, 'package.json'))
249
+ let pkgJson = pkgJsonWsRoot
199
250
  if (wsRoot !== cds.root) {
200
- pgkJson = require(path.join(cds.root, 'package.json'))
251
+ pkgJson = require(path.join(cds.root, 'package.json'))
201
252
  }
202
- const workspaces = []
203
- if (pgkJsonWsRoot.workspaces) {
253
+ const workspaceDependencies = []
254
+ if (pkgJsonWsRoot.workspaces) {
204
255
  if (resolve) {
205
- _validateWsDependencies(wsRoot, pgkJson.dependencies, devDependencies ? pgkJson.devDependencies : {})
256
+ _validateWsDependencies(wsRoot, pkgJson.dependencies, devDependencies ? pkgJson.devDependencies : {})
206
257
  }
207
258
  // read npm workspaces
259
+ const workspaces = await getWorkspacesDeclarations(wsRoot)
208
260
  const pkgs = await _execNpmWs(wsRoot)
209
- pkgs.forEach(pkg => {
261
+ for (const pkg of pkgs) {
210
262
  // only create tarball archives for dependencies with version '*'
211
- if (!resolve || devDependencies && pgkJson.devDependencies?.[pkg] === '*' || pgkJson.dependencies?.[pkg] === '*') {
212
- const workspace = _getWorkspace(wsRoot, pkg)
213
- if (workspace) {
214
- workspaces.push(workspace)
263
+ if (!resolve || devDependencies && pkgJson.devDependencies?.[pkg] === '*' || pkgJson.dependencies?.[pkg] === '*') {
264
+ const subproject = workspaces[pkg]
265
+ if (subproject && !visited.has(pkg)) {
266
+ visited.add(pkg)
267
+ workspaceDependencies.push(subproject)
268
+ // temporarily set cds.root to the dependency to resolve transitive deps thereof
269
+ const cdsr = cds.root
270
+ cds.root = subproject.path
271
+ workspaceDependencies.push(...(await getWorkspaces(wsRoot, resolve, devDependencies, visited)))
272
+ cds.root = cdsr
215
273
  }
216
274
  }
217
- })
275
+ }
218
276
  }
219
- DEBUG?.(`Found ${workspaces.length} workspaces ${workspaces.join(', ')}`)
220
- return workspaces
277
+ DEBUG?.(`Found ${workspaceDependencies.length} workspace dependencies ${workspaceDependencies.join(', ')}`)
278
+ return workspaceDependencies
221
279
  }
222
280
 
223
281
  function findWorkspaceRoot(currentPath, depth = 0) {
@@ -279,39 +337,33 @@ function filterFeaturePaths(model, modelPaths) {
279
337
 
280
338
  /**
281
339
  * Read installed npm workspaces using 'npm ls -ws --json'.
282
- * @returns {Array} list of package paths, or an empty array if either
340
+ * There is currently no easier way for this, as workspaces
341
+ * could contain globs. Glob resolution will be supported
342
+ * natively in the future (fs.glob is in experimental state as of Node23).
343
+ * @param {import('fs').PathLike} wsRoot - the root directory of the workspace
344
+ * @returns {Promise<string[]>} list of package paths, or an empty array if either
283
345
  * no workspaces are defined or have not been installed.
284
346
  */
285
347
  async function _execNpmWs(wsRoot) {
286
348
  try {
287
349
  const cmdLine = 'npm ls -ws --json'
288
350
  DEBUG?.(`execute ${cmdLine}`)
289
- let result = await execAsync(cmdLine, { shell: IS_WIN, stdio: ['inherit', 'pipe', 'inherit'], cwd: wsRoot })
351
+ // @ts-expect-error - TODO: shell should be a string. But can probably be removed altogether?
352
+ const result = await execAsync(cmdLine, { shell: IS_WIN, stdio: ['inherit', 'pipe', 'inherit'], cwd: wsRoot })
290
353
  // DEBUG?.(result.stdout)
291
- result = JSON.parse(result.stdout)
292
- if (result.dependencies) {
293
- return Object.keys(result.dependencies)
294
- } else if (result.name) {
295
- return [result.name]
354
+ const jsonResult = JSON.parse(result.stdout)
355
+ if (jsonResult.dependencies) {
356
+ return Object.keys(jsonResult.dependencies)
357
+ } else if (jsonResult.name) {
358
+ return [jsonResult.name]
296
359
  }
297
- } catch (e) {
360
+ } catch (/** @type {{stderr: string}} */e) {
298
361
  e.stderr = e.stderr?.replace(/npm ERR! /g, '')
299
362
  throw e
300
363
  }
301
364
  return []
302
365
  }
303
366
 
304
- function _getWorkspace(wsRoot, pkg) {
305
- try {
306
- const pkgPath = require.resolve(path.join(pkg, 'package.json'), { paths: [wsRoot] })
307
- if (!pkgPath.match(/(\/|\\)node_modules(\/|\\)?/) && pkgPath.startsWith(wsRoot)) {
308
- return path.relative(wsRoot, path.dirname(pkgPath))
309
- }
310
- } catch (e) {
311
- // ignore - workspace dependencies have already been validated
312
- }
313
- }
314
-
315
367
  function _validateWsDependencies(wsRoot, dependencies, devDependencies) {
316
368
  const allDependencies = { ...dependencies, ...devDependencies }
317
369
  for (const name in allDependencies) {
@@ -330,22 +382,22 @@ function _validateWsDependencies(wsRoot, dependencies, devDependencies) {
330
382
 
331
383
  /**
332
384
  * Returns a flat array of existing files and folders, trailing '/' or '\\' characters have been removed.
333
- * @param {Array} modelPaths array of files and folders - optionally nested
334
- * @returns
385
+ * @param {NestedStringArray} modelPaths array of files and folders - optionally nested
386
+ * @param {((m: string) => boolean) | undefined} filter filter function to apply to the found files
387
+ * @returns
335
388
  */
336
- function _filterModelPaths(modelPaths, filter) {
337
- const model = new Set()
389
+ function _filterModelPaths(modelPaths, filter = undefined) {
390
+ const model = /** @type {Set<string>}*/(new Set())
338
391
  // may contain nested arrays
339
- modelPaths = flatten(modelPaths)
392
+ const flatModelPaths = flatten(modelPaths)
340
393
  const { roots } = cds.env
341
- modelPaths.forEach(m => {
394
+ flatModelPaths.forEach(m => {
342
395
  if (m && !model.has(m) && !model.has(m + "/")) {
343
396
  // filter roots and absolute model paths that do not exist
344
397
  if (roots.includes(m)) {
345
398
  const dir = path.resolve(cds.root, m)
346
- if (fs.existsSync(dir)) {
347
- model.add(m)
348
- } else if (fs.existsSync(dir + FILE_EXT_CDS)) { //might be cds file name, compatibility to old build configs
399
+ // dir + FILE_EXT_CDS: might be cds file name, compatibility to old build configs
400
+ if (fs.existsSync(dir) || fs.existsSync(dir + FILE_EXT_CDS)) {
349
401
  model.add(m)
350
402
  }
351
403
  } else {
@@ -353,21 +405,23 @@ function _filterModelPaths(modelPaths, filter) {
353
405
  }
354
406
  }
355
407
  })
356
- return [...model].filter(m => filter ? filter(m) : true).map(m => normalizePath(m))
408
+ return [...model]
409
+ .filter(m => filter ? filter(m) : true)
410
+ .map(m => normalizePath(m))
357
411
  }
358
412
 
359
413
  /**
360
414
  * Returns a set of files found in the given directory and its subdirectories.
361
415
  * @param {string} srcDir - the directory to search for files
362
- * @param {Function} filter - the filter function to apply to the found files
363
- * @param {Array} files - the set of files to add the found files to
364
- * @returns {Array} files found in the given directory and its subdirectories
416
+ * @param {(file: string) => boolean} [filter] - the filter function to apply to the found files
417
+ * @param {string[]} files - the set of files to add the found files to
418
+ * @returns {string[]} files found in the given directory and its subdirectories
365
419
  */
366
420
  function getFiles(srcDir, filter, files = []) {
367
- function handleResource(entry, filter, files) {
421
+ function handleResource(/** @type {string} entry */ entry) {
368
422
  if (!filter || filter.call(this, entry)) {
369
423
  const stats = fs.lstatSync(entry)
370
- if (stats.isDirectory()) {
424
+ if (cds.utils.isdir(entry)) {
371
425
  getFiles(entry, filter, files)
372
426
  } else if (stats.isFile() || stats.isSymbolicLink()) {
373
427
  files.push(entry)
@@ -377,11 +431,11 @@ function getFiles(srcDir, filter, files = []) {
377
431
  let entries = []
378
432
  try {
379
433
  entries = fs.readdirSync(srcDir)
380
- } catch (e) {
434
+ } catch {
381
435
  // ignore if not existing
382
436
  }
383
437
  entries.map(subDirEntry => path.join(srcDir, subDirEntry)).forEach((entry) => {
384
- handleResource(entry, filter, files)
438
+ handleResource(entry)
385
439
  })
386
440
  return files
387
441
  }
@@ -392,7 +446,8 @@ function getFiles(srcDir, filter, files = []) {
392
446
  * - 'File:Line:StartCol-EndCol' if Line = start.line = end.line
393
447
  * - 'File:StartLine.StartCol-EndLine.EndCol' otherwise
394
448
  *
395
- * @param {CSN.Location|CSN.Location} location
449
+ * @param {SourceLocation | undefined} loc
450
+ * @private
396
451
  */
397
452
  function _locationString(loc) {
398
453
  if (!loc)
@@ -415,22 +470,24 @@ function _locationString(loc) {
415
470
 
416
471
  /**
417
472
  * Class for individual build message.
418
- *
419
- * @class BuildMessage
420
473
  */
421
474
  class BuildMessage {
422
475
  /**
423
476
  * Creates an instance of BuildMessage.
424
477
  * @param {string} message The message text
425
- * @param {string} [severity='Error'] Severity: Debug, Info, Warning, Error
426
- * @param {any} location Location of the message
427
- *
478
+ * @param {import('./constants').Severity} [severity='Error'] severity - severity of the message
479
+ * @param {SourceLocation | undefined} location Location of the message
480
+ *
428
481
  * @memberOf BuildMessage
429
482
  */
430
- constructor(message, severity = SEVERITY_ERROR, location) {
483
+ constructor(message, severity = SEVERITY_ERROR, location = undefined) {
484
+ /** @type {string} */
431
485
  this.message = message
486
+ /** @type {string} */
432
487
  this.name = "BuildMessage"
488
+ /** @type {import('./constants').Severity} */
433
489
  this.severity = severity
490
+ /** @type {SourceLocation} */
434
491
  this.$location = location
435
492
  }
436
493
  toString() {
@@ -441,11 +498,16 @@ class BuildMessage {
441
498
  /**
442
499
  * Class for combined build and compiler errors.
443
500
  * Additional members:
444
- * messages: array of detailed build messages
445
- * @class BuildError
446
- * @extends {Error}
501
+ * messages: vector of detailed build messages
447
502
  */
448
503
  class BuildError extends Error {
504
+ /** @type {string[] | {message: string}[]} */
505
+ messages
506
+
507
+ /**
508
+ * @param {string} message
509
+ * @param {string | string[]} messages
510
+ */
449
511
  constructor(message, messages = []) {
450
512
  super(message)
451
513
  this.name = "BuildError"
@@ -466,7 +528,6 @@ module.exports = {
466
528
  getProperty,
467
529
  setProperty,
468
530
  hasJavaNature,
469
- redactCredentials,
470
531
  hasOptionValue,
471
532
  relativePaths,
472
533
  resolveRequiredSapModels,
@@ -0,0 +1,5 @@
1
+ module.exports = {
2
+ options: /** @type{const} */(['--to', '--for', '--out', '--on', '--tunnel-address', '--vcap-file', '--delta-from']),
3
+ flags: /** @type{const} */(['--no-save', '--auto-undeploy', '--dry', '--xdry', '--with-mocks', '--store-credentials', '--no-build', '--model-only', '--script', '--with-auto-schema-evolution']),
4
+ shortcuts: ['-2', '-4', '-o', '-n']
5
+ }
@@ -1,7 +1,5 @@
1
1
  const path = require('path');
2
2
 
3
- const fs = require('fs').promises;
4
-
5
3
  const cds = require('../../../lib/cds');
6
4
  const { exists, read, write, mkdirp } = cds.utils;
7
5
 
@@ -9,7 +7,6 @@ const { build } = require('../../build');
9
7
 
10
8
  const hdi = require('./hdiDeployUtil');
11
9
  const cfUtil = require('../../util/cf');
12
- const gitUtil = require('../../util/git');
13
10
  const mtaUtil = require('./mtaUtil');
14
11
 
15
12
  const { bold, info, warn } = require('../../util/term');
@@ -17,8 +14,24 @@ const { bold, info, warn } = require('../../util/term');
17
14
  const { bind, storeBindings } = require('../../bind/shared');
18
15
  const { BUILD_TASK_HANA, OUTPUT_MODE, OUTPUT_MODE_FILESYSTEM, OUTPUT_MODE_RESULT } = require('../../build/constants');
19
16
 
17
+ const { options, flags } = require('../index');
18
+ const allOptions = [...options, ...flags];
19
+
20
20
  const DEBUG = cds.debug('cli');
21
21
 
22
+ /** @type { (typeof allOptions[number])[] } */
23
+ const allowList = [
24
+ '--auto-undeploy',
25
+ '--dry',
26
+ '--for',
27
+ '--on',
28
+ '--store-credentials',
29
+ '--to',
30
+ '--tunnel-address',
31
+ '--vcap-file',
32
+ '--with-mocks'
33
+ ]
34
+
22
35
  module.exports = new class HanaDeployer {
23
36
 
24
37
  getFromEnv(...varNames) {
@@ -36,7 +49,15 @@ module.exports = new class HanaDeployer {
36
49
  }
37
50
 
38
51
  async deploy(options = {}) {
39
- let { model, serviceName: pServiceName, tunnelAddress, vcapFile, undeployWhitelist, hdiOptions = {}, dry, storeCredentials } = options;
52
+ let unknownOptions = Object.keys(options)
53
+ .map(key => `--${key.replace(/([A-Z])/g, '-$1').toLowerCase()}`)
54
+ .filter(key => allOptions.includes(key) && !allowList.includes(key));
55
+
56
+ if (unknownOptions.length > 0) {
57
+ throw `One or more options are not supported by cds deploy to hana: ${unknownOptions.join(', ')}`;
58
+ }
59
+
60
+ let { model, serviceName: pServiceName, tunnelAddress, vcapFile, undeployWhitelist, hdiOptions = {}, dry } = options;
40
61
 
41
62
  options.for ??= 'hybrid';
42
63
 
@@ -89,7 +110,7 @@ Therefore, specifying the CDS model is not required when calling 'cds deploy'.`;
89
110
  }
90
111
  } else {
91
112
  // the primary database is always built and deployed
92
- const dest = path.join(cds.env.build.target, cds.env.folders.db);
113
+ const dest = buildResult.dest // path.join(cds.env.build.target, cds.env.folders.db);
93
114
 
94
115
  if (undeployWhitelist) {
95
116
  console.log('Writing undeploy.json');
@@ -167,9 +188,6 @@ spec:
167
188
  vcapEnv.VCAP_SERVICES = this._getVCAPServicesEntry(cfServiceInstanceName, serviceKey);
168
189
  }
169
190
 
170
- if (storeCredentials) {
171
- await this._addInstanceToDefaultEnvJson([dest, cds.root], cfServiceInstanceName, serviceKey);
172
- }
173
191
  if (!hasVCAPEnv) {
174
192
  bindOptions = {
175
193
  ...options,
@@ -185,10 +203,6 @@ spec:
185
203
  DEBUG?.({ dest, vcapEnv, hdiOptions })
186
204
  await hdi.deploy(dest, vcapEnv, hdiOptions);
187
205
 
188
- if (storeCredentials) {
189
- await gitUtil.ensureFileIsGitignored('default-env.json', cds.root);
190
- }
191
-
192
206
  if (!hasVCAPEnv) {
193
207
  const services = await bind(bindOptions)
194
208
  await storeBindings(services, bindOptions)
@@ -269,7 +283,7 @@ spec:
269
283
  async _build() {
270
284
  const buildTaskOptions = {
271
285
  for: BUILD_TASK_HANA,
272
- src: cds.env.folders.db,
286
+ // src: cds.env.folders.db,
273
287
  [OUTPUT_MODE]: OUTPUT_MODE_FILESYSTEM | OUTPUT_MODE_RESULT
274
288
  }
275
289
 
@@ -278,26 +292,6 @@ spec:
278
292
  return buildResult[0].result;
279
293
  }
280
294
 
281
- async _addInstanceToDefaultEnvJson(currentFolders, serviceInstanceName, serviceKey) {
282
- for (const currentFolder of currentFolders) {
283
- const defaultEnvJsonPath = path.join(currentFolder, 'default-env.json');
284
- const defaultEnvJson = exists(defaultEnvJsonPath) ? await read(defaultEnvJsonPath) : {};
285
-
286
- defaultEnvJson.VCAP_SERVICES ??= {};
287
- for (const [serviceKey, services] of Object.entries(defaultEnvJson.VCAP_SERVICES)) {
288
- defaultEnvJson.VCAP_SERVICES[serviceKey] = services.filter(service => service.name !== serviceInstanceName);
289
- }
290
-
291
- const hanaEntry = this._getVCAPServicesEntry(serviceInstanceName, serviceKey)
292
- Object.assign(defaultEnvJson.VCAP_SERVICES, hanaEntry);
293
-
294
- console.log(`Writing ${defaultEnvJsonPath}`);
295
- await fs.mkdir(path.dirname(defaultEnvJsonPath), { recursive: true })
296
- await fs.writeFile(defaultEnvJsonPath, JSON.stringify(defaultEnvJson, null, 2));
297
- }
298
- }
299
-
300
-
301
295
  _getVCAPServicesEntry(serviceInstanceName, serviceKey) {
302
296
  return {
303
297
  hana: [
@@ -5,13 +5,13 @@ const HDI_DEPLOY_LIB = '@sap/hdi-deploy';
5
5
 
6
6
  module.exports = new class HdiDeployUtil {
7
7
 
8
- async deploy(dbDir, vcapEnv = {}, { autoUndeploy } = {}) {
8
+ async deploy(dbDir, vcapEnv = {}, { autoUndeploy, parameter={} } = {}) {
9
9
  console.log(`Deploying to HANA from ${dbDir}`);
10
10
 
11
11
  const { clean_env, deploy } = this._loadHdiDeployLib(dbDir);
12
12
 
13
13
  const deployerEnv = JSON.parse(JSON.stringify(process.env));
14
- const options = deployerEnv.HDI_DEPLOY_OPTIONS ? JSON.parse(deployerEnv.HDI_DEPLOY_OPTIONS) : {};
14
+ const options = deployerEnv.HDI_DEPLOY_OPTIONS ? JSON.parse(deployerEnv.HDI_DEPLOY_OPTIONS) : { parameter };
15
15
  delete options.root;
16
16
 
17
17
  clean_env?.(deployerEnv);