@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.
- package/CHANGELOG.md +32 -2
- package/bin/add.js +3 -3
- package/bin/cds.js +28 -5
- package/bin/debug.js +4 -3
- package/bin/import.js +6 -2
- package/bin/logout.js +9 -12
- package/lib/bind/shared.js +2 -2
- package/lib/build/constants.js +0 -1
- package/lib/build/index.js +420 -463
- package/lib/build/plugins/hana/index.js +121 -2
- package/lib/build/plugins/helm/files/subcharts/content-deployment/Chart.yaml +1 -1
- package/lib/build/plugins/helm/files/subcharts/content-deployment/templates/job.yaml +1 -1
- package/lib/build/plugins/helm/files/subcharts/content-deployment/values.schema.json +4 -0
- package/lib/build/plugins/helm/files/subcharts/content-deployment/values.yaml +5 -2
- package/lib/build/plugins/internal.js +1 -1
- package/lib/build/plugins/mtx-extension/index.js +0 -2
- package/lib/deploy/index.js +23 -3
- package/lib/deploy/to-hana/hana.js +24 -15
- package/lib/import/common.js +6 -3
- package/lib/init/constants.js +3 -1
- package/lib/init/indent.js +0 -1
- package/lib/init/merge.js +0 -1
- package/lib/init/mvn.js +4 -2
- package/lib/init/projectReader.js +0 -1
- package/lib/init/registries/mta.js +4 -2
- package/lib/init/template/app-frontend/files/ui5.yaml.hbs +1 -1
- package/lib/init/template/app-frontend/index.js +39 -4
- package/lib/init/template/data/as-csv.js +2 -0
- package/lib/init/template/enterprise-messaging/index.js +1 -1
- package/lib/init/template/enterprise-messaging-shared/index.js +1 -1
- package/lib/init/template/esm/index.js +5 -0
- package/lib/init/template/extensibility/index.js +1 -1
- package/lib/init/template/hana/index.js +1 -1
- package/lib/init/template/html5-repo/files/mta.yaml.hbs +9 -20
- package/lib/init/template/html5-repo/files/ui5.yaml.hbs +1 -1
- package/lib/init/template/http/index.js +34 -6
- package/lib/init/template/kyma/index.js +1 -0
- package/lib/init/template/lint/files/package.json +1 -1
- package/lib/init/template/nodejs/files/package.json.hbs +1 -1
- package/lib/init/template/pipeline/files/ui5.yaml.hbs +1 -1
- package/lib/init/template/postgres/files/values.yaml.hbs +1 -1
- package/lib/init/template/postgres/index.js +1 -1
- package/lib/init/template/react/files/index.html +13 -0
- package/lib/init/template/react/files/package.json.hbs +22 -0
- package/lib/init/template/react/files/public/favicon.svg +1 -0
- package/lib/init/template/react/files/src/App.jsx.hbs +29 -0
- package/lib/init/template/react/files/src/index.css +28 -0
- package/lib/init/template/react/files/src/main.jsx +10 -0
- package/lib/init/template/react/files/vite.config.js +19 -0
- package/lib/init/template/react/index.js +29 -0
- package/lib/init/template/sample/index.js +1 -4
- package/lib/init/template/test/files/package.json +1 -1
- package/lib/init/template/test/files/test/test.xs.hbs +3 -3
- package/lib/init/template/typer/index.js +1 -1
- package/lib/init/template/typescript/files/package.json +1 -1
- package/lib/init/template/typescript/index.js +1 -1
- package/lib/init/template/ui5/index.js +2 -1
- package/lib/init/template/vue/files/index.html +13 -0
- package/lib/init/template/vue/files/package.json.hbs +19 -0
- package/lib/init/template/vue/files/public/favicon.svg +1 -0
- package/lib/init/template/vue/files/src/App.vue.hbs +34 -0
- package/lib/init/template/vue/files/src/main.js +5 -0
- package/lib/init/template/vue/files/src/style.css +20 -0
- package/lib/init/template/vue/files/vite.config.js +19 -0
- package/lib/init/template/vue/index.js +30 -0
- package/lib/init/template/workzone-standard/files/mta.yaml.hbs +2 -13
- package/lib/init/xml.js +0 -2
- package/lib/mtx/auth_manager.js +21 -11
- package/lib/mtx/download_legacy.js +9 -8
- package/lib/mtx/pull.js +9 -8
- package/lib/mtx/push.js +42 -28
- package/lib/mtx/settings_manager.js +17 -10
- package/lib/mtx/tenant_action.js +9 -9
- package/lib/mtx/util/request.js +158 -0
- package/lib/util/cf.js +30 -29
- package/lib/watch/node-watch.js +1 -1
- package/lib/watch/vite-watch.js +76 -0
- package/lib/watch/watched.js +5 -4
- package/npm-shrinkwrap.json +25 -165
- package/package.json +3 -4
- package/lib/init/template/sample/files/db/hana/index.cds +0 -10
- package/lib/init/template/sample/files/db/sqlite/index.cds +0 -10
- package/lib/init/template/sample/files/package.json.hbs +0 -14
- 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
|
|
264
|
-
|
|
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
|
}
|
|
@@ -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 })
|
|
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,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) {
|
|
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' }
|
|
@@ -2,15 +2,15 @@
|
|
|
2
2
|
{{#hasEsmStyle}}
|
|
3
3
|
import cds from '@sap/cds'
|
|
4
4
|
|
|
5
|
-
const { GET, POST, expect,
|
|
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,
|
|
10
|
+
const { GET, POST, expect, defaults } = cds.test (__dirname+'/..')
|
|
11
11
|
{{/hasEsmStyle}}
|
|
12
12
|
{{#user}}
|
|
13
|
-
|
|
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`))
|
|
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)
|
|
@@ -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()
|
|
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,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
|
-
|
|
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
package/lib/mtx/auth_manager.js
CHANGED
|
@@ -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
|
|
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
|
|
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
|
-
|
|
88
|
-
|
|
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 (!
|
|
97
|
-
const data =
|
|
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
|
-
|
|
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);
|