@sap/cds-dk 9.3.2 → 9.4.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 (74) hide show
  1. package/CHANGELOG.md +41 -0
  2. package/bin/bind.js +1 -1
  3. package/bin/cds.js +3 -0
  4. package/bin/compile/index.js +53 -3
  5. package/bin/data-products/get-csn-via-dp-cli.js +41 -0
  6. package/bin/data-products/get-csn.js +76 -0
  7. package/bin/data-products/import.js +114 -0
  8. package/bin/data-products/index.js +30 -0
  9. package/bin/data-products/merge-csns.js +46 -0
  10. package/bin/data-products/prepare-csn.js +282 -0
  11. package/bin/data-products/prepare-files.js +69 -0
  12. package/bin/export.js +240 -0
  13. package/bin/help/export.md +37 -0
  14. package/bin/help/import.md +11 -0
  15. package/bin/help/minify.md +23 -0
  16. package/bin/import.js +13 -3
  17. package/bin/inspect.js +226 -0
  18. package/bin/minify.js +123 -0
  19. package/bin/util/cli.js +132 -0
  20. package/bin/util/log.js +2 -0
  21. package/bin/version.js +2 -1
  22. package/bin/watch.js +21 -3
  23. package/lib/bind/shared.js +3 -1
  24. package/lib/build/buildTaskEngine.js +2 -1
  25. package/lib/build/plugins/helm/files/templates/NOTES.txt +1 -1
  26. package/lib/build/plugins/helm/index.js +1 -1
  27. package/lib/build/provider/internalBuildTaskProvider.js +3 -3
  28. package/lib/build/provider/nodejs/index.js +62 -3
  29. package/lib/deploy/index.js +1 -1
  30. package/lib/deploy/to-hana/hana.js +18 -16
  31. package/lib/import/odata/v2parser.js +2 -2
  32. package/lib/import/odata/v4parser.js +21 -2
  33. package/lib/import/odata.package.json +4 -4
  34. package/lib/init/constants.js +2 -1
  35. package/lib/init/index.js +2 -0
  36. package/lib/init/mvn.js +3 -2
  37. package/lib/init/projectReader.js +1 -0
  38. package/lib/init/registries/mta.js +6 -0
  39. package/lib/init/template/ams/files/manifest.yml.hbs +1 -1
  40. package/lib/init/template/ams/files/mta.yaml.hbs +1 -1
  41. package/lib/init/template/app-frontend/files/app-package.json.hbs +20 -0
  42. package/lib/init/template/app-frontend/files/index.html.hbs +34 -0
  43. package/lib/init/template/app-frontend/files/manifest.json +13 -0
  44. package/lib/init/template/app-frontend/files/mta.yaml.hbs +71 -0
  45. package/lib/init/template/app-frontend/files/package.json.hbs +17 -0
  46. package/lib/init/template/app-frontend/files/ui5-deploy.yaml.hbs +25 -0
  47. package/lib/init/template/app-frontend/files/ui5.yaml.hbs +6 -0
  48. package/lib/init/template/app-frontend/files/values.yaml.hbs +20 -0
  49. package/lib/init/template/app-frontend/files/xs-app.json.hbs +17 -0
  50. package/lib/init/template/app-frontend/index.js +98 -0
  51. package/lib/init/template/approuter/files/mta.yaml.hbs +0 -2
  52. package/lib/init/template/attachments/index.js +4 -10
  53. package/lib/init/template/audit-logging/files/package.json.hbs +5 -0
  54. package/lib/init/template/handler/index.js +1 -0
  55. package/lib/init/template/helm/index.js +5 -1
  56. package/lib/init/template/html5-repo/files/mta.yaml.hbs +7 -2
  57. package/lib/init/template/ias/files/manifest.yml.hbs +1 -1
  58. package/lib/init/template/ias/files/mta.yaml.hbs +6 -5
  59. package/lib/init/template/ias/files/values.yaml.hbs +10 -1
  60. package/lib/init/template/multitenancy/files/mta.yaml.hbs +16 -3
  61. package/lib/init/template/multitenancy/files/package.json.hbs +5 -0
  62. package/lib/init/template/multitenancy/files/values.yaml.hbs +1 -6
  63. package/lib/init/template/portal/files/mta.yaml.hbs +12 -2
  64. package/lib/init/template/sample/files/db/schema.cds +5 -8
  65. package/lib/init/template/typer/files/xsconfig.json +2 -1
  66. package/lib/init/template/typescript/files/package.json +1 -1
  67. package/lib/init/template/typescript/files/tsconfig.json +4 -1
  68. package/lib/init/template/workzone-standard/files/mta.yaml.hbs +4 -0
  69. package/lib/init/template/workzone-standard/index.js +3 -2
  70. package/lib/init/template/xsuaa/files/package.json.hbs +3 -0
  71. package/lib/lint/eslintHelp.json +1 -1
  72. package/lib/util/term.js +2 -4
  73. package/npm-shrinkwrap.json +25 -25
  74. package/package.json +1 -1
