@sap/cds-dk 7.2.0 → 7.3.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 (105) hide show
  1. package/bin/activate.js +2 -2
  2. package/bin/add.js +1 -1
  3. package/bin/build.js +6 -3
  4. package/bin/compile/index.js +9 -5
  5. package/bin/env.js +0 -3
  6. package/bin/extend.js +29 -3
  7. package/bin/import.js +7 -3
  8. package/bin/init.js +31 -37
  9. package/bin/push.js +10 -6
  10. package/bin/repl.js +23 -43
  11. package/bin/watch.js +5 -9
  12. package/lib/bind/bindingResolver.js +2 -1
  13. package/lib/bind/index.js +1 -1
  14. package/lib/build/{buildTaskHandler.js → buildPlugin.js} +40 -27
  15. package/lib/build/buildTaskEngine.js +75 -74
  16. package/lib/build/buildTaskFactory.js +39 -32
  17. package/lib/build/buildTaskProvider.js +24 -19
  18. package/lib/build/buildTaskProviderFactory.js +125 -58
  19. package/lib/build/constants.js +6 -4
  20. package/lib/build/index.js +7 -3
  21. package/lib/build/plugins.js +35 -0
  22. package/lib/build/provider/{buildTaskHandlerEdmx.js → edmxBuildPlugin.js} +11 -7
  23. package/lib/build/provider/{buildTaskHandlerFeatureToggles.js → featureToggleBuildPlugin.js} +5 -5
  24. package/lib/build/provider/fiori/index.js +5 -6
  25. package/lib/build/provider/hana/index.js +54 -30
  26. package/lib/build/provider/{buildTaskHandlerInternal.js → internalBuildPlugin.js} +14 -10
  27. package/lib/build/provider/{buildTaskProviderInternal.js → internalBuildTaskProvider.js} +27 -28
  28. package/lib/build/provider/java/index.js +30 -24
  29. package/lib/build/provider/mtx/index.js +4 -4
  30. package/lib/build/provider/mtx/resourcesTarBuilder.js +8 -8
  31. package/lib/build/provider/mtx-extension/index.js +17 -9
  32. package/lib/build/provider/mtx-sidecar/index.js +17 -11
  33. package/lib/build/provider/nodejs/index.js +14 -13
  34. package/lib/build/util.js +5 -0
  35. package/lib/client/auth_manager.js +6 -12
  36. package/lib/client/base_command.js +1 -1
  37. package/lib/client/download_legacy.js +43 -0
  38. package/lib/client/params.js +6 -0
  39. package/lib/client/push.js +41 -1
  40. package/lib/client/settings_manager.js +28 -17
  41. package/lib/compile/openapi/csdl2openapi.js +1 -1
  42. package/lib/env/schema.js +36 -0
  43. package/lib/env/schemas/cds-rc.json +101 -0
  44. package/lib/import/common.js +23 -10
  45. package/lib/import/importapi.js +2 -2
  46. package/lib/import/message.js +3 -1
  47. package/lib/import/odata/v4parser.js +23 -7
  48. package/lib/index.js +11 -0
  49. package/lib/init/constants.js +5 -2
  50. package/lib/init/index.js +2 -3
  51. package/lib/init/template/_merging/registry-mta.js +33 -0
  52. package/lib/init/template/approuter/files/mta.yaml.hbs +1 -1
  53. package/lib/init/template/approuter/index.js +9 -9
  54. package/lib/init/template/connectivity/index.js +5 -4
  55. package/lib/init/template/destination/index.js +2 -5
  56. package/lib/init/template/helm/subcharts/content-deployment/Chart.yaml +4 -4
  57. package/lib/init/template/helm/subcharts/content-deployment/templates/_helpers.tpl +4 -2
  58. package/lib/init/template/helm/subcharts/content-deployment/templates/job.yaml +4 -3
  59. package/lib/init/template/helm/subcharts/content-deployment/values.schema.json +9 -83
  60. package/lib/init/template/helm/subcharts/content-deployment/values.yaml +54 -15
  61. package/lib/init/template/helm/subcharts/service-instance/Chart.yaml +4 -4
  62. package/lib/init/template/helm/subcharts/service-instance/templates/serviceinstance.yaml +1 -1
  63. package/lib/init/template/helm/subcharts/service-instance/values.schema.json +0 -17
  64. package/lib/init/template/helm/subcharts/service-instance/values.yaml +28 -0
  65. package/lib/init/template/helm/subcharts/web-application/Chart.yaml +5 -5
  66. package/lib/init/template/helm/subcharts/web-application/templates/_api.tpl +60 -0
  67. package/lib/init/template/helm/subcharts/web-application/templates/_helpers.tpl +34 -73
  68. package/lib/init/template/helm/subcharts/web-application/templates/api-rule.yaml +4 -4
  69. package/lib/init/template/helm/subcharts/web-application/templates/deployment.yaml +96 -17
  70. package/lib/init/template/helm/subcharts/web-application/templates/network-policy.yaml +19 -3
  71. package/lib/init/template/helm/subcharts/web-application/templates/pod-disruption-budget.yaml +3 -3
  72. package/lib/init/template/helm/subcharts/web-application/templates/rbac.yaml +26 -0
  73. package/lib/init/template/helm/subcharts/web-application/templates/secret.yaml +1 -1
  74. package/lib/init/template/helm/subcharts/web-application/templates/service-account.yaml +1 -1
  75. package/lib/init/template/helm/subcharts/web-application/templates/service-binding.yaml +1 -1
  76. package/lib/init/template/helm/subcharts/web-application/templates/service.yaml +21 -3
  77. package/lib/init/template/helm/subcharts/web-application/templates/virtual-service.yaml +36 -0
  78. package/lib/init/template/helm/subcharts/web-application/values.schema.json +535 -142
  79. package/lib/init/template/helm/subcharts/web-application/values.yaml +168 -44
  80. package/lib/init/template/html5-repo/index.js +27 -3
  81. package/lib/init/template/mta/index.js +9 -3
  82. package/lib/init/template/nodejs/files/.vscode/launch.json +0 -3
  83. package/lib/init/template/notifications/files/mta.yaml.hbs +17 -0
  84. package/lib/init/template/notifications/files/notificationtypes.json.hbs +16 -0
  85. package/lib/init/template/notifications/files/package.json.hbs +14 -0
  86. package/lib/init/template/notifications/index.js +52 -0
  87. package/lib/init/template/postgres/files/mta.yaml.hbs +27 -0
  88. package/lib/init/template/postgres/files/package.json.hbs +5 -0
  89. package/lib/init/template/postgres/index.js +52 -0
  90. package/lib/init/util/projectReader.js +0 -1
  91. package/lib/util/pruneAxiosErrors.js +25 -8
  92. package/lib/util/term.js +1 -2
  93. package/lib/watch/livereload-server.js +38 -2
  94. package/lib/watch/node-watch.js +3 -2
  95. package/npm-shrinkwrap.json +334 -335
  96. package/package.json +2 -2
  97. package/lib/init/template/helm/subcharts/content-deployment/templating.md +0 -37
  98. package/lib/init/template/helm/subcharts/content-deployment/values-lint.yaml +0 -5
  99. package/lib/init/template/helm/subcharts/content-deployment/values.md +0 -1011
  100. package/lib/init/template/helm/subcharts/service-instance/templating.md +0 -18
  101. package/lib/init/template/helm/subcharts/service-instance/values-lint.yaml +0 -2
  102. package/lib/init/template/helm/subcharts/service-instance/values.md +0 -186
  103. package/lib/init/template/helm/subcharts/web-application/templating.md +0 -51
  104. package/lib/init/template/helm/subcharts/web-application/values-lint.yaml +0 -8
  105. package/lib/init/template/helm/subcharts/web-application/values.md +0 -1363
