@sap/cds-dk 9.7.2 → 9.8.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 (105) hide show
  1. package/CHANGELOG.md +35 -2
  2. package/bin/bind.js +7 -2
  3. package/bin/build.js +14 -4
  4. package/bin/cds.js +2 -2
  5. package/bin/completion/completionFs.js +1 -1
  6. package/bin/completion/index.js +2 -2
  7. package/bin/data-products/get-csn-via-dp-cli.js +1 -1
  8. package/bin/debug.js +1 -1
  9. package/bin/deploy/index.js +3 -2
  10. package/bin/gen.js +1 -1
  11. package/bin/help.js +1 -1
  12. package/bin/import.js +3 -0
  13. package/bin/minify.js +1 -1
  14. package/bin/mock.js +1 -1
  15. package/bin/repl.js +212 -39
  16. package/bin/test.js +0 -2
  17. package/bin/up.js +27 -3
  18. package/bin/util/containerize.js +1 -3
  19. package/bin/version.js +27 -7
  20. package/bin/watch.js +20 -4
  21. package/lib/bind/cf.js +2 -2
  22. package/lib/bind/k8s.js +0 -1
  23. package/lib/bind/plugin.js +2 -0
  24. package/lib/bind/shared.js +6 -1
  25. package/lib/build/constants.js +0 -8
  26. package/lib/build/index.js +569 -25
  27. package/lib/build/{provider/edmxBuildPlugin.js → plugins/edmx.js} +6 -15
  28. package/lib/build/{provider/featureToggleBuildPlugin.js → plugins/featureToggles.js} +3 -23
  29. package/lib/build/{provider → plugins}/fiori/index.js +27 -38
  30. package/lib/build/{provider → plugins}/hana/index.js +61 -83
  31. package/lib/build/{provider → plugins}/hana/migrationtable.js +4 -3
  32. package/lib/build/plugins/helm/index.js +6 -6
  33. package/lib/build/{provider/internalBuildPlugin.js → plugins/internal.js} +7 -31
  34. package/lib/build/{provider → plugins}/java/index.js +18 -26
  35. package/lib/build/plugins/mtx/index.js +41 -0
  36. package/lib/build/plugins/mtx/resourcesTarBuilder.js +41 -0
  37. package/lib/build/{provider → plugins}/mtx-extension/extensionCompilation.js +1 -1
  38. package/lib/build/{provider → plugins}/mtx-extension/index.js +35 -44
  39. package/lib/build/{provider → plugins}/mtx-sidecar/index.js +17 -3
  40. package/lib/build/{provider → plugins}/nodejs/index.js +29 -28
  41. package/lib/build/{plugin.js → plugins/plugin.js} +45 -59
  42. package/lib/build/plugins.js +11 -8
  43. package/lib/build/util.js +0 -8
  44. package/lib/compile/to-xsuaa.js +2 -2
  45. package/lib/deploy/to-hana/hana.js +64 -56
  46. package/lib/import/common.js +1 -1
  47. package/lib/import/odata/metadataconverter/MetadataConverter.js +4 -3
  48. package/lib/import/odata/v2parser.js +28 -28
  49. package/lib/import/odata/v4parser.js +37 -2
  50. package/lib/import/openapi/index.js +1 -1
  51. package/lib/init/constants.js +0 -1
  52. package/lib/init/template/app-frontend/index.js +1 -1
  53. package/lib/init/template/approuter/files/package.json +1 -1
  54. package/lib/init/template/completion/completionSetup.js +1 -1
  55. package/lib/init/template/data/as-csv.js +1 -1
  56. package/lib/init/template/github-actions/index.js +1 -1
  57. package/lib/init/template/handler/index.js +1 -1
  58. package/lib/init/template/helm/index.js +3 -3
  59. package/lib/init/template/kyma/index.js +3 -3
  60. package/lib/init/template/lint/index.js +7 -21
  61. package/lib/init/template/multitenancy/files/package.json.hbs +0 -5
  62. package/lib/init/template/postgres/files/containerize.yaml.hbs +15 -0
  63. package/lib/init/template/postgres/files/package.json.hbs +35 -0
  64. package/lib/init/template/postgres/files/values.yaml.hbs +23 -0
  65. package/lib/init/template/postgres/index.js +34 -9
  66. package/lib/init/template/test/files/package.json +1 -1
  67. package/lib/init/template/typescript/files/package.json +1 -1
  68. package/lib/init/template/workzone-standard/files/mta.yaml.hbs +1 -0
  69. package/lib/lint/eslintHelp.json +1 -1
  70. package/lib/lint/io.js +9 -13
  71. package/lib/mtx/pull.js +1 -1
  72. package/lib/mtx/settings_manager.js +11 -5
  73. package/lib/mtx/tenant_action.js +1 -1
  74. package/lib/mtx/util/fs.js +2 -2
  75. package/lib/util/fs.js +3 -3
  76. package/lib/util/git.js +1 -1
  77. package/lib/util/glob.js +1 -1
  78. package/lib/watch/hot-reload.js +202 -0
  79. package/lib/watch/livereload-server.js +6 -4
  80. package/lib/watch/node-watch.js +5 -1
  81. package/lib/watch/watched.js +7 -3
  82. package/lib/watch/watcher.js +41 -3
  83. package/npm-shrinkwrap.json +31 -22
  84. package/package.json +1 -1
  85. package/lib/build/buildTaskEngine.js +0 -377
  86. package/lib/build/buildTaskFactory.js +0 -198
  87. package/lib/build/buildTaskProvider.js +0 -38
  88. package/lib/build/buildTaskProviderFactory.js +0 -233
  89. package/lib/build/provider/internalBuildTaskProvider.js +0 -227
  90. package/lib/build/provider/java-cf/index.js +0 -1
  91. package/lib/build/provider/mtx/index.js +0 -36
  92. package/lib/build/provider/mtx/resourcesTarBuilder.js +0 -102
  93. package/lib/build/provider/node-cf/index.js +0 -1
  94. package/lib/init/template/lint/files/.eslint/rules/no-entity-moo.js +0 -40
  95. package/lib/init/template/lint/files/.eslint/tests/no-entity-moo/fixed/schema.cds +0 -4
  96. package/lib/init/template/lint/files/.eslint/tests/no-entity-moo/invalid/schema.cds +0 -4
  97. package/lib/init/template/lint/files/.eslint/tests/no-entity-moo/rule.test.js +0 -15
  98. package/lib/init/template/lint/files/.eslint/tests/no-entity-moo/valid/schema.cds +0 -6
  99. package/lib/init/types.d.ts +0 -23
  100. /package/lib/build/{provider → plugins}/hana/2migration.js +0 -0
  101. /package/lib/build/{provider → plugins}/hana/template/.hdiconfig-all +0 -0
  102. /package/lib/build/{provider → plugins}/hana/template/.hdiconfig-cloud +0 -0
  103. /package/lib/build/{provider → plugins}/hana/template/.hdinamespace +0 -0
  104. /package/lib/build/{provider → plugins}/hana/template/package.json +0 -0
  105. /package/lib/build/{provider → plugins}/hana/template/undeploy.json +0 -0
