@sap/cds-dk 8.4.2 → 8.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. package/CHANGELOG.md +41 -3
  2. package/bin/cds.js +49 -28
  3. package/bin/debug.js +90 -0
  4. package/bin/repl.js +50 -4
  5. package/bin/watch.js +13 -3
  6. package/lib/bind/userCdsrcJsonStorageProvider.js +0 -19
  7. package/lib/build/buildTaskEngine.js +4 -4
  8. package/lib/build/provider/hana/2migration.js +34 -20
  9. package/lib/build/provider/hana/index.js +74 -41
  10. package/lib/build/provider/internalBuildTaskProvider.js +1 -1
  11. package/lib/build/provider/mtx-extension/index.js +4 -4
  12. package/lib/build/provider/mtx-sidecar/index.js +2 -2
  13. package/lib/build/provider/nodejs/index.js +3 -19
  14. package/lib/build/util.js +41 -17
  15. package/lib/cds.js +10 -2
  16. package/lib/deploy/to-hana/hdiDeployUtil.js +14 -10
  17. package/lib/import/message.js +2 -1
  18. package/lib/import/odata/metadataconverter/MetadataConverter.js +5 -0
  19. package/lib/import/odata/v4parser.js +32 -43
  20. package/lib/init/add.js +26 -0
  21. package/lib/init/constants.js +5 -6
  22. package/lib/init/index.js +17 -25
  23. package/lib/init/merge.js +2 -5
  24. package/lib/init/projectReader.js +76 -9
  25. package/lib/init/registries/mta.js +0 -1
  26. package/lib/init/template/approuter/files/package.json +1 -1
  27. package/lib/init/template/cloud-logging/files/package.json.hbs +1 -12
  28. package/lib/init/template/cloud-logging/files/values.yaml.hbs +16 -0
  29. package/lib/init/template/cloud-logging/index.js +14 -10
  30. package/lib/init/template/data/as-json.js +1 -1
  31. package/lib/init/template/data/index.js +1 -26
  32. package/lib/init/template/esm/files/package.json.hbs +3 -0
  33. package/lib/init/template/esm/index.js +16 -0
  34. package/lib/init/template/handler/files/handler.xs.hbs +56 -0
  35. package/lib/init/template/handler/index.js +84 -9
  36. package/lib/init/template/helm/index.js +4 -1
  37. package/lib/init/template/html5-repo/files/xs-app.json.hbs +1 -0
  38. package/lib/init/template/html5-repo/index.js +2 -0
  39. package/lib/init/template/http/index.js +102 -35
  40. package/lib/init/template/nodejs/files/package.json.hbs +1 -1
  41. package/lib/init/template/sample/files/nodejs/{admin-service.js → admin-service.js.hbs} +8 -1
  42. package/lib/init/template/sample/files/nodejs/{cat-service.js → cat-service.js.hbs} +8 -3
  43. package/lib/init/template/sample/index.js +4 -2
  44. package/lib/init/template/telemetry/files/package.json.hbs +18 -0
  45. package/lib/init/template/telemetry/index.js +8 -2
  46. package/lib/init/template/typescript/files/package.json +2 -2
  47. package/lib/init/template/workzone-standard/index.js +1 -1
  48. package/lib/init/types.d.ts +0 -6
  49. package/lib/watch/node-watch.js +25 -9
  50. package/npm-shrinkwrap.json +63 -48
  51. package/package.json +1 -1
package/lib/init/add.js CHANGED
@@ -15,5 +15,31 @@ module.exports = {
15
15
  register: (name, plugin) => {
16
16
  registered.set(name, plugin)
17
17
  },
18
+ filterStringAsRegex,
18
19
  registered
19
20
  }
