@sap/cds-dk 8.8.2 → 8.9.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 (61) hide show
  1. package/CHANGELOG.md +24 -1
  2. package/bin/deploy/index.js +7 -3
  3. package/bin/init.js +5 -5
  4. package/bin/repl.js +4 -0
  5. package/bin/test.js +6 -4
  6. package/bin/up.js +77 -0
  7. package/bin/watch.js +0 -43
  8. package/lib/bind/shared.js +1 -1
  9. package/lib/build/buildTaskEngine.js +10 -9
  10. package/lib/build/buildTaskFactory.js +0 -45
  11. package/lib/build/plugins/helm/files/subcharts/content-deployment/Chart.yaml +1 -1
  12. package/lib/build/plugins/helm/files/subcharts/content-deployment/templates/job.yaml +8 -0
  13. package/lib/build/plugins/helm/files/subcharts/content-deployment/values.schema.json +87 -0
  14. package/lib/build/plugins/helm/files/subcharts/content-deployment/values.yaml +29 -1
  15. package/lib/build/plugins/helm/files/subcharts/service-instance/Chart.yaml +1 -1
  16. package/lib/build/plugins/helm/files/subcharts/service-instance/templates/serviceinstance.yaml +1 -0
  17. package/lib/build/plugins/helm/files/subcharts/service-instance/values.yaml +1 -1
  18. package/lib/build/plugins/helm/files/subcharts/web-application/Chart.yaml +1 -1
  19. package/lib/build/plugins/helm/files/subcharts/web-application/templates/deployment.yaml +8 -0
  20. package/lib/build/plugins/helm/files/subcharts/web-application/values.schema.json +87 -0
  21. package/lib/build/plugins/helm/files/subcharts/web-application/values.yaml +29 -1
  22. package/lib/build/provider/internalBuildTaskProvider.js +1 -1
  23. package/lib/build/provider/mtx-extension/index.js +5 -0
  24. package/lib/deploy/to-hana/hana.js +95 -43
  25. package/lib/deploy/to-hana/mtaUtil.js +1 -2
  26. package/lib/env/schema.js +12 -88
  27. package/lib/import/common.js +23 -15
  28. package/lib/import/odata.package.json +7 -0
  29. package/lib/init/constants.js +2 -1
  30. package/lib/init/index.js +13 -16
  31. package/lib/init/mvn.js +1 -1
  32. package/lib/init/plugin.js +1 -1
  33. package/lib/init/projectReader.js +5 -1
  34. package/lib/init/template/approuter/files/package.json +1 -4
  35. package/lib/init/template/attachments/files/package.json.hbs +1 -1
  36. package/lib/init/template/cloud-logging/files/java-cdsrc.json +5 -0
  37. package/lib/init/template/cloud-logging/index.js +9 -5
  38. package/lib/init/template/containerize/files/containerize.yaml.hbs +1 -1
  39. package/lib/init/template/data/index.js +3 -3
  40. package/lib/init/template/extension/files/.base/index.cds +15 -0
  41. package/lib/init/template/extension/files/app/extension.cds.hbs +47 -0
  42. package/lib/init/template/extension/files/test/data/Authors.csv +4 -0
  43. package/lib/init/template/extension/files/test/data/my.bookshop-Books.csv +8 -0
  44. package/lib/init/template/extension/index.js +99 -0
  45. package/lib/init/template/handler/files/extension/handler.xs.hbs +10 -0
  46. package/lib/init/template/handler/files/extension/package.json +10 -0
  47. package/lib/init/template/handler/index.js +83 -20
  48. package/lib/init/template/http/index.js +3 -3
  49. package/lib/init/template/java/index.js +3 -3
  50. package/lib/init/template/mta/index.js +1 -1
  51. package/lib/init/template/nodejs/index.js +3 -3
  52. package/lib/init/template/pipeline/index.js +1 -1
  53. package/lib/init/template/sample/index.js +6 -0
  54. package/lib/init/template/typer/files/package.json +0 -3
  55. package/lib/init/template/typer/index.js +2 -2
  56. package/lib/init/validate.js +4 -7
  57. package/lib/mtx/pull.js +9 -1
  58. package/lib/util/question.js +24 -21
  59. package/npm-shrinkwrap.json +18 -18
  60. package/package.json +2 -2
  61. /package/lib/init/template/handler/files/{handler.xs.hbs → standard/handler.xs.hbs} +0 -0
