@sap/cds-dk 9.8.4 → 9.9.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 (84) hide show
  1. package/CHANGELOG.md +32 -2
  2. package/bin/add.js +3 -3
  3. package/bin/cds.js +28 -5
  4. package/bin/debug.js +4 -3
  5. package/bin/import.js +6 -2
  6. package/bin/logout.js +9 -12
  7. package/lib/bind/shared.js +2 -2
  8. package/lib/build/constants.js +0 -1
  9. package/lib/build/index.js +420 -463
  10. package/lib/build/plugins/hana/index.js +121 -2
  11. package/lib/build/plugins/helm/files/subcharts/content-deployment/Chart.yaml +1 -1
  12. package/lib/build/plugins/helm/files/subcharts/content-deployment/templates/job.yaml +1 -1
  13. package/lib/build/plugins/helm/files/subcharts/content-deployment/values.schema.json +4 -0
  14. package/lib/build/plugins/helm/files/subcharts/content-deployment/values.yaml +5 -2
  15. package/lib/build/plugins/internal.js +1 -1
  16. package/lib/build/plugins/mtx-extension/index.js +0 -2
  17. package/lib/deploy/index.js +23 -3
  18. package/lib/deploy/to-hana/hana.js +24 -15
  19. package/lib/import/common.js +6 -3
  20. package/lib/init/constants.js +3 -1
  21. package/lib/init/indent.js +0 -1
  22. package/lib/init/merge.js +0 -1
  23. package/lib/init/mvn.js +4 -2
  24. package/lib/init/projectReader.js +0 -1
  25. package/lib/init/registries/mta.js +4 -2
  26. package/lib/init/template/app-frontend/files/ui5.yaml.hbs +1 -1
  27. package/lib/init/template/app-frontend/index.js +39 -4
  28. package/lib/init/template/data/as-csv.js +2 -0
  29. package/lib/init/template/enterprise-messaging/index.js +1 -1
  30. package/lib/init/template/enterprise-messaging-shared/index.js +1 -1
  31. package/lib/init/template/esm/index.js +5 -0
  32. package/lib/init/template/extensibility/index.js +1 -1
  33. package/lib/init/template/hana/index.js +1 -1
  34. package/lib/init/template/html5-repo/files/mta.yaml.hbs +9 -20
  35. package/lib/init/template/html5-repo/files/ui5.yaml.hbs +1 -1
  36. package/lib/init/template/http/index.js +34 -6
  37. package/lib/init/template/kyma/index.js +1 -0
  38. package/lib/init/template/lint/files/package.json +1 -1
  39. package/lib/init/template/nodejs/files/package.json.hbs +1 -1
  40. package/lib/init/template/pipeline/files/ui5.yaml.hbs +1 -1
  41. package/lib/init/template/postgres/files/values.yaml.hbs +1 -1
  42. package/lib/init/template/postgres/index.js +1 -1
  43. package/lib/init/template/react/files/index.html +13 -0
  44. package/lib/init/template/react/files/package.json.hbs +22 -0
  45. package/lib/init/template/react/files/public/favicon.svg +1 -0
  46. package/lib/init/template/react/files/src/App.jsx.hbs +29 -0
  47. package/lib/init/template/react/files/src/index.css +28 -0
  48. package/lib/init/template/react/files/src/main.jsx +10 -0
  49. package/lib/init/template/react/files/vite.config.js +19 -0
  50. package/lib/init/template/react/index.js +29 -0
  51. package/lib/init/template/sample/index.js +1 -4
  52. package/lib/init/template/test/files/package.json +1 -1
  53. package/lib/init/template/test/files/test/test.xs.hbs +3 -3
  54. package/lib/init/template/typer/index.js +1 -1
  55. package/lib/init/template/typescript/files/package.json +1 -1
  56. package/lib/init/template/typescript/index.js +1 -1
  57. package/lib/init/template/ui5/index.js +2 -1
  58. package/lib/init/template/vue/files/index.html +13 -0
  59. package/lib/init/template/vue/files/package.json.hbs +19 -0
  60. package/lib/init/template/vue/files/public/favicon.svg +1 -0
  61. package/lib/init/template/vue/files/src/App.vue.hbs +34 -0
  62. package/lib/init/template/vue/files/src/main.js +5 -0
  63. package/lib/init/template/vue/files/src/style.css +20 -0
  64. package/lib/init/template/vue/files/vite.config.js +19 -0
  65. package/lib/init/template/vue/index.js +30 -0
  66. package/lib/init/template/workzone-standard/files/mta.yaml.hbs +2 -13
  67. package/lib/init/xml.js +0 -2
  68. package/lib/mtx/auth_manager.js +21 -11
  69. package/lib/mtx/download_legacy.js +9 -8
  70. package/lib/mtx/pull.js +9 -8
  71. package/lib/mtx/push.js +42 -28
  72. package/lib/mtx/settings_manager.js +17 -10
  73. package/lib/mtx/tenant_action.js +9 -9
  74. package/lib/mtx/util/request.js +158 -0
  75. package/lib/util/cf.js +30 -29
  76. package/lib/watch/node-watch.js +1 -1
  77. package/lib/watch/vite-watch.js +76 -0
  78. package/lib/watch/watched.js +5 -4
  79. package/npm-shrinkwrap.json +25 -165
  80. package/package.json +3 -4
  81. package/lib/init/template/sample/files/db/hana/index.cds +0 -10
  82. package/lib/init/template/sample/files/db/sqlite/index.cds +0 -10
  83. package/lib/init/template/sample/files/package.json.hbs +0 -14
  84. package/lib/util/axios.js +0 -118