package/CHANGELOG.md CHANGED
@@ -6,7 +6,35 @@ This project adheres to [Semantic Versioning](https://semver.org/).
6
6
 
7
7
  The format is based on [Keep a Changelog](https://keepachangelog.com/).
8
8
 
9
- ## Version 9.7.2 - tbd
9
+ ## Version 9.8.0 - 2026-03-09
10
+
11
+ ### Added
12
+
13
+ - `cds up --overlay <file|name> (Cloud Foundry) now resolves MTA extension descriptors (<name>.mtaext) from the project root.`
14
+ - `cds add postgres` now supports Kyma with automatic database provisioning and IP whitelisting.
15
+ - `cds deploy --to hana --store-credentials` now warns about the deprecated option `--store-credentials`.
16
+ - `cds import` now uses the default options defined via `cds.import.options[<from>] = {option: 'value'}`
17
+ - `cds version` now shows version of `@sap/eslint-plugin-cds`.
18
+ - `cds version --json` returns a stable output format as JSON.
19
+
20
+ ### Changed
21
+
22
+ - `cds add multitenancy` does not add an explicit `java` profile to the main app any more.
23
+ - `cds deploy` and `cds bind` support Kyma-by-default for projects with Kyma-only deployment descriptors.
24
+ - `cds deploy --to hana --on k8s` now supports deploying to SAP HANA using an existing Kubernetes service binding, allowing non-default Kyma naming conventions.
25
+ - `cds import` now correctly imports Views with Parameters from a given OData EDMX service definition.
26
+ - `cds import` now interprets empty navigation path references as if it would be a null value.
27
+
28
+ ### Fixed
29
+
30
+ - `cds bind` reports additional error information while accessing Cloud Foundry services.
31
+ - `cds version` now works again if executed in subfolders of Java projects without a `pom.xml` file.
32
+ - `cds add workzone` in combination with `cds add ias` adds the `HTML5.IASDependencyName` for the `srv-api`.
33
+ - `cds add lint` now creates an ESLint config file again.
34
+ - `cds import` now correctly handles enum types as keys in EDMX v4 import.
35
+ - `cds push` now shows the correct passcode URL when the subdomain changes between logins.
36
+
37
+ ## Version 9.7.2 - 2026-02-13
10
38
 
11
39
  ### Fixed
12
40
 
@@ -21,7 +49,6 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/).
21
49
  ### Fixed
22
50
 
23
51
  - `cds add completion` now works without specifying the project runtime.
24
-
25
52
  - `cds add xsuaa` correctly adds the the binding for the `html5-apps-deployer` on Kyma.
26
53
  - `cds version` prints an older format in a legacy situation.
27
54
  - `cds add nodejs` is automatically added in BAS scenarios for compatibility if no other runtime is specified.
@@ -470,6 +497,12 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/).
470
497
  - `cds add ams` for Java adds a custom builder to the `mta.yaml` to circumvent the missing `srv/src/gen/policies`.
