@sap/cds-dk 7.2.0 → 7.3.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 (56) hide show
  1. package/bin/activate.js +2 -2
  2. package/bin/build.js +6 -3
  3. package/bin/compile/index.js +9 -5
  4. package/bin/env.js +0 -3
  5. package/bin/extend.js +25 -3
  6. package/bin/import.js +7 -3
  7. package/bin/push.js +10 -6
  8. package/bin/watch.js +1 -1
  9. package/lib/build/{buildTaskHandler.js → buildPlugin.js} +23 -25
  10. package/lib/build/buildTaskEngine.js +73 -73
  11. package/lib/build/buildTaskFactory.js +6 -23
  12. package/lib/build/buildTaskProvider.js +24 -19
  13. package/lib/build/buildTaskProviderFactory.js +170 -57
  14. package/lib/build/constants.js +4 -4
  15. package/lib/build/index.js +3 -2
  16. package/lib/build/provider/{buildTaskHandlerEdmx.js → edmxBuildPlugin.js} +6 -6
  17. package/lib/build/provider/{buildTaskHandlerFeatureToggles.js → featureToggleBuildPlugin.js} +3 -3
  18. package/lib/build/provider/fiori/index.js +5 -6
  19. package/lib/build/provider/hana/index.js +54 -30
  20. package/lib/build/provider/{buildTaskHandlerInternal.js → internalBuildPlugin.js} +10 -10
  21. package/lib/build/provider/{buildTaskProviderInternal.js → internalBuildTaskProvider.js} +27 -28
  22. package/lib/build/provider/java/index.js +24 -24
  23. package/lib/build/provider/mtx/index.js +4 -4
  24. package/lib/build/provider/mtx/resourcesTarBuilder.js +8 -8
  25. package/lib/build/provider/mtx-extension/index.js +17 -9
  26. package/lib/build/provider/mtx-sidecar/index.js +17 -11
  27. package/lib/build/provider/nodejs/index.js +14 -13
  28. package/lib/client/auth_manager.js +6 -12
  29. package/lib/client/base_command.js +1 -1
  30. package/lib/client/download_legacy.js +48 -0
  31. package/lib/client/params.js +5 -0
  32. package/lib/client/push.js +41 -1
  33. package/lib/client/settings_manager.js +12 -3
  34. package/lib/env/schema.js +36 -0
  35. package/lib/env/schemas/cds-rc.json +101 -0
  36. package/lib/import/common.js +23 -10
  37. package/lib/import/importapi.js +2 -2
  38. package/lib/import/message.js +3 -1
  39. package/lib/import/odata/v4parser.js +23 -7
  40. package/lib/index.js +5 -0
  41. package/lib/init/constants.js +4 -2
  42. package/lib/init/index.js +2 -3
  43. package/lib/init/template/_merging/registry-mta.js +21 -0
  44. package/lib/init/template/approuter/files/mta.yaml.hbs +1 -1
  45. package/lib/init/template/approuter/index.js +9 -9
  46. package/lib/init/template/connectivity/index.js +5 -4
  47. package/lib/init/template/destination/index.js +2 -5
  48. package/lib/init/template/html5-repo/index.js +27 -3
  49. package/lib/init/template/mta/index.js +9 -3
  50. package/lib/init/template/postgres/files/mta.yaml.hbs +27 -0
  51. package/lib/init/template/postgres/files/package.json.hbs +5 -0
  52. package/lib/init/template/postgres/index.js +52 -0
  53. package/lib/init/util/projectReader.js +0 -1
  54. package/lib/util/pruneAxiosErrors.js +22 -8
  55. package/npm-shrinkwrap.json +1829 -23
  56. package/package.json +1 -1
@@ -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
@@ -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,48 @@
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
+ if (!tar) {
17
+ throw `cds pull requires @sap/cds version >= 6.2. Current version is ${cds.version}\n`;
18
+ }
19
+
20
+ const params = await login(paramValues);
21
+
22
+ const projectFolder = params.get('projectFolder');
23
+ const downloadPath = join(projectFolder, DOWNLOAD_FILE_NAME);
24
+
25
+ if (fs.existsSync(downloadPath) && !(await require('./helper/question').askBooleanQuestion(`File ${DOWNLOAD_FILE_NAME} already exists. Continue (yN)? `, false, false))) {
26
+ console.log('Download cancelled');
27
+ return;
28
+ }
29
+
30
+ console.log(`Downloading migrated @sap/mtx extension project(s)`);
31
+ const downloadedTgz = await this.getTgz(params);
32
+ await fs.promises.writeFile(downloadPath, downloadedTgz);
33
+
34
+ //await tar.xz(baseModelTgz).to(target); // expand if feature is requested later
35
+
36
+ console.log(`Finished.`);
37
+ }
38
+
39
+ static async getTgz(params) {
40
+ const tagRule = params.get('tagRule');
41
+ const downloadUrl = `${(params.get('appUrl'))}/-/cds/extensibility/getMigratedProjects`;
42
+ const options = { ...params.get('reqAuth'), responseType: 'arraybuffer' };
43
+
44
+ return axios.post(downloadUrl, { tagRule }, options)
45
+ .then(response => Buffer.from(response.data))
46
+ .catch(error => this.handleHttpError(error, params, { url: downloadUrl }));
47
+ }
48
+ }
@@ -162,6 +162,8 @@ 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
+
165
167
  // - non-persisted, internal