@@ -1,5 +1,4 @@
1
1
  const https = require('node:https')
2
- const axios = require('axios')
3
2
  const asJson = require("../data/as-json")
4
3
  const cds = require('../../../cds')
5
4
  const cf = require('../../../util/cf')
@@ -255,13 +254,42 @@ module.exports = class HttpTemplate extends require('../../plugin') {
255
254
  const { url, clientid, clientsecret, certurl, certificate, key } = credentials
256
255
  if (!url) throw new Error('No authentication service found in the environment of ' + app)
257
256
 
258
- const auth = certificate ? { maxRedirects: 0, httpsAgent: new https.Agent({ cert: certificate, key }) }
259
- : { auth: { username: clientid, password: clientsecret } }
260
257
  const suffix = xsuaa ? 'oauth/token' : 'oauth2/token'
261
- const authUrl = `${certurl ?? url}/${suffix}`
258
+ const authUrl = new URL(`${certurl ?? url}/${suffix}`)
262
259
  const data = `grant_type=client_credentials&client_id=${encodeURI(clientid)}`
263
- const config = { method: 'POST', timeout: 5000, data, ...auth }
264
- const { access_token } = (await axios(authUrl, config)).data
260
+ const headers = { 'Content-Type': 'application/x-www-form-urlencoded' }
261
+ let access_token
262
+ if (certificate) {
263
+ const raw = await new Promise((resolve, reject) => {
264
+ const req = https.request({
265
+ hostname: authUrl.hostname,
266
+ port: authUrl.port || 443,
267
+ path: authUrl.pathname + authUrl.search,
268
+ cert: certificate, key,
269
+ method: 'POST',
270
+ headers
271
+ }, res => {
272
+ const chunks = []
273
+ res.on('data', x => chunks.push(x))
274
+ res.on('end', () => {
275
+ if (res.statusCode >= 200 && res.statusCode < 300) resolve(Buffer.concat(chunks).toString())
276
+ else reject(new Error(`Failed to fetch access token: ${res.statusCode}`))
277
+ })
278
+ })
279
+ req.on('error', reject)
280
+ req.end(data)
281
+ });
282
+ ({ access_token } = JSON.parse(raw))
283
+ } else {
284
+ headers['Authorization'] = 'Basic ' + Buffer.from(`${clientid}:${clientsecret}`).toString('base64')
285
+ const response = await fetch(authUrl.toString(), {
286
+ method: 'POST',
287
+ headers,
288
+ body: data
289
+ })
290
+ if (!response.ok) throw new Error(`Failed to fetch access token: ${response.status} ${response.statusText}`)
291
+ ;({ access_token } = await response.json())
292
+ }
265
293
  const approuterAuth = `x-approuter-authorization: bearer ${access_token}`
266
294
  return [hostname, approuterAuth]
267
295
  }