@@ -1,18 +1,18 @@
1
1
  const path = require('path')
2
2
  const cds = require('../../../cds')
3
3
  const fs = require('fs')
4
- const { FOLDER_GEN, DEFAULT_CSN_FILE_NAME, MTX_SIDECAR_DB_VALIDATION } = require('../../constants')
5
- const NodeCfModuleBuilder = require('../nodejs')
4
+ const { FOLDER_GEN, DEFAULT_CSN_FILE_NAME, MTX_SIDECAR_DB_VALIDATION, CONTENT_EDMX, FLAVOR_LOCALIZED_EDMX } = require('../../constants')
5
+ const NodejsBuildPlugin = require('../nodejs')
6
6
  const ResourcesTarProvider = require('../mtx/resourcesTarBuilder')
7
- const { ERROR } = NodeCfModuleBuilder
7
+ const { ERROR } = NodejsBuildPlugin
8
8
  const { relativePaths, BuildError, resolveRequiredSapModels, getI18nDefaultFolder } = require('../../util')
9
9
 
10
10
  const DEFAULT_MAIN_FOLDER = "_main"
11
11
 
12
- class MtxSidecarModuleBuilder extends NodeCfModuleBuilder {
12
+ class MtxSidecarBuildPlugin extends NodejsBuildPlugin {
13
13
  get priority() {
14
14
  // should be scheduled after 'hana' build tasks are finished
15
- return NodeCfModuleBuilder.PRIORITY_MIN_VALUE
15
+ return NodejsBuildPlugin.PRIORITY_MIN_VALUE
16
16
  }
17
17
  init() {
18
18
  super.init()
@@ -47,7 +47,6 @@ class MtxSidecarModuleBuilder extends NodeCfModuleBuilder {
47
47
  const destSidecarSrc = path.join(destSidecar, cds.env.folders.srv)
48
48
  const model = this._compileSidecarSync(sidecarEnv)
49
49
  await this.compileToJson(model, path.join(destSidecarSrc, DEFAULT_CSN_FILE_NAME))
50
-
51
50
  await this.collectLanguageBundles(model, path.join(destSidecarSrc, getI18nDefaultFolder()))
52
51
  await this.copySrvContent(this.task.src, destSidecar, destSidecar)
53
52
  }
@@ -76,16 +75,23 @@ class MtxSidecarModuleBuilder extends NodeCfModuleBuilder {
76
75
  const destRoot = this.task.dest
77
76
  const destMain = path.join(destRoot, main)
78
77
  const destMainSrv = path.join(destMain, cds.env.folders.srv)
79
- const csn = await this.model()
80
- if (!csn) {
78
+ const model = await this.model()
79
+ if (!model) {
81
80
  return
82
81
  }
83
- const { dictionary, sources } = await this.compileAll(csn, destMainSrv, destMain)
82
+ const { dictionary, sources } = await this.compileAll(model, destMainSrv, destMain)
84
83
  await this.collectAllLanguageBundles(dictionary, sources, destMainSrv, destMain)
85
84
 
85
+ if (!this.hasBuildOption(CONTENT_EDMX, false)) {
86
+ //inferred model expected by cds.compile.to.edmx()
87
+ const compileOptions = { [FLAVOR_LOCALIZED_EDMX]: this.hasBuildOption(FLAVOR_LOCALIZED_EDMX, true) } // disabled by default
88
+ const baseModel = cds.compiler.compileSources({ 'base.json': dictionary.base }, { messages: this.messages })
89
+ await this.compileToEdmx(baseModel, path.join(destMainSrv, 'odata', cds.env.odata.version), compileOptions)
90
+ }
91
+
86
92
  // create resources TAR
87
93
  // resources are determined based on available database build task, SQLite as fallback
88
- await new ResourcesTarProvider(this).createTar(destMain, csn)
94
+ await new ResourcesTarProvider(this).createTar(destMain, model)
89
95
 
90
96
  // copy package.json and .cdsrc.json from project root
91
97
  await this._copyMainConfigFiles(cds.root, destMain)
@@ -139,4 +145,4 @@ class MtxSidecarModuleBuilder extends NodeCfModuleBuilder {
139
145
  return promises
140
146
  }
141
147
  }
142
- module.exports = MtxSidecarModuleBuilder
148
+ module.exports = MtxSidecarBuildPlugin
@@ -1,13 +1,13 @@
1
1
  const fs = require('fs')
2
2
  const path = require('path')
3
3
  const cds = require('../../../cds')
4
- const BuildTaskHandlerEdmx = require('../buildTaskHandlerEdmx')
4
+ const EdmxBuildPlugin = require('../edmxBuildPlugin')
5
5
  const { BuildError } = require('../../util')
6
- const { OUTPUT_MODE, OUTPUT_MODE_FILESYSTEM, ODATA_VERSION_V2, FOLDER_GEN, CONTENT_EDMX,
7
- CONTENT_PACKAGELOCK_JSON, CONTENT_NPMRC, CONTENT_CDSRC_JSON, CONTENT_ENV, CONTENT_DEFAULT_ENV_JSON } = require('../../constants')
8
- const { WARNING } = BuildTaskHandlerEdmx
6
+ const { OUTPUT_MODE, OUTPUT_MODE_FILESYSTEM, ODATA_VERSION_V2, FOLDER_GEN, CONTENT_EDMX, CONTENT_PACKAGELOCK_JSON,
7
+ CONTENT_NPMRC, CONTENT_CDSRC_JSON, CONTENT_ENV, CONTENT_DEFAULT_ENV_JSON, FLAVOR_LOCALIZED_EDMX } = require('../../constants')
8
+ const { WARNING } = EdmxBuildPlugin
9
9
 
10
- class NodejsModuleBuilder extends BuildTaskHandlerEdmx {
10
+ class NodejsBuildPlugin extends EdmxBuildPlugin {
11
11
  init() {
12
12
  super.init()
13
13
  if (this.task.options.compileDest) {
@@ -28,8 +28,7 @@ class NodejsModuleBuilder extends BuildTaskHandlerEdmx {
28
28
  async build() {
29
29
  const destSrv = this.isStagingBuild() ? this.destSrv : path.resolve(this.destSrv, cds.env.folders.srv)
30
30
  const destRoot = this.isStagingBuild() ? this.task.dest : this.destSrv
31
-
32
- if (cds.env.odata?.version === ODATA_VERSION_V2) {
31
+ if (cds.env.odata.version === ODATA_VERSION_V2) {
33
32
  // log warning as nodejs is only supporting odata version V4
34
33
  this.pushMessage("OData v2 is not supported by node runtime. Make sure to define OData v2 in cds configuration.", WARNING)
35
34
  }
@@ -44,15 +43,17 @@ class NodejsModuleBuilder extends BuildTaskHandlerEdmx {
44
43
  // collect and write language bundles into single i18n.json file
45
44
  await this.collectAllLanguageBundles(dictionary, sources, destSrv, destRoot)
46
45
 
47
- if (this.hasBuildOption(CONTENT_EDMX, true)) {
48
- const m = await cds.load(sources.base, super.options()) // REVISIT: Quick hack to get inferred model as expected by cds.compile.to.edmx()
49
- await this.compileToEdmx(m, this.destSrv)
46
+ if (!this.hasBuildOption(CONTENT_EDMX, false)) {
47
+ //REVISIT: inferred model expected by cds.compile.to.edmx()
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 })
50
+ await this.compileToEdmx(baseModel, path.join(this.destSrv, 'odata', cds.env.odata.version), compileOptions)
50
51
  }
51
52
 
52
53
  if (this.isStagingBuild() && this.hasBuildOption(OUTPUT_MODE, OUTPUT_MODE_FILESYSTEM)) {
53
54
  const srcSrv = this.task.src === cds.root ? path.resolve(this.task.src, cds.env.folders.srv) : this.task.src
54
55
  await this._copyNativeContent(cds.root, srcSrv, destRoot, destSrv)
55
- if (this.context.options.workspaces) {
56
+ if (this.context.options.ws) {
56
57
  await this._addWorkspaceDependencies(destRoot)
57
58
  }
58
59
  }
@@ -91,7 +92,7 @@ class NodejsModuleBuilder extends BuildTaskHandlerEdmx {
91
92
  if (!workspaceNames.has(folder) && folder.match(regex)) {
92
93
  workspaceNames.add(folder)
93
94
  const plugin = require(require.resolve(path.join(workspaceRoot, folder, 'package.json')))
94
- const tarFile = `${plugin.name.replace(/\//g,'-').replace(/@/, '')}-${plugin.version}.tgz`
95
+ const tarFile = `${plugin.name.replace(/\//g, '-').replace(/@/, '')}-${plugin.version}.tgz`
95
96
  if (dependencies[plugin.name] === '*') {
96
97
  const tarFilePath = path.join(dest, tarFile)
97
98
  dependencies[plugin.name] = `file:${tarFile}`
@@ -209,4 +210,4 @@ class NodejsModuleBuilder extends BuildTaskHandlerEdmx {
209
210
  }
210
211
  }
211
212
 
212
- module.exports = NodejsModuleBuilder
213
+ module.exports = NodejsBuildPlugin
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
  }
@@ -1,5 +1,7 @@
1
1
  const axios = require('axios');
2
2
 
3
+ require('../util/pruneAxiosErrors');
4
+
3
5
  const cds = require('../cds');
4
6
  const DEBUG = cds.debug('cli');
5
7
  const { SettingsManager } = require('./settings_manager');
@@ -54,16 +56,7 @@ function* reqParams(params) {
54
56
  }
55
57
 
56
58
  async function retrieveTokenOrPasscodeUrl(params) {
57
- if (params.has('token')) {
58
- if (params.get('renewLogin') || params.get('tokenExpirationDate') <= Date.now()) {
59
- DEBUG?.((params.get('renewLogin') ? 'Renewing' : 'Refreshing expired') + ' authentication token');
60
- params.delete('token');
61
- params.delete('tokenExpirationDate');
62
- } else {
63
- return;
64
- }
65
- }
66
- if (!params.has('passcode') && !params.has('refreshToken') && params.has('passcodeUrl')) {
59
+ if (params.has('token') || !params.has('refreshToken') && !params.has('passcode') && params.has('passcodeUrl')) {
67
60
  return;
68
61
  }
69
62
 
@@ -76,7 +69,7 @@ async function retrieveTokenOrPasscodeUrl(params) {
76
69
  DEBUG?.(`Getting authentication token or passcode URL from ${method.toUpperCase()} ${(params.obfuscateQueryParams(url))}`);
77
70
  response = await axios[method](url, data);
78
71
  } catch (error) {
79
- if (error.response?.status === 404) { // may represent 405 in case of wrong method
72
+ if (error.status === 404) { // may represent 405 in case of wrong method
80
73
  continue;
81
74
  }
82
75
  throw getMessage('Failed to get authentication token', { error });
@@ -95,7 +88,8 @@ async function retrieveTokenOrPasscodeUrl(params) {
95
88
 
96
89
  if (authData.access_token) {
97
90
  addTokenInfo(params, authData);
98
- } else {
91
+ }
92
+ if (authData.passcode_url) {
99
93
  addUrlInfo(params, authData);
100
94
  }
101
95
  }
@@ -20,7 +20,7 @@ module.exports = class BaseCommand {
20
20
  static handleHttpError(error, params, { url, command } = {}) {
21
21
  url = url ?? params.get('appUrl');
22
22
  const prefix = `Request to ${url} failed`;
23
- switch (error.response?.status) {
23
+ switch (error.status) {
24
24
  case 401:
25
25
  throw getMessage(`${prefix}: invalid authentication.\nRetry with valid passcode${
26
26
  params.has('passcodeUrl') ? ` from ${params.get('passcodeUrl')}` : ''
@@ -0,0 +1,43 @@
1
+ const cds = require ('../cds')
2
+ const { tar } = cds.utils;
3
+
4
+ const fs = require('fs');
5
+ const { join } = require('path');
6
+ const axios = require('axios');
7
+
8
+ const { login } = require('./auth_manager');
9
+ const BaseCommand = require('./base_command');
10
+
11
+ const DOWNLOAD_FILE_NAME = 'migrated_projects.tgz'; // TODO try to add tenant later, if possible
12
+
13
+ module.exports = class Download extends BaseCommand {
14
+
15
+ static async run(paramValues) {
16
+ const params = await login(paramValues);
17
+
18
+ const projectFolder = params.get('projectFolder');
19
+ const downloadPath = join(projectFolder, DOWNLOAD_FILE_NAME);
20
+
21
+ if (fs.existsSync(downloadPath) && !(await require('./helper/question').askBooleanQuestion(`File ${DOWNLOAD_FILE_NAME} already exists. Continue (yN)? `, false, false))) {
22
+ console.log('Download cancelled');
23
+ return;
24
+ }
25
+
26
+ console.log(`Downloading migrated @sap/mtx extension project(s)`);
27
+ const downloadedTgz = await this.getTgz(params);
28
+ await fs.promises.writeFile(downloadPath, downloadedTgz);
29
+
30
+ console.log(`Finished.`);
31
+ }
32
+
33
+ static async getTgz(params) {
34
+ const tagRule = params.get('tagRule');
35
+ const defaultTag = params.get('defaultTag');
36
+ const downloadUrl = `${(params.get('appUrl'))}/-/cds/extensibility/getMigratedProjects`;
37
+ const options = { ...params.get('reqAuth'), responseType: 'arraybuffer' };
38
+
39
+ return axios.post(downloadUrl, { tagRule, defaultTag }, options)
40
+ .then(response => Buffer.from(response.data))
41
+ .catch(error => this.handleHttpError(error, params, { url: downloadUrl }));
42
+ }
43
+ }
@@ -162,6 +162,9 @@ class BasicParams {
162
162
  this.projectFolder = new Param('projectFolder', '');
163
163
  this.directory = this.projectFolder.newAlias('directory');
164
164
 
165
+ this.tagRule = new Param('tagRule', '');
166
+ this.defaultTag = new Param('defaultTag', '');
167
+
165
168
  // - non-persisted, internal
166
169
  this.reqAuth = new Param('reqAuth', null, { obfuscate: ObfuscationLevel.full, internal: true });
167
170
  this.clearOtherTokenStorage = new Param('clearOtherTokenStorage', false, { internal: true });
@@ -222,6 +225,9 @@ class AllParams extends BasicParams {
222
225
  this["delete-settings"] = this.deleteSettings.newAlias('delete-settings');
223
226
  this.clearInvalid = new Param('clearInvalid', false);
224
227
  this["clear-invalid"] = this.clearInvalid.newAlias('clear-invalid');
228
+
229
+ // download for mxts
230
+ this["download-migrated-projects"] = new Param('download-migrated-projects', false)
225
231
  }
226
232
  }
227
233
 
@@ -9,6 +9,13 @@ const { getMessage } = require('./helper/logging');
9
9
  const { schemaRegex } = require('./helper/url_helper');
10
10
  const buildSystem = require('../build')
11
11
 
12
+ const JOB_STATUS = {
13
+ QUEUED: 'QUEUED',
14
+ RUNNING: 'RUNNING',
15
+ FINISHED: 'FINISHED',
16
+ FAILED: 'FAILED'
17
+ };
18
+
12
19
  module.exports = class Push extends BaseCommand {
13
20
 
14
21
  static async run(paramValues, pushOptions = {}) {
@@ -27,7 +34,11 @@ module.exports = class Push extends BaseCommand {
27
34
  const target = Object.assign({ url }, subdomain && { subdomain });
28
35
 
29
36
  console.log(`\nPushing extension '${extensionName}' from`, { src }, 'to', target);
30
- await this.pushTgz(params, content, extensionName);
37
+ const push = await this.pushTgz(params, content, extensionName);
38
+ const async = !params.get('sync');
39
+ if (async) {
40
+ await this.pollUntilFinished(push.headers.location, params)
41
+ }
31
42
  console.log ('Activation succeeded.\n')
32
43
  }
33
44
 
@@ -61,6 +72,10 @@ module.exports = class Push extends BaseCommand {
61
72
  static async pushTgz(params, tgz, extensionName) {
62
73
  const pushUrl = `${params.get('appUrl')}/-/cds/extensibility/push`;
63
74
  const options = { ...params.get('reqAuth') };
75
+ const async = !params.get('sync');
76
+ if (async) {
77
+ options.headers = { prefer: 'respond-async', ...options.headers }
78
+ }
64
79
 
65
80
  return axios.post(pushUrl, {
66
81
  extension: tgz,
@@ -68,4 +83,29 @@ module.exports = class Push extends BaseCommand {
68
83
  }, options)
69
84
  .catch(error => this.handleHttpError(error, params, { url: pushUrl }));
70
85
  }
86
+
87
+ static async pollUntilFinished(jobUrl, params) {
88
+ return new Promise((resolve, reject) => {
89
+ const options = { ...params.get('reqAuth') };
90
+
91
+ const timeout = setTimeout(() => {
92
+ clearInterval(interval);
93
+ reject(new Error('cds push timed out after 5 minutes'));
94
+ }, 5 * 60 * 1000);
95
+
96
+ const interval = setInterval(async () => {
97
+ const { status, error } = (await axios.get(jobUrl, options)).data;
98
+ if (status === JOB_STATUS.FINISHED || status === JOB_STATUS.FAILED) {
99
+ clearInterval(interval);
100
+ clearTimeout(timeout);
101
+
102
+ if (status === JOB_STATUS.FINISHED) {
103
+ resolve();
104
+ } else if (status === JOB_STATUS.FAILED) {
105
+ reject(new Error(error));
106
+ }
107
+ }
108
+ }, 1000);
109
+ });
110
+ }
71
111
  }
@@ -112,7 +112,7 @@ async function getTokenBaseUrl(params, renewUrl) {
112
112
  existingPath = path;
113
113
  break;
114
114
  } catch (error) {
115
- if ([401, 404].includes(error.response?.status)) {
115
+ if ([401, 404].includes(error.status)) {
116
116
  continue;
117
117
  }
118
118
  throw error;
@@ -215,29 +215,31 @@ class SettingsManager {
215
215
 
216
216
  static async updateUrls(params, logout, loadedAppUrl) {
217
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
-
231
218
  if (!logout && !params.has('appUrl')) {
232
219
  // Get appUrl from CF
233
220
  DEBUG?.('App URL not given');
234
221
  await getAppUrlAndSubdomainFromSuggestions(params);
235
222
  }
236
223
 
237
- // Check required param: appUrl
238
224
  if (!params.has('appUrl')) {
239
225
  throw 'App URL not given. Please specify it or log in to Cloud Foundry and repeat this command.';
240
226
  }
227
+
228
+ // Prefix URL schema
229
+ let appUrl = params.get('appUrl');
230
+ if (appUrl.endsWith('/')) {
231
+ appUrl = appUrl.replace(/\/+$/, '');
232
+ DEBUG?.(`Removed trailing slash from app URL: ${appUrl}`);
233
+ }
234
+ if (!schemaRegex.test(appUrl)) {
235
+ if (localhostRegex.test(appUrl)) {
236
+ appUrl = httpSchema + appUrl;
237
+ } else {
238
+ appUrl = httpsSchema + appUrl;
239
+ }
240
+ DEBUG?.(`Added schema to app URL: ${appUrl}`);
241
+ }
242
+ params.set('appUrl', appUrl);
241
243
  }
242
244
  async function renewTokenUrl() {
243
245
  const renewUrl = params.get('appUrl') !== loadedAppUrl;
@@ -344,6 +346,13 @@ class SettingsManager {
344
346
  params.merge(auth[storage]);
345
347
  }
346
348
  }
349
+ function removeObsoleteToken() {
350
+ if (params.get('renewLogin') || params.get('tokenExpirationDate') <= Date.now()) {
351
+ DEBUG?.((params.get('renewLogin') ? 'Renewing' : 'Refreshing expired') + ' authentication token');
352
+ params.delete('token');
353
+ params.delete('tokenExpirationDate');
354
+ }
355
+ }
347
356
 
348
357
  if (params.has('username')) {
349
358
  if (!params.has('password') && !logout) {
@@ -381,8 +390,10 @@ class SettingsManager {
381
390
  };
382
391
 
383
392
  setRelevantAuth(auth);
384
-
385
- if (!logout && !params.has('token') && !params.has('passcode') && !params.has('clientsecret') && !params.has('key')) {
393
+ if (params.has('token')) {
394
+ removeObsoleteToken();
395
+ }
396
+ if (!logout && !params.has('token') && !params.has('refreshToken') && !params.has('passcode') && !params.has('clientsecret') && !params.has('key')) {
386
397
  await addPasscode();
387
398
  }
388
399
  }
@@ -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 = {};
@@ -0,0 +1,36 @@
1
+ const { join } = require('path')
2
+ const { mergeJSON } = require('../init/util/merge')
3
+ const { readJSON } = require('../util/fs')
4
+
5
+ module.exports = async function overlay4(schemaName, cwd = process.cwd()) {
6
+
7
+ // need to load @sap/cds from current cwd which might
8
+ // differ from initial cwd to get project specific cds version
9
+ const cds = require(require.resolve('@sap/cds', {
10
+ paths: [cwd, __dirname]
11
+ }))
12
+
13
+ // force cds.root to be project root
14
+ cds.root = cwd
15
+
16
+ // load defaults, throw err if schema not found
17
+ let result;
18
+ if (typeof cds.schema?.default4 === 'function') {
19
+ result = await cds.schema.default4(schemaName)
20
+ } else if (cds.env?.schemas?.[schemaName]) {
21
+ // backwards compatibility
22
+ result = await readJSON(cds.env.schemas[schemaName])
23
+ } else {
24
+ throw new Error(`cds ${cds.version} in ${cwd} does not support schema retrieval.`)
25
+ }
26
+
27
+ // optional, don't throw if file not found
28
+ try {
29
+ await mergeJSON(result, join(__dirname, 'schemas', schemaName))
30
+ result.description = result.description + ' - overlaid'
31
+ } catch (err) {
32
+ if (err.code !== 'ENOENT') throw err
33
+ }
34
+
35
+ return result
36
+ }
@@ -0,0 +1,101 @@
1
+ {
2
+ "title": "JSON schema for CDS-DK configuration",
3
+ "$schema": "https://json-schema.org/draft/2020-12/schema",
4
+ "description": "This is a JSON schema representation for CDS project configuration",
5
+ "type": "object",
6
+ "additionalProperties": true,
7
+ "properties": {
8
+ "build": {
9
+ "type": "object",
10
+ "description": "Customize CDS build settings.",
11
+ "additionalProperties": true,
12
+ "properties": {
13
+ "target": {
14
+ "type": "string",
15
+ "description": "Specify the folder which contains generated build results. Defaults to './gen'.",
16
+ "format": "uri-reference"
17
+ },
18
+ "tasks": {
19
+ "type": "array",
20
+ "description": "Control which tasks 'cds build' will execute on your project modules. This is a typical task configuration: '\"tasks\": [{ \"for\": \"hana\"}];",
21
+ "uniqueItems": true,
22
+ "default": [
23
+ {}
24
+ ],
25
+ "items": {
26
+ "type": "object",
27
+ "additionalProperties": true,
28
+ "properties": {
29
+ "dest": {
30
+ "type": "string",
31
+ "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'.",
32
+ "format": "uri-reference"
33
+ },
34
+ "src": {
35
+ "type": "string",
36
+ "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'.",
37
+ "format": "uri-reference"
38
+ },
39
+ "for": {
40
+ "type": "string",
41
+ "description": "Define the target technology for this build task.",
42
+ "minLength": 1,
43
+ "enum": [
44
+ "nodejs",
45
+ "java",
46
+ "hana",
47
+ "mtx-sidecar",
48
+ "mtx-extension",
49
+ "mtx",
50
+ "fiori",
51
+ "node-cf",
52
+ "java-cf"
53
+ ]
54
+ },
55
+ "options": {
56
+ "type": "object",
57
+ "description": "Optional: Set target technology specific options for this build task.",
58
+ "additionalProperties": true,
59
+ "properties": {
60
+ "model": {
61
+ "$ref": "#/$defs/foldersStringArray"
62
+ }
63
+ }
64
+ }
65
+ }
66
+ }
67
+ }
68
+ }
69
+ },
70
+ "deploy": {
71
+ "type": "object",
72
+ "description": "Customize 'cds deploy' settings.",
73
+ "additionalProperties": true,
74
+ "properties": {
75
+ "no_save": {
76
+ "type": "boolean",
77
+ "description": "Do / don't update configuration in package.json.",
78
+ "default": true
79
+ }
80
+ }
81
+ }
82
+ },
83
+ "$defs": {
84
+ "foldersStringArray": {
85
+ "oneOf": [
86
+ {
87
+ "type": "string",
88
+ "format": "uri-reference"
89
+ },
90
+ {
91
+ "type": "array",
92
+ "uniqueItems": true,
93
+ "items": {
94
+ "type": "string",
95
+ "format": "uri-reference"
96
+ }
97
+ }
98
+ ]
99
+ }
100
+ }
101
+ }
@@ -17,7 +17,7 @@ let optionsMapping = {
17
17
  };
18
18
 
19
19
  let apiOptions = ['includeNamespaces', 'keepNamespace'];
20
- let commonOptions = ['no_copy', 'no_save', 'out', 'dry', 'as', 'from', 'force'];
20
+ let commonOptions = ['no_copy', 'no_save', 'out', 'dry', 'as', 'from', 'force', 'config'];
21
21
 
22
22
  async function generateEDMX2JSON(edmx) {
23
23
  let cjson = convert.xml2json(edmx, {
@@ -148,6 +148,18 @@ async function _copyToSrvExternal(file, dst, cwd) {
148
148
  return file;
149
149
  }
150
150
 
151
+ async function isValidJsonObject(options){
152
+ let config;
153
+ try {
154
+ if (options.config) {
155
+ config = JSON.parse(options.config);
156
+ return config;
157
+ }
158
+ } catch (error) {
159
+ throw new Error(messages.SPECIFY_CONFIG);
160
+ }
161
+ }
162
+
151
163
  async function preProcess(file, options, cwd) {
152
164
  let srcFilePath = await isValidInputFile(path.resolve(cwd, file));
153
165
  if (options.as && !_supportedAsFormats().includes(options.as))
@@ -266,12 +278,12 @@ async function _generateChecksumValidate(dest, output, force) {
266
278
  return "/* checksum : " + currentChecksum + " */\n" + output;
267
279
  }
268
280
 
269
- async function _getResult(output, as, dest, force, version) {
281
+ async function _getResult(output, as, dest, force, kind) {
270
282
  let extension = dest.substring(dest.lastIndexOf(".") + 1);
271
283
  as = (as === "cds") ? "cdl" : as;
272
284
 
273
285
  // output filepath should simply contain .csn as extension
274
- if (version === 'rest' && ['swagger', 'openapi3'].includes(extension)) {
286
+ if (kind === 'rest' && ['swagger', 'openapi3'].includes(extension)) {
275
287
  dest = dest.replace('.' + extension, '');
276
288
  }
277
289
 
@@ -325,19 +337,19 @@ ${using}
325
337
  console.log(message);
326
338
  }
327
339
 
328
- async function _updatePackageJson(dest, services, version, cwd) {
340
+ async function _updatePackageJson(dest, services, kind, cwd, config) {
329
341
  const package_json = path.resolve(cwd, 'package.json');
330
342
  const conf = await exists(package_json) ? require(package_json) : {};
331
343
  const requires = ['cds', 'requires'].reduce((p, n) => p[n] || (p[n] = {}), conf);
332
- if (version !== 'rest') {
333
- version = (version === "V2") ? 'odata-v2' : 'odata';
344
+ if (kind !== 'rest') {
345
+ kind = (kind === "V2") ? 'odata-v2' : 'odata';
334
346
  }
335
347
  let package_json_updated = false;
336
348
  for (let service of services) {
337
349
  if (!requires[service]) {
338
350
  const initial_model = path.relative(cwd, dest);
339
351
  const model = isLinux() ? initial_model.replace(/\\/g, '/') : initial_model;
340
- cds.env.requires[service] = requires[service] = { kind: version, model };
352
+ cds.env.requires[service] = requires[service] = { kind: kind, model, ...config };
341
353
  await write(package_json, JSON.stringify(conf, null, ' '));
342
354
  package_json_updated = true;
343
355
  }
@@ -349,11 +361,12 @@ async function _updatePackageJson(dest, services, version, cwd) {
349
361
 
350
362
  async function postProcess(filePath, options, csn, cwd) {
351
363
  const services = _getServiceNames(csn);
364
+ let config = await isValidJsonObject(options);
352
365
  if (options.out) options.out = _validateAndComputeOutputFilePath(options.out, options.as, filePath);
353
- const version = options.inputFileVersion;
354
- const result = await _getResult(csn, options.as, options.out || filePath.replace(/\.[^.]+$/, ''), options.force, version);
366
+ const kind = options.inputFileKind;
367
+ const result = await _getResult(csn, options.as, options.out || filePath.replace(/\.[^.]+$/, ''), options.force, kind);
355
368
  const written = options.dry || await _write(result, services, cwd);
356
- const registered = options.dry || options.no_save || await _updatePackageJson(result[1].replace(/\.[^.]+$/, ''), services, version, cwd);
369
+ const registered = options.dry || options.no_save || await _updatePackageJson(result[1].replace(/\.[^.]+$/, ''), services, kind, cwd, config);
357
370
  if (options.dry) return console.log(result[0]);
358
371
  return Promise.all([written, registered]);
359
372
  }