@sap/cds-dk 7.3.1 → 7.4.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 (52) hide show
  1. package/bin/add.js +2 -0
  2. package/bin/build.js +24 -14
  3. package/bin/cds.js +4 -4
  4. package/bin/compile/index.js +11 -0
  5. package/bin/init.js +1 -1
  6. package/bin/migrate.js +2 -2
  7. package/bin/repl.js +0 -2
  8. package/bin/subscribe.js +10 -5
  9. package/bin/unsubscribe.js +10 -5
  10. package/bin/upgrade.js +10 -5
  11. package/bin/watch.js +5 -0
  12. package/bin/watched.js +1 -1
  13. package/lib/build/buildTaskFactory.js +1 -1
  14. package/lib/build/constants.js +1 -0
  15. package/lib/build/provider/edmxBuildPlugin.js +7 -3
  16. package/lib/build/provider/featureToggleBuildPlugin.js +2 -3
  17. package/lib/build/provider/hana/2migration.js +4 -4
  18. package/lib/build/provider/hana/index.js +20 -10
  19. package/lib/build/provider/java/index.js +2 -2
  20. package/lib/build/provider/nodejs/index.js +77 -56
  21. package/lib/client/auth_manager.js +1 -1
  22. package/lib/client/base_command.js +8 -18
  23. package/lib/client/download_legacy.js +0 -3
  24. package/lib/client/params.js +5 -4
  25. package/lib/client/pull.js +9 -1
  26. package/lib/client/settings_manager.js +22 -21
  27. package/lib/compile/asyncapi/components/schemas/annotations.js +20 -0
  28. package/lib/compile/openapi/csdl2openapi.js +3 -3
  29. package/lib/deploy/to-hana/hana.js +53 -35
  30. package/lib/index.js +6 -4
  31. package/lib/init/constants.js +3 -4
  32. package/lib/init/index.js +9 -4
  33. package/lib/init/template/approuter/files/package.json +1 -1
  34. package/lib/init/template/enterprise-messaging/files/event-mesh.json.hbs +1 -1
  35. package/lib/init/template/enterprise-messaging/index.js +1 -1
  36. package/lib/init/template/enterprise-messaging-shared/files/event-mesh.json.hbs +1 -1
  37. package/lib/init/template/enterprise-messaging-shared/index.js +19 -5
  38. package/lib/init/template/hana/files/db/package.json +1 -1
  39. package/lib/init/template/helm/index.js +1 -1
  40. package/lib/init/template/local-messaging/files/package.json.hbs +15 -0
  41. package/lib/init/template/local-messaging/index.js +25 -0
  42. package/lib/init/template/multitenancy/index.js +3 -5
  43. package/lib/init/template/nodejs/files/.gitignore.hbs +3 -0
  44. package/lib/init/template/notifications/files/package.json.hbs +1 -10
  45. package/lib/init/template/notifications/index.js +7 -16
  46. package/lib/init/template/postgres/index.js +4 -3
  47. package/lib/init/template/templateBase.js +0 -13
  48. package/lib/init/template/typer/files/package.json +1 -1
  49. package/lib/init/util/projectReader.js +1 -0
  50. package/npm-shrinkwrap.json +377 -330
  51. package/package.json +1 -1
  52. package/lib/init/template/notifications/files/notificationtypes.json.hbs +0 -16
@@ -1,8 +1,9 @@
1
1
  const fs = require('fs')
2
2
  const path = require('path')
3
3
  const cds = require('../../../cds')
4
+ const cmd = require('../../../util/command');
4
5
  const EdmxBuildPlugin = require('../edmxBuildPlugin')
5
- const { BuildError } = require('../../util')
6
+ const { BuildError, pathExists } = require('../../util')
6
7
  const { OUTPUT_MODE, OUTPUT_MODE_FILESYSTEM, ODATA_VERSION_V2, FOLDER_GEN, CONTENT_EDMX, CONTENT_PACKAGELOCK_JSON,
7
8
  CONTENT_NPMRC, CONTENT_CDSRC_JSON, CONTENT_ENV, CONTENT_DEFAULT_ENV_JSON, FLAVOR_LOCALIZED_EDMX } = require('../../constants')