@@ -6,10 +6,13 @@ const EdmxBuildPlugin = require('../edmxBuildPlugin')
6
6
  const { BuildError, getWorkspaces, findWorkspaceRoot } = require('../../util')
7
7
  const { OUTPUT_MODE, OUTPUT_MODE_FILESYSTEM, ODATA_VERSION_V2, FOLDER_GEN, CONTENT_EDMX, CONTENT_PACKAGELOCK_JSON,
8
8
  CONTENT_NPMRC, CONTENT_CDSRC_JSON, CONTENT_ENV, CONTENT_DEFAULT_ENV_JSON, FLAVOR_LOCALIZED_EDMX,
9
- OPTION_WS_PACK} = require('../../constants')
9
+ OPTION_WS_PACK} = require('../../constants');
10
10
  const { WARNING } = EdmxBuildPlugin
11
- const LOG = cds.log('cli|build'), DEBUG = cds.debug('cli|build')
11
+ const LOG = cds.log('cli|build')
12
+ const DEBUG = cds.debug('cli|build')
12
13
  const NODE_ENGINE_VERSION = '>=20'
14
+ const { exec } = require('node:child_process')
15
+ const pexec = require('node:util').promisify(exec);
13
16
 
14
17
  /**
15
18
  * @param {T | T[]} x
@@ -84,9 +87,65 @@ class NodejsBuildPlugin extends EdmxBuildPlugin {
84
87
  await this._packWorkspaceDependencies(destRoot)
85
88
  }
86
89
  }
90
+
91
+ await this.#cleanupPackageJson(destRoot)
92
+
87
93
  return this._result
88
94
  }
89
95
 
96
+ /**
97
+ * Removes devDeps that are invalid semver, pointing to workspace or file locations,
98
+ * and also removes workspace entries. If needed, creates a new package-lock.json in build destination.
99
+ */
100
+ async #cleanupPackageJson() {
101
+ /** @param {string | undefined} version */
102
+ const valid = version => version && !version.match('^(?:workspace|file):')
103
+
104
+ const packageJsonPath = path.join(this.task.dest, 'package.json')
105
+ try {
106
+ const packageJson = await cds.utils.read(packageJsonPath)
107
+
108
+ DEBUG?.(`Sanitizing the package.json for "${this.task.src}'...`)
109
+
110
+ let modified = false
111
+ // remove the workspace configuration
112
+ if (packageJson.workspaces) {
113
+ delete packageJson.workspaces
114
+ modified = true
115
+ }
116
+ // remove the workspace dev dependencies and the cds-plugin-ui5
117
+ if (packageJson.devDependencies) {
118
+ packageJson.devDependencies = Object.entries(packageJson.devDependencies)
119
+ .reduce((acc, [dep, version]) => {
120
+ if (valid(version)) {
121
+ acc[dep] = version
122
+ }
123
+ return acc
124
+ }, {})
125
+ modified = true
126
+ }
127
+ // overwrite the package.json if it was modified only
128
+ if (modified) {
129
+ await cds.utils.write(packageJsonPath, JSON.stringify(packageJson, null, 2), 'utf-8')
130
+ // update the package-lock.json if it exists
131
+ if (cds.utils.exists(path.join(this.task.dest, 'package-lock.json'))) {
132
+ DEBUG?.(`Updating the package-lock.json for '${this.task.src}'...`)
133
+ // run the npm install --package-lock-only to only update the package-lock.json
134
+ // without installing the dependencies to node_modules
135
+ try {
136
+ await pexec('npm install --package-lock-only', { cwd: this.task.dest })
137
+ } catch (e) {
138
+ DEBUG?.(`Failed to update the package-lock.json for '${this.task.src}'! Error: ${e.code} - ${e.message}`)
139
+ }
140
+ }
141
+ }
142
+ } catch (e) {
143
+ // ENOENT: package.json not found -> this is okay for monorepos
144
+ if (e.code !== 'ENOENT') throw e
145
+ }
146
+
147
+ }
148
+
90
149
  async clean() {
91
150
  // staging build content is deleted by BuildTaskEngine
92
151
  if (cds.env.build.target === '.') {
@@ -186,7 +245,7 @@ class NodejsBuildPlugin extends EdmxBuildPlugin {
186
245
  async _packWorkspaceDependencies(dest) {
187
246
  const wsRoot = findWorkspaceRoot(cds.root)
188
247
  const appPkg = require(path.join(dest, 'package.json'))
189
- const allWorkspaces = await getWorkspaces(wsRoot, true, false);
248
+ const allWorkspaces = await getWorkspaces(wsRoot, true, false)
190
249
  let workspaces
191
250
  if (this.task.options.workspaces) {
192
251
  const relevantWorkspaces = new Set(arrayify(this.task.options.workspaces))
@@ -1,5 +1,5 @@
1
1
  module.exports = {
2
2
  options: /** @type{const} */(['--to', '--for', '--out', '--on', '--tunnel-address', '--vcap-file', '--delta-from']),
3
- flags: /** @type{const} */(['--no-save', '--auto-undeploy', '--dry', '--xdry', '--with-mocks', '--store-credentials', '--no-build', '--model-only', '--script', '--with-auto-schema-evolution']),
3
+ flags: /** @type{const} */(['--no-save', '--auto-undeploy', '--dry', '--xdry', '--with-mocks', '--store-credentials', '--no-build', '--model-only', '--script', '--with-auto-schema-evolution', '--resolve-bindings']),
4
4
  shortcuts: ['-2', '-4', '-o', '-n']
5
5
  }
@@ -30,7 +30,8 @@ const allowList = [
30
30
  '--tunnel-address',
31
31
  '--vcap-file',
32
32
  '--with-mocks',
33
- '--no-build'
33
+ '--no-build',
34
+ '--resolve-bindings'
34
35
  ]
35
36
 
36
37
  module.exports = new class HanaDeployer {
@@ -190,7 +191,22 @@ spec:
190
191
  serviceName ??= cfServiceInstanceName;
191
192
  serviceKeyName ??= cfServiceInstanceKeyName
192
193
  if (!vcapEnv.VCAP_SERVICES || serviceName) {
193
- vcapEnv.VCAP_SERVICES = this._getVCAPServicesEntry(cfServiceInstanceName, serviceKey);
194
+ const base = process.env.VCAP_SERVICES ? JSON.parse(process.env.VCAP_SERVICES) : (vcapEnv.VCAP_SERVICES ?? {});
195
+ const hanas = Array.isArray(base.hana) ? base.hana : [];
196
+ let hanaTagged = hanas.filter(s => s?.name === cfServiceInstanceName || (Array.isArray(s?.tags) && s.tags.includes('hana')));
197
+ if (hanaTagged.length === 0) {
198
+ const entry = { name: cfServiceInstanceName, label: 'hana', tags: ['hana'], credentials: {} };
199
+ hanas.push(entry);
200
+ hanaTagged = [entry];
201
+ }
202
+ for (const entry of hanaTagged) {
203
+ entry.name ??= cfServiceInstanceName;
204
+ entry.tags = Array.from(new Set([...entry.tags || [], 'hana']));
205
+ entry.credentials = { ...(entry.credentials || {}), ...serviceKey };
206
+ }
207
+ base.hana = hanas;
208
+ vcapEnv.VCAP_SERVICES = base;
209
+ process.env.VCAP_SERVICES = JSON.stringify(base);
194
210
  }
195
211
 
196
212
  if (!hasVCAPEnv) {
@@ -229,7 +245,6 @@ spec:
229
245
  return buildResult;
230
246
  }
231
247
 
232
-
233
248
  async _getOrCreateCFService(currentModelFolder, serviceName, serviceKeyName, tunnelAddress) {
234
249
 
235
250
  // get from param
@@ -297,19 +312,6 @@ spec:
297
312
  return buildResult[0].result;
298
313
  }
299
314
 
300
- _getVCAPServicesEntry(serviceInstanceName, serviceKey) {
301
- return {
302
- hana: [
303
- {
304
- name: serviceInstanceName,
305
- tags: ['hana'],
306
- credentials: serviceKey
307
- }
308
- ]
309
- };
310
- }
311
-
312
-
313
315
  _injectTunnelAddress(credentials, tunnelAddress) {
314
316
  if (!/\w+:\d+/.test(tunnelAddress)) {
315
317
  throw `Invalid tunnel address '${tunnelAddress}' - must be in form 'host:port'`;
@@ -10,7 +10,7 @@ let versionInfo = require('../../../package.json').version;
10
10
  const { warn } = require('../../util/term');
11
11
 
12
12
  const edmxncdsdatatype = {
13
- "Edm.String": "cds.LargeString",
13
+ "Edm.String": "cds.String",
14
14
  "Edm.Boolean": "cds.Boolean",
15
15
  "Edm.Int16": "cds.Integer",
16
16
  "Edm.Int32": "cds.Integer",
@@ -592,7 +592,7 @@ function _getServiceEntityProperty(
592
592
  }
593
593
 
594
594
  if (defaultValue !== -1 && defaultValue) {
595
- propertyJson = propertyJson + ', \n"default": {\n"val": "' + defaultValue + '"\n}';
595
+ propertyJson = propertyJson + ', \n"default": {\n"val": ' + JSON.stringify(defaultValue) + '\n}';
596
596
  }
597
597
 
598
598
  if (propertyName)
@@ -33,7 +33,7 @@ const edmToCdsTypeMapping = {
33
33
  'Edm.Int16': 'cds.Integer',
34
34
  'Edm.Int32': 'cds.Integer',
35
35
  'Edm.Int64': 'cds.Integer64',
36
- 'Edm.String': 'cds.LargeString',
36
+ 'Edm.String': 'cds.String',
37
37
  'Edm.TimeOfDay': 'cds.Time',
38
38
  };
39
39
 
@@ -261,7 +261,26 @@ function _captureAnnotationInCSN(targetString, annotationObject, csnDefsTargetOb
261
261
  annotationObject = { "=": annotationObject.$Path };
262
262
  }
263
263
 
264
- csnDefsTargetObject[targetString] = annotationObject;
264
+ /**
265
+ * Annotations need to be flattened in CSN. E.g.
266
+ *
267
+ * @Core.ContentDisposition : {
268
+ * Type : 'inline',
269
+ * Filename: typedImageFileName
270
+ * }
271
+ *
272
+ * is represented as:
273
+ *
274
+ * @Core.ContentDisposition.Type : 'inline',
275
+ * @Core.ContentDisposition.Filename : {'=': 'typedImageFileName'},
276
+ */
277
+ if (annotationObject instanceof Object && !(annotationObject instanceof Array) && !(Object.keys(annotationObject).length === 1 && (Object.keys(annotationObject)[0] === '=' || Object.keys(annotationObject)[0] === 'xpr' || Object.keys(annotationObject)[0] === '#'))) {
278
+ Object.keys(annotationObject).forEach(innerTargetString => {
279
+ _captureAnnotationInCSN(targetString + '.' + innerTargetString, annotationObject[innerTargetString], csnDefsTargetObject);
280
+ })
281
+ } else {
282
+ csnDefsTargetObject[targetString] = annotationObject;
283
+ }
265
284
  }
266
285
 
267
286
  function _replaceSpecialCharacters(text) {
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "dependencies": {
3
- "@sap-cloud-sdk/connectivity": "^3",
4
- "@sap-cloud-sdk/http-client": "^3",
5
- "@sap-cloud-sdk/resilience": "^3"
3
+ "@sap-cloud-sdk/connectivity": "^4",
4
+ "@sap-cloud-sdk/http-client": "^4",
5
+ "@sap-cloud-sdk/resilience": "^4"
6
6
  }
7
- }
7
+ }
@@ -5,7 +5,7 @@ module.exports = {
5
5
  COMMAND_ADD: 'add',
6
6
 
7
7
  JAVA_LTS_VERSIONS: [17, 21],
8
- MAVEN_ARCHETYPE_VERSION: '4.3.0',
8
+ MAVEN_ARCHETYPE_VERSION: '4.4.0',
9
9
 
10
10
  OPTIONS: Object.freeze({
11
11
  COMPLETION: 'completion',
@@ -54,6 +54,7 @@ module.exports = {
54
54
  CONNECTIVITY: 'connectivity',
55
55
  DESTINATION: 'destination',
56
56
  HTML5_REPO: 'html5-repo',
57
+ APP_FRONTEND: 'app-frontend',
57
58
  PORTAL: 'portal',
58
59
  APPLICATION_LOGGING: 'application-logging',
59
60
  AUDIT_LOGGING: 'audit-logging',
package/lib/init/index.js CHANGED
@@ -233,6 +233,7 @@ module.exports = class CDSGenerator {
233
233
  const templates = new Map
234
234
  for (let plugin of plugins) {
235
235
  // Compat
236
+ if (plugin === 'appfront') plugin = 'app-front'
236
237
  if (plugin === 'helm' && cds.cli.options['internal-unified-runtime-charts']) {
237
238
  plugin = 'helm-unified-runtime'
238
239
  cds.cli.options.add.delete('helm')
@@ -242,6 +243,7 @@ module.exports = class CDSGenerator {
242
243
  if (plugin in { 'kibana-logging': 1, 'kibana': 1 }) plugin = 'application-logging'
243
244
  if (plugin === 'postgresql') plugin = 'postgres'
244
245
  if (plugin === 'gha') plugin = 'github-actions'
246
+ if (plugin === 'app-front') plugin = 'app-frontend'
245
247
 
246
248
  if (!templates.has(plugin)) {
247
249
  const template = await this._createTemplate(plugin)
package/lib/init/mvn.js CHANGED
@@ -21,8 +21,9 @@ module.exports.add = async function (feature, featureOptions={}) {
21
21
  }
22
22
  params.set(`-Dfeature`, feature)
23
23
 
24
- const profile = cds.cli.options.for
25
- params.set(`-Dprofile`, profile === 'production' ? 'cloud' : profile ?? 'default')
24
+ const profile = cds.cli.options.for === 'production' ? 'cloud' : cds.cli.options.for ?? 'default'
25
+ params.set(`-Dprofile`, profile)
26
+ if (feature === 'mtx' && profile === 'default') params.set('-Dprofile', 'with-mtx')
26
27
 
27
28
  Object.entries(featureOptions).forEach(([k,v]) => params.set(`-Doptions.${k}`, v))
28
29
 
@@ -141,6 +141,7 @@ module.exports = new class ProjectReader {
141
141
  devDependencies: () => pkgJson().devDependencies,
142
142
  has: x => _inProd(x),
143
143
  profile: () => cds.cli.options?.for,
144
+ profileSpecified: () => !!cds.cli.options.for,
144
145
  language: () => _isJava() ? 'java' : 'nodejs',
145
146
  isJava: () => _isJava(),
146
147
  isNodejs: () => !_isJava(),
@@ -10,6 +10,11 @@ const srv4 = isJava ? srvJava4 : srvNode4
10
10
 
11
11
  const approuter = { in: 'modules', where: { type: 'approuter.nodejs' } }
12
12
 
13
+ const appFront = {
14
+ in: 'resources',
15
+ where: { type: 'org.cloudfoundry.managed-service', 'parameters.service': 'app-front' }
16
+ }
17
+
13
18
  const xsuaa = {
14
19
  in: 'resources',
15
20
  where: { type: 'org.cloudfoundry.managed-service', 'parameters.service': 'xsuaa', 'parameters.service-plan': 'application' }
@@ -233,6 +238,7 @@ module.exports = {
233
238
  applicationLogging,
234
239
  approuterMTXRoute,
235
240
  approuterMTXRouteJava,
241
+ appFront,
236
242
  html5RepoHost,
237
243
  html5Runtime,
238
244
  appDeployer,
@@ -18,7 +18,7 @@ applications:
18
18
  buildpack: nodejs_buildpack
19
19
  command: (npm start && echo "This application may now be stopped to free resources." || echo "AMS Policy Deployment unsuccessful.") && sleep infinity
20
20
  services:
21
- - name: {{appName}}-auth
21
+ - name: {{appName}}-ias
22
22
  parameters:
23
23
  credential-type: X509_GENERATED
24
24
  app-identifier: ams-policy-deployer
@@ -29,7 +29,7 @@ modules:
29
29
  command: npm start
30
30
  memory: 512M
31
31
  requires:
32
- - name: {{appName}}-auth
32
+ - name: {{appName}}-ias
33
33
  parameters:
34
34
  config:
35
35
  credential-type: X509_GENERATED
@@ -0,0 +1,20 @@
1
+ {
2
+ "name": "{{app}}"{{#hasUI5}},
3
+ "version": "1.0.0",
4
+ "main": "webapp/index.html",
5
+ "scripts": {
6
+ "build": "ui5 build preload --clean-dest --config ui5-deploy.yaml",
7
+ "build-local": "ui5 build preload --clean-dest",
8
+ "start": "ui5 serve"
9
+ },
10
+ "devDependencies": {
11
+ "@ui5/cli": "^4",
12
+ "ui5-task-zipper": "^3"
13
+ },
14
+ "ui5": {
15
+ "dependencies": [
16
+ "ui5-task-zipper"
17
+ ]
18
+ }
19
+ {{/hasUI5}}
20
+ }
@@ -0,0 +1,34 @@
1
+ <!DOCTYPE html>
2
+ <html>
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>{{appName}} - {{app}}</title>
7
+ <style>
8
+ html, body, body > div, #container, #container-uiarea {
9
+ height: 100%;
10
+ }
11
+ </style>
12
+ <script
13
+ id="sap-ui-bootstrap"
14
+ src="https://sapui5.hana.ondemand.com/1.140.0/resources/sap-ui-core.js"
15
+ data-sap-ui-theme="sap_horizon"
16
+ data-sap-ui-resourceroots='{
17
+ "{{appName}}.{{app}}": "./"
18
+ }'
19
+ data-sap-ui-oninit="module:sap/ui/core/ComponentSupport"
20
+ data-sap-ui-compatVersion="edge"
21
+ data-sap-ui-async="true"
22
+ data-sap-ui-frameOptions="trusted"
23
+ ></script>
24
+ </head>
25
+ <body class="sapUiBody sapUiSizeCompact" id="content">
26
+ <div
27
+ data-sap-ui-component
28
+ data-name="{{appName}}.{{app}}"
29
+ data-id="container"
30
+ data-settings='{"id" : "{{appName}}.{{app}}" }'
31
+ data-handle-validation="true"
32
+ ></div>
33
+ </body>
34
+ </html>
@@ -0,0 +1,13 @@
1
+ {
2
+ "_version": "1.49.0",
3
+ "sap.app": {
4
+ "id": "{{appName}}",
5
+ "applicationVersion": {
6
+ "version": "1.0.0"
7
+ },
8
+ "type": "application",
9
+ "title": "{{appTitle}}",
10
+ "description": "{{appDescription}}",
11
+ "i18n": "i18n/i18n.properties"
12
+ }
13
+ }
@@ -0,0 +1,71 @@
1
+ ---
2
+ parameters:
3
+ deploy_mode: html5-repo
4
+ modules:
5
+ - name: {{appName}}-srv
6
+ type: {{language}}
7
+ path: {{& srvPath}}
8
+
9
+ - name: {{appName}}
10
+ type: approuter.nodejs
11
+ path: {{& approuterPath}}
12
+ requires:
13
+ - name: {{appName}}-html5-runtime
14
+
15
+ - name: {{appName}}-app-deployer
16
+ type: com.sap.application.content
17
+ path: {{#isNodejs}}gen{{/isNodejs}}{{#isJava}}.{{/isJava}}
18
+ build-parameters:
19
+ build-result: {{#isNodejs}}{{& appPath }}{{/isNodejs}}{{#isJava}}resources{{/isJava}}
20
+ requires:
21
+ {{#apps}}
22
+ - name: {{strippedAppName}}{{strippedApp}}
23
+ artifacts:
24
+ - {{app}}.zip
25
+ target-path: {{#isNodejs}}{{& appPath }}{{/isNodejs}}{{#isJava}}resources{{/isJava}}
26
+ {{/apps}}
27
+ requires:
28
+ - name: srv-api
29
+ {{#hasXsuaa}}
30
+ - name: {{appName}}-auth
31
+ {{/hasXsuaa}}
32
+ {{#hasIas}}
33
+ - name: {{appName}}-ias
34
+ parameters:
35
+ config:
36
+ credential-type: X509_GENERATED
37
+ app-identifier: app-deployer
38
+ {{/hasIas}}
39
+ - name: {{appName}}-app-front
40
+ parameters:
41
+ content-target: true
42
+ parameters:
43
+ config:
44
+ destinations:
45
+ - name: srv-api
46
+ url: ~{srv-api/srv-url}
47
+ forwardAuthToken: true
48
+ {{#hasUI5}}
49
+ - name: ui5
50
+ url: https://ui5.sap.com
51
+ {{/hasUI5}}
52
+
53
+ {{#apps}}
54
+ - name: {{strippedAppName}}{{strippedApp}}
55
+ type: html5
56
+ path: {{& appPath}}{{app}}
57
+ build-parameters:
58
+ build-result: dist
59
+ builder: custom
60
+ commands:
61
+ - npm ci
62
+ - npm run build
63
+ supported-platforms: []
64
+ {{/apps}}
65
+
66
+ resources:
67
+ - name: {{appName}}-app-front
68
+ type: org.cloudfoundry.managed-service
69
+ parameters:
70
+ service: app-front
71
+ service-plan: developer
@@ -0,0 +1,17 @@
1
+ {
2
+ {{#isNodejs}}
3
+ "cds": {
4
+ {{/isNodejs}}
5
+ "requires": {
6
+ {{#profile}}
7
+ "[{{profile}}]": {
8
+ {{/profile}}
9
+ "app-front": true
10
+ {{#profile}}
11
+ }
12
+ {{/profile}}
13
+ }
14
+ {{#isNodejs}}
15
+ }
16
+ {{/isNodejs}}
17
+ }
@@ -0,0 +1,25 @@
1
+ ---
2
+ # yaml-language-server: $schema=https://sap.github.io/ui5-tooling/schema/ui5.yaml.json
3
+ specVersion: '3.1'
4
+ metadata:
5
+ name: {{app}}
6
+ type: application
7
+ resources:
8
+ configuration:
9
+ propertiesFileSourceEncoding: UTF-8
10
+ builder:
11
+ resources:
12
+ excludes:
13
+ - "/test/**"
14
+ - "/localService/**"
15
+ customTasks:
16
+ - name: ui5-task-zipper
17
+ afterTask: generateVersionInfo
18
+ configuration:
19
+ archiveName: {{app}}
20
+ additionalFiles:
21
+ - xs-app.json
22
+ {{#isTypescript}}
23
+ - name: ui5-tooling-transpile-task
24
+ afterTask: replaceVersion
25
+ {{/isTypescript}}
@@ -0,0 +1,6 @@
1
+ ---
2
+ # yaml-language-server: $schema=https://sap.github.io/ui5-tooling/schema/ui5.yaml.json
3
+ specVersion: '3.1'
4
+ metadata:
5
+ name: {{app}}
6
+ type: application
@@ -0,0 +1,20 @@
1
+ ---
2
+ app-front:
3
+ serviceOfferingName: app-front
4
+ servicePlanName: developer
5
+ html5-apps-deployer:
6
+ env:
7
+ SAP_CLOUD_SERVICE: {{strippedAppName}}.service
8
+ image:
9
+ repository: {{appName}}-html5-deployer
10
+ bindings:
11
+ app-front:
12
+ serviceInstanceName: app-front
13
+ {{=<% %>=}}
14
+ envFrom:
15
+ - configMapRef:
16
+ name: "{{ .Release.Name }}-app-front-configmap"
17
+ <%={{ }}=%>
18
+ backendDestinations:
19
+ srv-api:
20
+ service: srv
@@ -0,0 +1,17 @@
1
+ {
2
+ "welcomeFile": "/index.html",
3
+ "authenticationMethod": "route",
4
+ "routes": [
5
+ {
6
+ "source": "^/?odata/(.*)$",
7
+ "target": "/odata/$1",
8
+ "destination": "srv-api"{{#hasXsuaa}},
9
+ "authenticationType": "xsuaa"{{/hasXsuaa}},
10
+ "csrfProtection": false
11
+ },
12
+ {
13
+ "source": ".*",
14
+ "service": "app-front"
15
+ }
16
+ ]
17
+ }
@@ -0,0 +1,98 @@
1
+ const { join } = require('path')
2
+ const { readProject } = require('../../projectReader')
3
+ const { merge } = require('../../merge')
4
+ const { srv4, appDeployer, appFront } = require('../../registries/mta')
5
+ const { odataApi } = require('../../registries/xs-app')
6
+ const { renderAndCopy } = require('../../template')
7
+ const { exists } = require('../../../cds').utils
8
+
9
+ module.exports = class extends require('../../plugin') {
10
+
11
+ static help() {
12
+ return 'SAP BTP AppFront service' // TODO: Product name?
13
+ }
14
+
15
+ static hasInProduction(env) {
16
+ return !!env.requires['app-front']
17
+ }
18
+
19
+ async run() {
20
+ const project = readProject()
21
+ const { appUIPaths, apps, appPath, configFile, hasUI5 } = project
22
+ await merge(__dirname, 'files/package.json.hbs').into(configFile, { project })
23
+
24
+ if (hasUI5) {
25
+ for (const { app } of apps) {
26
+ project.app = app
27
+ await merge(__dirname, 'files/ui5.yaml.hbs').into(join(appPath, app, 'ui5.yaml'), { project })
28
+ }
29
+ for (const { app } of apps) {
30
+ project.app = app
31
+ await merge(__dirname, 'files/ui5-deploy.yaml.hbs').into(join(appPath, app, 'ui5-deploy.yaml'), { project })
32
+ }
33
+ }
34
+
35
+ for (const { app } of apps) {
36
+ project.app = app
37
+ await merge(__dirname, 'files/app-package.json.hbs').into(join(appPath, app, 'package.json'), { project })
38
+ if (hasUI5) {
39
+ await renderAndCopy(join(__dirname, 'files/index.html.hbs'), join(appPath, app, 'webapp/index.html'), project)
40
+ }
41
+ }
42
+
43
+ // app deployer requires a manifest.json
44
+ await Promise.all(appUIPaths.map(p =>
45
+ merge(__dirname, 'files/manifest.json')
46
+ .into(join(appPath, p, 'webapp/manifest.json'), { project })
47
+ ))
48
+ }
49
+
50
+ async combine() {
51
+ const project = readProject()
52
+ const { srvPath, appPath, hasMta, apps } = project
53
+
54
+ await Promise.all(apps.map(async ({ app }) => {
55
+ project.app = app
56
+ project.hasIndexHtml = exists(join(appPath, app, 'webapp/index.html'))
57
+ const additions = [odataApi]
58
+ await merge(__dirname, 'files/xs-app.json.hbs').into(join(appPath, app, 'xs-app.json'), { with: project, additions })
59
+ }))
60
+
61
+ // later -> rather consider adding to app-front package.json
62
+ // if (hasMultitenancy) {
63
+ // await merge({ cds: { requires: { 'app-front':true } } }).into('mtx/sidecar/package.json')
64
+ // }
65
+
66
+ if (hasMta) {
67
+ const srv = srv4(srvPath)
68
+ project.apps = apps
69
+ const appModules = project.apps.map(app => ({
70
+ in: 'modules',
71
+ where: { type: 'html5', path: `${appPath}${app.app}` }
72
+ }))
73
+ const additions = [srv, appDeployer, appFront, ...appModules]
74
+ const relationships = [{
75
+ insert: [appFront, 'name'],
76
+ into: [appDeployer, 'requires', 'name'],
77
+ }]
78
+ await merge(__dirname, 'files/mta.yaml.hbs').into('mta.yaml', {
79
+ with: project,
80
+ additions,
81
+ relationships
82
+ })
83
+ }
84
+
85
+ // When Helm support is added
86
+ // if (hasContainerize) {
87
+ // const additions = [{
88
+ // in: 'modules',
89
+ // where: { name: `${project.appName}-content-deployer` }
90
+ // }]
91
+ // await merge(__dirname, '/files/containerize.yaml.hbs').into('containerize.yaml', { with: project, additions })
92
+ // }
93
+ // if (hasHelm || hasHelmUnifiedRuntime) {
94
+ // await renderAndCopy(join(__dirname, 'files/ui-resources'), 'ui-resources', project)
95
+ // await merge(__dirname, 'files/values.yaml.hbs').into('chart/values.yaml', { with: project })
96
+ // }
97
+ }
98
+ }