@sap/cds-dk 7.1.1 → 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 (84) hide show
  1. package/bin/activate.js +2 -2
  2. package/bin/build.js +5 -2
  3. package/bin/compile/index.js +13 -5
  4. package/bin/env.js +0 -3
  5. package/bin/extend.js +25 -3
  6. package/bin/gen.js +27 -0
  7. package/bin/help.js +2 -1
  8. package/bin/import.js +7 -3
  9. package/bin/push.js +10 -6
  10. package/bin/watch.js +3 -4
  11. package/bin/watched.js +2 -3
  12. package/lib/bind/bindingUtil.js +1 -0
  13. package/lib/build/{buildTaskHandler.js → buildPlugin.js} +25 -30
  14. package/lib/build/buildTaskEngine.js +84 -89
  15. package/lib/build/buildTaskFactory.js +23 -44
  16. package/lib/build/buildTaskProvider.js +24 -19
  17. package/lib/build/buildTaskProviderFactory.js +176 -66
  18. package/lib/build/constants.js +6 -5
  19. package/lib/build/index.js +4 -3
  20. package/lib/build/provider/{buildTaskHandlerEdmx.js → edmxBuildPlugin.js} +6 -6
  21. package/lib/build/provider/{buildTaskHandlerFeatureToggles.js → featureToggleBuildPlugin.js} +3 -3
  22. package/lib/build/provider/fiori/index.js +5 -6
  23. package/lib/build/provider/hana/index.js +67 -36
  24. package/lib/build/provider/{buildTaskHandlerInternal.js → internalBuildPlugin.js} +11 -11
  25. package/lib/build/provider/{buildTaskProviderInternal.js → internalBuildTaskProvider.js} +42 -53
  26. package/lib/build/provider/java/index.js +24 -24
  27. package/lib/build/provider/mtx/index.js +4 -4
  28. package/lib/build/provider/mtx/resourcesTarBuilder.js +8 -8
  29. package/lib/build/provider/mtx-extension/index.js +17 -9
  30. package/lib/build/provider/mtx-sidecar/index.js +17 -11
  31. package/lib/build/provider/nodejs/index.js +63 -29
  32. package/lib/build/util.js +11 -19
  33. package/lib/client/auth_manager.js +6 -12
  34. package/lib/client/base_command.js +1 -1
  35. package/lib/client/download_legacy.js +48 -0
  36. package/lib/client/params.js +5 -0
  37. package/lib/client/push.js +41 -1
  38. package/lib/client/settings_manager.js +12 -3
  39. package/lib/compile/openapi/csdl2openapi.js +30 -21
  40. package/lib/compile/openapi/index.js +7 -3
  41. package/lib/env/schema.js +36 -0
  42. package/lib/env/schemas/cds-rc.json +101 -0
  43. package/lib/import/common.js +24 -11
  44. package/lib/import/importapi.js +2 -2
  45. package/lib/import/message.js +3 -1
  46. package/lib/import/odata/v4parser.js +23 -7
  47. package/lib/index.js +5 -0
  48. package/lib/init/constants.js +9 -12
  49. package/lib/init/index.js +86 -74
  50. package/lib/init/template/_merging/registry-mta.js +21 -12
  51. package/lib/init/template/approuter/files/mta.yaml.hbs +1 -1
  52. package/lib/init/template/approuter/index.js +3 -2
  53. package/lib/init/template/cf-manifest/files/services-manifest.yml.hbs +0 -5
  54. package/lib/init/template/cf-manifest/index.js +2 -2
  55. package/lib/init/template/connectivity/index.js +5 -4
  56. package/lib/init/template/destination/index.js +2 -5
  57. package/lib/init/template/h2/index.js +16 -0
  58. package/lib/init/template/hana/index.js +2 -3
  59. package/lib/init/template/helm/chart/values.root.schema.json +0 -5
  60. package/lib/init/template/helm/chart/values.schema.json +0 -4
  61. package/lib/init/template/helm/index.js +1 -1
  62. package/lib/init/template/html5-repo/index.js +27 -3
  63. package/lib/init/template/lint/index.js +3 -7
  64. package/lib/init/template/mta/index.js +9 -3
  65. package/lib/init/template/multitenancy/files/package.json.hbs +0 -3
  66. package/lib/init/template/multitenancy/index.js +2 -0
  67. package/lib/init/template/nodejs/index.js +3 -3
  68. package/lib/init/template/pipeline/index.js +2 -4
  69. package/lib/init/template/postgres/files/mta.yaml.hbs +27 -0
  70. package/lib/init/template/postgres/files/package.json.hbs +5 -0
  71. package/lib/init/template/postgres/index.js +52 -0
  72. package/lib/init/template/sample/index.js +5 -7
  73. package/lib/init/template/sqlite/index.js +8 -3
  74. package/lib/init/template/tiny-sample/index.js +16 -30
  75. package/lib/init/util/mvn.js +25 -1
  76. package/lib/init/util/projectReader.js +2 -7
  77. package/lib/init/util/template.js +7 -6
  78. package/lib/util/pruneAxiosErrors.js +22 -8
  79. package/lib/watch/node-watch.js +6 -1
  80. package/npm-shrinkwrap.json +2207 -384
  81. package/package.json +3 -3
  82. package/lib/init/template/auditlog/files/mta.yaml.hbs +0 -14
  83. package/lib/init/template/auditlog/files/package.json.hbs +0 -27
  84. package/lib/init/template/auditlog/index.js +0 -74
