@sap/cds-dk 7.3.0 → 7.3.2

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 (88) hide show
  1. package/bin/add.js +3 -1
  2. package/bin/build.js +2 -1
  3. package/bin/cds.js +1 -1
  4. package/bin/compile/index.js +11 -0
  5. package/bin/extend.js +5 -1
  6. package/bin/init.js +31 -37
  7. package/bin/repl.js +23 -43
  8. package/bin/watch.js +5 -9
  9. package/lib/bind/bindingResolver.js +2 -1
  10. package/lib/bind/index.js +1 -1
  11. package/lib/build/buildPlugin.js +18 -3
  12. package/lib/build/buildTaskEngine.js +2 -1
  13. package/lib/build/buildTaskFactory.js +33 -9
  14. package/lib/build/buildTaskProvider.js +1 -1
  15. package/lib/build/buildTaskProviderFactory.js +15 -61
  16. package/lib/build/constants.js +2 -0
  17. package/lib/build/index.js +5 -2
  18. package/lib/build/plugins.js +35 -0
  19. package/lib/build/provider/edmxBuildPlugin.js +9 -1
  20. package/lib/build/provider/featureToggleBuildPlugin.js +2 -3
  21. package/lib/build/provider/internalBuildPlugin.js +4 -0
  22. package/lib/build/provider/internalBuildTaskProvider.js +1 -1
  23. package/lib/build/provider/java/index.js +12 -6
  24. package/lib/build/provider/nodejs/index.js +3 -2
  25. package/lib/build/util.js +5 -0
  26. package/lib/client/base_command.js +8 -18
  27. package/lib/client/download_legacy.js +2 -10
  28. package/lib/client/params.js +6 -4
  29. package/lib/client/settings_manager.js +38 -35
  30. package/lib/compile/openapi/csdl2openapi.js +1 -1
  31. package/lib/deploy/to-hana/hana.js +53 -35
  32. package/lib/index.js +7 -1
  33. package/lib/init/constants.js +4 -2
  34. package/lib/init/template/_merging/registry-mta.js +12 -0
  35. package/lib/init/template/enterprise-messaging/files/event-mesh.json.hbs +1 -1
  36. package/lib/init/template/enterprise-messaging/index.js +1 -1
  37. package/lib/init/template/enterprise-messaging-shared/files/event-mesh.json.hbs +1 -1
  38. package/lib/init/template/enterprise-messaging-shared/index.js +19 -5
  39. package/lib/init/template/helm/index.js +1 -1
  40. package/lib/init/template/helm/subcharts/content-deployment/Chart.yaml +4 -4
  41. package/lib/init/template/helm/subcharts/content-deployment/templates/_helpers.tpl +4 -2
  42. package/lib/init/template/helm/subcharts/content-deployment/templates/job.yaml +4 -3
  43. package/lib/init/template/helm/subcharts/content-deployment/values.schema.json +9 -83
  44. package/lib/init/template/helm/subcharts/content-deployment/values.yaml +54 -15
  45. package/lib/init/template/helm/subcharts/service-instance/Chart.yaml +4 -4
  46. package/lib/init/template/helm/subcharts/service-instance/templates/serviceinstance.yaml +1 -1
  47. package/lib/init/template/helm/subcharts/service-instance/values.schema.json +0 -17
  48. package/lib/init/template/helm/subcharts/service-instance/values.yaml +28 -0
  49. package/lib/init/template/helm/subcharts/web-application/Chart.yaml +5 -5
  50. package/lib/init/template/helm/subcharts/web-application/templates/_api.tpl +60 -0
  51. package/lib/init/template/helm/subcharts/web-application/templates/_helpers.tpl +34 -73
  52. package/lib/init/template/helm/subcharts/web-application/templates/api-rule.yaml +4 -4
  53. package/lib/init/template/helm/subcharts/web-application/templates/deployment.yaml +96 -17
  54. package/lib/init/template/helm/subcharts/web-application/templates/network-policy.yaml +19 -3
  55. package/lib/init/template/helm/subcharts/web-application/templates/pod-disruption-budget.yaml +3 -3
  56. package/lib/init/template/helm/subcharts/web-application/templates/rbac.yaml +26 -0
  57. package/lib/init/template/helm/subcharts/web-application/templates/secret.yaml +1 -1
  58. package/lib/init/template/helm/subcharts/web-application/templates/service-account.yaml +1 -1
  59. package/lib/init/template/helm/subcharts/web-application/templates/service-binding.yaml +1 -1
  60. package/lib/init/template/helm/subcharts/web-application/templates/service.yaml +21 -3
  61. package/lib/init/template/helm/subcharts/web-application/templates/virtual-service.yaml +36 -0
  62. package/lib/init/template/helm/subcharts/web-application/values.schema.json +535 -142
  63. package/lib/init/template/helm/subcharts/web-application/values.yaml +168 -44
  64. package/lib/init/template/local-messaging/files/package.json.hbs +15 -0
  65. package/lib/init/template/local-messaging/index.js +25 -0
  66. package/lib/init/template/mta/index.js +2 -2
  67. package/lib/init/template/nodejs/files/.gitignore.hbs +3 -0
  68. package/lib/init/template/nodejs/files/.vscode/launch.json +0 -3
  69. package/lib/init/template/notifications/files/mta.yaml.hbs +17 -0
  70. package/lib/init/template/notifications/files/package.json.hbs +5 -0
  71. package/lib/init/template/notifications/index.js +43 -0
  72. package/lib/init/template/typer/files/package.json +1 -1
  73. package/lib/init/util/projectReader.js +1 -0
  74. package/lib/util/pruneAxiosErrors.js +4 -1
  75. package/lib/util/term.js +1 -2
  76. package/lib/watch/livereload-server.js +38 -2
  77. package/lib/watch/node-watch.js +3 -2
  78. package/npm-shrinkwrap.json +438 -2205
  79. package/package.json +2 -2
  80. package/lib/init/template/helm/subcharts/content-deployment/templating.md +0 -37
  81. package/lib/init/template/helm/subcharts/content-deployment/values-lint.yaml +0 -5
  82. package/lib/init/template/helm/subcharts/content-deployment/values.md +0 -1011
  83. package/lib/init/template/helm/subcharts/service-instance/templating.md +0 -18
  84. package/lib/init/template/helm/subcharts/service-instance/values-lint.yaml +0 -2
  85. package/lib/init/template/helm/subcharts/service-instance/values.md +0 -186
  86. package/lib/init/template/helm/subcharts/web-application/templating.md +0 -51
  87. package/lib/init/template/helm/subcharts/web-application/values-lint.yaml +0 -8
  88. package/lib/init/template/helm/subcharts/web-application/values.md +0 -1363
