@sap/cds-dk 7.5.1 → 7.6.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 (74) hide show
  1. package/bin/add.js +28 -24
  2. package/bin/bind.js +59 -32
  3. package/bin/build.js +1 -1
  4. package/bin/cds.js +1 -1
  5. package/bin/env.js +1 -6
  6. package/bin/import.js +2 -2
  7. package/bin/init.js +7 -3
  8. package/bin/logout.js +2 -2
  9. package/bin/migrate.js +1 -1
  10. package/bin/version.js +1 -0
  11. package/bin/watch.js +8 -1
  12. package/lib/bind/bindingUtil.js +14 -20
  13. package/lib/bind/cfKeyProvider.js +8 -10
  14. package/lib/bind/index.js +4 -2
  15. package/lib/build/buildTaskFactory.js +31 -20
  16. package/lib/build/constants.js +9 -8
  17. package/lib/build/provider/featureToggleBuildPlugin.js +1 -7
  18. package/lib/build/provider/hana/index.js +44 -9
  19. package/lib/build/provider/internalBuildTaskProvider.js +7 -5
  20. package/lib/build/provider/java/index.js +0 -6
  21. package/lib/build/provider/mtx-sidecar/index.js +5 -0
  22. package/lib/build/provider/nodejs/index.js +32 -47
  23. package/lib/build/util.js +134 -33
  24. package/lib/deploy/to-hana/hana.js +47 -77
  25. package/lib/deploy/to-hana/hdiDeployUtil.js +15 -18
  26. package/lib/deploy/to-hana/mtaUtil.js +1 -1
  27. package/lib/env/schema.js +157 -17
  28. package/lib/env/schemas/cds-rc.js +118 -0
  29. package/lib/import/common.js +18 -23
  30. package/lib/import/odata/metadataconverter/MetadataConverter.js +12 -8
  31. package/lib/import/odata/v4parser.js +67 -12
  32. package/lib/init/constants.js +2 -4
  33. package/lib/init/merge.js +2 -1
  34. package/lib/init/template/application-logging/index.js +1 -2
  35. package/lib/init/template/approuter/files/mta.yaml.hbs +2 -0
  36. package/lib/init/template/approuter/files/package.json +1 -1
  37. package/lib/init/template/approuter/files/values.yaml.hbs +8 -1
  38. package/lib/init/template/hana/files/undeploy.json +3 -2
  39. package/lib/init/template/helm/files/chart/templates/NOTES.txt +2 -0
  40. package/lib/init/template/helm/files/chart/templates/_deployment_helpers.tpl +10 -2
  41. package/lib/init/template/helm/files/content-deployment/Chart.yaml +1 -1
  42. package/lib/init/template/helm/files/content-deployment/templates/_helpers.tpl +6 -5
  43. package/lib/init/template/helm/files/content-deployment/values.yaml +1 -1
  44. package/lib/init/template/helm/files/service-instance/Chart.yaml +1 -1
  45. package/lib/init/template/helm/files/service-instance/templates/_helpers.tpl +6 -5
  46. package/lib/init/template/helm/files/service-instance/values.yaml +1 -1
  47. package/lib/init/template/helm/files/web-application/Chart.yaml +1 -1
  48. package/lib/init/template/helm/files/web-application/templates/_api.tpl +6 -5
  49. package/lib/init/template/helm/files/web-application/values.yaml +1 -1
  50. package/lib/init/template/helm/index.js +1 -12
  51. package/lib/init/template/helm-unified-runtime/index.js +3 -2
  52. package/lib/init/template/html5-repo/files/values.yaml.hbs +9 -0
  53. package/lib/init/template/multitenancy/files/package.sidecar.json.hbs +1 -2
  54. package/lib/init/template/multitenancy/index.js +3 -2
  55. package/lib/init/template/xsuaa/files/package.json.hbs +1 -2
  56. package/lib/lint/Linter.js +2 -2
  57. package/lib/lint/checks.js +0 -51
  58. package/lib/mtx/activate.js +1 -1
  59. package/lib/mtx/auth_manager.js +50 -57
  60. package/lib/mtx/download_legacy.js +1 -1
  61. package/lib/mtx/extend.js +2 -2
  62. package/lib/mtx/pull.js +1 -1
  63. package/lib/mtx/push.js +1 -1
  64. package/lib/mtx/settings_manager.js +90 -44
  65. package/lib/mtx/tenant_action.js +1 -1
  66. package/lib/mtx/util/dependencies.js +1 -1
  67. package/lib/mtx/util/legacy_ext.js +0 -1
  68. package/lib/mtx/util/logging.js +10 -8
  69. package/lib/util/axios.js +6 -19
  70. package/lib/util/cf.js +15 -10
  71. package/npm-shrinkwrap.json +104 -48
  72. package/package.json +1 -10
  73. package/lib/env/schemas/cds-rc.json +0 -101
  74. package/lib/init/template/helm-unified-runtime/lib/build.js +0 -0