21
+
22
+ /**
23
+ * Turns a filter string with optional meta characters into a regular expression.
24
+ * If no meta characters are found, the string is treated as an include pattern.
25
+ *
26
+ * @param {string} spec the filter string
27
+ * @returns {RegExp} the regular expression
28
+ */
29
+ function filterStringAsRegex(spec) {
30
+ if (typeof spec === 'string') {
31
+ try {
32
+ if (spec.match(/[\^$|*]/)) {
33
+ spec = spec.replace(/^\*/, '.*') // * at start would be invalid, so make it a .*
34
+ return new RegExp(spec, 'i')
35
+ }
36
+ else { // no meta chars -> include semantics
37
+ spec = spec.replace(/\./g, '\\.') // escape literal dot
38
+ return new RegExp(spec, 'i')
39
+ }
40
+ } catch (err) {
41
+ throw err.message // user error, so cut off stack trace
42
+ }
43
+ }
44
+ return /.*/
45
+ }
@@ -5,7 +5,7 @@ module.exports = {
5
5
  COMMAND_ADD: 'add',
6
6
 
7
7
  JAVA_LTS_VERSIONS: [17, 21],
8
- MAVEN_ARCHETYPE_VERSION: '3.4.1',
8
+ MAVEN_ARCHETYPE_VERSION: '3.5.0',
9
9
 
10
10
  OPTIONS: Object.freeze({
11
11
  COMPLETION: 'completion',
@@ -13,9 +13,13 @@ module.exports = {
13
13
  JAVA_MVN: 'java:mvn',
14
14
  JAVA_PACKAGE: 'java:package',
15
15
  NODEJS: 'nodejs',
16
+ ESM: 'esm',
16
17
  SAMPLES: 'samples', // deprecated
17
18
  TINY_SAMPLE: 'tiny-sample',
18
19
  SAMPLE: 'sample',
20
+ TYPER: 'typer',
21
+ TYPESCRIPT: 'typescript',
22
+ HANDLER: 'handler', // influenced by esm, typer and typescript
19
23
  MTA: 'mta',
20
24
  CF_MANIFEST: 'cf-manifest',
21
25
  HELM: 'helm',
@@ -57,8 +61,6 @@ module.exports = {
57
61
  LINT_GLOBAL: 'lint:global',
58
62
  LINT_DEV: 'lint:dev',
59
63
  PIPELINE: 'pipeline',
60
- TYPER: 'typer',
61
- TYPESCRIPT: 'typescript'
62
64
  }),
63
65
 
64
66
  REGEX_JAVA_PACKAGE: /^[a-z_][a-z0-9_]*(\.[a-z_][a-z0-9_]*)*$/g,
@@ -71,9 +73,6 @@ module.exports = {
71
73
  CAPIRE: 'https://cap.cloud.sap',
72
74
  MAVEN_ARCHETYPE_HELP: `https://cap.cloud.sap/docs/java/developing-applications/building#the-maven-archetype`,
73
75
  MAVEN_INSTALL_HELP: `https://maven.apache.org/install.html`,
74
- CAP_JS_COMMUNITY_ODATA_V2: 'https://github.com/cap-js-community/odata-v2-adapter',
75
- CAP_JS_COMMUNITY_EVENT_QUEUE: 'https://github.com/cap-js-community/event-queue',
76
- CAP_JS_COMMUNITY_WEBSOCKET: 'https://github.com/cap-js-community/websocket',
77
76
  CF_CLI: 'https://github.com/cloudfoundry/cli'
78
77
  }),
79
78
 
package/lib/init/index.js CHANGED
@@ -12,17 +12,13 @@ const DEBUG = /\b(y|all|cli)\b/.test(process.env.DEBUG) ? console.debug : undefi
12
12
 
13
13
  module.exports = class CDSGenerator {
14
14
 
15
- /** @returns {string[]} */
16
- static #findPlugins(excluded) {
17
- // actually needs to be sync to be usable in module.exports of dk/bin/init.js
18
- const fromDk = readdirSync(join(__dirname, 'template'))
19
- const fromPlugins = Object.keys(Object.fromEntries(require('./add').registered))
20
- return [...fromDk, ...fromPlugins]
21
- .filter(plugin => !excluded?.includes(plugin))
15
+ constructor() {
16
+ this.cwd = process.cwd()
17
+ this.uiConfig = require('./bas') // Used by CAP Generator
22
18
  }
23
19
 
24
20
  static help({ exclude = [] } = {}) {
25
- if (!cds.add) return // use case shell completion: cds.add is undefined and not needed
21
+ if (!cds.add) return // shell completion: cds.add is undefined and not needed
26
22
 
27
23
  const plugins = this.readPlugins(exclude)
28
24
  const nameFixedLength = Math.max(...plugins.map(plugin => plugin.name.length))
@@ -35,10 +31,14 @@ module.exports = class CDSGenerator {
35
31
  }
36
32
 
37
33
  /**
34
+ * @param excluded {string[]}
38
35
  * @returns {{name: string, module: import('module')}[]}
39
36
  */
40
37
  static readPlugins(excluded = []) {
41
- const plugins = this.#findPlugins(excluded)
38
+ const fromDk = readdirSync(join(__dirname, 'template'))
39
+ const fromPlugins = Object.keys(Object.fromEntries(require('./add').registered))
40
+ const all = [...fromDk, ...fromPlugins]
41
+ .filter(plugin => !excluded?.includes(plugin))
42
42
  .map(plugin => ({
43
43
  name: plugin,
44
44
  module: cds.add.registered.get(plugin) ?? require('./template/' + plugin)
@@ -51,12 +51,7 @@ module.exports = class CDSGenerator {
51
51
  if (b === -1) return -1
52
52
  return a - b
53
53
  }
54
- return plugins.sort(byPriority)
55
- }
56
-
57
- constructor() {
58
- this.cwd = process.cwd()
59
- this.uiConfig = require('./bas') // Used by CAP Generator
54
+ return all.sort(byPriority)
60
55
  }
61
56
 
62
57
  /**
@@ -216,15 +211,14 @@ module.exports = class CDSGenerator {
216
211
  }
217
212
 
218
213
  const cmds = Object.values(OPTIONS)
219
- this.templateList = Array.from(templates.entries())
220
- .sort(([lhs], [rhs]) => {
221
- const a = cmds.indexOf(lhs), b = cmds.indexOf(rhs)
222
- if (a === -1 && b === -1) return 0
223
- if (a === -1) return 1
224
- if (b === -1) return -1
214
+ const priorities = new Map(cmds.map((cmd, i) => [cmd, i]))
215
+ this.templateList = Array.from(templates.keys())
216
+ .sort((lhs, rhs) => {
217
+ const a = priorities.get(lhs) ?? Infinity
218
+ const b = priorities.get(rhs) ?? Infinity
225
219
  return a - b
226
220
  })
227
- .map((entry) => entry[1])
221
+ .map(key => templates.get(key))
228
222
 
229
223
  DEBUG?.(`Features: ${this.templateList.map(({ name }) => name).join(', ')}`);
230
224
  }
@@ -240,11 +234,9 @@ module.exports = class CDSGenerator {
240
234
 
241
235
  const { options } = cds.cli
242
236
  if (typeof options.add === 'string') {
243
- tokens = options.add.split(/[,\s+]/g);
244
-
237
+ tokens = options.add.split(/[,\s+]/g)
245
238
  } else if (Array.isArray(options.add)) {
246
239
  tokens = options.add
247
-
248
240
  } else if (options.add instanceof Set) {
249
241
  tokens = [...options.add];
250
242
  }
package/lib/init/merge.js CHANGED
@@ -6,11 +6,8 @@ const { readYAML, writeYAML, readJSON } = require('../util/fs')
6
6
  const { detectIndent } = require('./indent')
7
7
  const { colors } = require('../util/term')
8
8
 
9
- function _isObject(item) {
10
- return item && typeof item === 'object' && !Array.isArray(item)
11
- }
12
-
13
9
  function _mergeObject(target, source) {
10
+ const _isObject = item => item && typeof item === 'object' && !Array.isArray(item)
14
11
  const unique = array => [...new Set(array.map(JSON.stringify))].map(JSON.parse)
15
12
  if (_isObject(target) && _isObject(source)) {
16
13
  for (const key in source) {
@@ -156,7 +153,7 @@ async function _mergeYAML(into, from, project, semantics = {}) {
156
153
  relationships?.forEach(relationship => {
157
154
  const [entry] = relationship.into
158
155
  const [insertEntry] = relationship.insert
159
- const hash = insertEntry + ' -> ' + entry
156
+ const hash = JSON.stringify(insertEntry) + ' -> ' + JSON.stringify(entry)
160
157
  entryMap.set(hash, undefined)
161
158
  })
162
159
 
@@ -1,6 +1,6 @@
1
1
  const cds = require('../cds')
2
2
  const { join, resolve, basename } = require('path')
3
- const { fs, exists, isdir, path } = cds.utils
3
+ const { fs, exists, isfile, isdir, path } = cds.utils
4
4
  const { parseXml } = require('./xml')
5
5
  const { REGEX_JAVA_VERSION, JAVA_LTS_VERSIONS } = require('./constants')
6
6
  const TRACE = cds.debug('trace')
@@ -12,13 +12,15 @@ module.exports = new class ProjectReader {
12
12
  }
13
13
 
14
14
  #setPackageInfo() {
15
+ this.isJava = (exists('pom.xml') || cds.cli.options?.add?.has('java'))
16
+ && !cds.env.for('cds').profiles.includes('node') // opt-out for hybrid projects like sflight
17
+
15
18
  // 1. Use project name and static default values
16
- this.appVersion = cds.cli.options?.add?.has('java') ? '1.0.0-SNAPSHOT' : '1.0.0'
19
+ this.appVersion = this.isJava ? '1.0.0-SNAPSHOT' : '1.0.0'
17
20
  this.appName = this.appId = basename(cds.root)
18
21
  this.appDescription = 'A simple CAP project.'
19
22
 
20
- const isJava = exists('pom.xml') || cds.cli.options?.add?.has('java')
21
- if (isJava) {
23
+ if (this.isJava) {
22
24
  let artifactId, description, version, properties
23
25
  try {
24
26
  ({ artifactId, description, version, properties } = parseXml(resolve(cds.root, 'pom.xml')) ?? {})
@@ -74,6 +76,10 @@ module.exports = new class ProjectReader {
74
76
  * @property {boolean} isUI5 Indicates if the project is a UI5 project.
75
77
  * @property {boolean} isJava Indicates if the project is using Java.
76
78
  * @property {boolean} isNodejs Indicates if the project is using Node.js.
79
+ * @property {boolean} isESM Indicates if the project is an ESM project (type 'module')
80
+ * @property {boolean} hasEsmStyle Indicates if code supports ESM-style imports and exports. This is true for ESM and Typescript projects.
81
+ * @property {boolean} isTypescript Indicates if the project is a Typescript project.
82
+ * @property {boolean} hasTyper Indicates if code supports CDS typer.
77
83
  * @property {boolean} hasHelmUnifiedRuntime Indicates if the project uses Unified Runtime Helm deployment.
78
84
  * @property {boolean} hasHelm Indicates if the project uses CAP Helm deployment.
79
85
  * @property {boolean} hasMta Indicates if the project uses MTA deployment.
@@ -95,7 +101,7 @@ module.exports = new class ProjectReader {
95
101
  * @property {boolean} hasMTXRouteJava Indicates if the approuter should have a multitenant subscription route (java).
96
102
  * @property {boolean} hasMalwareScanner Indicates if the project uses the SAP Malware Scanning service.
97
103
  * @property {boolean} hasDynatrace Indicates if the project uses Dynatrace.
98
- *
104
+ * @property {boolean} hasCloudLogging Indicates if the project uses Cloud Logging.
99
105
  */
100
106
 
101
107
  /**
@@ -110,18 +116,27 @@ module.exports = new class ProjectReader {
110
116
  const { appVersion, appName, appId, appDescription, jdkVersion } = this
111
117
  TRACE?.({ env })
112
118
 
119
+ const pkgJson = () => exists('package.json') ? JSON.parse(fs.readFileSync(join(cds.root, 'package.json'))) : {}
120
+ const _isESM = () => pkgJson().type === 'module'
121
+ const _isTypescript = () => !!pkgJson().dependencies?.typescript || !!pkgJson().devDependencies?.typescript
122
+ const _hasTyper = () => !!pkgJson().dependencies?.['@cap-js/cds-typer'] || !!pkgJson().devDependencies?.['@cap-js/cds-typer']
123
+
113
124
  const _inProd = plugin => require(`./template/${plugin}`).hasInProduction(env)
114
- const _ui5 = () => exists('package.json') && !!JSON.parse(fs.readFileSync(path.resolve(cds.root, 'package.json'))).sapux
125
+ const _ui5 = () => pkgJson().sapux
115
126
  const _uiEmbedded = () => exists(join(env.folders.app, 'xs-app.json'))
116
127
  const _uiModule = () => !_uiEmbedded() && isdir(env.folders.app) && fs.readdirSync(resolve(cds.root, env.folders.app)).length > 0
117
- const _isJava = () => exists('pom.xml') || cds.cli.options?.add?.has('java')
128
+ const _isJava = () => this.isJava
118
129
  const isApp = name => name !== 'appconfig' && !name.startsWith('_') && name !== 'router' && name !== 'portal'
119
130
  const reserved = {
120
131
  has: x => _inProd(x),
121
132
  profile: () => cds.cli.options?.for,
122
- language: () => exists('pom.xml') ? 'java' : 'nodejs',
133
+ language: () => _isJava() ? 'java' : 'nodejs',
123
134
  isJava: () => _isJava(),
124
135
  isNodejs: () => !_isJava(),
136
+ isTypescript: () => _isTypescript(),
137
+ isESM: () => _isESM,
138
+ hasEsmStyle: () => _isTypescript() || _isESM(),
139
+ hasTyper: () => _hasTyper(),
125
140
  srvPath: () => join(env.build.target, env.folders.srv.replace(/\/$/, '')).replace(/\\/g, '/'),
126
141
  archiveName: () => {
127
142
  const pomXmlPath = resolve(cds.root, env.folders.srv, 'pom.xml')
@@ -135,7 +150,7 @@ module.exports = new class ProjectReader {
135
150
  const path = join(env.build.target, name).replace(/\\/g, '/')
136
151
  return { folder, name, path }
137
152
  },
138
- configFile: () => exists('pom.xml') ? '.cdsrc.json' : 'package.json',
153
+ configFile: () => _isJava() ? '.cdsrc.json' : 'package.json',
139
154
  apps: () => {
140
155
  const apps = fs.readdirSync(path.resolve(cds.root, cds.env.folders.app)).filter(e =>
141
156
  isdir(path.join(cds.root, cds.env.folders.app, e)) && isApp(e)
@@ -152,6 +167,7 @@ module.exports = new class ProjectReader {
152
167
  appUIPaths: () => (fs.readdirSync(path.resolve(cds.root, cds.env.folders.app))).filter(e =>
153
168
  isdir(join(cds.root, cds.env.folders.app, e)) && isApp(e)
154
169
  ),
170
+ hasIndexHtml: () => {},
155
171
  appPath: () => env.folders.app,
156
172
  appVersion: () => appVersion,
157
173
  jdkVersion: () => jdkVersion,
@@ -191,3 +207,54 @@ module.exports = new class ProjectReader {
191
207
  return project
192
208
  }
193
209
  }
210
+
211
+ /**
212
+ * Finds and reads SpringBoot configuration in `cds.root/srv` dir.
213
+ * This is done in a best-effort way. In no way can we replciate all of SpringBoot's
214
+ * config variants.
215
+ * @param {string[]?} roots array of directories to search for application.yaml, like `srv`
216
+ * @returns {Promise<object[]>} array of objects with `cds` property
217
+ */
218
+ module.exports._readSpringBootConfig = async function (roots=[cds.env.folders.srv]) {
219
+ const configNodes = []
220
+ const rootDirs = roots.map(d => join(cds.root, d))
221
+ for (let r of rootDirs) {
222
+ const file = isfile (join (r,'./src/main/resources/application.yaml'))
223
+ if (file) {
224
+ const yaml = cds.load.yaml(file)
225
+ for (let yamlDoc of Array.isArray(yaml) ? yaml : [yaml]) {
226
+ let cds = yamlDoc?.cds;
227
+ if (!cds) continue
228
+ cds = _normalizeSpringBootCfg(cds)
229
+ configNodes.push({cds})
230
+ }
231
+ }
232
+ }
233
+ return configNodes
234
+ }
235
+
236
+ /**
237
+ * Normalize SpringBoot's dots in keys to express nested objects
238
+ *
239
+ * @example: `cds.foo.bar = 1` --> `cds: { foo: { bar: 1 } }`
240
+ * @param {object} obj
241
+ * @returns {object}
242
+ */
243
+ function _normalizeSpringBootCfg(obj) {
244
+ if (typeof obj !== 'object') return obj
245
+ Object.keys(obj).forEach(k => {
246
+ const prop = k.split('.')
247
+ const last = prop.pop()
248
+ // and define the object if not already defined
249
+ const res = prop.reduce((o, key) => {
250
+ // define the object if not defined and return
251
+ return o[key] = o[key] ?? {}
252
+ }, obj)
253
+ res[last] = obj[k]
254
+ // recursively normalize
255
+ _normalizeSpringBootCfg(obj[k])
256
+ // delete the original property from object if it was rewritten
257
+ if (prop.length) delete obj[k]
258
+ })
259
+ return obj
260
+ }
@@ -1,4 +1,3 @@
1
- // Registry of sequence matchers in mta.yaml files
2
1
  const cds = require('../../cds')
3
2
 
4
3
  const srvNode4 = path => ({ in: 'modules', where: { type: 'nodejs', path } })
@@ -4,7 +4,7 @@
4
4
  "@sap/approuter": "^17.0.0"
5
5
  },
6
6
  "engines": {
7
- "node": "^22.0.0"
7
+ "node": "^20"
8
8
  },
9
9
  "scripts": {
10
10
  "start": "node node_modules/@sap/approuter/approuter.js"
@@ -1,18 +1,7 @@
1
1
  {
2
- "dependencies": {
3
- "@cap-js/telemetry": ">=0",
4
- "@grpc/grpc-js": "^1",
5
- "@opentelemetry/exporter-trace-otlp-grpc": "^0",
6
- "@opentelemetry/exporter-metrics-otlp-grpc": "^0",
7
- "@opentelemetry/host-metrics": "^0"
8
- },
9
2
  "cds": {
10
3
  "requires": {
11
- "[{{profile}}]": {
12
- "telemetry": {
13
- "kind": "to-cloud-logging"
14
- }
15
- }
4
+ "cloud-logging": true
16
5
  }
17
6
  }
18
7
  }
@@ -0,0 +1,16 @@
1
+ ---
2
+ srv:
3
+ bindings:
4
+ cloud-logging:
5
+ serviceInstanceName: cloud-logging
6
+ credentialsRotationPolicy:
7
+ enabled: true
8
+ rotationFrequency: 168h
9
+ cloud-logging:
10
+ serviceOfferingName: cloud-logging
11
+ servicePlanName: standard
12
+ parameters:
13
+ ingest_otlp:
14
+ enabled: true
15
+
16
+
@@ -7,7 +7,7 @@ const { srv4, mtxSidecar4, approuter } = require('../../registries/mta')
7
7
  module.exports = class extends require('../../plugin') {
8
8
 
9
9
  static hasInProduction(env) {
10
- return env.requires?.telemetry?.kind === 'to-cloud-logging'
10
+ return env.requires?.['cloud-logging'];
11
11
  }
12
12
 
13
13
  options() {
@@ -24,23 +24,23 @@ module.exports = class extends require('../../plugin') {
24
24
  }
25
25
 
26
26
  async canRun() {
27
- const { hasMta, hasHelm, hasHelmUnifiedRuntime } = readProject()
28
- if ((hasHelmUnifiedRuntime || hasHelm) && !hasMta) throw `'cds add cloud-logging' is not available for Kyma yet`
29
27
  return true
30
28
  }
31
29
 
30
+ requires() {
31
+ const project = readProject(), { hasTelemetry } = project
32
+ if ((cds.cli.options['with-telemetry'] || hasTelemetry)) return ['telemetry']
33
+ }
34
+
32
35
  async run() {
33
- const project = readProject(), { hasTelemetry, isNodejs } = project
34
- if (isNodejs && (cds.cli.options['with-telemetry'] || hasTelemetry)) {
35
- project.profile ??= 'production'
36
- await merge(__dirname, 'files/package.json.hbs').into('package.json', { with: project })
37
- await sort('package.json', 'dependencies')
38
- }
36
+ const project = readProject();
37
+ await merge(__dirname, 'files/package.json.hbs').into('package.json', { with: project })
38
+ await sort('package.json', 'dependencies')
39
39
  }
40
40
 
41
41
  async combine() {
42
42
  const project = readProject()
43
- const { hasMta, hasApprouter, isJava, hasMultitenancy, srvPath } = project
43
+ const { hasMta, hasHelm, hasHelmUnifiedRuntime, hasApprouter, isJava, hasMultitenancy, srvPath } = project
44
44
 
45
45
  if (hasMta) {
46
46
  const cloudLogging = {
@@ -77,5 +77,9 @@ module.exports = class extends require('../../plugin') {
77
77
  with: project, additions, relationships
78
78
  })
79
79
  }
80
+ if (hasHelm || hasHelmUnifiedRuntime) {
81
+ await merge(__dirname, 'files/values.yaml.hbs').into('chart/values.yaml')
82
+ }
80
83
  }
81
84
  }
85
+
@@ -80,7 +80,7 @@ function fillRecord(record, recordID, artifact, csn, data, parent, depth, refere
80
80
  Object.defineProperty(record, '$id', {value: recordID, enumerable: false})
81
81
  if (data[artifact.name]) data[artifact.name].push(record) // add id to data, to be referenced by associations to prevent circular dependencies
82
82
 
83
- const elems = Object.values(artifact.elements ?? artifact.params).sort(asJson.elementsSorter(csn))
83
+ const elems = Object.values(artifact.elements ?? artifact.params ?? {}).sort(asJson.elementsSorter(csn))
84
84
  elems.forEach(el => {
85
85
  record[el.name] = dataForField(el, artifact, csn, record, data, parent, depth, referenceData)
86
86
  })
@@ -6,6 +6,7 @@ const { URLSearchParams } = require('url')
6
6
  const { env4 } = require('../../projectReader')
7
7
  const asJson = require('./as-json')
8
8
  const asCsv = require('./as-csv')
9
+ const { filterStringAsRegex } = require('../../add')
9
10
 
10
11
  module.exports = class DataTemplate extends require('../../plugin') {
11
12
 
@@ -92,32 +93,6 @@ module.exports = class DataTemplate extends require('../../plugin') {
92
93
  }
93
94
  module.exports.asJson = asJson
94
95
  module.exports.asCsv = asCsv
95
- module.exports.filterStringAsRegex = filterStringAsRegex
96
-
97
- /**
98
- * Turns a filter string with optional meta characters into a regular expression.
99
- * If no meta characters are found, the string is treated as an include pattern.
100
- *
101
- * @param {string} spec the filter string
102
- * @returns {RegExp} the regular expression
103
- */
104
- function filterStringAsRegex(spec) {
105
- if (typeof spec === 'string') {
106
- try {
107
- if (spec.match(/[\^$|*]/)) {
108
- spec = spec.replace(/^\*/, '.*') // * at start would be invalid, so make it a .*
109
- return new RegExp(spec, 'i')
110
- }
111
- else { // no meta chars -> include semantics
112
- spec = spec.replace(/\./g, '\\.') // escape literal dot
113
- return new RegExp(spec, 'i')
114
- }
115
- } catch (err) {
116
- throw err.message // user error, so cut off stack trace
117
- }
118
- }
119
- return /.*/
120
- }
121
96
 
122
97
  function parseQueryStr(queryStr) {
123
98
  const params = new URLSearchParams(queryStr) // e.g. key1&key2=false
@@ -0,0 +1,3 @@
1
+ {
2
+ "type": "module"
3
+ }
@@ -0,0 +1,16 @@
1
+ const { readProject } = require('../../projectReader')
2
+ const { merge } = require('../../merge')
3
+
4
+ module.exports = class EsmTemplate extends require('../../plugin') {
5
+
6
+ static help() {
7
+ return 'ESM-compatible Node.js project'
8
+ }
9
+
10
+ async run() {
11
+ const project = readProject()
12
+ const { configFile } = project
13
+ await merge(__dirname, 'files/package.json.hbs').into(configFile, { with: project, forceOverwrite: true })
14
+ }
15
+
16
+ }
@@ -0,0 +1,56 @@
1
+ {{! ESM }}
2
+ {{#hasEsmStyle}}
3
+ import cds from '@sap/cds'
4
+ {{#hasTyper}}
5
+ {{#allNames}}
6
+ import { {{allNames}} } from '#cds-models/{{{servicePath}}}'
7
+ {{/allNames}}
8
+ {{/hasTyper}}
9
+
10
+ {{! class declaration }}
11
+ export class {{serviceClass}} extends cds.ApplicationService { init() {
12
+ {{/hasEsmStyle}}
13
+ {{! CJS }}
14
+ {{^hasEsmStyle}}
15
+ const cds = require('@sap/cds')
16
+ {{#hasTyper}}
17
+ {{#allNames}}
18
+ const { {{allNames}} } = require('#cds-models/{{{servicePath}}}')
19
+ {{/allNames}}
20
+ {{/hasTyper}}
21
+
22
+ {{! class declaration }}
23
+ module.exports = class {{serviceClass}} extends cds.ApplicationService { init() {
24
+ {{/hasEsmStyle}}
25
+ {{! dynamic model imports }}
26
+ {{^hasTyper}}
27
+
28
+ {{#entityNames}}
29
+ const { {{entityNames}} } = cds.entities('{{serviceName}}')
30
+ {{/entityNames}}
31
+ {{/hasTyper}}
32
+
33
+ {{! entity handlers }}
34
+ {{#entities}}
35
+ this.before (['CREATE', 'UPDATE'], {{name}}, async (req) => {
36
+ console.log('Before CREATE/UPDATE {{name}}', req.data)
37
+ })
38
+ this.after ('READ', {{name}}, async ({{dataVar}}, req) => {
39
+ console.log('After READ {{name}}', {{dataVar}})
40
+ })
41
+ {{/entities}}
42
+
43
+ {{! action handlers }}
44
+ {{#operations}}
45
+ {{#hasTyper}}
46
+ this.on ({{name}}, async (req) => {
47
+ {{/hasTyper}}
48
+ {{^hasTyper}}
49
+ this.on ('{{name}}', async (req) => {
50
+ {{/hasTyper}}
51
+ console.log('On {{name}}', req.data)
52
+ })
53
+ {{/operations}}
54
+
55
+ return super.init()
56
+ }}