8
9
  const { WARNING } = EdmxBuildPlugin
@@ -44,9 +45,10 @@ class NodejsBuildPlugin extends EdmxBuildPlugin {
44
45
  await this.collectAllLanguageBundles(dictionary, sources, destSrv, destRoot)
45
46
 
46
47
  if (!this.hasBuildOption(CONTENT_EDMX, false)) {
47
- //REVISIT: inferred model expected by cds.compile.to.edmx()
48
48
  const compileOptions = { [FLAVOR_LOCALIZED_EDMX]: this.hasBuildOption(FLAVOR_LOCALIZED_EDMX, true) }
49
- const baseModel = cds.compiler.compileSources({ 'base.json': dictionary.base }, { messages: this.messages })
49
+ // inferred flavor is required by edmx compiler backend
50
+ // using cds.compile instead of cds.compiler.compileSources ensures that cds.env options are correctly read
51
+ const baseModel = dictionary.base.meta.flavor !== 'inferred' ? await cds.compile(sources.base, super.options(), 'inferred') : dictionary.base
50
52
  await this.compileToEdmx(baseModel, path.join(this.destSrv, 'odata', cds.env.odata.version), compileOptions)
51
53
  }
52
54
 
@@ -60,59 +62,6 @@ class NodejsBuildPlugin extends EdmxBuildPlugin {
60
62
  return this._result
61
63
  }
62
64
 
63
- // REVISIT: fix for simple mono repos using npm workspaces
64
- // Support for HANA native artifacts defined in sub-modules required
65
- async _addWorkspaceDependencies(dest) {
66
- try {
67
- const workspaceRoot = path.resolve(cds.root, this.getBuildOption("workspaceRoot") ?? '.')
68
- if (workspaceRoot !== cds.root && !fs.existsSync(workspaceRoot)) {
69
- throw new BuildError(`The given workspaceRoot path ${workspaceRoot} does not exist`)
70
- }
71
- const packageJson = require(path.join(dest, 'package.json'))
72
- const workspaces = workspaceRoot === cds.root ? packageJson.workspaces : require(path.join(workspaceRoot, 'package.json')).workspaces
73
- if (workspaces?.length) {
74
- this.logger._debug && this.logger.debug(`Existing npm workspace configuration '${JSON.stringify(workspaces, ' ')}'`)
75
- const { tar } = require('../../../../lib').utils
76
- const { dependencies } = packageJson
77
- const folders = fs.readdirSync(workspaceRoot).filter(res => {
78
- try {
79
- return fs.lstatSync(path.join(workspaceRoot, res)).isDirectory()
80
- } catch (e) {
81
- return false
82
- }
83
- })
84
- this.logger._debug && this.logger.debug(`Existing workspace subfolders '${JSON.stringify(folders, ' ')}'`)
85
-
86
- const workspaceNames = new Set()
87
- let changed
88
- for (const workspace of workspaces) {
89
- //REVISIT: workspaces must only use simple glob like 'foo*', nested folders are not supported
90
- const regex = new RegExp('^' + workspace.split('/').pop().replace(/\*/g, '.*') + '$')
91
- for (const folder of folders) {
92
- if (!workspaceNames.has(folder) && folder.match(regex)) {
93
- workspaceNames.add(folder)
94
- const plugin = require(require.resolve(path.join(workspaceRoot, folder, 'package.json')))
95
- const tarFile = `${plugin.name.replace(/\//g, '-').replace(/@/, '')}-${plugin.version}.tgz`
96
- if (dependencies[plugin.name] === '*') {
97
- const tarFilePath = path.join(dest, tarFile)
98
- dependencies[plugin.name] = `file:${tarFile}`
99
- await tar.czfd(tarFilePath, path.join(workspaceRoot, folder))
100
- this.pushFile(tarFilePath)
101
- changed = true
102
- }
103
- }
104
- }
105
- }
106
- if (changed) {
107
- return this.write(JSON.stringify(packageJson, null, ' ')).to(path.join(dest, 'package.json'))
108
- }
109
- }
110
- } catch (e) {
111
- this.pushMessage(`Failed to add npm workspace dependencies: ${e.message}`)
112
- this.logger.debug(e)
113
- }
114
- }
115
-
116
65
  async clean() {
117
66
  // staging build content is deleted by BuildTaskEngine
118
67
  if (cds.env.build.target === '.') {
@@ -208,6 +157,78 @@ class NodejsBuildPlugin extends EdmxBuildPlugin {
208
157
  return srvAllowList.test(entry) && (!filter || filter.call(this, entry))
209
158
  })
210
159
  }
160
+
161
+ async _addWorkspaceDependencies(dest) {
162
+ let packageDescriptors
163
+ try {
164
+ packageDescriptors = await this._execNpmPack(dest)
165
+ } catch (e) {
166
+ this.logger.debug(e)
167
+ throw new BuildError(`Failed to package npm workspace dependencies\n${e.message}`)
168
+ }
169
+ if (packageDescriptors?.length) {
170
+ const packageJson = require(path.join(dest, 'package.json'))
171
+ const dependencies = packageJson.dependencies
172
+ const devDependencies = packageJson.devDependencies
173
+ let changed
174
+ const promises = []
175
+ for (const packageDescriptor of packageDescriptors) {
176
+ const {name, filename: fileName} = packageDescriptor
177
+ const filePath = path.join(dest, fileName)
178
+ if (dependencies?.[name] === '*' || devDependencies?.[name] === '*') {
179
+ if (dependencies?.[name] === '*') {
180
+ dependencies[name] = `file:${fileName}`
181
+ } else {
182
+ devDependencies[name] = `file:${fileName}`
183
+ }
184
+ this.pushFile(filePath)
185
+ changed |= true
186
+ } else {
187
+ if (await pathExists(filePath)) {
188
+ promises.push(fs.promises.rm(filePath))
189
+ }
190
+ }
191
+ }
192
+ if (changed) {
193
+ promises.push(this.write(JSON.stringify(packageJson, 0, 2)).to(path.join(dest, 'package.json')))
194
+ } else {
195
+ throw new BuildError('No matching workspaces found!')
196
+ }
197
+ await Promise.all(promises)
198
+ }
199
+ }
200
+
201
+ async _execNpmPack(dest) {
202
+ function findWorkspaceRoot(dir) {
203
+ if(fs.existsSync(path.join(dir, 'package.json'))) {
204
+ if(require(path.join(dir, 'package.json')).workspaces) {
205
+ return dir
206
+ }
207
+ }
208
+ if(fs.existsSync(path.join(dir, '.gitmodules')) || fs.existsSync(path.join(dir, 'node_modules'))) {
209
+ return // project root reached
210
+ }
211
+ if (path.dirname(dir) !== dir) {
212
+ return findWorkspaceRoot(path.dirname(dir))
213
+ }
214
+ }
215
+ const wsRoot = findWorkspaceRoot(cds.root)
216
+ if (!wsRoot) {
217
+ throw new BuildError('No workspace configuration found!')
218
+ }
219
+ const args = ['pack', '-ws', '--json', `--pack-destination=${dest}`]
220
+ if (this.task.options?.workspaces) {
221
+ let {workspaces} = this.task.options
222
+ workspaces = Array.isArray(workspaces) ? workspaces : [workspaces]
223
+ workspaces.forEach(w => args.push(`-w=${w}`))
224
+ }
225
+ this.logger._debug && this.logger.debug(`execute command: npm ${args}`)
226
+
227
+ const result = await cmd.spawnCommand( 'npm', args, { cwd: wsRoot || cds.root }, true, !!this.logger._debug)
228
+
229
+ this.logger._debug && this.logger.debug(result)
230
+ return JSON.parse(result)
231
+ }
211
232
  }