@@ -1283,6 +1283,56 @@
1283
1283
  }
1284
1284
  }
1285
1285
  }
1286
+ },
1287
+ "nodeSelector": {
1288
+ "$id": "#/properties/nodeSelector",
1289
+ "title": "Node selector",
1290
+ "type": "object",
1291
+ "patternProperties": {
1292
+ "^[-._a-zA-Z][-._a-zA-Z0-9]*$": {
1293
+ "type": "string"
1294
+ }
1295
+ }
1296
+ },
1297
+ "nodeAffinity": {
1298
+ "$id": "#/properties/nodeAffinity",
1299
+ "title": "Node affinity",
1300
+ "type": "object",
1301
+ "additionalProperties": false,
1302
+ "properties": {
1303
+ "requiredDuringSchedulingIgnoredDuringExecution": {
1304
+ "type": "object",
1305
+ "additionalProperties": false,
1306
+ "properties": {
1307
+ "nodeSelectorTerms": {
1308
+ "type": "array",
1309
+ "description": "A node selector terms, associated with the corresponding weight.",
1310
+ "items": {
1311
+ "$ref": "#/definitions/NodeSelectorTerm"
1312
+ }
1313
+ }
1314
+ },
1315
+ "required": ["nodeSelectorTerms"]
1316
+ },
1317
+ "preferredDuringSchedulingIgnoredDuringExecution": {
1318
+ "type": "array",
1319
+ "items": {
1320
+ "type": "object",
1321
+ "additionalProperties": false,
1322
+ "properties": {
1323
+ "weight": {
1324
+ "type": "integer",
1325
+ "description": "Weight associated with matching the corresponding nodeSelectorTerm, in the range 1-100."
1326
+ },
1327
+ "preference": {
1328
+ "description": "A node selector term, associated with the corresponding weight.",
1329
+ "$ref": "#/definitions/NodeSelectorTerm"
1330
+ }
1331
+ },
1332
+ "required": ["weight", "preference"]
1333
+ }
1334
+ }
1335
+ }
1286
1336
  }
1287
1337
  },
1288
1338
  "definitions": {
@@ -1355,6 +1405,43 @@
1355
1405
  "type": "integer",
1356
1406
  "minimum": 1,
1357
1407
  "maximum": 65535
1408
+ },
1409
+ "NodeSelectorRequirement": {
1410
+ "description": "A node selector requirement is a selector that contains values, a key, and an operator that relates the key and values.",
1411
+ "type": "object",
1412
+ "additionalProperties": false,
1413
+ "properties": {
1414
+ "key": {
1415
+ "description": "The label key that the selector applies to.",
1416
+ "type": "string"
1417
+ },
1418
+ "operator": {
1419
+ "description": "Represents a key's relationship to a set of values. Valid operators are In, NotIn, Exists, DoesNotExist. Gt, and Lt.",
1420
+ "type": "string",
1421
+ "enum": ["In", "NotIn", "Exists", "DoesNotExist", "Gt", "Lt"]
1422
+ },
1423
+ "values": {
1424
+ "description": "An array of string values. If the operator is In or NotIn, the values array must be non-empty. If the operator is Exists or DoesNotExist, the values array must be empty. If the operator is Gt or Lt, the values array must have a single element, which will be interpreted as an integer. This array is replaced during a strategic merge patch.",
1425
+ "items": {
1426
+ "type": "string"
1427
+ },
1428
+ "type": "array"
1429
+ }
1430
+ },
1431
+ "required": ["key", "operator"]
1432
+ },
1433
+ "NodeSelectorTerm": {
1434
+ "description": "A null or empty node selector term matches no objects. The requirements of them are ANDed.",
1435
+ "type": "object",
1436
+ "additionalProperties": false,
1437
+ "properties": {
1438
+ "matchExpressions": {
1439
+ "type": "array",
1440
+ "items": {
1441
+ "$ref": "#/definitions/NodeSelectorRequirement"
1442
+ }
1443
+ }
1444
+ }
1358
1445
  }
1359
1446
  }
1360
1447
  }
@@ -14,7 +14,7 @@ global:
14
14
  _internal:
15
15
  web-application:
16
16
  versions:
