@sap/cds-dk 7.7.1 → 7.8.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 (57) hide show
  1. package/bin/add.js +42 -17
  2. package/bin/bind.js +21 -12
  3. package/bin/build.js +14 -2
  4. package/bin/deploy/to-sqlite.js +1 -1
  5. package/bin/help.js +4 -1
  6. package/bin/import.js +5 -1
  7. package/bin/version.js +54 -22
  8. package/lib/bind/userCdsrcJsonStorageProvider.js +1 -1
  9. package/lib/build/buildTaskFactory.js +2 -2
  10. package/lib/build/provider/hana/index.js +1 -1
  11. package/lib/build/provider/internalBuildTaskProvider.js +19 -5
  12. package/lib/build/provider/java/index.js +4 -4
  13. package/lib/build/provider/mtx/resourcesTarBuilder.js +16 -4
  14. package/lib/build/provider/mtx-extension/extensionCompilation.js +13 -2
  15. package/lib/build/provider/mtx-extension/index.js +6 -2
  16. package/lib/build/provider/nodejs/index.js +4 -4
  17. package/lib/build/util.js +1 -12
  18. package/lib/compile/openapi/index.js +2 -1
  19. package/lib/deploy/to-hana/hana.js +19 -17
  20. package/lib/import/common.js +1 -1
  21. package/lib/import/importapi.js +1 -1
  22. package/lib/import/odata/v4parser.js +18 -11
  23. package/lib/index.js +2 -2
  24. package/lib/init/constants.js +2 -1
  25. package/lib/init/index.js +9 -23
  26. package/lib/init/mvn.js +1 -5
  27. package/lib/init/plugin.js +8 -0
  28. package/lib/init/registries/mta.js +6 -0
  29. package/lib/init/template/data/as-csv.js +60 -0
  30. package/lib/init/template/data/as-json.js +441 -0
  31. package/lib/init/template/data/index.js +115 -86
  32. package/lib/init/template/helm/files/content-deployment/Chart.yaml +1 -1
  33. package/lib/init/template/helm/files/content-deployment/templates/job.yaml +7 -0
  34. package/lib/init/template/helm/files/content-deployment/values.schema.json +17 -0
  35. package/lib/init/template/helm/files/content-deployment/values.yaml +6 -1
  36. package/lib/init/template/helm/files/service-instance/Chart.yaml +1 -1
  37. package/lib/init/template/helm/files/service-instance/templates/serviceinstance.yaml +4 -0
  38. package/lib/init/template/helm/files/service-instance/values.schema.json +17 -1
  39. package/lib/init/template/helm/files/service-instance/values.yaml +4 -1
  40. package/lib/init/template/helm/files/values.yaml.hbs +2 -2
  41. package/lib/init/template/helm/files/web-application/Chart.yaml +1 -1
  42. package/lib/init/template/helm/files/web-application/templates/api-rule.yaml +4 -0
  43. package/lib/init/template/helm/files/web-application/templates/network-policy.yaml +15 -0
  44. package/lib/init/template/helm/files/web-application/values.schema.json +41 -0
  45. package/lib/init/template/helm/files/web-application/values.yaml +19 -1
  46. package/lib/init/template/helm-unified-runtime/files/values.yaml.hbs +2 -2
  47. package/lib/init/template/helm-unified-runtime/index.js +9 -6
  48. package/lib/init/template/http/index.js +182 -0
  49. package/lib/init/template/kafka/files/mta.yaml.hbs +14 -0
  50. package/lib/init/template/kafka/files/package.json.hbs +22 -0
  51. package/lib/init/template/kafka/index.js +40 -0
  52. package/lib/init/template/lint/index.js +4 -4
  53. package/lib/init/template/mta/files/mta.yaml.hbs +4 -0
  54. package/lib/init/template/multitenancy/files/values.yaml.hbs +2 -2
  55. package/lib/util/cf.js +6 -2
  56. package/npm-shrinkwrap.json +300 -276
  57. package/package.json +1 -1