471
498
  - `cds add multitenancy` requires the `srv-api` instead of the `mtx-api` for Java projects.
472
499
 
500
+ ## Version 8.9.14 - 2026-02-25
501
+
502
+ ### Fixed
503
+
504
+ - CVE-2026-25639: Update `axios` version to 1.13.5
505
+
473
506
  ## Version 8.9.13 - 2029-01-09
474
507
 
475
508
  ### Fixed
package/bin/bind.js CHANGED
@@ -32,7 +32,8 @@ module.exports = Object.assign(cds_bind, {
32
32
 
33
33
  *-n | --on* cf | k8s
34
34
 
35
- Target platform (Cloud Foundry or Kubernetes) to bind to. Defaults to *cf*.
35
+ Target platform (Cloud Foundry or Kubernetes) to bind to.
36
+ Defaults to *k8s* when Helm charts are present and no *mta.yaml* exists, otherwise *cf*.
36
37
 
37
38
  *-k | --kind* <kind>
38
39
 
@@ -79,11 +80,15 @@ module.exports = Object.assign(cds_bind, {
79
80
  const os = require('os');
80
81
  const IS_WIN = os.platform() === 'win32';
81
82
 
83
+ const cds = require('../lib/cds')
82
84
  const { bind, storeBindings } = require('../lib/bind/shared');
85
+ const { readProject } = require('../lib/init/projectReader')
83
86
 
84
87
 
85
88
  async function cds_bind(args, options) {
86
- const { on = 'cf', 'to-app-services': app, to, out, 'output-file': outputFile, exec, credentials } = options
89
+ const { hasKyma, hasMta } = readProject()
90
+ const onKyma = cds.cli.options.on === 'k8s' || (hasKyma && !hasMta && cds.cli.options.on !== 'cf')
91
+ const { on = onKyma ? 'k8s' : 'cf', 'to-app-services': app, to, out, 'output-file': outputFile, exec, credentials } = options
87
92
 
88
93
  if (exec) {
89
94
  process.env.CDS_ENV ??= 'hybrid'
package/bin/build.js CHANGED
@@ -165,17 +165,27 @@ async function build([project], options = {}) {
165
165
  options.taskOptions = scanTaskOptionParams(options.opts)
166
166
  delete options.opts
167
167
  }
168
- const { build } = require('../lib/build')
169
- await build(options)
168
+ const { build, BuildError } = require('../lib/build')
169
+ try {
170
+ return build(options)
171
+ } catch (e) {
172
+ if (e instanceof BuildError || e.constructor.name === 'CompilationError') {
173
+ cds._log(e.messages.length ? e.messages : e.message, { 'log-level': options['log-level'] || cds.env['log-level'] })
174
+ console.log()
175
+ // CompilationError.message also includes the detail messages - do not log twice
176
+ throw e.constructor.name === 'CompilationError' ? 'CDS compilation failed' : e.messages.length ? e.message : 'CDS build failed'
177
+ }
178
+ throw e
179
+ }
170
180
  }
171
181
 
182
+ const quoteRegex = /([\w-]+)=([\w/.-]+|\[([\w/,.-]+)\])/g
172
183
  function scanTaskOptionParams(optsParams) {
173
- // need to create new regex every call since a constant would keep the match state
174
- const quoteRegex = /([\w-]+)=([\w/.-]+|\[([\w/,.-]+)\])/g
175
184
 
176
185
  // captures a=1 => a:1
177
186
  // a=[x,y,z] => a:[x,y,z]
178
187
  // a=1,b=[x,y,z] => a:1 b=[x,y,z]
188
+ quoteRegex.lastIndex = 0 // so we don't need to create a new regex every call since a constant would keep the match state
179
189
  let match = quoteRegex.exec(optsParams)
180
190
  const taskOptions = {}
181
191
 
package/bin/cds.js CHANGED
@@ -78,7 +78,7 @@ const cli = {
78
78
  const flags = all.filter(([,f]) => f.type === 'boolean')
79
79
  const options = all.filter(([,o]) => o.type !== 'boolean')
80
80
  task.options.push(...options.map(([key]) => '--'+key))
81
- task.shortcuts.push(...options.map(([_, { short }]) => short ? '-'+short : undefined).filter(Boolean))
81
+ task.shortcuts.push(...options.map(([, { short }]) => short ? '-'+short : undefined).filter(Boolean))
82
82
  const byShortcut = (a, b) => (b.short ? 1 : 0) - (a.short ? 1 : 0)
83
83
  task.options.sort(byShortcut)
84
84
  task.shortcuts.sort(byShortcut)
@@ -226,7 +226,7 @@ function getCommands() {
226
226
  }
227
227
 
228
228
  function _require (id,o) {
229
- try { var resolved = require.resolve(id,o) } catch(e){ DEBUG?.(`[cds] - Command not found: ${id}`); return }
229
+ try { var resolved = require.resolve(id,o) } catch { DEBUG?.(`[cds] - Command not found: ${id}`); return }
230
230
  DEBUG?.(`[cds] - Command resolved: ${resolved}`)
231
231
  return require (resolved)
232
232
  }
@@ -17,7 +17,7 @@ module.exports = new class CompletionFS {
17
17
  let entries;
18
18
  try {
19
19
  entries = await fsp.readdir(folder, { withFileTypes: true });
20
- } catch (err) {
20
+ } catch {
21
21
  folder = path.dirname(folder);
22
22
  entries = await fsp.readdir(folder, { withFileTypes: true });
23
23
  }
@@ -123,7 +123,7 @@ async function completion(_argv, options) {
123
123
  reply(completions);
124
124
  }
125
125
 
126
- async function handleCompletion(currentWord, previousWord, argv, _util) {
126
+ async function handleCompletion(currentWord, previousWord, argv) {
127
127
  const allOptionsFlags = ['--add', '--remove', '--shell']
128
128
  .filter(e => !argv.includes(e));
129
129
 
@@ -222,7 +222,7 @@ async function getCompletions(cdsCmd, previousWord, currentWord, segments) {
222
222
  }
223
223
 
224
224
  return all;
225
- } catch (err) {
225
+ } catch {
226
226
  return [];
227
227
  }
228
228
  }
@@ -18,7 +18,7 @@ function dpCliIsAvailable() {
18
18
  try {
19
19
  const output = child_process.execSync('which dp-cli').toString();
20
20
  return typeof output === 'string' && output.length > 0;
21
- } catch (e) {
21
+ } catch {
22
22
  return false;
23
23
  }
24
24
  }
package/bin/debug.js CHANGED
@@ -259,7 +259,7 @@ async function debug() {
259
259
  function _execSyncSafe(...args) {
260
260
  try {
261
261
  return _execSync(...args)
262
- } catch (_) {/* ignore */ }
262
+ } catch { /* ignore */ }
263
263
  }
264
264
  function _execSync(cmd, options={ encoding: 'utf-8', shell: true }) {
265
265
  try {
@@ -50,7 +50,8 @@ module.exports = Object.assign(deploy, {
50
50
 
51
51
  *--on* | *-n* cf|k8s
52
52
 
53
- The target platform. Default is 'cf'.
53
+ The target platform.
54
+ Defaults to *k8s* when Helm charts are present and no *mta.yaml* exists, otherwise *cf*.
54
55
 
55
56
  *--with-mocks*
56
57
 
@@ -137,7 +138,7 @@ async function deploy(_model, options) {
137
138
  var deployer = require('./to-' + kind)
138
139
  } catch (e) {
139
140
  if (e.code === 'MODULE_NOT_FOUND') {
140
- throw new Error(`Didn't find a deployer module for '${kind}'`)
141
+ throw new Error(`Didn't find a deployer module for '${kind}'`) // eslint-disable-line preserve-caught-error
141
142
  }
142
143
  }
143
144
  try {
package/bin/gen.js CHANGED
@@ -8,7 +8,7 @@ function global (id, subpath) {
8
8
  let mod
9
9
  try {
10
10
  mod = require.resolve (pkgPath, { paths: [ process.cwd(), __dirname ] })
11
- } catch (err) { // one more try w/ NPM global paths
11
+ } catch { // one more try w/ NPM global paths
12
12
  const paths = [ process.cwd(), __dirname, npmGlobalModules() ]
13
13
  try {
14
14
  mod = require.resolve (pkgPath, { paths })
package/bin/help.js CHANGED
@@ -42,7 +42,7 @@ module.exports = exports = Object.assign (help, {formatHelp, handleCompletion, h
42
42
 
43
43
  `});
44
44
 
45
- async function handleCompletion(_currentWord, _previousWord, _argv, _util) {
45
+ async function handleCompletion() {
46
46
  const cds = require('./cds');
47
47
  return Object.values(cds.cmds);
48
48
  }
package/bin/import.js CHANGED
@@ -168,6 +168,9 @@ async function _import(args, options = {}) {
168
168
  // the required plugin might not be installed, e.g. rfc importer
169
169
  throw messages.UNSUPPORTED_FROM_OPTION.replace('{0}', options.from).replace('{1}', Object.keys(cds.import.from).join(', '));
170
170
  }
171
+ if (options.from && cds.import.options && cds.import.options[options.from]) {
172
+ options = Object.assign(cds.import.options[options.from], options)
173
+ }
171
174
  csn = await cds.import.from[options.from](input, apiOptions);
172
175
  } else {
173
176
  csn = await cds.import(input, apiOptions);
package/bin/minify.js CHANGED
@@ -101,7 +101,7 @@ class Dryifier extends Minifier {
101
101
  if (each in this.kept) return
102
102
  if (each in this.defs) this.capture[each] = ++this.level
103
103
  super.keep (each, d)
104
- this.level--
104
+ if (each in this.defs) this.level--
105
105
  }
106
106
 
107
107
  minify (csn,o) {
package/bin/mock.js CHANGED
@@ -1,5 +1,5 @@
1
1
  const cds = require ('../lib/cds'); require = cds._local // eslint-disable-line no-global-assign
2
- const serve = require ('@sap/cds/bin/serve') // eslint-disable-line cds-dk/use-api-require, cds-dk/use-relative-require
2
+ const serve = require ('@sap/cds/bin/serve')
3
3
 
4
4
  module.exports = exports = Object.assign (cds_mock, serve)
5
5
 
package/bin/repl.js CHANGED
@@ -1,5 +1,5 @@
1
1
  module.exports = Object.assign (repl, {
2
- options: [ '--run', '--use' ],
2
+ options: [ '--run', '--use', '--ql' ],
3
3
  shortcuts: [ '-r', '-u' ],
4
4
  help: `
5
5
  # SYNOPSIS
@@ -24,6 +24,11 @@ module.exports = Object.assign (repl, {
24
24
  if you specify _xl_ it makes the _cds.xl_ module's methods available.
25
25
  It's the same as doing _{ref,val,xpr,...} = cds.xl_ within the repl.
26
26
 
27
+ *--ql*
28
+
29
+ Starts the repl in cql evaluation mode.
30
+ It's the same as using the _.ql_ command within the repl.
31
+
27
32
  # EXAMPLES
28
33
 
29
34
  *cds repl* --run bookshop
@@ -37,9 +42,10 @@ module.exports = Object.assign (repl, {
37
42
 
38
43
  async function repl ([project], options={}) {
39
44
 
40
- const GREEN = '\x1b[32m', RESET = '\x1b[0m'
45
+ const isTerminal = Boolean(process.stdin.isTTY && process.stdout.isTTY);
41
46
  const cds = require('../lib/cds')
42
- console.log('\x1b[32m\x1b[1m'+'Welcome to cds repl v', cds.version, '\x1b[0m')
47
+ const { GREEN, PINK, BLUE, CYAN, YELLOW, GRAY, BOLD, RESET } = cds.utils.colors
48
+ console.log(`${GREEN}${BOLD}Welcome to cds repl v${cds.version}${RESET}`)
43
49
 
44
50
  const { inspect } = require('util')
45
51
  inspect.defaultOptions.colors = true
@@ -50,48 +56,57 @@ async function repl ([project], options={}) {
50
56
  if ((options.run ??= project)) await _run (options.run)
51
57
  if (options.use) _export (...options.use.split(','))
52
58
 
53
- repl = require('repl').start ({
54
- ...options,
55
- ignoreUndefined: true,
56
- useGlobal: true,
57
- writer: o => {
58
- const options = {}
59
- if (!o || typeof o !== 'object') return o
60
- if ('init' in o && 'on' in o && 'run' in o) options.depth = 0
61
- return inspect(o,options).replace(/\[Object: null prototype\] /g, '')
62
- }
63
- })
64
-
65
- if (Object.keys(context).length) Object.assign (repl.context, context)
66
-
67
- cds.extend (repl.context) .with ( class {
68
- get Foo() { return ctx.Foo ??= new cds.entity({ name: 'Foo' }) }; set Foo(v) { ctx.Foo = v }
69
- get Bar() { return ctx.Bar ??= new cds.entity({ name: 'Bar' }) }; set Bar(v) { ctx.Bar = v }
70
- get expect() { return ctx.expect ??= cds.test.expect }; set expect(v) { ctx.expect = v }
71
- } .prototype )
72
- const ctx={}
73
-
74
- repl.defineCommand ('run', { action: _run,
75
- help: 'Runs a cds server from a given CAP project folder, or module name like @capire/bookshop.',
76
- })
77
-
78
- repl.defineCommand ('inspect', { action: _inspect,
79
- help: 'Sets options for util.inspect, e.g. `.inspect .depth=1`.',
80
- })
81
-
82
- repl.history = new Array(222)
83
- const home = process.env.HOME || process.env.USERPROFILE
84
- const history = require('path').join(home,'.cds-repl-history')
85
- const fs = require('fs')
86
- fs.readFile(history, 'utf-8', (e, txt) => e || Object.assign(repl.history, txt.split('\n')))
87
- repl.on('exit', () => fs.writeFile(history, repl.history.join('\n'), () => {}))
88
- repl.on('exit', cds.shutdown)
59
+ if (options.ql) _ql()
60
+ else _js()
61
+
89
62
  process.on('uncaughtException', console.error)
90
63
  process.on('unhandledRejection', console.error)
91
64
 
92
65
  // Ugly hack to prevent "[ERR_INVALID_REPL_INPUT]: Listeners for `uncaughtException` cannot be used in the REPL" errors caused by winston when connecting to remote services
93
66
  process.on = (event, listener) => event !== 'uncaughtException' && event !== 'unhandledRejection' && process.addListener(event, listener)
94
67
 
68
+
69
+ function _js() {
70
+ repl = require('repl').start ({
71
+ ...options,
72
+ ignoreUndefined: true,
73
+ useGlobal: true,
74
+ terminal: isTerminal,
75
+ writer: o => {
76
+ const options = {}
77
+ if (!o || typeof o !== 'object') return o
78
+ if ('init' in o && 'on' in o && 'run' in o) options.depth = 0
79
+ return inspect(o,options).replace(/\[Object: null prototype\] /g, '')
80
+ }
81
+ })
82
+
83
+ _initHistory(repl, '.cds-repl-history')
84
+ repl.on('exit', () => options.context = repl.context)
85
+ repl.on('exit', () => { if (!transition.started) cds.shutdown() })
86
+
87
+ if (Object.keys(context).length) Object.assign (repl.context, context)
88
+ if (!Object.hasOwn(repl.context, 'Foo')) cds.extend (repl.context) .with ( class {
89
+ get Foo() { return ctx.Foo ??= new cds.entity({ name: 'Foo' }) }; set Foo(v) { ctx.Foo = v }
90
+ get Bar() { return ctx.Bar ??= new cds.entity({ name: 'Bar' }) }; set Bar(v) { ctx.Bar = v }
91
+ get expect() { return ctx.expect ??= cds.test.expect }; set expect(v) { ctx.expect = v }
92
+ } .prototype )
93
+ const ctx={}
94
+
95
+ repl.defineCommand ('run', { action: _run,
96
+ help: 'Runs a cds server from a given CAP project folder, or module name like @capire/bookshop.',
97
+ })
98
+
99
+ repl.defineCommand ('inspect', { action: _inspect,
100
+ help: 'Sets options for util.inspect, e.g. `.inspect .depth=1`.',
101
+ })
102
+
103
+ repl.defineCommand ('ql', { action: transition(_ql),
104
+ help: 'Switch to cql repl mode, evaluating cql queries'
105
+ })
106
+
107
+ repl.displayPrompt()
108
+ }
109
+
95
110
  async function _run (project) {
96
111
  await require('../bin/serve') ([project], { project, port: 0,
97
112
  'with-bindings': true,
@@ -143,6 +158,164 @@ async function repl ([project], options={}) {
143
158
  console.log()
144
159
  repl.displayPrompt()
145
160
  }
161
+
162
+ function _initHistory(repl, file) {
163
+ repl.history = []
164
+ const home = process.env.HOME || process.env.USERPROFILE
165
+ const history = require('path').join(home,file)
166
+ const fs = require('fs')
167
+ fs.readFile(history, 'utf-8', (e, txt) => e || Object.assign(repl.history, txt.split('\n')))
168
+ repl.on('exit', () => fs.writeFile(history, repl.history.join('\n'), () => {}))
169
+ }
170
+
171
+ function transition(start) {
172
+ return function () {
173
+ transition.started = true
174
+ this.clearBufferedCommand()
175
+ this.close()
176
+ setImmediate(() => {
177
+ start()
178
+ transition.started = false
179
+ })
180
+ }
181
+ }
182
+
183
+
184
+
185
+ // CQL Mode
186
+ function _ql() {
187
+ const cqlColors = {
188
+ keyword: BLUE,
189
+ entity: CYAN,
190
+ string: GREEN,
191
+ number: YELLOW,
192
+ token: PINK,
193
+ suggestion: GRAY,
194
+ reset: RESET,
195
+ }
196
+ const highlight = createHighlighter(cds, cqlColors)
197
+ const prompt = `cql${PINK}>${RESET} `
198
+
199
+ repl = require('repl').start ({
200
+ prompt,
201
+ eval: _cql,
202
+ terminal: isTerminal,
203
+ completer: (line) => {
204
+ if (line.startsWith('.'))
205
+ return [Object.keys(repl.commands).map(key => `.${key}`).filter(c => c.startsWith(line)), line]
206
+ return [[], line]
207
+ },
208
+ })
209
+
210
+ _initHistory(repl, '.cds-repl-ql-history')
211
+ repl.on('exit', () => { if (!transition.started) cds.shutdown() })
212
+
213
+ repl.defineCommand ('target', { action: _target,
214
+ help: 'Specify the service to evaluate against'
215
+ })
216
+
217
+ repl.defineCommand ('namespace', { action: _namespace,
218
+ help: 'Specify a default namespace'
219
+ })
220
+
221
+ repl.defineCommand ('js', { action: transition(_js),
222
+ help: 'Switch to js repl mode, evaluating javascript'
223
+ })
224
+
225
+ // Syntax highlighting for input line
226
+ let terminated = false
227
+ function refresh() { setTimeout(() => {if (terminated) return; repl._refreshLine(); }, 0) }
228
+ repl._writeToOutput = function _writeToOutput(str) {
229
+ if (terminated) return
230
+ if (!str.startsWith(prompt)) return repl.output.write(str)
231
+ repl.output.write(prompt + highlight(str.slice(prompt.length)));
232
+ };
233
+ process.stdin.on('keypress', refresh);
234
+ repl.on('exit', () => {
235
+ terminated = true
236
+ process.stdin.off('keypress', refresh)
237
+ })
238
+
239
+ repl.displayPrompt()
240
+ }
241
+
242
+ async function _cql(code, context, replResourceName, callback) {
243
+ const { service = cds.db, user } = repl.context
244
+ if (code.trim() === '') return callback()
245
+ cds.context = { user }
246
+ const query = cds.ql(code)
247
+ const result = await service.run(query)
248
+ callback(null, result)
249
+ }
250
+
251
+ function _target(args) {
252
+ if (!args) return console.info(
253
+ `Specify a service. Available services: ${Object.keys(cds.services).join(', ')}
254
+ Examples:
255
+ .target db
256
+ .target AdminService --roles admin`)
257
+ const [s] = args.split(' ')
258
+ const service = cds.services[s]
259
+ if (!service) throw new Error(`No service with name ${s}. Available services: ${Object.keys(cds.services).join(', ')}`)
260
+ repl.context.service = service
261
+
262
+ const roles = args.match(/(-r|--roles) ([^\s]+)/)?.[2]?.split(',')
263
+ repl.context.user = roles? {roles} : undefined
264
+ }
265
+
266
+ function _namespace(ns) {
267
+ cds.model.namespace = ns
268
+ }
269
+ }
270
+
271
+ function createHighlighter(cds, colors) {
272
+ const { compiler } = cds
273
+
274
+ const keywords = new Set(['as', 'and', 'or', 'not', 'null'])
275
+
276
+ function category(token) {
277
+ const type = token.type?.toLowerCase()
278
+ if (['number', 'string'].includes(type)) return type;
279
+
280
+ let cat = token.parsedAs?.toLowerCase()
281
+ .replace('ext','').replace('implicit','')
282
+ .replace(/^.*alias/,'alias')
283
+ .replace('from', 'entity').replace('artref', 'entity')
284
+ .replace('item', 'element')
285
+
286
+ if (cat === 'keyword' && !keywords.has(token.text?.trim())) return '_keyword'
287
+ return cat
288
+ }
289
+
290
+ return input => {
291
+ const prefix = 'entity __query as '
292
+ const options = { attachTokens: true, messages: [] }
293
+ const { tokenStream: { tokens } } = compiler.$lsp.parse(prefix + input, '<query>.cds', options)
294
+
295
+ const highlighted = []
296
+ let lastToken
297
+ for (const token of tokens) {
298
+ if (lastToken) {
299
+ const start = lastToken.start - prefix.length
300
+ const stop = token.start - prefix.length
301
+ if (start >= 0) {
302
+ const value = input.slice(start, stop)
303
+ let cat = category(lastToken)
304
+ const colored = _color(cat, value)
305
+ highlighted.push(colored)
306
+ }
307
+ }
308
+ lastToken = token
309
+ }
310
+
311
+ return highlighted.join('')
312
+ }
313
+
314
+ function _color(type, value) {
315
+ const start = colors[type] || ''
316
+ const reset = colors.reset || ''
317
+ return start ? `${start}${value}${reset}` : value
318
+ }
146
319
  }
147
320
 
148
321
  if (!module.parent) repl()
package/bin/test.js CHANGED
@@ -2,6 +2,4 @@
2
2
  const {resolve} = require, paths = [ process.cwd(), __dirname ]
3
3
  require = id => module.require (resolve(id,{paths})) // eslint-disable-line no-global-assign
4
4
 
5
- // FIXME: that check below doesn't work correctly!
6
- // eslint-disable-next-line cds-dk/no-missing-dependencies
7
5
  module.exports = require ('@cap-js/cds-test/bin/chest.js')
package/bin/up.js CHANGED
@@ -1,5 +1,5 @@
1
1
  module.exports = Object.assign(up, {
2
- options: ['--to', '--namespace'],
2
+ options: ['--to', '--namespace', '--overlay'],
3
3
  shortcuts: ['-2', '-n'],
4
4
  flags: [],
5
5
  help: `
@@ -17,6 +17,12 @@ module.exports = Object.assign(up, {
17
17
 
18
18
  Defaults to *k8s* when Helm charts are present and no *mta.yaml* exists, otherwise *cf*.
19
19
 
20
+ *--overlay* <file> (Cloud Foundry only)
21
+
22
+ Use a deployment overlay.
23
+
24
+ Path to an MTA extension descriptor (*.mtaext*). Optionally omit the *.mtaext* suffix.
25
+
20
26
  *-n* | *--namespace* <name>
21
27
 
22
28
  (Kubernetes only) Target namespace. If it does not yet exist it will be
@@ -27,6 +33,7 @@ module.exports = Object.assign(up, {
27
33
  *cds up*
28
34
  *cds up --to k8s*
29
35
  *cds up --to k8s --namespace e2e-tests*
36
+ *cds up --to cf --overlay .deploy/mtaext/eu10.mtaext*
30
37
  `})
31
38
 
32
39
  const { execSync } = require('node:child_process')
@@ -82,7 +89,7 @@ function readStoredChecksum(appName) {
82
89
  if (exists(checksumFile)) {
83
90
  try {
84
91
  return fs.readFileSync(checksumFile, 'utf-8').trim()
85
- } catch (e) {
92
+ } catch {
86
93
  // Ignore read errors and assume no checksum is stored
87
94
  }
88
95
  }
@@ -99,11 +106,24 @@ function writeStoredChecksum(appName, checksum) {
99
106
  fs.writeFileSync(checksumFile, checksum, 'utf-8')
100
107
  }
101
108
 
109
+ const _mtaextForOverlay = overlay => {
110
+ if (!overlay) return
111
+ if (exists(overlay)) return overlay
112
+ if (!overlay.endsWith('.mtaext')) {
113
+ const file = `${overlay}.mtaext`
114
+ if (exists(file)) return file
115
+ }
116
+ }
117
+
102
118
  async function up() {
103
119
  const project = readProject()
104
120
  const { apps, appName, hasApprouter, hasXsuaa, isMonorepoMicroservice, hasKyma, hasMta, k8s } = project
105
121
  const toKyma = cds.cli.options.to === 'k8s' || hasKyma && !hasMta, toCf = !toKyma
106
122
 
123
+ if (cds.cli.options.overlay && toKyma) {
124
+ console.log(warn(`Ignoring --overlay ${cds.cli.options.overlay} (Cloud Foundry only)`))
125
+ }
126
+
107
127
  if (toCf) {
108
128
  if (!hasCommand('mbt')) {
109
129
  throw `Missing 'mbt' command. Run ${bold('npm install -g mbt')} to install the Cloud MTA Build Tool.`
@@ -286,8 +306,12 @@ async function up() {
286
306
  if (exists('mtx/sidecar')) run(`kubectl rollout status deployment ${appName}-sidecar ${nsKubectl} --timeout=8m`)
287
307
  } else {
288
308
  run('mbt build -t gen --mtar mta.tar')
309
+ const overlayOption = cds.cli.options.overlay
310
+ const overlay = overlayOption && _mtaextForOverlay(overlayOption)
311
+ if (overlayOption && !overlay) console.log(`MTA extension descriptor not found at '${overlayOption}' - continuing with base mta.yaml`)
312
+ const ext = overlay ? `-e ${overlay}` : ''
289
313
  const retries = process.env.CI ? '' : '--retries 0'
290
- run(`cf deploy gen/mta.tar -f ${retries}`)
314
+ run(`cf deploy gen/mta.tar ${ext} -f ${retries}`)
291
315
  }
292
316
  }
293
317
 
@@ -133,9 +133,7 @@ async function executeModules(modules, options) {
133
133
  run(`docker inspect --format='{{index .RepoTags 0}}' ${m.image}:${m.checksum}`, { silent: true })
134
134
  console.log(info(`image ${m.image}:${m.checksum} already exists – skipping build and push`))
135
135
  continue
136
- } catch (error) {
137
- // ignore
138
- }
136
+ } catch { /* ignore */ }
139
137
  }
140
138
 
141
139
  for (const cmd of m.buildCmd) run(cmd)