@@ -1,11 +1,10 @@
1
1
  const fs = require('fs')
2
2
  const cds = require('../cds')
3
3
  const BuildTaskEngine = require('./buildTaskEngine')
4
- const BuildTaskFactory = require('./buildTaskFactory')
5
4
  const BuildPlugin = require('./buildPlugin')
6
5
  const { BuildError } = require('./util')
7
6
 
8
- module.exports = { build, BuildTaskFactory, BuildTaskEngine, BuildPlugin, BuildError }
7
+ module.exports = { build, register, BuildPlugin }
9
8
 
10
9
  /**
11
10
  * Executes cds build in the directory defined by cds.root.
@@ -28,4 +27,8 @@ async function build(options = {}) {
28
27
  }
29
28
  throw e
30
29
  }
30
+ }
31
+
32
+ function register(id, options) {
33
+ return require('./plugins').register(id, options)
31
34
  }
@@ -0,0 +1,35 @@
1
+ const cds = require('../cds')
2
+
3
+ module.exports = new (class BuildPlugins {
4
+ constructor() {
5
+ this._plugins = new Map()
6
+ }
7
+ getPlugins() {
8
+ return this._plugins
9
+ }
10
+ getPlugin(id) {
11
+ return this._plugins.get(id)
12
+ }
13
+ register(id, options) {
14
+ if (!id || !options?.impl) {
15
+ throw Error("Invalid build plugin parameter")
16
+ }
17
+ if (this._plugins.has(id)) {
18
+ throw Error(`Build plugin ${id} already registered`)
19
+ }
20
+ this._plugins.set(id, {
21
+ id,
22
+ class: this._loadPlugin(id, options.impl),
23
+ description: options.description,
24
+ taskDefaults: options.taskDefaults ?? {}
25
+ })
26
+ }
27
+ _loadPlugin(id, impl) {
28
+ try {
29
+ // project-local module is preferred - otherwise from our own dependencies
30
+ return require(require.resolve(impl, { paths: [cds.root, __dirname] }))
31
+ } catch (e) {
32
+ throw new Error(`Custom build plugin implementation '${impl}' for '${id}' cannot be loaded:\n` + e)
33
+ }
34
+ }
35
+ })
@@ -51,7 +51,15 @@ class EdmxBuildPlugin extends FeatureToggleBuildPlugin {
51
51
  }
52
52
 
53
53
  _isOdataProtocol(srv) {
54
- return cds.service.protocols.protocol4(srv).startsWith('odata')
54
+ let protocols = cds.service.protocols4?.(srv)
55
+ if (!protocols) {
56
+ // compatibility with @sap/cds < 7.3.0
57
+ protocols = cds.service.protocols.protocol4(srv)
58
+ if (typeof protocols === "string") {
59
+ protocols = [protocols]
60
+ }
61
+ }
62
+ return protocols.some(p => p && p.startsWith('odata'))
55
63
  }
56
64
 
57
65
  _options4odata() {
@@ -124,9 +124,8 @@ class FeatureToggleBuildPlugin extends InternalBuildPlugin {
124
124
 
125
125
  _validateFeature(ftCsn, baseCsn) {
126
126
  // features must not have other dependencies than base model dependencies - cross feature dependencies are not supported
127
- // do not pass options.messages in order to avoid duplicate compiler messages
128
- // version 2.13.9 of compileSources does not read the requires definition of parsed CSNs
129
- cds.compiler.compileSources({ '../..csn.json': baseCsn, 'feature.csn': ftCsn }, this.options())
127
+ // assuming only core compiler options are relevant for validation scenario
128
+ cds.compiler.compileSources({ [ftCsn.requires[0]]: baseCsn, 'feature.csn': ftCsn }, { ...super.options(), ...cds.env.cdsc ?? {}})
130
129
  }
131
130
  }
132
131
 
@@ -22,6 +22,10 @@ class InternalBuildPlugin extends BuildPlugin {
22
22
  return InternalBuildPlugin.PRIORITY_MAX_VALUE
23
23
  }
24
24
 
25
+ init() {
26
+ // REVISIT: no default gen folder for now
27
+ }
28
+
25
29
  async clean() {
26
30
  // build results are deleted by the BuildTaskEngine if the build.target !== '.'
27
31
  // make sure that src is not a subfolder of dest
@@ -43,7 +43,7 @@ Upgrade to @sap/cds-mtxs following the migration guide https://cap.cloud.sap/doc
43
43
  InternalBuildTaskProvider._setDefaultModel(task)
44
44
  }
45
45
 
46
- loadPlugin(task) {
46
+ getPluginClass(task) {
47
47
  return require(`./${InternalBuildTaskProvider._getForValueFromTask(task)}`)
48
48
  }
49
49
 
@@ -2,8 +2,8 @@ const fs = require('fs')
2
2
  const path = require('path')
3
3
  const cds = require('../../../cds')
4
4
  const EdmxBuildPlugin = require('../edmxBuildPlugin')
5
- const { OUTPUT_MODE, OUTPUT_MODE_RESULT, FILE_EXT_CDS, CONTENT_LANGUAGE_BUNDLES, CONTENT_DEFAULT_CSN, DEFAULT_CSN_FILE_NAME,
6
- OUTPUT_MODE_FILESYSTEM, FLAVOR_LOCALIZED_EDMX } = require('../../constants')
5
+ const { OUTPUT_MODE, OUTPUT_MODE_RESULT, FILE_EXT_CDS, CONTENT_LANGUAGE_BUNDLES, CONTENT_DEFAULT_CSN, DEFAULT_CSN_FILE_NAME,
6
+ OUTPUT_MODE_FILESYSTEM, FLAVOR_LOCALIZED_EDMX, CONTENT_EDMX, CONTENT_LOCALIZED_EDMX } = require('../../constants')
7
7
  const { getI18nDefaultFolder } = require('../../util')
8
8
  const DEFAULT_COMPILE_DEST_FOLDER = path.normalize('src/main/resources/edmx')
9
9
 
@@ -20,11 +20,17 @@ class JavaBuildPlugin extends EdmxBuildPlugin {
20
20
  if (!model) {
21
21
  return
22
22
  }
23
-
24
- // generate edmx files containing all features
25
23
  const odata = await this._compileForOdata(model, compileDest)
26
- await this.compileToEdmx(odata, compileDest) // localized edmx for backward compatibility
27
- await this.compileToEdmx(odata, path.join(compileDest, 'odata', cds.env.odata.version), { [FLAVOR_LOCALIZED_EDMX]: false }) // non-localized edmx
24
+
25
+ if (!this.hasBuildOption(CONTENT_EDMX, false)) {
26
+ // generate edmx files containing all features
27
+ // localized edmx for backward compatibility
28
+ if (!this.hasBuildOption(CONTENT_LOCALIZED_EDMX, false)) {
29
+ await this.compileToEdmx(odata, compileDest, { [FLAVOR_LOCALIZED_EDMX]: true })
30
+ }
31
+ // non-localized edmx
32
+ await this.compileToEdmx(odata, path.join(compileDest, 'odata', cds.env.odata.version), { [FLAVOR_LOCALIZED_EDMX]: false })
33
+ }
28
34
 
29
35
  if (!this.hasBuildOption(CONTENT_LANGUAGE_BUNDLES, false)) {
30
36
  // collect and write language bundles containing all features
@@ -44,9 +44,10 @@ class NodejsBuildPlugin extends EdmxBuildPlugin {
44
44
  await this.collectAllLanguageBundles(dictionary, sources, destSrv, destRoot)
45
45
 
46
46
  if (!this.hasBuildOption(CONTENT_EDMX, false)) {
47
- //REVISIT: inferred model expected by cds.compile.to.edmx()
48
47
  const compileOptions = { [FLAVOR_LOCALIZED_EDMX]: this.hasBuildOption(FLAVOR_LOCALIZED_EDMX, true) }
49
- const baseModel = cds.compiler.compileSources({ 'base.json': dictionary.base }, { messages: this.messages })
48
+ // inferred flavor is required by edmx compiler backend
49
+ // using cds.compile instead of cds.compiler.compileSources ensures that cds.env options are correctly read
50
+ const baseModel = dictionary.base.meta.flavor !== 'inferred' ? await cds.compile(sources.base, super.options(), 'inferred') : dictionary.base
50
51
  await this.compileToEdmx(baseModel, path.join(this.destSrv, 'odata', cds.env.odata.version), compileOptions)
51
52
  }
52
53
 
package/lib/build/util.js CHANGED
@@ -208,6 +208,10 @@ function _locationString(loc) {
208
208
  : `${loc.file}:${loc.line}.${loc.col}-${loc.endLine}.${loc.endCol}`;
209
209
  }
210
210
 
211
+ async function pathExists(path) {
212
+ return fs.promises.access(path).then(() => true).catch(() => false);
213
+ }
214
+
211
215
  /**
212
216
  * Class for individual build message.
213
217
  *
@@ -271,6 +275,7 @@ module.exports = {
271
275
  getI18nDefaultFolder,
272
276
  isClassicMtx,
273
277
  normalizePath,
278
+ pathExists,
274
279
  BuildMessage,
275
280
  BuildError
276
281
  }
@@ -38,27 +38,17 @@ module.exports = class BaseCommand {
38
38
  }
39
39
  }
40
40
 
41
- static concatUrls(base, ...paths) {
42
- function trimBeginSlash(str) {
43
- return str.replace(/^\//, '');
44
- }
45
- function trimEndSlash(str) {
46
- return str.replace(/\/$/, '');
47
- }
48
- function trimSlashes(str) {
49
- return trimBeginSlash(trimEndSlash(str));
50
- }
51
- const concatenated = new URL(base);
52
- concatenated.pathname = trimEndSlash(concatenated.pathname) + '/' +
53
- paths.map((p, i) => i < paths.length - 1 ? trimSlashes(p) : trimBeginSlash(p)).join('/');
54
- return concatenated.toString();
41
+ static concatUrls(...parts) {
42
+ return parts
43
+ .map((p, i) => i === 0
44
+ ? p.replace(/\/$/ /*end slash*/, '')
45
+ : i === parts.length - 1
46
+ ? p.replace(/^\// /*begin slash*/, '')
47
+ : p.replace(/^\/|\/$/g /*both*/, ''))
48
+ .join('/');
55
49
  }