@@ -2,15 +2,15 @@
2
2
  const fs = require('fs')
3
3
  const path = require('path')
4
4
  const cds = require('../../../cds')
5
- const BuildTaskHandlerEdmx = require('../buildTaskHandlerEdmx')
5
+ const EdmxBuildPlugin = require('../edmxBuildPlugin')
6
6
  const ResourcesTarProvider = require('./resourcesTarBuilder')
7
7
  const { FOLDER_GEN } = require('../../constants')
8
8
  const { BuildError } = require('../../util')
9
9
 
10
- class MtxModuleBuilder extends BuildTaskHandlerEdmx {
10
+ class MtxBuildPlugin extends EdmxBuildPlugin {
11
11
  get priority() {
12
12
  // should be scheduled after 'hana' build tasks are finished
13
- return BuildTaskHandlerEdmx.PRIORITY_MIN_VALUE
13
+ return EdmxBuildPlugin.PRIORITY_MIN_VALUE
14
14
  }
15
15
  init() {
16
16
  super.init()
@@ -40,4 +40,4 @@ Upgrade to @sap/cds-mtxs following the migration guide https://cap.cloud.sap/doc
40
40
  }
41
41
  }
42
42
  }
43
- module.exports = MtxModuleBuilder
43
+ module.exports = MtxBuildPlugin
@@ -1,24 +1,24 @@
1
1
  const path = require('path')
2
2
  const cds = require('../../../cds')
3
- const BuildTaskHandlerInternal = require('../buildTaskHandlerInternal')
3
+ const InternalBuildPlugin = require('../internalBuildPlugin')
4
4
 
5
5
  const { BUILD_TASK_HANA } = require('../../constants')
6
6
  const fs = require('fs')
7
- const { WARNING } = BuildTaskHandlerInternal
7
+ const { WARNING } = InternalBuildPlugin
8
8
  const DEFAULT_TAR_NAME = "resources.tgz"
9
9
 