212
233
 
213
234
  module.exports = NodejsBuildPlugin
@@ -66,7 +66,7 @@ async function retrieveTokenOrPasscodeUrl(params) {
66
66
  let response;
67
67
  for (const { method, url, data } of reqParams(params)) {
68
68
  try {
69
- DEBUG?.(`Getting authentication token or passcode URL from ${method.toUpperCase()} ${(params.obfuscateQueryParams(url))}`);
69
+ DEBUG?.(`Getting authentication token or passcode URL from ${method.toUpperCase()} ${params.obfuscateQueryParams(url)}`);
70
70
  response = await axios[method](url, data);
71
71
  } catch (error) {
72
72
  if (error.status === 404) { // may represent 405 in case of wrong method
@@ -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');
@@ -170,7 +170,7 @@ class BasicParams {
170
170
  this.clearOtherTokenStorage = new Param('clearOtherTokenStorage', false, { internal: true });
171
171
 
172
172
  // - persisted
173
- this.appUrl = new Param('appUrl', '', { persist: Persistence.setting });
173
+ this.appUrl = new Param('appUrl', '', { persist: Persistence.setting }); // any trailing slash (/) is removed
174
174
  this.url = this.appUrl.newAlias('url');
175
175
  this.from = this.appUrl.newAlias('from');
176
176
  this.to = this.appUrl.newAlias('to');
@@ -276,6 +276,7 @@ class ParamCollection {
276
276
 
277
277
  /**
278
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.
279
280
  * @param name the param in question
280
281
  */
281
282
  static mainName(name) {
@@ -287,7 +288,7 @@ class ParamCollection {
287
288
  }
288
289
 
289
290
  /**
290
- * Whether this includes the given param.
291
+ * Return whether the given param is included in this collection.
291
292
  * @param name the param in question
292
293
  */
293
294
  has(name) {
@@ -295,7 +296,7 @@ class ParamCollection {
295
296
  }
296
297
 
297
298
  /**
298
- * Return the value of the given param.
299
+ * Return the value of the given param or `undefined` if not part of this collection.
299
300
  * @param name the param in question
300
301
  */
301
302
  get(name) {
@@ -304,7 +305,7 @@ class ParamCollection {
304
305
  }
305
306
 
306
307
  /**
307
- * 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.
308
309
  * @param name the param in question
309
310
  */
310
311
  getParam(name) {
@@ -49,6 +49,14 @@ module.exports = class Pull extends BaseCommand {
49
49
  .catch(error => this.handleHttpError(error, params, { url: pullUrl }));
50
50
  }
51
51
 
52
+ static getPackageJsonIndentation(packageContents) {
53
+ return /^ +/m.exec(packageContents)?.[0].length ?? 2;
54
+ }
55
+
56
+ static formatPackageJson(newObj, oldContents) {
57
+ return JSON.stringify(newObj, undefined, this.getPackageJsonIndentation(oldContents));
58
+ }
59
+
52
60
  static amendPackageJson(projectFolder, env) {
53
61
  if ('extends' in env) {
54
62
  return;
@@ -61,7 +69,7 @@ module.exports = class Pull extends BaseCommand {
61
69
  } catch (error) {
62
70
  throw getMessage('package.json missing or unreadable', { error });
63
71
  }
64
- fs.writeFileSync(packageJson, JSON.stringify({
72
+ fs.writeFileSync(packageJson, this.formatPackageJson({
65
73
  ...packageContents,
66
74
  cds: {
67
75
  ...(packageContents.cds ?? {}),
@@ -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,14 +204,14 @@ 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() {
213
+ static async updateUrls(params, logout, loadedSettings) {
214
+ async function updateAppUrl() {
218
215
  if (!logout && !params.has('appUrl')) {
219
216
  // Get appUrl from CF
220
217
  DEBUG?.('App URL not given');
@@ -240,19 +237,23 @@ class SettingsManager {
240
237
  DEBUG?.(`Added schema to app URL: ${appUrl}`);
241
238
  }
242
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
+ }
243
243
  }
244
- async function renewTokenUrl() {
245
- const renewUrl = params.get('appUrl') !== loadedAppUrl;
246
- if (!params.get('tokenUrl') || renewUrl) {
247
- 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}'`);
248
252
  }
249
253
  }
250
254
 
251
- await addAppUrl();
252
-
253
- if (!logout && !params.get('skipToken')) {
254
- await renewTokenUrl();
255
- }
255
+ await updateAppUrl();
256
+ await updateTokenUrl();
256
257
  }
257
258
 
258
259
  static async setKeytar(params, logout = false) {
@@ -1,3 +1,17 @@
1
+
2
+ const odmAnnotationsMapping = Object.freeze({
3
+ '@ODM.entityName': 'x-sap-odm-entity-name',
4
+ '@ODM.oid': 'x-sap-odm-oid'
5
+ });
6
+
7
+ function addOdmExtensions(definition, object) {
8
+ for (const [annotation, asyncApiExtension] of Object.entries(odmAnnotationsMapping)) {
9
+ if (definition[annotation]) {
10
+ object[asyncApiExtension] = definition[annotation];
11
+ }
12
+ }
13
+ }
14
+
1
15
  module.exports = function addAnnotations(definition, object) {
2
16
  if (typeof (definition['@title']) === 'string') {
3
17
  object.title = definition['@title'];
@@ -9,5 +23,11 @@ module.exports = function addAnnotations(definition, object) {
9
23
  object.description = definition.doc.replace(/\n/g, ' ');
10
24
  }
11
25
 
26
+ if (definition['@ODM.root']) {
27
+ object['x-sap-root-entity'] = definition['@ODM.root']
28
+ }
29
+
30
+ addOdmExtensions(definition, object);
31
+
12
32
  return object;
13
33
  }
@@ -479,7 +479,7 @@ module.exports.csdl2openapi = function (
479
479
  if (description) tag.description = description;
480
480
  tags.push(tag);
481
481
  })
482
- return tags.sort(function(pre, next){return pre.name.localeCompare(next.name)});
482
+ return tags.sort(function (pre, next) { return pre.name.localeCompare(next.name) });
483
483
  }
484
484
 
485
485
  /**
@@ -655,7 +655,7 @@ module.exports.csdl2openapi = function (
655
655
  schema: ref(element.$Type, SUFFIX.create),
656
656
  }
657
657
  }
658
- },
658
+ },
659
659
  responses: response(201, 'Created ' + lname, { $Type: element.$Type }, insertRestrictions.ErrorResponses, !countRestrictions),
660
660
  };
661
661
  if (insertRestrictions.LongDescription) pathItem.post.description = insertRestrictions.LongDescription;
@@ -1620,7 +1620,7 @@ see [Expand](http://docs.oasis-open.org/odata/odata/v4.01/odata-v4.01-part1-prot
1620
1620
  r[code] = {
1621
1621
  description: description
1622
1622
  };
1623
- let CountPropertyObj = {[csdl.$Version > '4.0' ? '@count' : '@odata.count'] : ref('count')} ;
1623
+ let CountPropertyObj = { [csdl.$Version > '4.0' ? '@count' : '@odata.count']: ref('count') };
1624
1624
  if (code != 204) {
1625
1625
  const s = getSchema(type);
1626
1626
  r[code].content = {
@@ -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
@@ -19,13 +19,15 @@ if (cds.compile.to.constructor.name === 'Object') { // not a class as in cds 7
19
19
  cds.compile.to.xsuaa = require('./compile/to-xsuaa')
20
20
  }
21
21
 
22
- cds.schema = cds.schema || {} // backward compatibility
23
- cds.extend (cds.schema) .with ( {
24
- overlay4: require('./env/schema')
22
+ // add overlay4 method to cds.schema to retrieve schema with cds-dk features
23
+ // schema is cds version specific; add cds-dk parts not present in cds, e.g. build
24
+ // failsafe for legacy cds without schema.constructor
25
+ cds.extend(cds.schema?.constructor || {}).with(class {
26
+ get overlay4() { return super.overlay4 = require('./env/schema') }
25
27
  })
26
28
 
27
29
  // set build API, potentially removing the old API facade from sap/cds 7
28
- delete cds.__proto__?.build
30
+ // delete cds.__proto__?.build //> not required, as its overriden below anyways
29
31
  cds.extend (cds.constructor) .with (class {
30
32
  get build() { return super.build = require('./build') }
31
33
  })
@@ -4,7 +4,7 @@ module.exports = {
4
4
  COMMAND_INIT: 'init',
5
5
  COMMAND_ADD: 'add',
6
6
 
7
- MAVEN_ARCHETYPE_VERSION: '2.3.0',
7
+ MAVEN_ARCHETYPE_VERSION: '2.4.0',
8
8
 
9
9
  OPTIONS: Object.freeze({
10
10
  JAVA: 'java',
@@ -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',
@@ -44,8 +45,6 @@ module.exports = {
44
45
  NOTIFICATIONS: 'notifications'
45
46
  }),
46
47
 
47
- PROJECT_TYPE: Object.freeze({ nodejs: 1, java: 2, unknown: 3 }),
48
-
49
48
  REGEX_JAVA_PACKAGE: /^[a-z_][a-z0-9_]*(\.[a-z_][a-z0-9_]*)*$/g,
50
49
  // copied from mbt error message
51
50
  REGEX_JAVA_VERSION: /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(-(0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*)(\.(0|[1-9]\d*|\d*[a-zA-Z-][0-9a-zA-Z-]*))*)?(\+[0-9a-zA-Z-]+(\.[0-9a-zA-Z-]+)*)?$/,
package/lib/init/index.js CHANGED
@@ -6,7 +6,7 @@ const { OPTIONS, COMMAND_INIT, PROJECT_FILES, REGEX_PROJECT_NAME } = require('./
6
6
  const {
7
7
  NODEJS, JAVA,
8
8
  HANA, APPROUTER, XSUAA, KIBANA, ENTERPRISE_MESSAGING,
9
- MTA, HELM, MULTITENANCY, PIPELINE,
9
+ MTA, MULTITENANCY, PIPELINE,
10
10
  TINY_SAMPLE, SAMPLE
11
11
  } = OPTIONS
12
12
 
@@ -23,7 +23,7 @@ module.exports = class CDSGenerator {
23
23
  default: '',
24
24
  target: 'projectName',
25
25
  validate: async (value) => {
26
- try {
26
+ try {
27
27
  cds.root = path.resolve(this.cwd, value || '.');
28
28
 
29
29
  await this._validateProjectName(value);
@@ -74,7 +74,7 @@ module.exports = class CDSGenerator {
74
74
  default: '',
75
75
  choices: [
76
76
  { name: 'Cloud Foundry: MTA Deployment', value: MTA },
77
- { name: 'Kyma: Helm Deployment', value: HELM },
77
+ // { name: 'Kyma: Helm Deployment', value: HELM },
78
78
  { name: 'Multitenancy', value: MULTITENANCY },
79
79
  { name: 'CI/CD Pipeline Integration', value: PIPELINE },
80
80
  ],
@@ -182,7 +182,11 @@ module.exports = class CDSGenerator {
182
182
 
183
183
  if (cds.cli.command === COMMAND_INIT) {
184
184
  const relativeProjectPath = path.relative(this.cwd, cds.root)
185
- console.log(`\nSuccessfully created project. Continue with '${term.bold('cd '+relativeProjectPath)}'.\n`)
185
+ let message = 'Successfully created project.'
186
+ if (relativeProjectPath) {
187
+ message = message + ` Continue with '${term.bold('cd ' + relativeProjectPath)}'.`
188
+ }
189
+ console.log('\n' + message)
186
190
  } else {
187
191
  console.log(`\nSuccessfully added features to your project.`)
188
192
  }
@@ -219,6 +223,7 @@ module.exports = class CDSGenerator {
219
223
  // Compat
220
224
  if (facet === 'sample-tiny') facet = 'tiny-sample'
221
225
  if (facet === 'kibana-logging') facet = 'kibana'
226
+ if (facet === 'postgresql') facet = 'postgres'
222
227
 
223
228
  if (!templates.has(facet)) {
224
229
  const template = await this._createTemplate(facet)