@@ -1,15 +1,14 @@
1
- const cp = require('child_process');
2
1
  const path = require('path');
3
- const util = require('util');
4
2
 
5
3
  const fs = require('fs').promises;
6
4
 
7
5
  const cds = require('../../../lib/cds');
6
+ const { exists, read } = cds.utils;
8
7
 
9
8
  const { build } = require('../../build');
10
9
  const buildConstants = require('../../build/constants');
11
10
 
12
- const hdiDeployUtil = require('./hdiDeployUtil');
11
+ const hdi = require('./hdiDeployUtil');
13
12
  const cfUtil = require('../../util/cf');
14
13
  const gitUtil = require('../../util/git');
15
14
  const mtaUtil = require('./mtaUtil');
@@ -18,15 +17,9 @@ const { bold, info } = require('../../util/term');
18
17
 
19
18
  const { bind } = require('../../bind');
20
19
 
21
- const UTF_8 = 'utf-8';
20
+ const DEBUG = cds.debug('deploy');
22
21
 
23
-
24
- class HanaDeployer {
25
-
26
- constructor() {
27
- this.execAsync = util.promisify(cp.exec);
28
- this.spawnSync = cp.spawnSync;
29
- }
22
+ module.exports = new class HanaDeployer {
30
23
 
31
24
  getFromEnv(...varNames) {
32
25
  const result = {}
@@ -42,11 +35,8 @@ class HanaDeployer {
42
35
  return result;
43
36
  }
44
37
 
45
- // arg object
46
- // async deploy(model, serviceName, tunnelAddress, buildTaskOptions, vcapFile, undeployWhitelist, hdiOptions, bindCallback) { // NOSONAR
47
- async deploy(options = {}) { // NOSONAR
48
- let { model, serviceName, tunnelAddress, vcapFile, undeployWhitelist, hdiOptions, dry } = options;
49
-
38
+ async deploy(options = {}) {
39
+ let { model, serviceName, tunnelAddress, vcapFile, undeployWhitelist, hdiOptions = {}, dry, storeCredentials } = options;
50
40
  if (model) {
51
41
  throw `Specifying a CDS model for HANA deployment is not supported.
52
42
  'cds deploy --to hana' performs 'cds build --for hana' which in turn uses the configured HANA build task.
@@ -65,15 +55,16 @@ Therefore, specifying the CDS model is not required when calling 'cds deploy'.`;
65
55
 
66
56
  const buildResults = await this._build();
67
57
 
68
- let globalVcapEnv = vcapFile ? await this._loadEnvFile(vcapFile) : {};
69
- globalVcapEnv = { ...globalVcapEnv, ...this.getFromEnv('VCAP_SERVICES', 'SERVICE_REPLACEMENTS') };
58
+ const fromFile = vcapFile && exists(vcapFile) ? await read(vcapFile) : {};
59
+ const fromEnv = this.getFromEnv('VCAP_SERVICES', 'SERVICE_REPLACEMENTS');
60
+ let vcapEnv = { ...fromFile, ...fromEnv };
70
61
  if (process.env.TARGET_CONTAINER) {
71
- globalVcapEnv.TARGET_CONTAINER = process.env.TARGET_CONTAINER;
62
+ vcapEnv.TARGET_CONTAINER = process.env.TARGET_CONTAINER;
72
63
  }
73
64
 
74
65
  if (dry) {
75
- for (const buildResult of buildResults) {
76
- const model = await cds.load(buildResult.task.src);
66
+ for (const { task } of buildResults) {
67
+ const model = await cds.load(task.src);
77
68
  const data = cds.compile.to.hdbtable(model);
78
69
  for (const [src, { file }] of data) {
79
70
  console.log(`-- ${file}`);
@@ -82,49 +73,47 @@ Therefore, specifying the CDS model is not required when calling 'cds deploy'.`;
82
73
  }
83
74
  }
84
75
  } else {
85
- for (const buildResult of buildResults) {
86
- let vcapEnv = globalVcapEnv;
87
- let serviceKeyName;
88
- const currentModelFolder = buildResult.task.dest;
76
+ for (const { task } of buildResults) {
77
+ const dest = task.dest;
89
78
 
90
79
  if (undeployWhitelist) {
91
80
  console.log('Writing undeploy.json');
92
- await fs.write(path.join(currentModelFolder, 'undeploy.json'), JSON.stringify(undeployWhitelist, null, 2));
81
+ await fs.write(path.join(dest, 'undeploy.json'), JSON.stringify(undeployWhitelist, null, 2));
93
82
  }
94
83
 
95
84
  const hasVCAPEnv = Object.keys(vcapEnv).length > 0;
96
85
  if (!serviceName && hasVCAPEnv) {
97
- await fs.mkdir(currentModelFolder, { recursive: true });
98
- } else {
99
- const { cfServiceInstanceName, cfServiceInstanceKeyName, serviceKey } =
100
- await this._getOrCreateCFService(projectPath, currentModelFolder, serviceName, tunnelAddress);
101
- serviceKeyName = cfServiceInstanceKeyName;
102
- serviceName = serviceName || cfServiceInstanceName;
86
+ await fs.mkdir(dest, { recursive: true });
87
+ }
88
+ const { cfServiceInstanceName, cfServiceInstanceKeyName, serviceKey } =
89
+ await this._getOrCreateCFService(projectPath, dest, serviceName, tunnelAddress);
103
90
 
104
- vcapEnv = this._getVCAPServicesEntry(cfServiceInstanceName, serviceKey);
91
+ serviceName ??= cfServiceInstanceName;
92
+ if (!vcapEnv.VCAP_SERVICES || serviceName) {
93
+ vcapEnv.VCAP_SERVICES = this._getVCAPServicesEntry(cfServiceInstanceName, serviceKey);
94
+ }
105
95
 
106
- if (options.storeCredentials) {
107
- await this._addInstanceToDefaultEnvJson([currentModelFolder, projectPath], cfServiceInstanceName, serviceKey);
108
- }
96
+ if (storeCredentials) {
97
+ await this._addInstanceToDefaultEnvJson([dest, projectPath], cfServiceInstanceName, serviceKey);
109
98
  }
110
99
 
111
100
  console.log(`${bold('Starting deployment to SAP HANA ...')}`);
112
101
 
113
- hdiOptions = hdiOptions || {}
114
- await hdiDeployUtil.deploy(currentModelFolder, vcapEnv, hdiOptions);
102
+ DEBUG?.({ dest, vcapEnv, hdiOptions })
103
+ await hdi.deploy(dest, vcapEnv, hdiOptions);
115
104
 
116
105
  if (!hasVCAPEnv) {
117
106
  const bindOptions = {
118
107
  ...options,
119
- to: `${serviceName}:${serviceKeyName}`,
108
+ to: `${serviceName}:${cfServiceInstanceKeyName}`,
120
109
  for: options.for,
121
- kind: buildResult.task.for
110
+ kind: task.for
122
111
  }
123
112
  await bind(bindOptions);
124
113
  }
125
114
  }
126
115
 
127
- if (options.storeCredentials) {
116
+ if (storeCredentials) {
128
117
  await gitUtil.ensureFileIsGitignored('default-env.json', projectPath);
129
118
  }
130
119
 
@@ -155,11 +144,11 @@ Therefore, specifying the CDS model is not required when calling 'cds deploy'.`;
155
144
  this._validateServiceInstanceName(cfServiceInstanceName);
156
145
  console.log(`Using container ${bold(cfServiceInstanceName)}`);
157
146
 
158
- let cfConfig = cfServiceInstanceMta && cfServiceInstanceMta.parameters && cfServiceInstanceMta.parameters.config;
147
+ const cfConfig = cfServiceInstanceMta?.parameters?.config;
159
148
  const serviceInstance = await this.createHanaService(cfServiceInstanceName, cfConfig);
160
149
 
161
150
  const cfServiceInstanceKeyName = `${cfServiceInstanceName}-key`;
162
- let serviceKey = await cfUtil.getOrCreateServiceKey(serviceInstance, cfServiceInstanceKeyName);
151
+ let serviceKey = await cfUtil.getOrCreateServiceKey(serviceInstance, cfServiceInstanceKeyName, { permissions: 'development' });
163
152
  this._validateServiceKey(serviceKey, cfServiceInstanceKeyName);
164
153
 
165
154
  if (tunnelAddress) {
@@ -191,13 +180,17 @@ Therefore, specifying the CDS model is not required when calling 'cds deploy'.`;
191
180
 
192
181
  _validateServiceKey(serviceKey, cfServiceInstanceKey) {
193
182
  if (!serviceKey) {
194
- throw new Error(`Could not create service key ${bold(cfServiceInstanceKey)}.`);
183
+ throw `Could not create service key ${bold(cfServiceInstanceKey)}.`;
184
+ }
185
+
186
+ if (serviceKey['sm_url']) {
187
+ throw `Service key credentials are matching to a Service Manager instance of a multitenant environment. Make sure there is no conflict with ${cfServiceInstanceKey}.`;
195
188
  }
196
189
 
197
190
  const fields = ['schema', 'user', 'password', 'url'];
198
191
  for (const field of fields) {
199
192
  if (!serviceKey[field]) {
200
- throw new Error(`Service key is missing mandatory field '${field}'. Make sure you are ${bold('not')} using a managed service.`);
193
+ throw `Service key is missing mandatory field '${field}'. Make sure you are ${bold('not')} using a managed service.`;
201
194
  }
202
195
  }
203
196
  }
@@ -209,40 +202,21 @@ Therefore, specifying the CDS model is not required when calling 'cds deploy'.`;
209
202
  }
210
203
 
211
204
  console.log(`Running build`);
212
- const buildResults = await build(buildTaskOptions);
213
- return buildResults;
205
+ return build(buildTaskOptions);
214
206
  }
215
207
 
216
- async _loadEnvFile(defaultEnvFile) {
217
- try {
218
- const content = await fs.readFile(defaultEnvFile);
219
- return JSON.parse(content);
220
- } catch (err) {
221
- throw new Error(`Error reading default env file: ${err.message}`);
222
- }
223
- }
224
-
225
-
226
208
  async _addInstanceToDefaultEnvJson(currentFolders, serviceInstanceName, serviceKey) {
227
209
  for (const currentFolder of currentFolders) {
228
- let defaultEnvJson = {};
229
210
  const defaultEnvJsonPath = path.join(currentFolder, 'default-env.json');
211
+ const defaultEnvJson = exists(defaultEnvJsonPath) ? await read(defaultEnvJsonPath) : {};
230
212
 
231
- try {
232
- defaultEnvJson = JSON.parse(await fs.readFile(defaultEnvJsonPath, UTF_8));
233
- } catch (err) {
234
- // ignore any errors
235
- }
236
-
237
- defaultEnvJson.VCAP_SERVICES = defaultEnvJson.VCAP_SERVICES || {};
238
- for (const serviceKey of Object.keys(defaultEnvJson.VCAP_SERVICES)) {
239
- defaultEnvJson.VCAP_SERVICES[serviceKey] = defaultEnvJson.VCAP_SERVICES[serviceKey].filter((service) => {
240
- return (service.name !== serviceInstanceName);
241
- });
213
+ defaultEnvJson.VCAP_SERVICES ??= {};
214
+ for (const [serviceKey, services] of Object.entries(defaultEnvJson.VCAP_SERVICES)) {
215
+ defaultEnvJson.VCAP_SERVICES[serviceKey] = services.filter(service => service.name !== serviceInstanceName);
242
216
  }
243
217
 
244
218
  const hanaEntry = this._getVCAPServicesEntry(serviceInstanceName, serviceKey)
245
- Object.assign(defaultEnvJson, hanaEntry);
219
+ Object.assign(defaultEnvJson.VCAP_SERVICES, hanaEntry);
246
220
 
247
221
  console.log(`Writing ${defaultEnvJsonPath}`);
248
222
  await fs.mkdir(path.dirname(defaultEnvJsonPath), { recursive: true })
@@ -253,15 +227,13 @@ Therefore, specifying the CDS model is not required when calling 'cds deploy'.`;
253
227
 
254
228
  _getVCAPServicesEntry(serviceInstanceName, serviceKey) {
255
229
  return {
256
- VCAP_SERVICES: {
257
230
  hana: [
258
231
  {
259
232
  name: serviceInstanceName,
260
233
  tags: ['hana'],
261
234
  credentials: serviceKey
262
235
  }
263
- ]
264
- }
236
+ ]
265
237
  };
266
238
  }
267
239
 
@@ -269,14 +241,14 @@ Therefore, specifying the CDS model is not required when calling 'cds deploy'.`;
269
241
  _validateServiceInstanceName(serviceInstanceName) {
270
242
  // valid service name chars: alpha-numeric, hyphens, and underscores
271
243
  if (/[^\w-_]+/g.exec(serviceInstanceName)) {
272
- throw new Error(`Service name ${serviceInstanceName} must only contain alpha-numeric, hyphens, and underscores.`);
244
+ throw `Service name ${serviceInstanceName} must only contain alpha-numeric, hyphens, and underscores.`;
273
245
  }
274
246
  }
275
247
 
276
248
 
277
249
  _injectTunnelAddress(serviceKey, tunnelAddress) {
278
250
  if (!/\w+:\d+/.test(tunnelAddress)) {
279
- throw new Error(`Invalid tunnel address '${tunnelAddress}' - must be in form 'host:port'`)
251
+ throw `Invalid tunnel address '${tunnelAddress}' - must be in form 'host:port'`;
280
252
  }
281
253
  const [tunnelHost, tunnelPort] = tunnelAddress.split(':')
282
254
  const { host, port } = serviceKey
@@ -288,5 +260,3 @@ Therefore, specifying the CDS model is not required when calling 'cds deploy'.`;
288
260
  return serviceKey
289
261
  }
290
262
  }
291
-
292
- module.exports = new HanaDeployer();
@@ -1,22 +1,21 @@
1
1
  const path = require('path');
2
2
  const util = require('util');
3
+ const { bold } = require('../../util/term');
3
4
 
4
5
  const HDI_DEPLOY_LIB = '@sap/hdi-deploy';
5
6
 
6
- class HdiDeployUtil {
7
+ module.exports = new class HdiDeployUtil {
7
8
 
8
- async deploy(dbDir, vcapEnv = {}, options = {}) {
9
+ async deploy(dbDir, vcapEnv = {}, { autoUndeploy } = {}) {
9
10
  console.log(`Deploying to HANA from ${dbDir}`);
10
11
 
11
- const hdiDeployLib = await this._loadHdiDeployLib(dbDir);
12
+ const { clean_env, deploy } = this._loadHdiDeployLib(dbDir);
12
13
 
13
- let deployerEnv = JSON.parse(JSON.stringify(process.env));
14
+ const deployerEnv = JSON.parse(JSON.stringify(process.env));
14
15
  const hdiDeployOptions = deployerEnv.HDI_DEPLOY_OPTIONS ? JSON.parse(deployerEnv.HDI_DEPLOY_OPTIONS) : {};
15
16
  delete hdiDeployOptions.root;
16
17
 
17
- if (typeof hdiDeployLib.clean_env === 'function') {
18
- deployerEnv = hdiDeployLib.clean_env(deployerEnv);
19
- }
18
+ clean_env?.(deployerEnv);
20
19
 
21
20
  if (vcapEnv.VCAP_SERVICES) {
22
21
  deployerEnv.VCAP_SERVICES = JSON.stringify(vcapEnv.VCAP_SERVICES);
@@ -28,8 +27,8 @@ class HdiDeployUtil {
28
27
  deployerEnv.TARGET_CONTAINER = vcapEnv.TARGET_CONTAINER;
29
28
  }
30
29
 
31
- if (options.autoUndeploy) {
32
- console.log(`Hdi deployer automatically undeploys deleted resources using --auto-undeploy.`);
30
+ if (autoUndeploy) {
31
+ console.log(`HDI deployer automatically undeploys deleted resources using --auto-undeploy.`);
33
32
  hdiDeployOptions.auto_undeploy = true;
34
33
  }
35
34
 
@@ -37,12 +36,12 @@ class HdiDeployUtil {
37
36
  deployerEnv.HDI_DEPLOY_OPTIONS = JSON.stringify(hdiDeployOptions);
38
37
  }
39
38
 
40
- const asyncDeploy = util.promisify(hdiDeployLib.deploy);
41
- const response = await asyncDeploy(dbDir, deployerEnv);
42
- if (response?.exitCode) {
43
- let message = `HDI deployment failed with exit code ${response.exitCode}`;
44
- if (response.signal) {
45
- message += `. ${response.signal}`;
39
+ const asyncDeploy = util.promisify(deploy);
40
+ const { exitCode, signal } = await asyncDeploy(dbDir, deployerEnv);
41
+ if (exitCode) {
42
+ let message = `HDI deployment failed with exit code ${exitCode}`;
43
+ if (signal) {
44
+ message += `. ${signal}`;
46
45
  }
47
46
  throw new Error(message);
48
47
  }
@@ -55,7 +54,7 @@ class HdiDeployUtil {
55
54
  if (!libPath) {
56
55
  throw new Error(`Required library '${HDI_DEPLOY_LIB}' not found in
57
56
  ${searchPaths.join('\n ')}
58
- Add it either as a devDependency using 'npm install -D ${HDI_DEPLOY_LIB}' or install it globally using 'npm install -g ${HDI_DEPLOY_LIB}'.`);
57
+ Add it either to ${bold('devDependencies')} using 'npm install -D ${HDI_DEPLOY_LIB}' or install it globally using 'npm install -g ${HDI_DEPLOY_LIB}'.`);
59
58
  }
60
59
 
61
60
  console.log(`Using HDI deployer from ${libPath}`)
@@ -64,5 +63,3 @@ Add it either as a devDependency using 'npm install -D ${HDI_DEPLOY_LIB}' or ins
64
63
  return require(libPath);
65
64
  }
66
65
  }
67
-
68
- module.exports = new HdiDeployUtil();
@@ -98,7 +98,7 @@ async function _getProjectInfo(projectPath, mta) {
98
98
  }
99
99
 
100
100
  async function _getMta(projectPath) {
101
- // yaml.parse oesn't like null
101
+ // yaml.parse doesn't like null
102
102
  const mtaFilePath = path.join(projectPath, MTA_YAML)
103
103
 
104
104
  const existsMtaYaml = existsSync(mtaFilePath)
package/lib/env/schema.js CHANGED
@@ -1,35 +1,175 @@
1
- const { merge } = require('../init/merge')
2
- const { readJSON } = require('../util/fs')
3
1
 
4
- module.exports = async function overlay4(schemaName, cwd = process.cwd()) {
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
+ function mergePluginSchema(pluginSchema) {
68
+ return {
69
+ into: async (targetSchema) => {
70
+ if (pluginSchema.buildTaskType) {
71
+ targetSchema.$defs?.buildTaskType?.enum?.push(pluginSchema.buildTaskType.name);
72
+ targetSchema.$defs?.buildTaskType?.enumDescriptions?.push(pluginSchema.buildTaskType.description);
73
+ targetSchema._cds_schema_overlays.push('buildTaskType')
74
+ }
75
+
76
+ if (pluginSchema.databaseType) {
77
+ targetSchema.$defs?.databaseType?.enum?.push(pluginSchema.databaseType.name);
78
+ targetSchema.$defs?.databaseType?.enumDescriptions?.push(pluginSchema.databaseType.description);
79
+ targetSchema._cds_schema_overlays.push('databaseType')
80
+ }
81
+
82
+ if (pluginSchema.cds) {
83
+ if (targetSchema.$defs?.cdsRoot?.properties) {
84
+ Object.assign(targetSchema.$defs.cdsRoot.properties, pluginSchema.cds)
85
+ targetSchema._cds_schema_overlays.push('cdsRoot')
86
+ }
87
+
88
+ // backwards compatibility
89
+ if (targetSchema.properties) {
90
+ Object.assign(targetSchema.properties, pluginSchema.cds)
91
+ targetSchema._cds_schema_overlays.push('root')
92
+ }
93
+ }
94
+ }
95
+ }
96
+ }
97
+
98
+ module.exports = async function overlay4(schemaName) {
99
+ const path = require('path')
100
+ const { merge } = require('../init/merge')
5
101
 
6
- // need to load @sap/cds from current cwd which might
7
- // differ from initial cwd to get project specific cds version
8
- const cds = require(require.resolve('@sap/cds', {
9
- paths: [cwd, __dirname]
10
- }))
102
+ const cds = require('../cds')
103
+ cds.root = process.cwd()
11
104
 
12
- // force cds.root to be project root
13
- cds.root = cwd
105
+ const { readJSON } = require('../util/fs')
14
106
 
15
- // load defaults, throw err if schema not found
16
107
  let result;
17
108
  if (typeof cds.schema?.default4 === 'function') {
18
109
  result = await cds.schema.default4(schemaName)
19
110
  } else if (cds.env?.schemas?.[schemaName]) {
20
- // backwards compatibility
111
+ // backwards compatibility, fails for invalid json files
21
112
  result = await readJSON(cds.env.schemas[schemaName])
22
113
  } else {
23
- throw new Error(`cds ${cds.version} in ${cwd} does not support schema retrieval.`)
114
+ throw new Error(`cds ${cds.version} in ${cds.root} does not support schema retrieval.`)
115
+ }
116
+
117
+ if (schemaName !== 'cds-rc.json') {
118
+ return result
119
+ }
120
+
121
+ const oldTitle = result.title
122
+ const oldDescription = result.description
123
+ result._cds_schema_overlays = []
124
+
125
+ try {
126
+ const scheamFile = path.join(__dirname, 'schemas', schemaName.replace(/\.json$/, '.js'))
127
+ const schema = require(scheamFile)
128
+
129
+ await merge(schema).into(result)
130
+ result._cds_schema_overlays.push('cdsRoot')
131
+
132
+ if (result.properties) {
133
+ // backwards compatibility
134
+ await merge(schema.$defs.cdsRoot.properties).into(result.properties)
135
+ result._cds_schema_overlays.push('root')
136
+ }
137
+ } catch (err) {
138
+ console.error(err.message)
24
139
  }
25
140
 
26
- // optional, don't throw if file not found
27
141
  try {
28
- await merge(__dirname, 'schemas', schemaName).into(result)
29
- result.description = result.description + ' - overlaid'
142
+ const pluginsLib = require(_local('@sap/cds/lib/plugins.js'))
143
+ if (pluginsLib) {
144
+ const oldRequire = pluginsLib.require
145
+ try {
146
+ pluginsLib.require = _robustRequire
147
+ const plugins = pluginsLib.fetch?.()
148
+ if (plugins) {
149
+ for (const [key, { impl, packageJson }] of Object.entries(plugins)) {
150
+ const pluginPackageJsonFile = packageJson || path.join(path.dirname(impl), 'package.json')
151
+ try {
152
+ const pluginPackageJson = require(pluginPackageJsonFile)
153
+ if (pluginPackageJson.cds?.schema) {
154
+ await mergePluginSchema(pluginPackageJson.cds.schema).into(result);
155
+ }
156
+ } catch (err) {
157
+ if (err.code !== 'ENOENT') {
158
+ console.error(`Error loading schema from plugin ${key}: ${err.message}`)
159
+ }
160
+ }
161
+ }
162
+ }
163
+ } finally {
164
+ pluginsLib.require = oldRequire
165
+ }
166
+ }
30
167
  } catch (err) {
31
- if (err.code !== 'ENOENT') throw err
168
+ console.error(err.message)
32
169
  }
33
170
 
171
+
172
+ result.title = oldTitle
173
+ result.description = oldDescription
34
174
  return result
35
175
  }
@@ -0,0 +1,118 @@
1
+ module.exports = {
2
+ title: "JSON schema for CDS-DK configuration",
3
+ $schema: "https://json-schema.org/draft/2020-12/schema",
4
+ description: "Build overlay",
5
+ $defs: {
6
+ cdsRoot: {
7
+ properties: {
8
+ // build specific schema
9
+ build: {
10
+ type: "object",
11
+ description: "Customize CDS build settings.",
12
+ additionalProperties: true,
13
+ properties: {
14
+ target: {
15
+ type: "string",
16
+ description: "Specify the folder which contains generated build results. Defaults to './gen'.",
17
+ format: "uri-reference"
18
+ },
19
+ tasks: {
20
+ type: "array",
21
+ description: "Control which tasks 'cds build' will execute on your project modules. This is a typical task configuration: '\"tasks\": [{ \"for\": \"hana\"}];",
22
+ uniqueItems: true,
23
+ default: [
24
+ {}
25
+ ],
26
+ items: {
27
+ type: "object",
28
+ additionalProperties: true,
29
+ properties: {
30
+ dest: {
31
+ type: "string",
32
+ description: "Optional: Define the destination folder for this build task, relative to build target folder. \nFor example: Destination defined as 'custom' results in an output written to 'gen/custom'.",
33
+ format: "uri-reference"
34
+ },
35
+ src: {
36
+ type: "string",
37
+ description: "Optional: Prefer defining 'cds.folders'. Use 'src' if you want it only to be valid for this specific build task, which excludes, for example, 'cds watch'.",
38
+ format: "uri-reference"
39
+ },
40
+ for: {
41
+ type: "string",
42
+ description: "Define the target technology for this build task.",
43
+ minLength: 1,
44
+ $ref: "#/$defs/buildTaskType"
45
+ },
46
+ options: {
47
+ type: "object",
48
+ description: "Optional: Set target technology specific options for this build task.",
49
+ additionalProperties: true,
50
+ properties: {
51
+ model: {
52
+ $ref: "#/$defs/foldersStringArray"
53
+ }
54
+ }
55
+ }
56
+ }
57
+ }
58
+ }
59
+ }
60
+ },
61
+
62
+ // deploy specific schema
63
+ deploy: {
64
+ type: "object",
65
+ description: "Customize 'cds deploy' settings.",
66
+ additionalProperties: true,
67
+ properties: {
68
+ no_save: {
69
+ type: "boolean",
70
+ description: "Do / don't update configuration in package.json.",
71
+ default: true
72
+ }
73
+ }
74
+ }
75
+ }
76
+ },
77
+
78
+ buildTaskType: {
79
+ enum: [
80
+ "nodejs",
81
+ "java",
82
+ "hana",
83
+ "mtx-sidecar",
84
+ "mtx-extension",
85
+ "mtx",
86
+ "node-cf",
87
+ "java-cf"
88
+ ],
89
+ enumDescriptions: [
90
+ "Node.js build plugin",
91
+ "Java build plugin",
92
+ "SAP HANA build plugin",
93
+ "MTX sidecar build plugin",
94
+ "MTX extension build plugin",
95
+ "MTX build plugin",
96
+ "Node.js Cloud Foundry build plugin",
97
+ "Java Cloud Foundry build plugin"
98
+ ]
99
+ },
100
+
101
+ foldersStringArray: {
102
+ oneOf: [
103
+ {
104
+ type: "string",
105
+ format: "uri-reference"
106
+ },
107
+ {
108
+ type: "array",
109
+ uniqueItems: true,
110
+ items: {
111
+ type: "string",
112
+ format: "uri-reference"
113
+ }
114
+ }
115
+ ]
116
+ }
117
+ }
118
+ }