@@ -16,6 +16,7 @@ module.exports = class KymaTemplate extends require('../../plugin') {
16
16
  },
17
17
  'y': {
18
18
  type: 'boolean',
19
+ help: 'Skip interactive prompts.'
19
20
  }
20
21
  }
21
22
  }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "devDependencies": {
3
3
  "@sap/eslint-plugin-cds": "^4",
4
- "eslint": "^9"
4
+ "eslint": "^10"
5
5
  }
6
6
  }
@@ -5,7 +5,7 @@
5
5
  "@sap/cds": "^9"
6
6
  },
7
7
  "devDependencies": {
8
- "@cap-js/sqlite": "^2"
8
+ "@cap-js/sqlite": "^2.4"
9
9
  },
10
10
  "scripts": {
11
11
  "start": "cds-serve"
@@ -1,5 +1,5 @@
1
1
  ---
2
- # yaml-language-server: $schema=https://sap.github.io/ui5-tooling/schema/ui5.yaml.json
2
+ # yaml-language-server: $schema=https://ui5.github.io/cli/schema/ui5.yaml.json
3
3
  specVersion: '4.0'
4
4
  metadata:
5
5
  name: {{app}}
@@ -3,7 +3,7 @@ srv:
3
3
  bindings:
4
4
  postgres:
5
5
  serviceInstanceName: postgres
6
- postgres-db-deployer:
6
+ postgres-deployer:
7
7
  image:
8
8
  repository: {{appName}}-postgres-deployer
9
9
  bindings:
@@ -21,7 +21,7 @@ module.exports = class PostgresTemplate extends require('../../plugin') {
21
21
  const project = readProject()
22
22
  const { configFile, isJava } = project
23
23
  project.shortcut = !exists(configFile) || typeof (await read(configFile)).cds?.requires?.db !== 'object'
24
- await merge(__dirname, 'files/package.json.hbs').into(configFile, { with: project, forceOverwrite: true }) // REVISIT: forceOverwrite: true as default?
24
+ await merge(__dirname, 'files/package.json.hbs').into(configFile, { with: project, forceOverwrite: true })
25
25
  if (isJava) await mvn.add('postgresql')
26
26
 
27
27
  await sort('package.json', 'dependencies')
@@ -0,0 +1,13 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>catalog</title>
8
+ </head>
9
+ <body>
10
+ <div id="root"></div>
11
+ <script type="module" src="/src/main.jsx"></script>
12
+ </body>
13
+ </html>
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "{{app}}",
3
+ "private": true,
4
+ "version": "1.0.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "vite build",
9
+ "preview": "vite preview"
10
+ },
11
+ "dependencies": {
12
+ "react": "^19",
13
+ "react-dom": "^19"
14
+ },
15
+ "devDependencies": {
16
+ "@types/react": "^19",
17
+ "@types/react-dom": "^19",
18
+ "@vitejs/plugin-react": "^6",
19
+ "adm-zip": "^0",
20
+ "vite": "^8"
21
+ }
22
+ }
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 56 56" aria-hidden="true"><path fill="#5A7A94" d="M45.84 21.462a8.489 8.489 0 0 0-12.783-5.713A10.747 10.747 0 0 0 22.41 8.5C10 8.5 11.09 21.488 11.09 21.488A10.3 10.3 0 0 0 13 41.5v-3a7.477 7.477 0 0 1-5.639-5 7.247 7.247 0 0 1 4.666-9.164l2.279-.768-.286-1.5A7.521 7.521 0 0 1 13.9 20a10.535 10.535 0 0 1 2.188-5.828 7.375 7.375 0 0 1 6.4-2.824c3.264 0 7.286 2.9 8.221 5.636l.934 2.732 2.424-1.318a7.6 7.6 0 0 1 3.39-.894 5.546 5.546 0 0 1 5.443 4.518l.288 1.528 1.125.514A7.809 7.809 0 0 1 48.891 29.7 7.809 7.809 0 0 1 43 38.5v3.02A10.793 10.793 0 0 0 52 31a10.483 10.483 0 0 0-6.16-9.538"/><g fill="#0092D1" transform="scale(1.05 1.2)"><g transform="translate(-2 -7)"><path d="M24.561 38.939l-6-6a1.5 1.5 0 0 0-2.122 2.122L21.379 40l-4.94 4.939a1.5 1.5 0 0 0 2.122 2.122l6-6a1.5 1.5 0 0 0 0-2.122"/><path d="M26.5 35.5h12a1.5 1.5 0 0 0 0-3h-12a1.5 1.5 0 0 0 0 3"/><path d="M38.5 38.5h-9a1.5 1.5 0 0 0 0 3h9a1.5 1.5 0 0 0 0-3"/><path d="M38.5 44.5h-12a1.5 1.5 0 0 0 0 3h12a1.5 1.5 0 0 0 0-3"/></g></g></svg>
@@ -0,0 +1,29 @@
1
+ import { useEffect, useState } from 'react'
2
+
3
+ export default function App() {
4
+ const [books, setBooks] = useState([])
5
+
6
+ useEffect(() => {
7
+ fetch('/odata/v4/{{#isJava}}CatalogService{{/isJava}}{{^isJava}}catalog{{/isJava}}/Books')
8
+ .then(r => r.json())
9
+ .then(r => setBooks(r.value))
10
+ }, [])
11
+
12
+ return (
13
+ <>
14
+ <h1>Books</h1>
15
+ <table>
16
+ <tbody>
17
+ {books.map(b => (
18
+ <tr key={b.ID}>
19
+ <td>
20
+ <span className="title">{b.title}</span>{{^isJava}}
21
+ <span className="author"><i> by {b.author}</i></span>{{/isJava}}
22
+ </td>
23
+ </tr>
24
+ ))}
25
+ </tbody>
26
+ </table>
27
+ </>
28
+ )
29
+ }
@@ -0,0 +1,28 @@
1
+ :root {
2
+ --text: #111;
3
+ --text-muted: #777;
4
+ --bg: #fff;
5
+ --sans: system-ui, 'Segoe UI', Roboto, sans-serif;
6
+ font-family: var(--sans);
7
+ }
8
+
9
+ @media (prefers-color-scheme: dark) {
10
+ :root {
11
+ --text: #fff;
12
+ --text-muted: #a3a3a3;
13
+ --bg: #16171d;
14
+ }
15
+ }
16
+
17
+ body {
18
+ color: var(--text);
19
+ background: var(--bg);
20
+ }
21
+
22
+ .title {
23
+ color: var(--text);
24
+ }
25
+
26
+ .author {
27
+ color: var(--text-muted);
28
+ }
@@ -0,0 +1,10 @@
1
+ import { StrictMode } from 'react'
2
+ import { createRoot } from 'react-dom/client'
3
+ import './index.css'
4
+ import App from './App.jsx'
5
+
6
+ createRoot(document.getElementById('root')).render(
7
+ <StrictMode>
8
+ <App />
9
+ </StrictMode>,
10
+ )
@@ -0,0 +1,19 @@
1
+ import { defineConfig } from 'vite'
2
+ import AdmZip from 'adm-zip'
3
+ import react from '@vitejs/plugin-react'
4
+
5
+ export default defineConfig({
6
+ plugins: [react(), {
7
+ name: 'zip-dist', // zipping ./dist is required for BTP HTML5 repo or Application Frontend deployment
8
+ closeBundle() {
9
+ const zip = new AdmZip()
10
+ zip.addLocalFolder('dist')
11
+ zip.writeZip('dist/catalog.zip')
12
+ }
13
+ }],
14
+ server: {
15
+ proxy: {
16
+ '/odata': 'http://localhost:4004'
17
+ }
18
+ }
19
+ })
@@ -0,0 +1,29 @@
1
+ const cds = require('../../../cds')
2
+ const { path, sh: $ } = cds.utils
3
+ const { ask4 } = require('../../../util/question')
4
+ const { renderAndCopy } = require('../../template')
5
+
6
+ module.exports = class ReactTemplate extends require('../../plugin') {
7
+
8
+ options() {
9
+ return {
10
+ 'into': {
11
+ type: 'string',
12
+ short: 'i',
13
+ help: `Name of the app folder in 'app/'. Example: 'catalog' → 'app/catalog'.`
14
+ }
15
+ }
16
+ }
17
+
18
+ static help() {
19
+ return 'user interface for React'
20
+ }
21
+
22
+ async run() {
23
+ const app = cds.cli.options.into ?? await ask4([`app name: `])
24
+ let into = path.join(cds.env.folders.app, app)
25
+ await renderAndCopy(path.join(__dirname, 'files'), into, { app, isJava: cds.env['project-nature'] === 'java' })
26
+ if (cds.cli.command === 'init') into = path.join(cds.cli.argv[0], into)
27
+ await $`npm i --prefix ${into}`
28
+ }
29
+ }
@@ -2,7 +2,6 @@ const { join } = require('path')
2
2
  const cds = require('../../../cds')
