@sap/cds-dk 8.6.1 → 8.7.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.
- package/CHANGELOG.md +25 -0
- package/bin/cds.js +1 -1
- package/bin/debug.js +122 -26
- package/bin/deploy/index.js +7 -0
- package/bin/import.js +54 -29
- package/bin/repl.js +2 -2
- package/bin/util/fuzzySearch.js +10 -10
- package/bin/watch.js +46 -16
- package/lib/build/buildTaskProviderFactory.js +8 -5
- 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 +3 -2
- package/lib/build/plugins/helm/files/subcharts/content-deployment/values.yaml +1 -1
- package/lib/build/plugins/helm/files/subcharts/web-application/Chart.yaml +1 -1
- package/lib/build/plugins/helm/files/subcharts/web-application/templates/_helpers.tpl +3 -3
- package/lib/build/plugins/helm/files/subcharts/web-application/templates/api-rule.yaml +1 -1
- package/lib/build/plugins/helm/files/subcharts/web-application/templates/deployment.yaml +13 -5
- package/lib/build/plugins/helm/files/subcharts/web-application/templates/service-binding.yaml +1 -0
- package/lib/build/plugins/helm/files/subcharts/web-application/values.schema.json +28 -15
- package/lib/build/plugins/helm/files/subcharts/web-application/values.yaml +22 -1
- package/lib/import/common.js +174 -44
- package/lib/import/importapi.js +1 -1
- package/lib/import/message.js +1 -1
- package/lib/import/odata/ParserContext.js +46 -0
- package/lib/import/odata/index.js +20 -11
- package/lib/import/odata/v4parser.js +25 -51
- package/lib/init/add.js +0 -2
- package/lib/init/constants.js +3 -3
- package/lib/init/index.js +1 -1
- package/lib/init/merge.js +67 -66
- package/lib/init/mvn.js +16 -12
- package/lib/init/projectReader.js +2 -2
- package/lib/init/template/approuter/files/mta.yaml.hbs +2 -4
- package/lib/init/template/hana/index.js +12 -8
- package/lib/init/template/ias/files/mta.yaml.hbs +15 -0
- package/lib/init/template/ias/index.js +22 -5
- package/lib/init/template/mta/files/mta.yaml.hbs +3 -13
- package/lib/init/template/multitenancy/files/mta.yaml.hbs +1 -0
- package/lib/init/template/multitenancy/index.js +5 -0
- package/lib/init/template/nodejs/files/package.json.hbs +1 -1
- package/lib/init/template/portal/files/portal-site/i18n/catalog.properties +1 -1
- package/lib/init/template/portal/files/portal-site/i18n/catalog_de.properties +1 -1
- package/lib/init/template/portal/files/portal-site/i18n/group.properties +1 -1
- package/lib/init/template/portal/files/portal-site/i18n/group_de.properties +1 -1
- package/lib/init/template/sample/files/typescript/admin-service.ts.hbs +16 -0
- package/lib/init/template/sample/files/typescript/cat-service.ts.hbs +38 -0
- package/lib/init/template/sample/index.js +16 -12
- package/lib/init/template/typescript/files/package.json +1 -1
- package/lib/lint/io.js +6 -16
- package/lib/watch/node-watch.js +3 -3
- package/npm-shrinkwrap.json +49 -33
- package/package.json +1 -1
- /package/lib/init/template/sample/files/{nodejs → javascript}/admin-service.js.hbs +0 -0
- /package/lib/init/template/sample/files/{nodejs → javascript}/cat-service.js.hbs +0 -0
|
@@ -19,9 +19,8 @@ spec:
|
|
|
19
19
|
{{- if .Values.istio }}
|
|
20
20
|
sidecar.istio.io/inject: {{ .Values.istio.enabled | quote }}
|
|
21
21
|
{{ end }}
|
|
22
|
-
annotations:
|
|
23
|
-
container.apparmor.security.beta.kubernetes.io/{{ include "web-application.name" . }}: runtime/default
|
|
24
22
|
{{- if or .Values.annotations.deployment }}
|
|
23
|
+
annotations:
|
|
25
24
|
{{- tpl (toYaml .Values.annotations.deployment) $ | nindent 8 }}
|
|
26
25
|
{{- end }}
|
|
27
26
|
spec:
|
|
@@ -73,9 +72,9 @@ spec:
|
|
|
73
72
|
{{- if not (eq (len $envVars.vars) 0) }}
|
|
74
73
|
{{- $envVars.vars | toYaml | nindent 8}}
|
|
75
74
|
{{- end }}
|
|
76
|
-
{{ include "web-application.internal.healthCheck" (dict "checkName" "livenessProbe" "checkValues" .Values.health.liveness "context" $) | nindent 8}}
|
|
77
|
-
{{ include "web-application.internal.healthCheck" (dict "checkName" "readinessProbe" "checkValues" .Values.health.readiness "context" $) | nindent 8}}
|
|
78
|
-
{{ include "web-application.internal.healthCheck" (dict "checkName" "startupProbe" "checkValues" .Values.health.liveness "context" $) | nindent 8}}
|
|
75
|
+
{{ include "web-application.internal.healthCheck" (dict "checkName" "livenessProbe" "checkValues" .Values.health.liveness "values" .Values.health "context" $) | nindent 8}}
|
|
76
|
+
{{ include "web-application.internal.healthCheck" (dict "checkName" "readinessProbe" "checkValues" .Values.health.readiness "values" .Values.health "context" $) | nindent 8}}
|
|
77
|
+
{{ include "web-application.internal.healthCheck" (dict "checkName" "startupProbe" "checkValues" .Values.health.liveness "values" .Values.health "context" $) | nindent 8}}
|
|
79
78
|
volumeMounts:
|
|
80
79
|
- name: tmp
|
|
81
80
|
mountPath: /tmp
|
|
@@ -101,6 +100,8 @@ spec:
|
|
|
101
100
|
readOnlyRootFilesystem: {{ .Values.readOnlyRootFilesystem | default false }}
|
|
102
101
|
seccompProfile:
|
|
103
102
|
type: RuntimeDefault
|
|
103
|
+
appArmorProfile:
|
|
104
|
+
type: RuntimeDefault
|
|
104
105
|
{{- range $key, $sidecar := .Values.sidecars }}
|
|
105
106
|
- image: {{ include "web-application.internal.imageName" (dict "image" $sidecar.image "context" $) }}
|
|
106
107
|
{{- if ( or $.Values.global.imagePullPolicy $sidecar.imagePullPolicy ) }}
|
|
@@ -143,6 +144,11 @@ spec:
|
|
|
143
144
|
resources:
|
|
144
145
|
{{- toYaml $sidecar.resources | nindent 10 }}
|
|
145
146
|
{{- end }}
|
|
147
|
+
{{- if $sidecar.health }}
|
|
148
|
+
{{ include "web-application.internal.healthCheck" (dict "checkName" "livenessProbe" "checkValues" $sidecar.health.liveness "values" $sidecar.health "context" $) | nindent 8}}
|
|
149
|
+
{{ include "web-application.internal.healthCheck" (dict "checkName" "readinessProbe" "checkValues" $sidecar.health.readiness "values" $sidecar.health "context" $) | nindent 8}}
|
|
150
|
+
{{ include "web-application.internal.healthCheck" (dict "checkName" "startupProbe" "checkValues" $sidecar.health.liveness "values" $sidecar.health "context" $) | nindent 8}}
|
|
151
|
+
{{- end }}
|
|
146
152
|
securityContext:
|
|
147
153
|
allowPrivilegeEscalation: false
|
|
148
154
|
capabilities:
|
|
@@ -156,6 +162,8 @@ spec:
|
|
|
156
162
|
readOnlyRootFilesystem: {{ $.Values.readOnlyRootFilesystem | default false }}
|
|
157
163
|
seccompProfile:
|
|
158
164
|
type: RuntimeDefault
|
|
165
|
+
appArmorProfile:
|
|
166
|
+
type: RuntimeDefault
|
|
159
167
|
{{- end }}
|
|
160
168
|
restartPolicy: Always
|
|
161
169
|
terminationGracePeriodSeconds: {{ .Values.terminationGracePeriodSeconds }}
|
package/lib/build/plugins/helm/files/subcharts/web-application/templates/service-binding.yaml
CHANGED
|
@@ -28,6 +28,7 @@ spec:
|
|
|
28
28
|
{{- end }}
|
|
29
29
|
{{- if $mergedParams.parametersFrom }}
|
|
30
30
|
parametersFrom: {{ tpl ($mergedParams.parametersFrom | toYaml) $ | nindent 4 }}
|
|
31
|
+
watchParametersFromChanges: true
|
|
31
32
|
{{- end }}
|
|
32
33
|
{{- if $mergedParams.credentialsRotationPolicy }}
|
|
33
34
|
credentialsRotationPolicy: {{ $mergedParams.credentialsRotationPolicy | toYaml | nindent 4 }}
|
|
@@ -72,9 +72,7 @@
|
|
|
72
72
|
},
|
|
73
73
|
"port": {
|
|
74
74
|
"$id": "#/properties/port",
|
|
75
|
-
"
|
|
76
|
-
"minimum": 1,
|
|
77
|
-
"maximum": 65535,
|
|
75
|
+
"$ref": "#/definitions/Port",
|
|
78
76
|
"default": 8080
|
|
79
77
|
},
|
|
80
78
|
"serviceAccountName": {
|
|
@@ -333,18 +331,7 @@
|
|
|
333
331
|
},
|
|
334
332
|
"health": {
|
|
335
333
|
"$id": "#/properties/health",
|
|
336
|
-
"
|
|
337
|
-
"additionalProperties": false,
|
|
338
|
-
"properties": {
|
|
339
|
-
"liveness": {
|
|
340
|
-
"$id": "#/properties/health/properties/liveness",
|
|
341
|
-
"$ref": "#/definitions/Probe"
|
|
342
|
-
},
|
|
343
|
-
"readiness": {
|
|
344
|
-
"$id": "#/properties/health/properties/readiness",
|
|
345
|
-
"$ref": "#/definitions/Probe"
|
|
346
|
-
}
|
|
347
|
-
}
|
|
334
|
+
"$ref": "#/definitions/Health"
|
|
348
335
|
},
|
|
349
336
|
"startupTimeoutSeconds": {
|
|
350
337
|
"$id": "#/properties/startupTimeoutSeconds",
|
|
@@ -1193,6 +1180,10 @@
|
|
|
1193
1180
|
"title": "User to be used for running the container.",
|
|
1194
1181
|
"type": "integer",
|
|
1195
1182
|
"minimum": 1
|
|
1183
|
+
},
|
|
1184
|
+
"health": {
|
|
1185
|
+
"$id": "#/properties/health",
|
|
1186
|
+
"$ref": "#/definitions/Health"
|
|
1196
1187
|
}
|
|
1197
1188
|
}
|
|
1198
1189
|
}
|
|
@@ -1313,6 +1304,22 @@
|
|
|
1313
1304
|
"type": "string",
|
|
1314
1305
|
"enum": ["Always", "IfNotPresent", "Never"]
|
|
1315
1306
|
},
|
|
1307
|
+
"Health": {
|
|
1308
|
+
"title": "Health Configuration",
|
|
1309
|
+
"type": "object",
|
|
1310
|
+
"additionalProperties": false,
|
|
1311
|
+
"properties": {
|
|
1312
|
+
"port": {
|
|
1313
|
+
"$ref": "#/definitions/Port"
|
|
1314
|
+
},
|
|
1315
|
+
"liveness": {
|
|
1316
|
+
"$ref": "#/definitions/Probe"
|
|
1317
|
+
},
|
|
1318
|
+
"readiness": {
|
|
1319
|
+
"$ref": "#/definitions/Probe"
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
1322
|
+
},
|
|
1316
1323
|
"Probe": {
|
|
1317
1324
|
"title": "Probe configuration",
|
|
1318
1325
|
"type": "object",
|
|
@@ -1339,6 +1346,12 @@
|
|
|
1339
1346
|
"minimum": 1
|
|
1340
1347
|
}
|
|
1341
1348
|
}
|
|
1349
|
+
},
|
|
1350
|
+
"Port": {
|
|
1351
|
+
"title": "Port configuration",
|
|
1352
|
+
"type": "integer",
|
|
1353
|
+
"minimum": 1,
|
|
1354
|
+
"maximum": 65535
|
|
1342
1355
|
}
|
|
1343
1356
|
}
|
|
1344
1357
|
}
|
|
@@ -14,7 +14,7 @@ global:
|
|
|
14
14
|
_internal:
|
|
15
15
|
web-application:
|
|
16
16
|
versions:
|
|
17
|
-
1.
|
|
17
|
+
1.22.0: true
|
|
18
18
|
## Will be used instead of the `.Chart.Name`, e.g. when generating the Deployment name.
|
|
19
19
|
# nameOverride: name
|
|
20
20
|
|
|
@@ -94,6 +94,8 @@ availability:
|
|
|
94
94
|
- pod-template-hash
|
|
95
95
|
# Health-check configuration
|
|
96
96
|
health:
|
|
97
|
+
## If the probes are using ports other than the application port, specify them here.
|
|
98
|
+
# port: 8081
|
|
97
99
|
# Liveness-probe configuration.
|
|
98
100
|
liveness:
|
|
99
101
|
# HTTP path used by Kubernetes, to perform liveness-probe calls.
|
|
@@ -286,6 +288,20 @@ annotations:
|
|
|
286
288
|
# envFrom:
|
|
287
289
|
# - configMapRef:
|
|
288
290
|
# name: my-configmap1
|
|
291
|
+
# health:
|
|
292
|
+
# port: 8082
|
|
293
|
+
# liveness:
|
|
294
|
+
# path: /healthz
|
|
295
|
+
# periodSeconds: 3
|
|
296
|
+
# failureThreshold: 5
|
|
297
|
+
# successThreshold: 1
|
|
298
|
+
# timeoutSeconds: 1
|
|
299
|
+
# readiness:
|
|
300
|
+
# path: /healthz
|
|
301
|
+
# periodSeconds: 3
|
|
302
|
+
# failureThreshold: 5
|
|
303
|
+
# successThreshold: 1
|
|
304
|
+
# timeoutSeconds: 1
|
|
289
305
|
# sidecar-2:
|
|
290
306
|
# image:
|
|
291
307
|
# repository: gcr.io/my-sidecar-2
|
|
@@ -305,7 +321,12 @@ annotations:
|
|
|
305
321
|
# envFrom:
|
|
306
322
|
# - configMapRef:
|
|
307
323
|
# name: my-configmap1
|
|
324
|
+
|
|
325
|
+
# Optional configuration for creating Horizontal Pod Autoscaling
|
|
308
326
|
hpa:
|
|
309
327
|
enabled: false
|
|
310
328
|
minReplicas: 1
|
|
311
329
|
maxReplicas: 3
|
|
330
|
+
## See https://kubernetes.io/docs/reference/kubernetes-api/workload-resources/horizontal-pod-autoscaler-v2/#HorizontalPodAutoscalerSpec
|
|
331
|
+
# behavior: {}
|
|
332
|
+
# metrics: {}
|
package/lib/import/common.js
CHANGED
|
@@ -1,13 +1,15 @@
|
|
|
1
1
|
/* eslint-disable no-prototype-builtins */
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
|
+
/** @type { import('@sap/cds') } */
|
|
5
|
+
const cds = require('../cds');
|
|
4
6
|
let convert = require('xml-js');
|
|
5
7
|
let axios = require('axios');
|
|
6
8
|
const messages = require("./message").getMessages();
|
|
7
|
-
const cds = require('../cds');
|
|
8
9
|
const { fs, path, write, mkdirp, exists, read } = cds.utils;
|
|
9
10
|
const cdsCompiler = require('..').compile.to;
|
|
10
11
|
const md5 = data => require('crypto').createHash('md5').update(data).digest('hex');
|
|
12
|
+
const { info } = require("../util/term")
|
|
11
13
|
|
|
12
14
|
let optionsMapping = {
|
|
13
15
|
'keep-namespace': 'keepNamespace',
|
|
@@ -16,8 +18,9 @@ let optionsMapping = {
|
|
|
16
18
|
'no-save': 'no_save'
|
|
17
19
|
};
|
|
18
20
|
|
|
19
|
-
let apiOptions = ['includeNamespaces', 'keepNamespace'
|
|
20
|
-
let commonOptions = ['no_copy', 'no_save', 'out', 'dry', 'as', 'from', 'force', 'config', 'destination', 'group', 'name'];
|
|
21
|
+
let apiOptions = ['includeNamespaces', 'keepNamespace'];
|
|
22
|
+
let commonOptions = ['for', 'no_copy', 'no_save', 'out', 'dry', 'as', 'from', 'force', 'config', 'destination', 'group', 'name'];
|
|
23
|
+
let usingStatements = [];
|
|
21
24
|
|
|
22
25
|
async function generateEDMX2JSON(edmx) {
|
|
23
26
|
let cjson = convert.xml2json(edmx, {
|
|
@@ -163,6 +166,7 @@ async function processOptions(cliOptions, options) {
|
|
|
163
166
|
delete cliOptions[key];
|
|
164
167
|
}
|
|
165
168
|
}
|
|
169
|
+
|
|
166
170
|
// segregating cli/odata options
|
|
167
171
|
apiOptions.forEach(function (item) {
|
|
168
172
|
if (cliOptions[item]) {
|
|
@@ -268,7 +272,18 @@ async function _generateChecksumValidate(dest, output, force) {
|
|
|
268
272
|
return "/* checksum : " + currentChecksum + " */\n" + output;
|
|
269
273
|
}
|
|
270
274
|
|
|
271
|
-
|
|
275
|
+
// Modify the output to replace the using statements with the dependency services
|
|
276
|
+
function _replaceUsingStatementsForDependent(output) {
|
|
277
|
+
let using = usingStatements.join('\n');
|
|
278
|
+
usingStatements = [];
|
|
279
|
+
let startIndex = output.indexOf('using');
|
|
280
|
+
let endIndex = output.indexOf(';', startIndex);
|
|
281
|
+
let usingStatement = output.substring(startIndex, endIndex+1);
|
|
282
|
+
output = output.replace(usingStatement, using);
|
|
283
|
+
return output;
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
async function _getResult(output, as, dest, force, kind, isDependent) {
|
|
272
287
|
let extension = dest.substring(dest.lastIndexOf(".") + 1);
|
|
273
288
|
as = (as === "cds") ? "cdl" : as;
|
|
274
289
|
|
|
@@ -286,6 +301,9 @@ async function _getResult(output, as, dest, force, kind) {
|
|
|
286
301
|
if (as && as != "csn" && as != "json") {
|
|
287
302
|
// cdsCompiler[as] will convert the csn output into various output formats
|
|
288
303
|
output = cdsCompiler[as](output);
|
|
304
|
+
if (isDependent && kind === 'V4') {
|
|
305
|
+
output = _replaceUsingStatementsForDependent(output);
|
|
306
|
+
}
|
|
289
307
|
}
|
|
290
308
|
|
|
291
309
|
switch (as) {
|
|
@@ -304,52 +322,64 @@ async function _getResult(output, as, dest, force, kind) {
|
|
|
304
322
|
return [output, dest];
|
|
305
323
|
}
|
|
306
324
|
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
output = result[0];
|
|
310
|
-
dest = result[1];
|
|
311
|
-
await write(dest, output, "utf-8");
|
|
312
|
-
extension = dest.substring(dest.lastIndexOf('.'));
|
|
325
|
+
function _printUsageHint(dest, service, cwd) {
|
|
326
|
+
const extension = dest.substring(dest.lastIndexOf('.'));
|
|
313
327
|
dest = dest.replace(extension, '');
|
|
314
|
-
|
|
328
|
+
let using
|
|
329
|
+
if (service.length > 1){
|
|
315
330
|
using = `using { ${service.join(', ')}} from './${path.relative(cds.env.folders.srv, dest).replace(/\\/g, '/')}'`;
|
|
316
|
-
|
|
317
|
-
else{
|
|
331
|
+
} else {
|
|
318
332
|
using = `using { ${service} as external } from './${path.relative(cds.env.folders.srv, dest).replace(/\\/g, '/')}'`;
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
message = `[cds] - imported API to ${path.relative(cwd, dest)}
|
|
333
|
+
}
|
|
334
|
+
const message = `\n[cds] - imported API to ${path.relative(cwd, dest)}
|
|
322
335
|
> use it in your CDS models through the like of:
|
|
323
336
|
|
|
324
|
-
${using}
|
|
337
|
+
${require('../util/term').info(using)}
|
|
325
338
|
`;
|
|
326
|
-
|
|
327
339
|
console.log(message);
|
|
328
340
|
}
|
|
329
341
|
|
|
330
|
-
async function
|
|
342
|
+
async function _updateNodeConfig(dest, services, kind, cwd, options) {
|
|
343
|
+
let config
|
|
344
|
+
if (typeof options.config === 'string') {
|
|
345
|
+
if (options.config.trim().startsWith('{')) {
|
|
346
|
+
config = JSON.parse(options.config.trim());
|
|
347
|
+
} else {
|
|
348
|
+
config = {};
|
|
349
|
+
options.config.trim().split(',').forEach(kv => {
|
|
350
|
+
const [key, val] = kv.split('=');
|
|
351
|
+
config[key] = val;
|
|
352
|
+
})
|
|
353
|
+
normalizeToDeepObject(config);
|
|
354
|
+
}
|
|
355
|
+
}
|
|
356
|
+
|
|
331
357
|
// try to find package.json in the srv folder
|
|
332
358
|
let package_json = _findPackageJson(path.resolve(cwd, dest), cwd);
|
|
333
|
-
let
|
|
359
|
+
let pkgConf = {};
|
|
334
360
|
if (package_json) {
|
|
335
|
-
|
|
361
|
+
pkgConf = JSON.parse(await read(package_json, "utf-8"));
|
|
336
362
|
} else {
|
|
337
363
|
package_json = path.resolve(cwd, 'package.json');
|
|
338
364
|
}
|
|
339
|
-
|
|
365
|
+
|
|
366
|
+
const requires = ['cds', 'requires'].reduce((p, n) => p[n] || (p[n] = {}), pkgConf);
|
|
340
367
|
if (kind !== 'rest' && kind !== 'rfc') {
|
|
341
368
|
kind = (kind === "V2") ? 'odata-v2' : 'odata';
|
|
342
369
|
}
|
|
370
|
+
|
|
343
371
|
// add destination configuration
|
|
344
|
-
if (!config && destination) {
|
|
345
|
-
|
|
372
|
+
if (!config && options.destination) {
|
|
373
|
+
const profile = options.for || 'production';
|
|
374
|
+
config = { [`[${profile}]`]: { credentials: { destination: options.destination } }};
|
|
346
375
|
}
|
|
347
376
|
|
|
348
377
|
let package_json_updated = false;
|
|
349
378
|
for (let service of services) {
|
|
350
379
|
// existing service configuration data can be updated, e.g. in case of delta imports
|
|
351
380
|
const model = path.relative(cwd, dest).replace(/\\/g, '/'); // cds env must not be platform specific
|
|
352
|
-
|
|
381
|
+
let serviceConfig = { kind, model, ...config }
|
|
382
|
+
if (options.for) serviceConfig = { [`[${options.for}]`]: serviceConfig };
|
|
353
383
|
if (!requires[service]) {
|
|
354
384
|
cds.env.requires[service] = requires[service] = serviceConfig;
|
|
355
385
|
package_json_updated = true;
|
|
@@ -359,12 +389,44 @@ async function _updatePackageJson(dest, services, kind, cwd, config, destination
|
|
|
359
389
|
}
|
|
360
390
|
}
|
|
361
391
|
}
|
|
392
|
+
|
|
362
393
|
if (package_json_updated) {
|
|
363
|
-
await write(package_json, JSON.stringify(
|
|
394
|
+
await write(package_json, JSON.stringify(pkgConf, null, ' '));
|
|
364
395
|
console.log(`[cds] - updated ./package.json`);
|
|
365
396
|
}
|
|
366
397
|
}
|
|
367
398
|
|
|
399
|
+
async function _updateJavaConfig(kind, serviceNames, csn, options={}) {
|
|
400
|
+
const mvn = require('../init/mvn')
|
|
401
|
+
for (let name of serviceNames) {
|
|
402
|
+
let type = kind
|
|
403
|
+
if (!options.from || options.from === 'edmx') type = `odata-${kind.toLowerCase()}`
|
|
404
|
+
const javaOptions = {
|
|
405
|
+
name, type,
|
|
406
|
+
'destination.name': options.destination ?? name,
|
|
407
|
+
}
|
|
408
|
+
// if the name is not in the model, set the first/only service there as 'model', assuming only one was imported
|
|
409
|
+
if (!csn.definitions[name]) {
|
|
410
|
+
const srv = cds.reflect(csn).find('service')
|
|
411
|
+
if (srv) javaOptions['service.model'] = srv.name
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
// normalize config object into our flat list of key-value pairs
|
|
415
|
+
if (typeof options.config === 'string') {
|
|
416
|
+
if (options.config.trim().startsWith('{')) {
|
|
417
|
+
const cfg = JSON.parse(options.config)
|
|
418
|
+
_flattenConfigInto(cfg, javaOptions)
|
|
419
|
+
} else {
|
|
420
|
+
options.config.trim().split(',').forEach(kv => {
|
|
421
|
+
const [key, val] = kv.split('=')
|
|
422
|
+
javaOptions[key] = val
|
|
423
|
+
})
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
await mvn.add('remote-service', javaOptions)
|
|
427
|
+
}
|
|
428
|
+
}
|
|
429
|
+
|
|
368
430
|
function _findPackageJson(dir, cwd) {
|
|
369
431
|
const packageJsonPath = path.join(dir, 'package.json');
|
|
370
432
|
if (fs.existsSync(packageJsonPath)) {
|
|
@@ -380,29 +442,96 @@ function _findPackageJson(dir, cwd) {
|
|
|
380
442
|
return _findPackageJson(parentDir, cwd);
|
|
381
443
|
}
|
|
382
444
|
|
|
383
|
-
function
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
445
|
+
async function postProcess(filePath, options, csn, cwd) {
|
|
446
|
+
const services = _getServiceNames(csn);
|
|
447
|
+
let destFilePath;
|
|
448
|
+
|
|
449
|
+
if (options.isDependency || options.isDependent) {
|
|
450
|
+
destFilePath = path.resolve(cwd, options.out ? path.join(options.out, path.basename(filePath)) : path.join(cds.env.folders.srv, 'external', path.basename(filePath)));
|
|
451
|
+
} else {
|
|
452
|
+
destFilePath = path.resolve(cwd, options.out ? path.join(path.dirname(options.out), path.basename(filePath)) : path.join(cds.env.folders.srv, 'external', path.basename(filePath)));
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
if (options.out) {
|
|
456
|
+
options.out = _validateAndComputeOutputFilePath(options.out, options.as, destFilePath);
|
|
457
|
+
if (options.isDependency || options.isDependent) {
|
|
458
|
+
// output is a dirname in case of import with dependencies, then append the filename to it
|
|
459
|
+
let fileName = path.basename(filePath);
|
|
460
|
+
let index = fileName.lastIndexOf(".");
|
|
461
|
+
options.out = path.join(options.out, fileName.substring(0, index));
|
|
387
462
|
}
|
|
388
|
-
} catch (error) {
|
|
389
|
-
throw new Error(messages.SPECIFY_CONFIG);
|
|
390
463
|
}
|
|
391
|
-
}
|
|
392
464
|
|
|
393
|
-
async function postProcess(filePath, options, csn, cwd) {
|
|
394
|
-
const services = _getServiceNames(csn);
|
|
395
|
-
let config = _parseValidJsonConfig(options.config);
|
|
396
|
-
const destFilePath = path.resolve(cwd, options.out ? path.join(options.out, path.basename(filePath)) : path.join(cds.env.folders.srv, 'external', path.basename(filePath)));
|
|
397
|
-
if (options.out) options.out = _validateAndComputeOutputFilePath(options.out, options.as, destFilePath);
|
|
398
465
|
const kind = options.inputFileKind;
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
466
|
+
let result;
|
|
467
|
+
try {
|
|
468
|
+
result = await _getResult(csn, options.as, options.out || destFilePath.replace(/\.[^.]+$/, ''), options.force, kind, options.isDependent);
|
|
469
|
+
} catch (error) {
|
|
470
|
+
if (options.isDependency && error.message === messages.FILE_MODIFIED) {
|
|
471
|
+
return console.log(info(`[cds] - skipping import of ${path.basename(filePath)} as it is already imported`));
|
|
472
|
+
} else {
|
|
473
|
+
throw error;
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
|
|
477
|
+
const [serviceCsn, csnFile] = result;
|
|
478
|
+
|
|
479
|
+
if (!options.dry && !options.no_save) {
|
|
480
|
+
if (cds.env['project-nature'] === 'java') {
|
|
481
|
+
await _updateJavaConfig(kind, services, csn, options)
|
|
482
|
+
} else {
|
|
483
|
+
await _updateNodeConfig(result[1].replace(/\.[^.]+$/, ''), services, kind, cwd, options);
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
if (!options.dry)
|
|
487
|
+
await write(csnFile, serviceCsn, "utf-8");
|
|
488
|
+
|
|
489
|
+
if (options.isDependency) {
|
|
490
|
+
if (services.length > 1){
|
|
491
|
+
usingStatements.push(`using { ${services.join(', ')}} from './${path.basename(csnFile)}';`);
|
|
492
|
+
} else {
|
|
493
|
+
usingStatements.push(`using { ${services} } from './${path.basename(csnFile)}';`);
|
|
494
|
+
}
|
|
495
|
+
}
|
|
496
|
+
|
|
497
|
+
_printUsageHint(csnFile, services, cwd);
|
|
498
|
+
|
|
402
499
|
if (!options.dry && !options.no_copy)
|
|
403
500
|
await _copyToSrvExternal(filePath, destFilePath, cwd);
|
|
404
|
-
|
|
405
|
-
|
|
501
|
+
|
|
502
|
+
if (options.dry)
|
|
503
|
+
return console.log(result[0]);
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
function _flattenConfigInto (config, target, prefix='') {
|
|
507
|
+
for (let each in config) {
|
|
508
|
+
if (typeof config[each] === 'object') _flattenConfigInto (config[each], target, prefix + each + '.')
|
|
509
|
+
else target[prefix + each] = config[each]
|
|
510
|
+
}
|
|
511
|
+
return target
|
|
512
|
+
}
|
|
513
|
+
|
|
514
|
+
/**
|
|
515
|
+
* Normalizes an dot-separated keys in the object to a deep structure.
|
|
516
|
+
* Example is the SpringBoot config that allows dots in keys to express nested objects.
|
|
517
|
+
*/
|
|
518
|
+
function normalizeToDeepObject(obj) {
|
|
519
|
+
if (typeof obj !== 'object') return obj
|
|
520
|
+
Object.keys(obj).forEach(k => {
|
|
521
|
+
const prop = k.split('.')
|
|
522
|
+
const last = prop.pop()
|
|
523
|
+
// and define the object if not already defined
|
|
524
|
+
const res = prop.reduce((o, key) => {
|
|
525
|
+
// define the object if not defined and return
|
|
526
|
+
return o[key] = o[key] ?? {}
|
|
527
|
+
}, obj)
|
|
528
|
+
res[last] = obj[k]
|
|
529
|
+
// recursively normalize
|
|
530
|
+
normalizeToDeepObject(obj[k])
|
|
531
|
+
// delete the original property from object if it was rewritten
|
|
532
|
+
if (prop.length) delete obj[k]
|
|
533
|
+
})
|
|
534
|
+
return obj
|
|
406
535
|
}
|
|
407
536
|
|
|
408
537
|
async function identifyFile(filepath) {
|
|
@@ -454,5 +583,6 @@ module.exports = {
|
|
|
454
583
|
postProcess,
|
|
455
584
|
identifyFile,
|
|
456
585
|
readEnvVariables,
|
|
457
|
-
processOptions
|
|
586
|
+
processOptions,
|
|
587
|
+
normalizeToDeepObject
|
|
458
588
|
};
|
package/lib/import/importapi.js
CHANGED
|
@@ -64,7 +64,7 @@ async function cds_import_from_edmx(filepath, options = { keepNamespace: false }
|
|
|
64
64
|
filepath = await common.isValidInputFile(path.resolve(process.cwd(), filepath));
|
|
65
65
|
const src = await common.getContentFromFile(filepath);
|
|
66
66
|
// takes the api specific options and reads it from env
|
|
67
|
-
let odataOptions = { include_namespaces: options.includeNamespaces, keep_namespace: options.keepNamespace
|
|
67
|
+
let odataOptions = { include_namespaces: options.includeNamespaces, keep_namespace: options.keepNamespace};
|
|
68
68
|
if (cds.env.import) {
|
|
69
69
|
common.readEnvVariables(odataOptions, false);
|
|
70
70
|
}
|
package/lib/import/message.js
CHANGED
|
@@ -37,8 +37,8 @@ const messages = {
|
|
|
37
37
|
FIXED_PRECISION_VARIABLE_SCALE: 'WARNING: Fixed Precision with Variable or Floating Scale is not supported in CDS.',
|
|
38
38
|
NULLABLE_KEY: 'WARNING: Key element cannot be Nullable.',
|
|
39
39
|
COLLISION_DETECTED: `INFO: Element name collision detected. Adding suffix to affected type category.`,
|
|
40
|
-
SPECIFY_CONFIG: 'For --config option, please specify correct JSON Object.',
|
|
41
40
|
INVALID_ANNOTATION: 'Annotation element must have a Term attribute',
|
|
41
|
+
DEPENDENCY_NOT_SUPPORTED: '--dependencies option is only supported for OData V4 files, not for other imports.',
|
|
42
42
|
};
|
|
43
43
|
|
|
44
44
|
function getMessages() {
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
// Singleton class to hold the parser context
|
|
2
|
+
const MultiMap = require("./util/MultiMap");
|
|
3
|
+
|
|
4
|
+
module.exports = class ParserContext {
|
|
5
|
+
|
|
6
|
+
static #instance = null;
|
|
7
|
+
|
|
8
|
+
static getInstance(mockServerUc) {
|
|
9
|
+
if (!ParserContext.#instance) {
|
|
10
|
+
ParserContext.#instance = new ParserContext(mockServerUc);
|
|
11
|
+
}
|
|
12
|
+
return ParserContext.#instance;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
static resetInstance() {
|
|
16
|
+
ParserContext.#instance = null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
constructor(mockServerUc) {
|
|
20
|
+
this.#initialize(mockServerUc);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
#initialize(mockServerUc) {
|
|
24
|
+
this.unboundActions = {};
|
|
25
|
+
this.unboundFunctions = {};
|
|
26
|
+
this.entityTypes = [];
|
|
27
|
+
this.entityTypeOpenEntries = [];
|
|
28
|
+
this.complexTypeOpenEntries = [];
|
|
29
|
+
this.complexTypes = [];
|
|
30
|
+
this.enumTypes = [];
|
|
31
|
+
this.typeDefinitions = [];
|
|
32
|
+
this.entityContainerName = "";
|
|
33
|
+
this.entityToEntitySetMap = new MultiMap();
|
|
34
|
+
this.entityToSingletonMap = new MultiMap();
|
|
35
|
+
this.actionToActionImportMap = new MultiMap();
|
|
36
|
+
this.functionToFunctionImportMap = new MultiMap();
|
|
37
|
+
this.primarySchema = "";
|
|
38
|
+
this.schemaAliasToNamespace = {};
|
|
39
|
+
this.schemaToSchemaDataIndex = {};
|
|
40
|
+
this.vocabNamespaceToAlias = {};
|
|
41
|
+
this.entityToBoundActions = {};
|
|
42
|
+
this.entityToBoundFunctions = {};
|
|
43
|
+
this.entityContainerAnnotations = {};
|
|
44
|
+
this.mockServerUc = mockServerUc;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
@@ -6,6 +6,8 @@ const messages = require("../message").getMessages();
|
|
|
6
6
|
let common = require("../common");
|
|
7
7
|
const { path } = require('../../cds').utils;
|
|
8
8
|
|
|
9
|
+
let namespaceFileObject = {};
|
|
10
|
+
|
|
9
11
|
function getNamespaces(context) {
|
|
10
12
|
if (context.include_namespaces === "*") context.include_all_namespaces = true;
|
|
11
13
|
else if (typeof context.include_namespaces === 'string' && !context.include_namespaces.startsWith('-')) context.namespaces = context.include_namespaces.replace(/ /g, "").split(',');
|
|
@@ -73,9 +75,11 @@ async function edmx2csn(edmx, filename, context) {
|
|
|
73
75
|
edmx = _convertEDMXWithMultipleSchemas(edmx, multipleSchemas, namespace);
|
|
74
76
|
}
|
|
75
77
|
let csn = await _generateCSN(edmx, false, true, context);
|
|
76
|
-
|
|
77
|
-
// Regex checks for this condition “A-B” must start with a letter or underscore, followed by at most 127 letters, underscores or digits in the fileName
|
|
78
|
+
|
|
78
79
|
if (filename) {
|
|
80
|
+
// Regex checks for this condition “A-B” must start with a letter or underscore, followed by at most 127 letters, underscores or digits in the fileName
|
|
81
|
+
const regex = /^[\p{Letter}\p{Nl}_]{1}[_\p{Letter}\p{Nl}\p{Nd}\p{Mn}\p{Mc}\p{Pc}\p{Cf}]{0,127}$/gu
|
|
82
|
+
|
|
79
83
|
/**
|
|
80
84
|
* V4: <Schema Namespace="Full.Schema.Name" xmlns="http://docs.oasis-open.org/odata/ns/edm" Alias="aliasedValue">
|
|
81
85
|
* V2: <Schema Namespace="Full.Schema.Name" xmlns="http://schemas.microsoft.com/ado/2008/09/edm" Alias="aliasedValue">
|
|
@@ -83,17 +87,22 @@ async function edmx2csn(edmx, filename, context) {
|
|
|
83
87
|
* context.schemaNamespace is set to correct namespace value by the respective parsers
|
|
84
88
|
*/
|
|
85
89
|
if (context.schemaNamespace) {
|
|
86
|
-
|
|
90
|
+
namespaceFileObject[context.schemaNamespace] = filename;
|
|
91
|
+
} else {
|
|
92
|
+
namespaceFileObject[namespace] = filename;
|
|
87
93
|
}
|
|
88
|
-
|
|
89
|
-
const
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
name =
|
|
93
|
-
|
|
94
|
+
|
|
95
|
+
for (const [namespace, filename] of Object.entries(namespaceFileObject)) {
|
|
96
|
+
// Replace namespace by given filename in the csn file instead of edmx file
|
|
97
|
+
const re = RegExp(`([("/])${namespace}([/").])`, 'g');
|
|
98
|
+
let name = path.parse(filename).name;
|
|
99
|
+
if (!(name.match(regex))) { // check if the filename is in the right format with the regex if not replace unaccepted characters with underscore (_)
|
|
100
|
+
name = name.replace(/^[^a-z\p{Nl}_]/gui, '_');
|
|
101
|
+
name = name.replace(/[^a-z0-9_\p{Nl}\p{Nd}\p{Mn}\p{Mc}\p{Pc}\p{Cf}]/gui, '_');
|
|
102
|
+
}
|
|
103
|
+
const fn = `$1${name}$2`;
|
|
104
|
+
csn = csn.replace(re, fn);
|
|
94
105
|
}
|
|
95
|
-
const fn = `$1${name}$2`;
|
|
96
|
-
csn = csn.replace(re, fn);
|
|
97
106
|
}
|
|
98
107
|
return JSON.parse(csn);
|
|
99
108
|
}
|