17
- 1.23.0: true
17
+ 1.24.0: true
18
18
  ## Will be used instead of the `.Chart.Name`, e.g. when generating the Deployment name.
19
19
  # nameOverride: name
20
20
 
@@ -331,3 +331,31 @@ hpa:
331
331
  ## See https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/horizontal-pod-autoscaler-v2/#HorizontalPodAutoscalerSpec
332
332
  # behavior: {}
333
333
  # metrics: {}
334
+ ## Node label selector. See https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#nodeselector
335
+ # nodeSelector:
336
+ # kubernetes.io/os: linux
337
+
338
+ ## Node Affinity configuration. See https://kubernetes.io/docs/concepts/scheduling-eviction/assign-pod-node/#affinity-and-anti-affinity
339
+ # nodeAffinity:
340
+ # requiredDuringSchedulingIgnoredDuringExecution:
341
+ # nodeSelectorTerms:
342
+ # - matchExpressions:
343
+ # - key: kubernetes.io/os
344
+ # operator: In
345
+ # values:
346
+ # - linux
347
+ # preferredDuringSchedulingIgnoredDuringExecution:
348
+ # - weight: 1
349
+ # preference:
350
+ # matchExpressions:
351
+ # - key: label-1
352
+ # operator: In
353
+ # values:
354
+ # - key-1
355
+ # - weight: 50
356
+ # preference:
357
+ # matchExpressions:
358
+ # - key: label-2
359
+ # operator: In
360
+ # values:
361
+ # - key-2
@@ -171,7 +171,7 @@ class InternalBuildTaskProvider extends BuildTaskProvider {
171
171
  }
172
172
  throw new BuildError(`Invalid MTX sidecar configuration - profile 'mtx-sidecar' not set.`)
173
173
  }
174
- return { for: BUILD_TASK_MTX_SIDECAR }
174
+ return { src: MTX_SIDECAR_FOLDER, for: BUILD_TASK_MTX_SIDECAR }
175
175
  }
176
176
 