3
3
  const { read, write, copy } = cds.utils
4
4
  const { readProject } = require('../../projectReader')
5
- const { merge } = require('../../merge')
6
5
  const { renderAndCopy } = require('../../template')
7
6
  const mvn = require('../../mvn')
8
7
 
@@ -46,8 +45,6 @@ module.exports = class SampleTemplate extends require('../../plugin') {
46
45
  ])
47
46
  }
48
47
 
49
- await merge(__dirname, 'files/package.json.hbs').into('package.json', { with: { appPath }})
50
-
51
48
  // manifest.json IDs must be unique -> otherwise deployment failures on conflicts
52
49
  await Promise.all(project.appUIPaths.map(async p => {
53
50
  const manifest = await read(join(appPath, p, 'webapp/manifest.json'))
@@ -55,7 +52,7 @@ module.exports = class SampleTemplate extends require('../../plugin') {
55
52
  await write(join(appPath, p, 'webapp/manifest.json'), manifest, { spaces: 2 })
56
53
  }))
57
54
 
58
- if (project.isNodejs) { // REVISIT: align with Java samples
55
+ if (project.isNodejs) {
59
56
  const fioriSandboxConfigPath = join(appPath, 'appconfig/fioriSandboxConfig.json')
60
57
  const fioriSandboxConfig = await read(fioriSandboxConfigPath)
61
58
  const sampleInbounds = { BrowseBooks: 'browse', ManageAuthors: 'admin-authors', BrowseGenres: 'genres', ManageBooks: 'admin-books' }
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "devDependencies": {
3
- "@cap-js/cds-test": ">=0.4.1"
3
+ "@cap-js/cds-test": "^1"
4
4
  },
5
5
  "scripts": {
6
6
  "test": "node --test"
@@ -2,15 +2,15 @@
2
2
  {{#hasEsmStyle}}
3
3
  import cds from '@sap/cds'
4
4
 
5
- const { GET, POST, expect, axios } = cds.test (import.meta.dirname+'/..')
5
+ const { GET, POST, expect, defaults } = cds.test (import.meta.dirname+'/..')
6
6
  {{/hasEsmStyle}}
7
7
  {{^hasEsmStyle}}
8
8
  const cds = require('@sap/cds')
9
9
 
10
- const { GET, POST, expect, axios } = cds.test (__dirname+'/..')
10
+ const { GET, POST, expect, defaults } = cds.test (__dirname+'/..')
11
11
  {{/hasEsmStyle}}
12
12
  {{#user}}
13
- axios.defaults.auth = { username: '{{user}}', password: '{{password}}' }
13
+ defaults.auth = { username: '{{user}}', password: '{{password}}' }
14
14
  {{/user}}
15
15
 
16
16
  describe('{{serviceName}} OData APIs', () => {
@@ -28,7 +28,7 @@ module.exports = class TyperTemplate extends require('../../plugin') {
28
28
  }
29
29
  await merge(template('package.json')).into('package.json')
30
30
  await mergeGitignore('.gitignore', template('gitignore'))
31
- console.log(dim(` adding '@cds-models' to .gitignore to ignore generated types`)) // REVISIT: other facets are silent wrt. what they change
31
+ console.log(dim(` adding '@cds-models' to .gitignore to ignore generated types`))
32
32
  console.log(dim(' added new dependencies to the project. Run ') + bold('npm i') + dim(' to install.'))
33
33
  try {
34
34
  await merge(source).into(target)
@@ -3,6 +3,6 @@
3
3
  "@cap-js/cds-types": "^0.16.0",
4
4
  "@types/node": "^24.0.0",
5
5
  "tsx": "^4",
6
- "typescript": "^5"
6
+ "typescript": "^6.0.0"
7
7
  }
8
8
  }
@@ -34,6 +34,6 @@ module.exports = class TypescriptTemplate extends require('../../plugin') {
34
34
  : e
35
35
  }
36
36
  await merge(join(__dirname, 'files', 'package.json')).into('package.json')
37
- await new TyperTemplate().run() // REVISIT: we should avoid referencing other plugins
37
+ await new TyperTemplate().run()
38
38
  }
39
39
  }
@@ -8,7 +8,8 @@ module.exports = class extends require('../../plugin') {
8
8
  const startPage = path.join(cds.root, cds.env.folders.app, 'index.html')
9
9
  const packageJson = path.join(cds.root, 'package.json')
10
10
  return exists(startPage) && fs.readFileSync(startPage).includes('https://sapui5') ||
11
- exists(packageJson) && fs.readFileSync(packageJson).includes('"sapux":')
11
+ exists(packageJson) && fs.readFileSync(packageJson).includes('"sapux":') ||
12
+ exists(path.join(cds.root, cds.env.folders.app, 'fiori-apps.html'))
12
13
  }
13
14
 
14
15
  static help() {
@@ -0,0 +1,13 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+ <link rel="icon" type="image/svg+xml" href="/favicon.svg" />
6
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
7
+ <title>catalog</title>
8
+ </head>
9
+ <body>
10
+ <div id="app"></div>
11
+ <script type="module" src="/src/main.js"></script>
12
+ </body>
13
+ </html>
@@ -0,0 +1,19 @@
1
+ {
2
+ "name": "{{app}}",
3
+ "private": true,
4
+ "version": "1.0.0",
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite",
8
+ "build": "vite build",
9
+ "preview": "vite preview"
10
+ },
11
+ "dependencies": {
12
+ "vue": "^3"
13
+ },
14
+ "devDependencies": {
15
+ "@vitejs/plugin-vue": "^6",
16
+ "adm-zip": "^0",
17
+ "vite": "^8"
18
+ }
19
+ }
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" width="32" height="32" viewBox="0 0 56 56" aria-hidden="true"><path fill="#5A7A94" d="M45.84 21.462a8.489 8.489 0 0 0-12.783-5.713A10.747 10.747 0 0 0 22.41 8.5C10 8.5 11.09 21.488 11.09 21.488A10.3 10.3 0 0 0 13 41.5v-3a7.477 7.477 0 0 1-5.639-5 7.247 7.247 0 0 1 4.666-9.164l2.279-.768-.286-1.5A7.521 7.521 0 0 1 13.9 20a10.535 10.535 0 0 1 2.188-5.828 7.375 7.375 0 0 1 6.4-2.824c3.264 0 7.286 2.9 8.221 5.636l.934 2.732 2.424-1.318a7.6 7.6 0 0 1 3.39-.894 5.546 5.546 0 0 1 5.443 4.518l.288 1.528 1.125.514A7.809 7.809 0 0 1 48.891 29.7 7.809 7.809 0 0 1 43 38.5v3.02A10.793 10.793 0 0 0 52 31a10.483 10.483 0 0 0-6.16-9.538"/><g fill="#0092D1" transform="scale(1.05 1.2)"><g transform="translate(-2 -7)"><path d="M24.561 38.939l-6-6a1.5 1.5 0 0 0-2.122 2.122L21.379 40l-4.94 4.939a1.5 1.5 0 0 0 2.122 2.122l6-6a1.5 1.5 0 0 0 0-2.122"/><path d="M26.5 35.5h12a1.5 1.5 0 0 0 0-3h-12a1.5 1.5 0 0 0 0 3"/><path d="M38.5 38.5h-9a1.5 1.5 0 0 0 0 3h9a1.5 1.5 0 0 0 0-3"/><path d="M38.5 44.5h-12a1.5 1.5 0 0 0 0 3h12a1.5 1.5 0 0 0 0-3"/></g></g></svg>
@@ -0,0 +1,34 @@
1
+ <script setup>
2
+ import { ref, onMounted } from 'vue'
3
+
4
+ const books = ref([])
5
+
6
+ onMounted(async () => {
7
+ const r = await fetch('/odata/v4/{{#isJava}}CatalogService{{/isJava}}{{^isJava}}catalog{{/isJava}}/Books')
8
+ books.value = (await r.json()).value
9
+ })
10
+ </script>
11
+
12
+ <template>
13
+ <main>
14
+ <h1>Books</h1>
15
+ <table>
16
+ <tr v-for="b in books" :key="b.ID">
17
+ <td>
18
+ <span class="title">{{#literal}}{{ b.title }}{{/literal}}</span>{{^isJava}}
19
+ <span class="author"><i>{{#literal}} by {{ b.author }}{{/literal}}</i></span>{{/isJava}}
20
+ </td>
21
+ </tr>
22
+ </table>
23
+ </main>
24
+ </template>
25
+
26
+ <style scoped>
27
+ .title {
28
+ color: var(--text);
29
+ }
30
+
31
+ .author {
32
+ color: var(--text-muted);
33
+ }
34
+ </style>
@@ -0,0 +1,5 @@
1
+ import { createApp } from 'vue'
2
+ import './style.css'
3
+ import App from './App.vue'
4
+
5
+ createApp(App).mount('#app')
@@ -0,0 +1,20 @@
1
+ :root {
2
+ --text: #111;
3
+ --text-muted: #777;
4
+ --bg: #fff;
5
+ --sans: system-ui, 'Segoe UI', Roboto, sans-serif;
6
+ font-family: var(--sans);
7
+ }
8
+
9
+ @media (prefers-color-scheme: dark) {
10
+ :root {
11
+ --text: #fff;
12
+ --text-muted: #a3a3a3;
13
+ --bg: #16171d;
14
+ }
15
+ }
16
+
17
+ body {
18
+ color: var(--text);
19
+ background: var(--bg);
20
+ }
@@ -0,0 +1,19 @@
1
+ import { defineConfig } from 'vite'
2
+ import AdmZip from 'adm-zip'
3
+ import vue from '@vitejs/plugin-vue'
4
+
5
+ export default defineConfig({
6
+ plugins: [vue(), {
7
+ name: 'zip-dist', // zipping ./dist is required for BTP HTML5 repo or Application Frontend deployment
8
+ closeBundle() {
9
+ const zip = new AdmZip()
10
+ zip.addLocalFolder('dist')
11
+ zip.writeZip('dist/catalog.zip')
12
+ }
13
+ }],
14
+ server: {
15
+ proxy: {
16
+ '/odata': 'http://localhost:4004'
17
+ }
18
+ }
19
+ })
@@ -0,0 +1,30 @@
1
+ const cds = require('../../../cds')
2
+ const { path, sh: $ } = cds.utils
3
+ const { ask4 } = require('../../../util/question')
4
+ const { renderAndCopy } = require('../../template')
5
+
6
+ module.exports = class VueTemplate extends require('../../plugin') {
7
+
8
+ options() {
9
+ return {
10
+ 'into': {
11
+ type: 'string',
12
+ short: 'i',
13
+ help: `Name of the app folder in 'app/'. Example: 'catalog' → 'app/catalog'.`
14
+ }
15
+ }
16
+ }
17
+
18
+ static help() {
19
+ return 'user interface for Vue.js'
20
+ }
21
+
22
+ async run() {
23
+ const app = cds.cli.options.into ?? await ask4([`app name: `])
24
+ let into = path.join(cds.env.folders.app, app)
25
+ await renderAndCopy(path.join(__dirname, 'files'), into, { app, isJava: cds.env['project-nature'] === 'java', literal: () => text => text,
26
+ })
27
+ if (cds.cli.command === 'init') into = path.join(cds.cli.argv[0], into)
28
+ await $`npm i --prefix ${into}`
29
+ }
30
+ }
@@ -56,10 +56,10 @@ resources:
56
56
  parameters:
57
57
  service: destination
58
58
  service-plan: lite
59
+ {{#hasMultitenancy}}
59
60
  config:
60
61
  HTML5Runtime_enabled: true
61
62
  init_data:
62
- {{#hasMultitenancy}}
63
63
  subaccount:
64
64
  existing_destinations_policy: update
65
65
  destinations:
@@ -69,18 +69,7 @@ resources:
69
69
  ProxyType: Internet
70
70
  URL: https://${org}.launchpad.${default-domain}
71
71
  CEP.HTML5contentprovider: true
72
- {{/hasMultitenancy}}
73
- instance:
74
- existing_destinations_policy: update
75
- destinations:
76
- - Name: srv-api
77
- URL: ~{srv-api/srv-url}
78
- Authentication: NoAuthentication
79
- Type: HTTP
80
- ProxyType: Internet
81
- HTML5.ForwardAuthToken: true
82
- HTML5.DynamicDestination: true
83
- {{#hasIas}}HTML5.IASDependencyName: {{appName}}-ias-api{{/hasIas}}
72
+ {{/hasMultitenancy}}
84
73
  requires:
85
74
  - name: srv-api
86
75
 
package/lib/init/xml.js CHANGED
@@ -16,8 +16,6 @@ function _removeTextAttributes(value, parentElement) {
16
16
  }
17
17
  }
18
18
 
19
- // REVISIT: Simplify config
20
- // REVISIT: fast-xml-parser
21
19
  /**
22
20
  * Parse a given string containing xml
23
21
  * used to remove the _text object from converted json i.e. from xml-js.
@@ -1,11 +1,8 @@
1
- const axios = require('axios');
2
-
3
1
  const cds = require('../cds');
4
2
  const { SettingsManager } = require('./settings_manager');
5
3
  const { ParamCollection } = require('./params');
6
4
  const { getMessage } = require('./util/logging');
7
-
8
- require('../util/axios').pruneErrors();
5
+ const { buildResponseError, buildNetworkError, parseResponseBody } = require('./util/request');
9
6
 
10
7
  const DEBUG = cds.debug('cli');
11
8
  const REASONS = { invalid_scope: /\binvalid_scope\b/ };
@@ -71,7 +68,7 @@ async function fetchToken(params) {
71
68
  return;
72
69
  }
73
70
 
74
- let response, error;
71
+ let responseData, error;
75
72
  const method = 'post';
76
73
  do {
77
74
  const { url, data, ignored } = reqParams(method, params);
@@ -81,11 +78,23 @@ async function fetchToken(params) {
81
78
  DEBUG?.(`Getting authentication token from ${method.toUpperCase()} ${params.obfuscateQueryParams(url)}`);
82
79
  error = undefined;
83
80
  try {
84
- response = await axios[method](url, data);
81
+ const response = await fetch(url, {
82
+ method: method.toUpperCase(),
83
+ body: data,
84
+ headers: { 'content-type': 'application/x-www-form-urlencoded' }
85
+ });
86
+ if (!response.ok) {
87
+ throw await buildResponseError(method, url, response);
88
+ }
89
+ responseData = await parseResponseBody(response);
85
90
  break;
86
91
  } catch (e) {
87
- error = e;
88
- DEBUG?.(`HTTP status ${e.status}`);
92
+ if (e.status) {
93
+ error = e;
94
+ } else {
95
+ error = buildNetworkError(method, url, e);
96
+ }
97
+ DEBUG?.(`HTTP status ${error.status}`);
89
98
  if (params.has('refreshToken')) {
90
99
  DEBUG?.('Discarding invalid refresh token');
91
100
  params.delete('refreshToken');
@@ -93,8 +102,8 @@ async function fetchToken(params) {
93
102
  break;
94
103
  }
95
104
  }
96
- } while (!response);
97
- const data = response?.data ?? error?.auth;
105
+ } while (!responseData);
106
+ const data = responseData ?? error?.auth;
98
107
  assign(params, data);
99
108
  if (error) {
100
109
  let message;
@@ -126,7 +135,8 @@ module.exports = class AuthManager {
126
135
  const params = new ParamCollection(paramValues);
127
136
  await SettingsManager.loadAndMergeSettings(params);
128
137
  if (params.has('username')) {
129
- params.set('reqAuth', { auth: { username: params.get('username'), password: params.get('password') } });
138
+ const encoded = Buffer.from(`${params.get('username')}:${params.get('password')}`).toString('base64');
139
+ params.set('reqAuth', { headers: { Authorization: `Basic ${encoded}` } });
130
140
  } else if (!params.get('skipToken')) {
131
141
  try {
132
142
  await fetchToken(params);