56
50
 
57
51
  static getMtxApiUrl(url, action) {
58
52
  return BaseCommand.concatUrls(url, MTX_URL_PATH_PREFIX, action);
59
53
  }
60
-
61
- static getMtxApiUrlSync(url, action) {
62
- return BaseCommand.concatUrls(url, MTX_URL_PATH_PREFIX, action);
63
- }
64
54
  }
@@ -1,6 +1,3 @@
1
- const cds = require ('../cds')
2
- const { tar } = cds.utils;
3
-
4
1
  const fs = require('fs');
5
2
  const { join } = require('path');
6
3
  const axios = require('axios');
@@ -13,10 +10,6 @@ const DOWNLOAD_FILE_NAME = 'migrated_projects.tgz'; // TODO try to add tenant la
13
10
  module.exports = class Download extends BaseCommand {
14
11
 
15
12
  static async run(paramValues) {
16
- if (!tar) {
17
- throw `cds pull requires @sap/cds version >= 6.2. Current version is ${cds.version}\n`;
18
- }
19
-
20
13
  const params = await login(paramValues);
21
14
 
22
15
  const projectFolder = params.get('projectFolder');
@@ -31,17 +24,16 @@ module.exports = class Download extends BaseCommand {
31
24
  const downloadedTgz = await this.getTgz(params);
32
25
  await fs.promises.writeFile(downloadPath, downloadedTgz);
33
26
 
34
- //await tar.xz(baseModelTgz).to(target); // expand if feature is requested later
35
-
36
27
  console.log(`Finished.`);
37
28
  }
38
29
 
39
30
  static async getTgz(params) {
40
31
  const tagRule = params.get('tagRule');
32
+ const defaultTag = params.get('defaultTag');
41
33
  const downloadUrl = `${(params.get('appUrl'))}/-/cds/extensibility/getMigratedProjects`;
42
34
  const options = { ...params.get('reqAuth'), responseType: 'arraybuffer' };
43
35
 
44
- return axios.post(downloadUrl, { tagRule }, options)
36
+ return axios.post(downloadUrl, { tagRule, defaultTag }, options)
45
37
  .then(response => Buffer.from(response.data))
46
38
  .catch(error => this.handleHttpError(error, params, { url: downloadUrl }));
47
39
  }
@@ -163,13 +163,14 @@ class BasicParams {
163
163
  this.directory = this.projectFolder.newAlias('directory');
164
164
 
165
165
  this.tagRule = new Param('tagRule', '');
166
+ this.defaultTag = new Param('defaultTag', '');
166
167
 
167
168
  // - non-persisted, internal
168
169
  this.reqAuth = new Param('reqAuth', null, { obfuscate: ObfuscationLevel.full, internal: true });
169
170
  this.clearOtherTokenStorage = new Param('clearOtherTokenStorage', false, { internal: true });
170
171
 
171
172
  // - persisted
172
- this.appUrl = new Param('appUrl', '', { persist: Persistence.setting });
173
+ this.appUrl = new Param('appUrl', '', { persist: Persistence.setting }); // any trailing slash (/) is removed
173
174
  this.url = this.appUrl.newAlias('url');
174
175
  this.from = this.appUrl.newAlias('from');
175
176
  this.to = this.appUrl.newAlias('to');
@@ -275,6 +276,7 @@ class ParamCollection {
275
276
 
276
277
  /**
277
278
  * Get the name of a main param (as opposed to an alias).
279
+ * Throws an error if the param is not part of this collection.
278
280
  * @param name the param in question
279
281
  */
280
282
  static mainName(name) {
@@ -286,7 +288,7 @@ class ParamCollection {
286
288
  }
287
289
 
288
290
  /**
289
- * Whether this includes the given param.
291
+ * Return whether the given param is included in this collection.
290
292
  * @param name the param in question
291
293
  */
292
294
  has(name) {
@@ -294,7 +296,7 @@ class ParamCollection {
294
296
  }
295
297
 
296
298
  /**
297
- * Return the value of the given param.
299
+ * Return the value of the given param or `undefined` if not part of this collection.
298
300
  * @param name the param in question
299
301
  */
300
302
  get(name) {
@@ -303,7 +305,7 @@ class ParamCollection {
303
305
  }
304
306
 
305
307
  /**
306
- * Return the `Param` object for the given param.
308
+ * Return the `Param` object for the given param or `undefined` if not part of this collection.
307
309
  * @param name the param in question
308
310
  */
309
311
  getParam(name) {
@@ -96,19 +96,17 @@ async function getTokenBaseUrl(params, renewUrl) {
96
96
  if (!params.has('appUrl')) {
97
97
  throw 'Failed to determine token URL: app URL not given';
98
98
  }
99
- const baseUrl = new URL(params.get('appUrl'));
100
- const cleanPathname = pathname => baseUrl.pathname.replace(/\/?$/, pathname);
101
- const getBaseUrl = pathname => {
102
- const url = new URL(baseUrl.toString());
103
- url.pathname = cleanPathname(pathname);
104
- return url;
99
+ const appUrlWith = pathname => {
100
+ const url = new URL(params.get('appUrl'));
101
+ url.pathname = url.pathname.replace(/\/*$/, pathname);
102
+ return url.toString();
105
103
  };
106
104
 
107
105
  let existingPath;
108
106
  const oauthPaths = [OAUTH_PATH_STREAMLINED_MTX];
109
107
  for (const path of oauthPaths) {
110
108
  try {
111
- await axios.head(getBaseUrl(path).toString());
109
+ await axios.head(appUrlWith(path));
112
110
  existingPath = path;
113
111
  break;
114
112
  } catch (error) {
@@ -119,7 +117,7 @@ async function getTokenBaseUrl(params, renewUrl) {
119
117
  }
120
118
  }
121
119
  existingPath = existingPath ?? OAUTH_PATH_OLD_MTX; // REVISIT can this just be included in the array?
122
- return getBaseUrl(existingPath).toString();
120
+ return appUrlWith(existingPath);
123
121
  }
124
122
 
125
123
  function getKeyringAccountName(params) {
@@ -194,7 +192,6 @@ class SettingsManager {
194
192
 
195
193
  const loadedSettings = await this._loadFromFile(projectFolder);
196
194
  DEBUG?.(`Loaded project settings for ${params.get('projectFolder')}: ${loadedSettings.format()}`);
197
- const loadedAppUrl = loadedSettings.get('appUrl');
198
195
 
199
196
  if (params.has('passcode') && loadedSettings.has('username')) {
200
197
  console.log(`Unsetting username${(loadedSettings.has('isEmptyPassword') ? ' and empty-password hint' : '')}`);
@@ -207,50 +204,56 @@ class SettingsManager {
207
204
  if (params.has('username')) {
208
205
  params.set('skipToken', true);
209
206
  }
210
- await this.updateUrls(params, logout, loadedAppUrl);
207
+ await this.updateUrls(params, logout, loadedSettings);
211
208
  await this.setKeytar(params, logout);
212
209
  await this.addAuth(params, logout);
213
210
  DEBUG?.(`Effective project settings: ${params.format()}`);
214
211
  }
215
212
 
216
- static async updateUrls(params, logout, loadedAppUrl) {
217
- async function addAppUrl() {
218
- // Prefix URL schema
219
- if (params.has('appUrl')) {
220
- const appUrl = params.get('appUrl');
221
- if (!schemaRegex.test(appUrl)) {
222
- if (localhostRegex.test(appUrl)) {
223
- params.set('appUrl', httpSchema + appUrl);
224
- } else {
225
- params.set('appUrl', httpsSchema + appUrl);
226
- }
227
- DEBUG?.(`Added schema to app URL: ${params.get('appUrl')}`);
228
- }
229
- }
230
-
213
+ static async updateUrls(params, logout, loadedSettings) {
214
+ async function updateAppUrl() {
231
215
  if (!logout && !params.has('appUrl')) {
232
216
  // Get appUrl from CF
233
217
  DEBUG?.('App URL not given');
234
218
  await getAppUrlAndSubdomainFromSuggestions(params);
235
219
  }
236
220
 
237
- // Check required param: appUrl
238
221
  if (!params.has('appUrl')) {
239
222
  throw 'App URL not given. Please specify it or log in to Cloud Foundry and repeat this command.';
240
223
  }
224
+
225
+ // Prefix URL schema
226
+ let appUrl = params.get('appUrl');
227
+ if (appUrl.endsWith('/')) {
228
+ appUrl = appUrl.replace(/\/+$/, '');
229
+ DEBUG?.(`Removed trailing slash from app URL: ${appUrl}`);
230
+ }
231
+ if (!schemaRegex.test(appUrl)) {
232
+ if (localhostRegex.test(appUrl)) {
233
+ appUrl = httpSchema + appUrl;
234
+ } else {
235
+ appUrl = httpsSchema + appUrl;
236
+ }
237
+ DEBUG?.(`Added schema to app URL: ${appUrl}`);
238
+ }
239
+ params.set('appUrl', appUrl);
240
+ if (params.get('appUrl') !== loadedSettings.get('appUrl')) {
241
+ DEBUG?.(`Updated app URL from '${loadedSettings.get('appUrl')}' (loaded) to '${appUrl}'`);
242
+ }
241
243
  }
242
- async function renewTokenUrl() {
243
- const renewUrl = params.get('appUrl') !== loadedAppUrl;
244
- if (!params.get('tokenUrl') || renewUrl) {
245
- params.set('tokenUrl', await getTokenBaseUrl(params, renewUrl));
244
+ async function updateTokenUrl() {
245
+ const tokenUrl = params.get('tokenUrl');
246
+ const renewUrl = !logout && !params.get('skipToken')
247
+ && (!tokenUrl || tokenUrl.includes('//-/cds') || params.get('appUrl') !== loadedSettings.get('appUrl'));
248
+ if (renewUrl) {
249
+ const tokenUrl = await getTokenBaseUrl(params, renewUrl);
250
+ params.set('tokenUrl', tokenUrl);
251
+ DEBUG?.(`Updated token URL from '${loadedSettings.get('tokenUrl')}' (loaded) to '${tokenUrl}'`);
246
252
  }
247
253
  }
248
254
 
249
- await addAppUrl();
250
-
251
- if (!logout && !params.get('skipToken')) {
252
- await renewTokenUrl();
253
- }
255
+ await updateAppUrl();
256
+ await updateTokenUrl();
254
257
  }
255
258
 
256
259
  static async setKeytar(params, logout = false) {
@@ -64,7 +64,7 @@ module.exports.csdl2openapi = function (
64
64
  csdl = JSON.parse(JSON.stringify(csdl))
65
65
  csdl.$Version = odataVersion ? odataVersion : '4.01'
66
66
  serviceRoot = serviceRoot || (scheme + '://' + host + basePath);
67
- const queryOptionPrefix = csdl.$Version <= '4.0' ? '$' : '';
67
+ const queryOptionPrefix = csdl.$Version <= '4.01' ? '$' : '';
68
68
  const typesToInline = {}; // filled in schema() and used in inlineTypes()
69
69
  const boundOverloads = {};
70
70
  const derivedTypes = {};
@@ -45,13 +45,16 @@ class HanaDeployer {
45
45
  // arg object
46
46
  // async deploy(model, serviceName, tunnelAddress, buildTaskOptions, vcapFile, undeployWhitelist, hdiOptions, bindCallback) { // NOSONAR
47
47
  async deploy(options = {}) { // NOSONAR
48
- let { model, serviceName, tunnelAddress, vcapFile, undeployWhitelist, hdiOptions } = options;
48
+ let { model, serviceName, tunnelAddress, vcapFile, undeployWhitelist, hdiOptions, dry } = options;
49
49
 
50
50
  if (model) {
51
51
  throw 'Specifying a model for hana deployment is not supported.';
52
52
  }
53
53
 
54
- console.log(`${bold('Starting deploy to SAP HANA ...')}`);
54
+ if (dry) {
55
+ console.log('-- running in dry mode ---');
56
+ }
57
+
55
58
  if (vcapFile) {
56
59
  console.log(`Using VCAP_SERVICES from file ${vcapFile} (beta feature).`);
57
60
  }
@@ -66,51 +69,66 @@ class HanaDeployer {
66
69
  globalVcapEnv.TARGET_CONTAINER = process.env.TARGET_CONTAINER;
67
70
  }
68
71
 
69
- for (const buildResult of buildResults) {
70
- let vcapEnv = globalVcapEnv;
71
- let serviceKeyName;
72
- const currentModelFolder = buildResult.task.dest;
73
-
74
- if (undeployWhitelist) {
75
- console.log('Writing undeploy.json');
76
- await fs.write(path.join(currentModelFolder, 'undeploy.json'), JSON.stringify(undeployWhitelist, null, 2));
72
+ if (dry) {
73
+ for (const buildResult of buildResults) {
74
+ const model = await cds.load(buildResult.task.src);
75
+ const data = cds.compile.to.hdbtable(model);
76
+ for (const [src, { file }] of data) {
77
+ console.log(`-- ${file}`);
78
+ console.log(src);
79
+ console.log();
80
+ }
77
81
  }
82
+ } else {
83
+ for (const buildResult of buildResults) {
84
+ let vcapEnv = globalVcapEnv;
85
+ let serviceKeyName;
86
+ const currentModelFolder = buildResult.task.dest;
87
+
88
+ if (undeployWhitelist) {
89
+ console.log('Writing undeploy.json');
90
+ await fs.write(path.join(currentModelFolder, 'undeploy.json'), JSON.stringify(undeployWhitelist, null, 2));
91
+ }
78
92
 
79
- const hasVCAPEnv = Object.keys(vcapEnv).length > 0;
80
- if (!serviceName && hasVCAPEnv) {
81
- await fs.mkdir(currentModelFolder, { recursive: true });
82
- } else {
83
- const { cfServiceInstanceName, cfServiceInstanceKeyName, serviceKey } =
84
- await this._getOrCreateCFService(projectPath, currentModelFolder, serviceName, tunnelAddress);
85
- serviceKeyName = cfServiceInstanceKeyName;
86
- serviceName = serviceName || cfServiceInstanceName;
93
+ const hasVCAPEnv = Object.keys(vcapEnv).length > 0;
94
+ if (!serviceName && hasVCAPEnv) {
95
+ await fs.mkdir(currentModelFolder, { recursive: true });
96
+ } else {
97
+ const { cfServiceInstanceName, cfServiceInstanceKeyName, serviceKey } =
98
+ await this._getOrCreateCFService(projectPath, currentModelFolder, serviceName, tunnelAddress);
99
+ serviceKeyName = cfServiceInstanceKeyName;
100
+ serviceName = serviceName || cfServiceInstanceName;
87
101
 
88
- vcapEnv = this._getVCAPServicesEntry(cfServiceInstanceName, serviceKey);
102
+ vcapEnv = this._getVCAPServicesEntry(cfServiceInstanceName, serviceKey);
89
103
 
90
- if (options.storeCredentials) {
91
- await this._addInstanceToDefaultEnvJson([currentModelFolder, projectPath], cfServiceInstanceName, serviceKey);
104
+ if (options.storeCredentials) {
105
+ await this._addInstanceToDefaultEnvJson([currentModelFolder, projectPath], cfServiceInstanceName, serviceKey);
106
+ }
92
107
  }
93
- }
94
108
 
95
- hdiOptions = hdiOptions || {}
96
- await hdiDeployUtil.deploy(currentModelFolder, vcapEnv, hdiOptions);
109
+ console.log(`${bold('Starting deployment to SAP HANA ...')}`);
110
+
111
+ hdiOptions = hdiOptions || {}
112
+ await hdiDeployUtil.deploy(currentModelFolder, vcapEnv, hdiOptions);
97
113
 
98
- if (!hasVCAPEnv) {
99
- const bindOptions = {
100
- ...options,
101
- to: `${serviceName}:${serviceKeyName}`,
102
- for: options.for,
103
- kind: buildResult.task.for
114
+ if (!hasVCAPEnv) {
115
+ const bindOptions = {
116
+ ...options,
117
+ to: `${serviceName}:${serviceKeyName}`,
118
+ for: options.for,
119
+ kind: buildResult.task.for
120
+ }
121
+ await bind(bindOptions);
104
122
  }
105
- await bind(bindOptions);
106
123
  }
107
- }
108
124
 
109
- if (options.storeCredentials) {
110
- await gitUtil.ensureFileIsGitignored('default-env.json', projectPath);
125
+ if (options.storeCredentials) {
126
+ await gitUtil.ensureFileIsGitignored('default-env.json', projectPath);
127
+ }
128
+
129
+ console.log(`If not already done, use ${info('cds add hana')} to configure the project for SAP HANA.\n`);
111
130
  }
112
131
 
113
- console.log(`If not already done, use ${info('cds add hana')} to configure the project for SAP HANA.\n`);
114
132
  console.log(`Done.`);
115
133
 
116
134
  return { buildResults };
package/lib/index.js CHANGED
@@ -21,5 +21,11 @@ if (cds.compile.to.constructor.name === 'Object') { // not a class as in cds 7
21
21
 
22
22
  cds.schema = cds.schema || {} // backward compatibility
23
23
  cds.extend (cds.schema) .with ( {
24
- overlay4: require('./env/schema')
24
+ get overlay4() { return super.overlay4 = require('./env/schema') }
25
+ })
26
+
27
+ // set build API, potentially removing the old API facade from sap/cds 7
28
+ delete cds.__proto__?.build
29
+ cds.extend (cds.constructor) .with (class {
30
+ get build() { return super.build = require('./build') }
25
31
  })
@@ -27,9 +27,10 @@ module.exports = {
27
27
  POSTGRES: 'postgres',
28
28
  SQLITE: 'sqlite',
29
29
  H2: 'h2',
30
+ LOCAL_MESSAGING: 'local-messaging',
31
+ FILE_BASED_MESSAGING: 'file-based-messaging',
30
32
  ENTERPRISE_MESSAGING: 'enterprise-messaging',
31
33
  ENTERPRISE_MESSAGING_SHARED: 'enterprise-messaging-shared',
32
- FILE_BASED_MESSAGING: 'file-based-messaging',
33
34
  REDIS_MESSAGING: 'redis-messaging',
34
35
  APPROUTER: 'approuter',
35
36
  DESTINATION: 'destination',
@@ -40,7 +41,8 @@ module.exports = {
40
41
  LINT_DEV: 'lint:dev',
41
42
  HELM: 'helm',
42
43
  TYPER: 'typer',
43
- HTML5_REPO: 'html5-repo'
44
+ HTML5_REPO: 'html5-repo',
45
+ NOTIFICATIONS: 'notifications'
44
46
  }),
45
47
 
46
48
  PROJECT_TYPE: Object.freeze({ nodejs: 1, java: 2, unknown: 3 }),
@@ -112,6 +112,17 @@ const hdiContainer = {
112
112
  }]
113
113
  }
114
114
 
115
+ const notificationTypesDeployer = {
116
+ in: 'modules',
117
+ where: [{
118
+ property: 'name',
119
+ isEqualTo: 'notification-content-deployment'
120
+ }, {
121
+ property: 'type',
122
+ isEqualTo: 'nodejs'
123
+ }]
124
+ }
125
+
115
126
  const serviceManager = {
116
127
  in: 'resources',
117
128
  where: [{
@@ -273,6 +284,7 @@ module.exports = {
273
284
  enterpriseMessaging,
274
285
  hdbDeployer,
275
286
  hdiContainer,
287
+ notificationTypesDeployer,
276
288
  serviceManager,
277
289
  postgres,
278
290
  postgresDeployer,
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "emname": "{{appName}}-emname",
3
3
  "version": "1.1.0",
4
- "namespace": "default/{{appName}}/1",
4
+ "namespace": "default/{{cleanedAppName}}/1",
5
5
  "options": {
6
6
  "management": true,
7
7
  "messagingrest": true,
@@ -23,6 +23,7 @@ module.exports = class EnterpriseMessagingTemplate extends require('../templateB
23
23
  const project = await readProject()
24
24
  const { configFile } = project
25
25
  await mergeJSON(configFile, join(__dirname, 'files', 'package.json.hbs'), project)
26
+ await mergeJSON('event-mesh.json', join(__dirname, 'files', 'event-mesh.json.hbs'), project)
26
27
  }
27
28
 
28
29
  async runDependentMerging(context = {}) {
@@ -30,7 +31,6 @@ module.exports = class EnterpriseMessagingTemplate extends require('../templateB
30
31
  const { hasXsuaa, hasHelm, hasMta, isNodejs, srvPath, appName } = project
31
32
 
32
33
  if (hasMta) {
33
- await mergeJSON('event-mesh.json', join(__dirname, 'files', 'event-mesh.json.hbs'), project)
34
34
  const srv = (isNodejs ? srvNode4 : srvJava4)(srvPath)
35
35
  await mergeYAML(
36
36
  'mta.yaml',