10
10
  class ResourcesTarBuilder {
11
- constructor(handler) {
12
- this._handler = handler
11
+ constructor(plugin) {
12
+ this._plugin = plugin
13
13
  }
14
14
 
15
- get handler() { return this._handler }
15
+ get plugin() { return this._plugin }
16
16
 
17
17
  async createTar(dest, model) {
18
18
  const { root, resources } = await this._getResources(model)
19
19
  if (resources.length === 0) {
20
20
  // packTarArchive fails otherwise
21
- this.handler.pushMessage("No deployment resources found - skip resources.tgz", WARNING)
21
+ this.plugin.pushMessage("No deployment resources found - skip resources.tgz", WARNING)
22
22
  return
23
23
  }
24
24
  await this.writeTarFile(path.join(dest, DEFAULT_TAR_NAME), root, resources)
@@ -36,7 +36,7 @@ class ResourcesTarBuilder {
36
36
  async writeTarFile(tarFile, root, resources) {
37
37
  const { tar } = require('../../../../lib').utils
38
38
  await tar.czfd(tarFile, root, resources)
39
- this.handler.pushFile(tarFile)
39
+ this.plugin.pushFile(tarFile)
40
40
  }
41
41
 
42
42
  async _getResources(model) {
@@ -83,7 +83,7 @@ class ResourcesTarBuilder {
83
83
  }
84
84
 
85
85
  async _getHanaTenantDbDest() {
86
- const hanaTask = this.handler.context.tasks.find(task => task.for === BUILD_TASK_HANA)
86
+ const hanaTask = this.plugin.context.tasks.find(task => task.for === BUILD_TASK_HANA)
87
87
  return hanaTask?.dest
88
88
  }
89
89
  }
@@ -2,12 +2,12 @@ const path = require('path')
2
2
  const fs = require('fs')
3
3
  const cds = require('../../../cds')
4
4
 
5
- const BuildTaskHandlerInternal = require('../buildTaskHandlerInternal')
5
+ const InternalBuildPlugin = require('../internalBuildPlugin')
6
6
  const { FOLDER_GEN, EXTENSION_POINT_VALIDATION } = require('../../constants')
7
7
  const ResourcesTarBuilder = require('../mtx/resourcesTarBuilder')
8
8
  const { BuildError, BuildMessage } = require('../../util')
9
9
 
10
- class MtxExtensionModuleBuilder extends BuildTaskHandlerInternal {
10
+ class MtxExtensionBuildPlugin extends InternalBuildPlugin {
11
11
  init() {
12
12
  super.init()
13
13
  if (cds.env.build.target === '.') {
@@ -37,12 +37,10 @@ class MtxExtensionModuleBuilder extends BuildTaskHandlerInternal {
37
37
  extModel.requires.length = 0
38
38
  }
39
39
 
40
- if (!this.hasBuildOption(EXTENSION_POINT_VALIDATION, false)) {
41
- this._lintExtModel(extModel, model) // throws error in case of linting errors
42
- }
43
-
40
+ // !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
41
+ // perform all model operations before the linter is called as the linter
42
+ // will modify the model causing the creation of 'unresolved' associations
44
43
  await this.compileToJson(extModel, path.join(destExt, 'extension.csn'))
45
-
46
44
  await this.collectLanguageBundles(extModel, path.join(destExt, 'i18n'))
47
45
 
48
46
  const files = Object.keys(await cds.deploy.resources(model))
@@ -56,6 +54,16 @@ class MtxExtensionModuleBuilder extends BuildTaskHandlerInternal {
56
54
  })
57
55
  )
58
56
  }
57
+ if (!this.hasBuildOption(EXTENSION_POINT_VALIDATION, false)) {
58
+ try {
59
+ // throws error in case of linting errors
60
+ this._lintExtModel(extModel, model)
61
+ } catch(error) {
62
+ // cleanup existing files in case of an error
63
+ await fs.promises.rm(this.task.dest, { recursive: true })
64
+ throw error
65
+ }
66
+ }
59
67
  }
60
68
 
61
69
  const packageJson = path.join(src, 'package.json')
@@ -90,7 +98,7 @@ class MtxExtensionModuleBuilder extends BuildTaskHandlerInternal {
90
98
 
91
99
  return paths
92
100
  }
93
-
101
+
94
102
  _lintExtModel(extModel, model) {
95
103
  const linter = this._linter()
96
104
  if (!linter) {
@@ -128,4 +136,4 @@ class MtxExtensionModuleBuilder extends BuildTaskHandlerInternal {
128
136
  }
129
137
  }
130
138
 
131
- module.exports = MtxExtensionModuleBuilder
139
+ module.exports = MtxExtensionBuildPlugin
@@ -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,39 +43,74 @@ 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
- await this._fixCapireSamplesDeps(destRoot)
56
+ if (this.context.options.ws) {
57
+ await this._addWorkspaceDependencies(destRoot)
58
+ }
56
59
  }
57
60
  return this._result
58
61
  }
59
62
 
60
- // REVISIT: fix for capire samples - needs to be reworked
61
- // Instead of adding relative paths we may better delete the entire entry as relative paths
62
- // cannot be resolved in cloud deployments anyway.
63
+ // REVISIT: fix for simple mono repos using npm workspaces
63
64
  // Support for HANA native artifacts defined in sub-modules required
64
- async _fixCapireSamplesDeps(dest) {
65
- const packageJson = path.join(dest, 'package.json')
65
+ async _addWorkspaceDependencies(dest) {
66
66
  try {
67
- const content = require(packageJson)
68
- const { dependencies } = content
69
- let changed
70
- for (let dependency in dependencies) {
71
- if (dependency.startsWith('@capire/')) {
72
- dependencies[dependency] = path.relative(dest, path.dirname(require.resolve(dependency + '/package.json')))
73
- changed = true
74
- }
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`)
75
70
  }
76
- if (changed) {
77
- return this.write(JSON.stringify(content, null, ' ')).to(packageJson)
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
+ }
78
109
  }
79
- } catch (e) {/*ignore*/ }
110
+ } catch (e) {
111
+ this.pushMessage(`Failed to add npm workspace dependencies: ${e.message}`)
112
+ this.logger.debug(e)
113
+ }
80
114
  }
81
115
 
82
116
  async clean() {
@@ -91,7 +125,7 @@ class NodejsModuleBuilder extends BuildTaskHandlerEdmx {
91
125
 
92
126
  async _copyNativeContent(srcRoot, srcSrv, destRoot, destSrv) {
93
127
  // project/srv/** -> 'gen/srv/srv/**'
94
- const filesFilter = await this.copySrvContent(srcSrv, destRoot, this.hasBuildOption('flatDeployLayout', true)? destRoot : destSrv)
128
+ const filesFilter = await this.copySrvContent(srcSrv, destRoot, this.hasBuildOption('flatDeployLayout', true) ? destRoot : destSrv)
95
129
 
96
130
  // project/* -> 'gen/srv/*'
97
131
  await this.copyProjectRootContent(srcRoot, destRoot, (entry) => !filesFilter.includes(path.basename(entry)))
@@ -176,4 +210,4 @@ class NodejsModuleBuilder extends BuildTaskHandlerEdmx {
176
210
  }
177
211
  }
178
212
 
179
- module.exports = NodejsModuleBuilder
213
+ module.exports = NodejsBuildPlugin
package/lib/build/util.js CHANGED
@@ -119,6 +119,14 @@ function getDefaultModelOptions() {
119
119
  return _pushModelPaths(modelPaths)
120
120
  }
121
121
 
122
+ /**
123
+ * For valid paths replace '\\' with '/' and remove trailing '/'. Otherwise return as is
124
+ * @param {*} dir
125
+ */
126
+ function normalizePath(dir) {
127
+ return typeof dir === "string" ? dir.replace(/\\/g, '/').replace(/\/$/, '') : dir
128
+ }
129
+
122
130
  function _pushModelPaths(...modelPaths) {
123
131
  const model = new Set()
124
132
  // may contain nested arrays
@@ -131,7 +139,7 @@ function _pushModelPaths(...modelPaths) {
131
139
  if (roots.includes(m)) {
132
140
  const dir = path.resolve(cds.root, m)
133
141
  if (fs.existsSync(dir)) {
134
- model.add(m.replace(/\/$/, ''))
142
+ model.add(m)
135
143
  } else if (fs.existsSync(dir + FILE_EXT_CDS)) { //might be cds file name, compatibility to old build configs
136
144
  model.add(m)
137
145
  }
@@ -140,7 +148,7 @@ function _pushModelPaths(...modelPaths) {
140
148
  }
141
149
  }
142
150
  })
143
- return [...model]
151
+ return [...model].map(m => normalizePath(m))
144
152
  }
145
153
 
146
154
  function flatten(modelPaths) {
@@ -154,22 +162,6 @@ function flatten(modelPaths) {
154
162
  }, [])
155
163
  }
156
164
 
157
- /**
158
- * Copy a file or directory. The directory can have contents.
159
- * REVISIT: 'fs.promises.cp' replacement for nodejs 14
160
- * @param src <String> Note that if src is a directory it will copy everything inside of this directory, not the entire directory itself.
161
- * @param dest <String> Note that if src is a file, dest cannot be a directory.
162
- */
163
- async function copy(src, dest) {
164
- if ((await fs.promises.stat(src)).isDirectory()) {
165
- const entries = await fs.promises.readdir(src)
166
- return Promise.all(entries.map(async each => copy(path.join(src, each), path.join(dest, each))))
167
- } else {
168
- await fs.promises.mkdir(path.dirname(dest), { recursive: true })
169
- return fs.promises.copyFile(src, dest)
170
- }
171
- }
172
-
173
165
  function getI18nDefaultFolder() {
174
166
  // keep '_i18n' as preferred folder for compatibility reasons
175
167
  if (cds.env.i18n.folders.includes('_i18n')) return '_i18n'
@@ -276,9 +268,9 @@ module.exports = {
276
268
  resolveRequiredSapModels,
277
269
  getDefaultModelOptions,
278
270
  flatten,
279
- copy,
280
271
  getI18nDefaultFolder,
281
272
  isClassicMtx,
273
+ normalizePath,
282
274
  BuildMessage,
283
275
  BuildError
284
276
  }
@@ -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
  }