@@ -0,0 +1,182 @@
1
+ const cds = require('../../../cds')
2
+ const cf = require("../../../util/cf")
3
+ const { write, exists } = cds.utils
4
+
5
+ module.exports = class HttpTemplate extends require('../../plugin') {
6
+
7
+ #defaultHost = "http://localhost:4004"
8
+ #defaultPathPrefix = "/odata/v4/"
9
+ #defaultDirName = "*"
10
+ #defaultOutputDir = "http"
11
+ #defaultAuth = `Authorization: Basic yves:`
12
+
13
+ options() {
14
+ return {
15
+ 'filter': {
16
+ type: 'string',
17
+ short: 'f',
18
+ help: `Specify the path to the service definition file or directory. Default is ${this.#defaultDirName}.`
19
+ },
20
+ 'for-app': {
21
+ type: 'string',
22
+ short: 'a',
23
+ help: 'Specify the name of the app to generate requests for. If not specified, localhost and default auth will be used.'
24
+ },
25
+ 'out': {
26
+ type: 'string',
27
+ short: 'o',
28
+ help: 'The output directory. By default, an `http` dir will be created in one of the following: test/tests/__tests__, whichever already exists. If none exists, an `http` dir will be created at the root level.'
29
+ }
30
+ }
31
+ }
32
+
33
+ async run() {
34
+
35
+ const path = cds.cli.options['filter'] || this.#defaultDirName
36
+ const csn = await cds.load(path)
37
+ cds.linked(csn)
38
+
39
+ let [hostname, auth] = await this.#getHostnameAndAuth()
40
+
41
+ let endpoints = this.#collectEndpointInfo(csn)
42
+
43
+ this.#generateRequests(endpoints, hostname, auth)
44
+ }
45
+
46
+ async #getHostnameAndAuth() {
47
+ if (!cds.cli.options['for-app']) {
48
+ return [this.#defaultHost, this.#defaultAuth]
49
+ }
50
+
51
+ const env = await cf.getEnv(cds.cli.options['for-app'])
52
+ const hostname = `https://${env.application_env_json.VCAP_APPLICATION.application_uris[0]}`
53
+ const { url, clientid, clientsecret } = env.system_env_json.VCAP_SERVICES?.xsuaa[0]?.credentials || {}
54
+
55
+ const response = await fetch(`${url}/oauth/token?${new URLSearchParams({
56
+ grant_type: 'client_credentials',
57
+ response_type: 'token',
58
+ })}`, {
59
+ method: 'GET',
60
+ headers: {
61
+ 'Authorization': `Basic ${Buffer.from(`${clientid}:${clientsecret}`).toString('base64')}`
62
+ }
63
+ })
64
+ if (response.ok) {
65
+ const { access_token } = await response.json()
66
+ const auth = `x-approuter-authorization: bearer ` + access_token
67
+ return [hostname, auth]
68
+ } else {
69
+ throw new Error('Error authenticating ', { url, response })
70
+ }
71
+ }
72
+
73
+ #collectEndpointInfo(csn) {
74
+ const mapping = {}
75
+
76
+ csn.services.map((s) => {
77
+ const protocols = this.#getProtocols(s)
78
+ const paths = this.#getPaths(s, protocols)
79
+
80
+ let entityInfo = []
81
+ const entities = csn.entities(s.name)
82
+ for (const e in entities) {
83
+ if (entities[e].name.endsWith('.texts')) {
84
+ continue
85
+ }
86
+ entityInfo.push(this.#addNameAndReadOnly(entities[e]))
87
+ }
88
+
89
+ mapping[s.name] = { entities: entityInfo, paths: paths, protocols: protocols, actions: [] }
90
+ })
91
+
92
+ csn.foreach('action', (a) => {
93
+ mapping[a.name.split(".")[0]].actions.push(a)
94
+ })
95
+
96
+ return mapping
97
+ }
98
+
99
+ #getProtocols(s) {
100
+ return s["@protocol"]
101
+ ? typeof s["@protocol"] === "string"
102
+ ? [s["@protocol"]]
103
+ : s["@protocol"]
104
+ : ["odata"]
105
+ }
106
+
107
+ #getPaths(s, protocols) {
108
+ let paths = []
109
+ if (s["@path"]) {
110
+ paths.push(s["@path"])
111
+ } else {
112
+ const serviceName = s.name.replace("Service", "").toLowerCase()
113
+
114
+ let prefix = ""
115
+ if (protocols) {
116
+ for (const p of protocols) {
117
+ if (p === "odata-v2") {
118
+ prefix = "/odata/v2/"
119
+ } else if (p === "rest") {
120
+ prefix = "/rest/"
121
+ } else {
122
+ prefix = this.#defaultPathPrefix
123
+ }
124
+ paths.push(prefix + serviceName)
125
+ }
126
+ }
127
+ }
128
+ return paths
129
+ }
130
+
131
+ #addNameAndReadOnly(e) {
132
+ return {
133
+ name: e.name.split(".")[1],
134
+ readOnly: (e["@readonly"] || e["@cds.autoexpose"] || e['@odata.singleton']) ?? false,
135
+ }
136
+ }
137
+
138
+ #generateRequests(mapping, hostname, auth) {
139
+ for (const service in mapping) {
140
+ let requests = []
141
+
142
+ requests.push(`@server = ${hostname}\n@auth = ${auth}\n`)
143
+
144
+ mapping[service].entities.forEach((e) => {
145
+ for (const path of mapping[service].paths) {
146
+ const url = `{{server}}${path}/${e.name}`
147
+ const contentType = `Content-Type: application/json`
148
+ const authHeader = `{{auth}}`
149
+
150
+ requests.push(`GET ${url}\n${authHeader}\n${contentType}\n`)
151
+
152
+ if (!e.readOnly) {
153
+ const allHeaders = `${contentType}\n${authHeader}`
154
+ requests.push(`POST ${url}\n${allHeaders}\n\n{}\n`)
155
+
156
+ requests.push(`PATCH ${url}\n${allHeaders}\n\n{}\n`)
157
+ requests.push(`DELETE ${url}\n${authHeader}\n`)
158
+ }
159
+ }
160
+ })
161
+
162
+ const outputPath = this.#determineOutputPath(service)
163
+ write(requests.join("\n###\n")).to(outputPath)
164
+ console.log(`${outputPath} has been created.`)
165
+ }
166
+ }
167
+
168
+ #determineOutputPath(service) {
169
+ let outputDir
170
+ const { out } = cds.cli.options
171
+ if (out) {
172
+ outputDir = out.endsWith('/') ? out.slice(0, -1) : out
173
+ } else if (exists('test') || exists('tests') || exists('__tests__')) {
174
+ outputDir = exists('test') ? 'test/http' : (exists('tests') ? 'tests/http' : '__tests__/http')
175
+ } else {
176
+ outputDir = this.#defaultOutputDir
177
+ }
178
+ return `${outputDir}/${service}.http`
179
+ }
180
+ }
181
+
182
+
@@ -0,0 +1,14 @@
1
+ ---
2
+ modules:
3
+ - name: {{appName}}-srv
4
+ type: {{language}}
5
+ path: {{& srvPath}}
6
+ requires:
7
+ - name: {{appName}}-kafka
8
+
9
+ resources:
10
+ - name: {{appName}}-kafka
11
+ type: org.cloudfoundry.managed-service
12
+ parameters:
13
+ service: kafka
14
+ service-plan: standard
@@ -0,0 +1,22 @@
1
+ {
2
+ {{#isNodejs}}
3
+ "dependencies": {
4
+ "kafkajs": "^2"
5
+ },
6
+ "cds": {
7
+ {{/isNodejs}}
8
+ "requires": {
9
+ {{#profile}}
10
+ "[{{profile}}]": {
11
+ {{/profile}}
12
+ "messaging": {
13
+ "kind": "kafka"
14
+ }
15
+ {{#profile}}
16
+ }
17
+ {{/profile}}
18
+ }
19
+ {{#isNodejs}}
20
+ }
21
+ {{/isNodejs}}
22
+ }
@@ -0,0 +1,40 @@
1
+ const { readProject } = require('../../projectReader')
2
+ const { merge } = require('../../merge')
3
+ const { srv4, kafkaMessaging } = require('../../registries/mta')
4
+ const mvn = require('../../mvn')
5
+
6
+ module.exports = class KafkaTemplate extends require('../../plugin') {
7
+
8
+ static hasInProduction(env) {
9
+ return !!env.requires?.messaging?.kind === 'kafka'
10
+ }
11
+
12
+ async run() {
13
+ const project = readProject()
14
+ const { configFile, isNodejs, isJava } = project
15
+ if (isNodejs) await merge(__dirname, 'files/package.json.hbs').into(configFile, { with: project })
16
+ if (isJava) await mvn.add('kafka')
17
+ }
18
+
19
+ async combine() {
20
+ const project = readProject()
21
+ const { hasMta, srvPath } = project
22
+
23
+ if (hasMta) {
24
+ const srv = srv4(srvPath)
25
+ await merge(__dirname, 'files/mta.yaml.hbs').into('mta.yaml', {
26
+ project,
27
+ additions: [srv, kafkaMessaging],
28
+ relationships: [{
29
+ insert: [kafkaMessaging, 'name'],
30
+ into: [srv, 'requires', 'name']
31
+ }]
32
+ })
33
+ }
34
+
35
+ // REVISIT: Helm support
36
+ // if (hasHelm || hasHelmUnifiedRuntime) {
37
+ // await merge(__dirname, 'files', 'values.yaml').into('chart/values.yaml')
38
+ // }
39
+ }
40
+ }
@@ -7,12 +7,9 @@ const io = require("../../../lint/io");
7
7
  const readline = require("readline");
8
8
  const { renderAndCopy } = require('../../template')
9
9
  const { LINT_DEV, LINT_GLOBAL } = require("../../constants").OPTIONS;
10
- const { getConfigPath, getFileExtensions } = require("@sap/eslint-plugin-cds/lib/api");
11
10
  const cds = require("../../../cds");
12
11
  const { mkdirp, exists } = cds.utils;
13
-
14
12
  const IS_WIN = os.platform() === "win32";
15
- const ALLOWED_FILE_EXTENSIONS = getFileExtensions();
16
13
  const VSCODE_FILE_ASSOCIATIONS = {
17
14
  "csv": [ "csv", "csv (semicolon)", "tsv", "tab" ]
18
15
  }
@@ -70,7 +67,9 @@ module.exports = class LintTemplate extends require('../../plugin') {
70
67
  }
71
68
 
72
69
  // Generate VS Code extension settings
73
- const extensions = ALLOWED_FILE_EXTENSIONS.map((ext) => {
70
+ const { getFileExtensions } = require("@sap/eslint-plugin-cds/lib/api");
71
+ const ALLOWED_FILE_EXTENSIONS = getFileExtensions();
72
+ const extensions = ALLOWED_FILE_EXTENSIONS.map((ext) => {
74
73
  return path.extname(ext).replace('.', '');
75
74
  })
76
75
  extensions.forEach((ext) => {
@@ -151,6 +150,7 @@ module.exports = class LintTemplate extends require('../../plugin') {
151
150
  }
152
151
 
153
152
  async _addEslintPluginConfig() {
153
+ const { getConfigPath } = require("@sap/eslint-plugin-cds/lib/api");
154
154
  let configPath = getConfigPath(cds.root);
155
155
  if (!configPath) {
156
156
  configPath = path.join(cds.root, ".eslintrc.json");
@@ -20,12 +20,16 @@ modules:
20
20
  {{#isNodejs}}
21
21
  parameters:
22
22
  buildpack: nodejs_buildpack
23
+ readiness-health-check-type: http
24
+ readiness-health-check-http-endpoint: /health
23
25
  build-parameters:
24
26
  builder: npm
25
27
  {{/isNodejs}}
26
28
  {{#isJava}}
27
29
  parameters:
28
30
  buildpack: sap_java_buildpack
31
+ readiness-health-check-type: http
32
+ readiness-health-check-http-endpoint: /
29
33
  properties:
30
34
  SPRING_PROFILES_ACTIVE: cloud,sandbox
31
35
  JBP_CONFIG_COMPONENTS: "jres: ['com.sap.xs.java.buildpack.jre.SAPMachineJRE']"
@@ -65,6 +65,6 @@ sidecar:
65
65
  memory: 500M
66
66
  health:
67
67
  liveness:
68
- path: /
68
+ path: /health
69
69
  readiness:
70
- path: /
70
+ path: /health
package/lib/util/cf.js CHANGED
@@ -190,12 +190,16 @@ Deployment will stop in the near future for Cloud Foundry client versions < ${CF
190
190
  }
191
191
 
192
192
  async getServices(app) {
193
+ const env = await this.getEnv(app)
194
+ return Object.values(env.system_env_json.VCAP_SERVICES).flat();
195
+ }
196
+
197
+ async getEnv(app) {
193
198
  const { spaceGuid: space_guids, orgGuid: organization_guids } = await this.getCfSpaceInfo();
194
199
  const apps = await this._cfRequest('/v3/apps', { space_guids, organization_guids, names: app });
195
200
  if (apps.resources.length < 1) throw `Cannot find app with name '${app}'`
196
201
  const guid = apps.resources[0].guid;
197
- const env = await this._cfRequest('/v3/apps/'+guid+'/env', { space_guids, organization_guids, names: app });
198
- return Object.values(env.system_env_json.VCAP_SERVICES).flat();
202
+ return await this._cfRequest('/v3/apps/' + guid + '/env', { space_guids, organization_guids, names: app });
199
203
  }
200
204
 
201
205
  async getService(serviceName, showMessage = true) {