177
177
  if (sidecarEnv?.requires["cds.xt.ModelProviderService"]?._in_sidecar || sidecarEnv?.profiles?.includes('mtx-sidecar')) {
@@ -54,6 +54,11 @@ class MtxExtensionBuildPlugin extends InternalBuildPlugin {
54
54
  // reclassify base model warnings as info messages
55
55
  extensionCompilation.reclassifyBaseModelMessages(this.messages, saasPkgPath)
56
56
 
57
+ // Throw error if messages contain errors
58
+ if (this.messages.some(msg => msg.severity === 'Error')) {
59
+ throw new BuildError('CDS compilation failed', this.messages);
60
+ }
61
+
57
62
  if (extensionModel) {
58
63
  // IMPORTANT NOTE: perform all model operations before the linter is called as the linter
59
64
  // will modify the model causing the creation of 'unresolved' associations
@@ -47,7 +47,7 @@ Therefore, specifying the CDS model is not required when calling 'cds deploy'.`;
47
47
  }
48
48
 
49
49
  if (dry) {
50
- console.log('-- running in dry mode ---');
50
+ console.log('--- running in dry mode ---');
51
51
  }
52
52
 
53
53
  if (vcapFile) {
@@ -91,60 +91,116 @@ Therefore, specifying the CDS model is not required when calling 'cds deploy'.`;
91
91
  // the primary database is always built and deployed
92
92
  const dest = path.join(cds.env.build.target, cds.env.folders.db);
93
93
 
94
- let serviceName = pServiceName;
95
- let serviceKeyName;
96
- const { instance, key } = cds.requires.db?.binding ?? {};
97
- if (!serviceName && instance) {
98
- serviceName = instance;
99
- serviceKeyName = key;
100
- }
101
-
102
94
  if (undeployWhitelist) {
103
95
  console.log('Writing undeploy.json');
104
96
  await write(undeployWhitelist).to(dest, 'undeploy.json')
105
97
  }
106
98
 
107
99
  const hasVCAPEnv = Object.keys(vcapEnv).length > 0;
108
- if (!serviceName && hasVCAPEnv) {
109
- await mkdirp(dest);
110
- } else {
111
- const { cfServiceInstanceName, cfServiceInstanceKeyName, serviceKey } =
112
- await this._getOrCreateCFService(dest, serviceName, serviceKeyName, tunnelAddress);
113
100
 
114
- serviceName ??= cfServiceInstanceName;
115
- serviceKeyName ??= cfServiceInstanceKeyName
116
- if (!vcapEnv.VCAP_SERVICES || serviceName) {
117
- vcapEnv.VCAP_SERVICES = this._getVCAPServicesEntry(cfServiceInstanceName, serviceKey);
118
- }
101
+ let bindOptions
102
+ if (cds.cli.options.on === 'k8s') {
103
+ const cp = require('node:child_process')
104
+ const appName = pServiceName ?? path.basename(cds.root)
105
+ const instance =
106
+ `apiVersion: services.cloud.sap.com/v1
107
+ kind: ServiceInstance
108
+ metadata:
109
+ name: ${appName}-db
110
+ spec:
111
+ serviceOfferingName: hana
112
+ servicePlanName: hdi-shared\n`
113
+ const binding =
114
+ `apiVersion: services.cloud.sap.com/v1
115
+ kind: ServiceBinding
116
+ metadata:
117
+ name: ${appName}-db-binding
118
+ spec:
119
+ serviceInstanceName: ${appName}-db
120
+ secretName: ${appName}-db-secret
121
+ \n`
119
122
 
120
- if (storeCredentials) {
121
- await this._addInstanceToDefaultEnvJson([dest, cds.root], cfServiceInstanceName, serviceKey);
123
+ try {
124
+ cp.execSync('kubectl apply -f -', { input: instance })
125
+ cp.execSync('kubectl apply -f -', { input: binding })
126
+ cp.execSync(`kubectl wait --for=condition=Ready servicebinding/${appName}-db-binding --timeout=120s`)
127
+ cp.execSync(`kubectl wait --for=condition=Ready serviceinstance/${appName}-db --timeout=120s`)
128
+ } catch (error) {
129
+ const descr = cp.execSync(`kubectl describe serviceinstance/${appName}-db`).toString()
130
+ const message = descr.match(/Message:\s+(.*)/)?.[1] || 'None'
131
+ throw error.message + `\nInstance Message: ${message}`
122
132
  }
123
- }
124
-
125
- console.log(`${bold('Starting deployment to SAP HANA ...')}`);
126
- DEBUG?.({ dest, vcapEnv, hdiOptions })
127
- await hdi.deploy(dest, vcapEnv, hdiOptions);
128
133
 
129
- if (!hasVCAPEnv) {
130
- const bindOptions = {
134
+ const { data: encoded } = JSON.parse(cp.execSync(`kubectl get secrets/${appName}-db-secret -o json`).toString())
135
+ const credentials = {}
136
+ for (const [key, value] of Object.entries(encoded)) {
137
+ credentials[key] = Buffer.from(value, 'base64').toString('utf-8');
138
+ }
139
+ vcapEnv.VCAP_SERVICES ??= {
140
+ hana: [{
141
+ tags: ['hana'],
142
+ credentials
143
+ }]
144
+ }
145
+ bindOptions = {
131
146
  ...options,
132
- to: `${serviceName}:${serviceKeyName}`,
147
+ to: `${appName}-db-binding`,
133
148
  for: options.for,
134
149
  kind: BUILD_TASK_HANA
135
150
  }
136
- const services = await bind(bindOptions);
137
- await storeBindings(services, bindOptions);
151
+ } else {
152
+ let serviceName = pServiceName;
153
+ let serviceKeyName;
154
+ const { instance, key } = cds.requires.db?.binding ?? {};
155
+ if (!serviceName && instance) {
156
+ serviceName = instance;
157
+ serviceKeyName = key;
158
+ }
159
+
160
+ if (!serviceName && hasVCAPEnv) {
161
+ await mkdirp(dest);
162
+ } else {
163
+ const { cfServiceInstanceName, cfServiceInstanceKeyName, serviceKey } = await this._getOrCreateCFService(dest, serviceName, serviceKeyName, tunnelAddress);
164
+ serviceName ??= cfServiceInstanceName;
165
+ serviceKeyName ??= cfServiceInstanceKeyName
166
+ if (!vcapEnv.VCAP_SERVICES || serviceName) {
167
+ vcapEnv.VCAP_SERVICES = this._getVCAPServicesEntry(cfServiceInstanceName, serviceKey);
168
+ }
169
+
170
+ if (storeCredentials) {
171
+ await this._addInstanceToDefaultEnvJson([dest, cds.root], cfServiceInstanceName, serviceKey);
172
+ }
173
+ if (!hasVCAPEnv) {
174
+ bindOptions = {
175
+ ...options,
176
+ to: `${serviceName}:${serviceKeyName}`,
177
+ for: options.for,
178
+ kind: BUILD_TASK_HANA
179
+ }
180
+ }
181
+ }
138
182
  }
139
183
 
184
+ console.log(`${bold('starting deployment to SAP HANA ...')}`);
185
+ DEBUG?.({ dest, vcapEnv, hdiOptions })
186
+ await hdi.deploy(dest, vcapEnv, hdiOptions);
187
+
140
188
  if (storeCredentials) {
141
189
  await gitUtil.ensureFileIsGitignored('default-env.json', cds.root);
142
190
  }
143
191
 
144
- console.log(`If not already done, use ${info('cds add hana')} to configure the project for SAP HANA.\n`);
192
+ if (!hasVCAPEnv) {
193
+ const services = await bind(bindOptions)
194
+ await storeBindings(services, bindOptions)
195
+ }
196
+
197
+ const { env4 } = require('../../init/projectReader')
198
+ if (env4('production').requires?.db?.kind !== 'hana') {
199
+ console.log(`use ${info('cds add hana')} to configure the project for SAP HANA.\n`);
200
+ }
145
201
  }
146
202
 
147
- console.log(`Done.`);
203
+ console.log(`successfully finished deployment`);
148
204
 
149
205
  if (warnings.length > 0) {
150
206
  console.warn(warn('\nWARNING:'));
@@ -156,7 +212,6 @@ Therefore, specifying the CDS model is not required when calling 'cds deploy'.`;
156
212
 
157
213
 
158
214
  async _getOrCreateCFService(currentModelFolder, serviceName, serviceKeyName, tunnelAddress) {
159
- const modelName = path.basename(currentModelFolder);
160
215
 
161
216
  // get from param
162
217
  let cfServiceInstanceMta;
@@ -164,13 +219,18 @@ Therefore, specifying the CDS model is not required when calling 'cds deploy'.`;
164
219
  if (serviceName) {
165
220
  cfServiceInstanceName = serviceName;
166
221
  } else {
222
+ const modelName = path.basename(currentModelFolder);
167
223
  const cfServiceDescriptor = await mtaUtil.getHanaDbModuleDescriptor(cds.root, modelName);
168
224
  cfServiceInstanceName = cfServiceDescriptor.hdiServiceName;
169
225
  cfServiceInstanceMta = cfServiceDescriptor.hdiService
170
226
  }
171
227
 
172
228
  console.log();
173
- this._validateServiceInstanceName(cfServiceInstanceName);
229
+
230
+ // valid service name chars: alpha-numeric, hyphens, and underscores
231
+ if (/[^\w-_]+/g.exec(cfServiceInstanceName)) {
232
+ throw `Service name ${cfServiceInstanceName} must only contain alpha-numeric, hyphens, and underscores.`;
233
+ }
174
234
  console.log(`Using container ${bold(cfServiceInstanceName)}`);
175
235
 
176
236
  const cfConfig = cfServiceInstanceMta?.parameters?.config;
@@ -251,14 +311,6 @@ Therefore, specifying the CDS model is not required when calling 'cds deploy'.`;
251
311
  }
252
312
 
253
313
 
254
- _validateServiceInstanceName(serviceInstanceName) {
255
- // valid service name chars: alpha-numeric, hyphens, and underscores
256
- if (/[^\w-_]+/g.exec(serviceInstanceName)) {
257
- throw `Service name ${serviceInstanceName} must only contain alpha-numeric, hyphens, and underscores.`;
258
- }
259
- }
260
-
261
-
262
314
  _injectTunnelAddress(credentials, tunnelAddress) {
263
315
  if (!/\w+:\d+/.test(tunnelAddress)) {
264
316
  throw `Invalid tunnel address '${tunnelAddress}' - must be in form 'host:port'`;
@@ -153,6 +153,5 @@ function _findHdiResource(mta, moduleName) {
153
153
  }
154
154
 
155
155
  module.exports = {
156
- getHanaDbModuleDescriptor,
157
- MTA_YAML
156
+ getHanaDbModuleDescriptor
158
157
  }
package/lib/env/schema.js CHANGED
@@ -1,69 +1,3 @@
1
-
2
- function _local(id) {
3
- return require.resolve(id, {
4
- paths: [
5
- process.cwd(), // project-local module is preferred
6
- __dirname // otherwise from our own dependencies
7
- ]
8
- })
9
- }
10
-
11
- function _robustRequire(file) {
12
- const fs = require('fs')
13
- const path = require('path')
14
-
15
- file = path.normalize(file)
16
- let content
17
- try {
18
- content = fs.readFileSync(file, 'utf8')
19
- } catch (e) {
20
- // file is not readable -> nothing to do
21
- return
22
- }
23
-
24
- try {
25
- return JSON.parse(content)
26
- } catch (e) {
27
- // invalid json -> ignore and try regex
28
- }
29
-
30
- const result = {}
31
- try {
32
- const deps = JSON.parse(content.match(/"dependencies"\s*:\s*({[^}]*})/)[1])
33
- result.dependencies = deps
34
- } catch (e) { /* ignore */ }
35
-
36
- try {
37
- const devDeps = JSON.parse(content.match(/"devDependencies"\s*:\s*({[^}]*})/)[1])
38
- result.devDependencies = devDeps
39
- } catch (e) { /* ignore */ }
40
-
41
- return result
42
- }
43
-
44
- _robustRequire.resolve = function (id, options = {}) {
45
- const fs = require('fs')
46
- const path = require('path')
47
-
48
- try {
49
- return require.resolve(id, options)
50
- } catch (err) {
51
- // ignore
52
- }
53
-
54
- const [, mod, file] = /(.+)\/([^/]+)$/.exec(id)
55
- let modPath = path.join(process.cwd(), 'node_modules', mod, file)
56
- const ext = path.extname(modPath)
57
- if (!ext) {
58
- modPath = modPath + '.js'
59
- }
60
- if (fs.existsSync(modPath)) {
61
- return modPath
62
- }
63
-
64
- throw new Error('not found')
65
- }
66
-
67
1
  function mergePluginSchema(pluginSchema) {
68
2
  // contribution points also appear in schema file ./schemas/cds-rc.js
69
3
  return {
@@ -140,36 +74,26 @@ module.exports = async function overlay4(schemaName) {
140
74
  }
141
75
 
142
76
  try {
143
- const pluginsLib = require(_local('@sap/cds/lib/plugins.js'))
144
- if (pluginsLib) {
145
- const oldRequire = pluginsLib.require
146
- try {
147
- pluginsLib.require = _robustRequire
148
- const plugins = pluginsLib.fetch?.()
149
- if (plugins) {
150
- for (const [key, { impl, packageJson }] of Object.entries(plugins)) {
151
- const pluginPackageJsonFile = packageJson || path.join(path.dirname(impl), 'package.json')
152
- try {
153
- const pluginPackageJson = require(pluginPackageJsonFile)
154
- if (pluginPackageJson.cds?.schema) {
155
- await mergePluginSchema(pluginPackageJson.cds.schema).into(result);
156
- }
157
- } catch (err) {
158
- if (err.code !== 'ENOENT') {
159
- console.error(`Error loading schema from plugin ${key}: ${err.message}`)
160
- }
161
- }
77
+ const plugins = await cds.plugins;
78
+ if (plugins) {
79
+ for (const [key, { impl }] of Object.entries(plugins)) {
80
+ const pluginPackageJsonFile = path.join(path.dirname(impl), 'package.json')
81
+ try {
82
+ const pluginPackageJson = require(pluginPackageJsonFile)
83
+ if (pluginPackageJson.cds?.schema) {
84
+ await mergePluginSchema(pluginPackageJson.cds.schema).into(result);
85
+ }
86
+ } catch (err) {
87
+ if (err.code !== 'ENOENT') {
88
+ console.error(`Error loading schema from plugin ${key}: ${err.message}`)
162
89
  }
163
90
  }
164
- } finally {
165
- pluginsLib.require = oldRequire
166
91
  }
167
92
  }
168
93
  } catch (err) {
169
94
  console.error(err.message)
170
95
  }
171
96
 
172
-
173
97
  result.title = oldTitle
174
98
  result.description = oldDescription
175
99
  return result
@@ -282,15 +282,14 @@ async function _generateChecksumValidate(dest, output, force) {
282
282
  return "/* checksum : " + currentChecksum + " */\n" + output;
283
283
  }
284
284
 
285
- // Modify the output to replace the using statements with the dependency services
286
- function _replaceUsingStatementsForDependent(output) {
287
- let using = usingStatements.join('\n');
285
+ // Modify the output to add using statements with the dependency services
286
+ function _addUsingStatementsForDependent(output) {
287
+ if (usingStatements.length === 0)
288
+ return output;
289
+
290
+ let usings = usingStatements.join('\n') + '\n';
288
291
  usingStatements = [];
289
- let startIndex = output.indexOf('using');
290
- let endIndex = output.indexOf(';', startIndex);
291
- let usingStatement = output.substring(startIndex, endIndex+1);
292
- output = output.replace(usingStatement, using);
293
- return output;
292
+ return usings + output;
294
293
  }
295
294
 
296
295
  async function _getResult(output, as, dest, force, kind, isDependent) {
@@ -308,11 +307,11 @@ async function _getResult(output, as, dest, force, kind, isDependent) {
308
307
  dest += '.' + extension;
309
308
  }
310
309
 
311
- if (as && as != "csn" && as != "json") {
310
+ if (as && as !== "csn" && as !== "json") {
312
311
  // cdsCompiler[as] will convert the csn output into various output formats
313
312
  output = cdsCompiler[as](output);
314
313
  if (isDependent && kind === 'V4') {
315
- output = _replaceUsingStatementsForDependent(output);
314
+ output = _addUsingStatementsForDependent(output);
316
315
  }
317
316
  }
318
317
 
@@ -400,6 +399,17 @@ async function _updateNodeConfig(dest, services, kind, cwd, options) {
400
399
  }
401
400
  }
402
401
 
402
+ if (kind.startsWith('odata')) {
403
+ pkgConf.dependencies ??= {};
404
+ const { dependencies } = require('./odata.package.json');
405
+ for (const dep in dependencies) {
406
+ if (!pkgConf.dependencies?.[dep]) {
407
+ pkgConf.dependencies[dep] = dependencies[dep];
408
+ package_json_updated = true;
409
+ }
410
+ }
411
+ }
412
+
403
413
  if (package_json_updated) {
404
414
  await write(package_json, JSON.stringify(pkgConf, null, ' '));
405
415
  console.log(`[cds] - updated ./package.json`);
@@ -495,11 +505,9 @@ async function postProcess(filePath, options, csn, cwd) {
495
505
  await write(csnFile, serviceCsn, "utf-8");
496
506
 
497
507
  if (options.isDependency) {
498
- if (services.length > 1){
499
- usingStatements.push(`using { ${services.join(', ')}} from './${path.basename(csnFile)}';`);
500
- } else {
501
- usingStatements.push(`using { ${services} } from './${path.basename(csnFile)}';`);
502
- }
508
+ // `cdsc.to.cdl() adds `using { foo.bar }` on-demand. We only need to include correct path.
509
+ // TODO: Should be part of `csn.requires`, and let ´to.cdl()` render them.
510
+ usingStatements.push(`using from './${path.basename(csnFile.replace(`'`, `''`))}';`);
503
511
  }
504
512
 
505
513
  _printUsageHint(csnFile, services, cwd);
@@ -0,0 +1,7 @@
1
+ {
2
+ "dependencies": {
3
+ "@sap-cloud-sdk/connectivity": "^3",
4
+ "@sap-cloud-sdk/http-client": "^3",
5
+ "@sap-cloud-sdk/resilience": "^3"
6
+ }
7
+ }
@@ -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.8.0',
8
+ MAVEN_ARCHETYPE_VERSION: '3.8.1',
9
9
 
10
10
  OPTIONS: Object.freeze({
11
11
  COMPLETION: 'completion',
@@ -16,6 +16,7 @@ module.exports = {
16
16
  ESM: 'esm',
17
17
  TYPER: 'typer',
18
18
  TYPESCRIPT: 'typescript', // must run before SAMPLE, as that influences whether .js or .ts files are created
19
+ EXTENSION: 'extension',
19
20
  SAMPLES: 'samples', // deprecated
20
21
  TINY_SAMPLE: 'tiny-sample',
21
22
  SAMPLE: 'sample',