166
168
  this.reqAuth = new Param('reqAuth', null, { obfuscate: ObfuscationLevel.full, internal: true });
167
169
  this.clearOtherTokenStorage = new Param('clearOtherTokenStorage', false, { internal: true });
@@ -222,6 +224,9 @@ class AllParams extends BasicParams {
222
224
  this["delete-settings"] = this.deleteSettings.newAlias('delete-settings');
223
225
  this.clearInvalid = new Param('clearInvalid', false);
224
226
  this["clear-invalid"] = this.clearInvalid.newAlias('clear-invalid');
227
+
228
+ // download for mxts
229
+ this["download-migrated-projects"] = new Param('download-migrated-projects', false)
225
230
  }
226
231
  }
227
232
 
@@ -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;
@@ -344,6 +344,13 @@ class SettingsManager {
344
344
  params.merge(auth[storage]);
345
345
  }
346
346
  }
347
+ function removeObsoleteToken() {
348
+ if (params.get('renewLogin') || params.get('tokenExpirationDate') <= Date.now()) {
349
+ DEBUG?.((params.get('renewLogin') ? 'Renewing' : 'Refreshing expired') + ' authentication token');
350
+ params.delete('token');
351
+ params.delete('tokenExpirationDate');
352
+ }
353
+ }
347
354
 
348
355
  if (params.has('username')) {
349
356
  if (!params.has('password') && !logout) {
@@ -381,8 +388,10 @@ class SettingsManager {
381
388
  };
382
389
 
383
390
  setRelevantAuth(auth);
384
-
385
- if (!logout && !params.has('token') && !params.has('passcode') && !params.has('clientsecret') && !params.has('key')) {
391
+ if (params.has('token')) {
392
+ removeObsoleteToken();
393
+ }
394
+ if (!logout && !params.has('token') && !params.has('refreshToken') && !params.has('passcode') && !params.has('clientsecret') && !params.has('key')) {
386
395
  await addPasscode();
387
396
  }
388
397
  }
@@ -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
  }
@@ -91,7 +91,7 @@ async function cds_import_from_openapi(filepath, options = {}) {
91
91
  filepath = await common.isValidInputFile(path.resolve(process.cwd(), filepath));
92
92
  const src = await common.getContentFromFile(filepath);
93
93
  let csn = openapi.openAPI2csn(src);
94
- options.inputFileVersion = 'rest';
94
+ options.inputFileKind = 'rest';
95
95
  return csn;
96
96
  }
97
97
 
@@ -106,6 +106,6 @@ async function cds_import_from_asyncapi(filepath, options = {}) {
106
106
  filepath = await common.isValidInputFile(path.resolve(process.cwd(), filepath));
107
107
  const src = await common.getContentFromFile(filepath);
108
108
  let csn = asyncapi.asyncapi2csn(src);
109
- options.inputFileVersion = 'odata';
109
+ options.inputFileKind = 'odata';
110
110
  return csn;
111
111
  }
@@ -34,7 +34,9 @@ const messages = {
34
34
  BINARY_MAXLENGTH: 'WARNING: MaxLength for type Edm.Binary should not exceed 5000.',
35
35
  SCALE_GT_PRECISION: 'WARNING: Scale value should not be greater than Precision value.',
36
36
  FIXED_PRECISION_VARIABLE_SCALE: 'WARNING: Fixed Precision with Variable or Floating Scale is not supported in CDS.',
37
- NULLABLE_KEY: 'WARNING: Key element cannot be Nullable.'
37
+ NULLABLE_KEY: 'WARNING: Key element cannot be Nullable.',
38
+ COLLISION_DETECTED: `INFO: Element name collision detected. Adding suffix to affected type category.`,
39
+ SPECIFY_CONFIG: 'For --config option, please specify correct JSON Object.',
38
40
  };
39
41
 
40
42
  function getMessages() {