@sap/cds-dk 4.9.5 → 6.0.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 +73 -0
- package/bin/activate.js +4 -4
- package/bin/add.js +11 -5
- package/bin/bind.js +16 -28
- package/bin/build/all.js +2 -2
- package/bin/build/index.js +12 -19
- package/bin/cds.js +16 -17
- package/bin/compile/index.js +12 -3
- package/bin/deploy/index.js +14 -14
- package/bin/deploy/to-hana.js +4 -4
- package/bin/deploy/to-sqlite.js +1 -1
- package/bin/env.js +13 -4
- package/bin/eval.js +1 -1
- package/bin/fix-redirects.js +1 -1
- package/bin/import.js +44 -179
- package/bin/repl.js +1 -1
- package/bin/watch.js +10 -15
- package/lib/bind/BindingManager.js +87 -0
- package/lib/bind/bindingResolver.js +14 -85
- package/lib/bind/bindingUtil.js +18 -11
- package/lib/bind/cfKeyProvider.js +58 -29
- package/lib/bind/kubernetesCredentialsProvider.js +44 -21
- package/lib/bind/userCdsrcJsonStorageProvider.js +12 -18
- package/lib/cds.js +13 -0
- package/lib/client/base_command.js +3 -2
- package/lib/client/extend.js +6 -7
- package/lib/client/helper/fs_helper.js +7 -8
- package/lib/client/helper/string_helper.js +6 -1
- package/lib/client/settings_manager.js +38 -45
- package/lib/compile/index.js +9 -10
- package/lib/compile/openapi/csdl2openapi.js +151 -43
- package/lib/compile/openapi/index.js +2 -2
- package/lib/compile/to-xsuaa.js +3 -2
- package/lib/import/common.js +342 -27
- package/lib/import/importapi.js +81 -13
- package/lib/import/message.js +4 -4
- package/lib/import/odata/index.js +141 -0
- package/lib/import/{utils → odata/metadataconverter}/MetadataConverter.js +2 -2
- package/lib/import/{utils → odata/metadataconverter}/MetadataConverterFactory.js +1 -1
- package/lib/import/{utils → odata/util}/MultiMap.js +0 -0
- package/lib/import/{v2parser.js → odata/v2parser.js} +405 -468
- package/lib/import/{v4parser.js → odata/v4parser.js} +311 -322
- package/lib/import/{vocabularies → odata/vocabularies}/Org.OData.Aggregation.V1.json +0 -0
- package/lib/import/{vocabularies → odata/vocabularies}/Org.OData.Authorization.V1.json +0 -0
- package/lib/import/{vocabularies → odata/vocabularies}/Org.OData.Capabilities.V1.json +0 -0
- package/lib/import/{vocabularies → odata/vocabularies}/Org.OData.Core.V1.json +0 -0
- package/lib/import/{vocabularies → odata/vocabularies}/Org.OData.Measures.V1.json +0 -0
- package/lib/import/{vocabularies → odata/vocabularies}/Org.OData.Validation.V1.json +0 -0
- package/lib/import/openapi/importOpenAPI.js +521 -0
- package/lib/import/openapi/index.js +19 -0
- package/lib/import/openapi/utilities.js +38 -0
- package/lib/index.js +1 -1
- package/lib/init/constants.js +7 -2
- package/lib/init/index.js +10 -3
- package/lib/init/template/_merging/existences.js +79 -19
- package/lib/init/template/approuter/files/mta.yaml.hbs +25 -11
- package/lib/init/template/approuter/files/package.json +1 -1
- package/lib/init/template/approuter/files/xs-app.json.hbs +7 -0
- package/lib/init/template/approuter/index.js +61 -54
- package/lib/init/template/auditlog/index.js +2 -2
- package/lib/init/template/data/index.js +2 -2
- package/lib/init/template/hana/files/mta.yaml.hbs +17 -17
- package/lib/init/template/hana/index.js +8 -4
- package/lib/init/template/helm/index.js +39 -0
- package/lib/init/template/helm/update/index.js +28 -0
- package/lib/init/template/java/index.js +7 -9
- package/lib/init/template/kibana-logging/index.js +5 -6
- package/lib/init/template/mta/index.js +4 -4
- package/lib/init/template/mtx/files/java/package.json +9 -9
- package/lib/init/template/mtx/files/mta.yaml.hbs +0 -7
- package/lib/init/template/mtx/files/xs-security.json.hbs +11 -20
- package/lib/init/template/mtx/index.js +50 -57
- package/lib/init/template/nodejs/files/package.json.hbs +2 -2
- package/lib/init/template/notebook/index.js +2 -0
- package/lib/init/template/templateBase.js +6 -4
- package/lib/init/template/xsuaa/index.js +9 -5
- package/lib/init/util/fsUtil.js +25 -10
- package/lib/init/util/logger.js +1 -1
- package/lib/init/util/mvnArchetypeUtil.js +4 -10
- package/lib/init/util/projectReader.js +16 -8
- package/lib/init/util/templateUtil.js +30 -34
- package/lib/k8s/ChartAdder.js +65 -0
- package/lib/k8s/ChartBuilder.js +186 -0
- package/lib/k8s/Context.js +71 -0
- package/lib/k8s/ImageUtil.js +21 -0
- package/lib/k8s/Model.d.ts +53 -0
- package/lib/k8s/auditlog/index.js +29 -0
- package/lib/k8s/auditlog/templates/auditlog.yaml +1 -0
- package/lib/k8s/buildModel.js +70 -0
- package/lib/k8s/connectivity/index.js +74 -0
- package/lib/k8s/connectivity/templates/connectivity-binding.yaml +9 -0
- package/lib/k8s/connectivity/templates/connectivity-proxy-info.yaml +16 -0
- package/lib/k8s/connectivity/templates/connectivity.yaml +1 -0
- package/lib/k8s/deployment/index.js +81 -0
- package/lib/k8s/deployment/subchart/Chart.yaml +6 -0
- package/lib/k8s/deployment/subchart/templates/_helpers.tpl +156 -0
- package/lib/k8s/deployment/subchart/templates/api-rule.yaml +24 -0
- package/lib/k8s/deployment/subchart/templates/deployment.yaml +110 -0
- package/lib/k8s/deployment/subchart/templates/network-policy.yaml +16 -0
- package/lib/k8s/deployment/subchart/templates/pod-disruption-budget.yaml +11 -0
- package/lib/k8s/deployment/subchart/templates/secret.yaml +10 -0
- package/lib/k8s/deployment/subchart/templates/service-binding.yaml +27 -0
- package/lib/k8s/deployment/subchart/templates/service.yaml +11 -0
- package/lib/k8s/deployment/subchart/values-lint.yaml +8 -0
- package/lib/k8s/deployment/subchart/values.schema.json +449 -0
- package/lib/k8s/deployment/subchart/values.yaml +76 -0
- package/lib/k8s/deployment/subchart-override/templates/_web_application_helpers.tpl +81 -0
- package/lib/k8s/destinations/index.js +40 -0
- package/lib/k8s/destinations/lib.js +16 -0
- package/lib/k8s/destinations/templates/destinations.yaml +1 -0
- package/lib/k8s/docker/push.js +8 -0
- package/lib/k8s/event-mesh/index.js +53 -0
- package/lib/k8s/event-mesh/lib.js +9 -0
- package/lib/k8s/event-mesh/templates/event-mesh.yaml +1 -0
- package/lib/k8s/hana-deployer/index.js +38 -0
- package/lib/k8s/hana-deployer/templates/hana-deployer-job.yaml +41 -0
- package/lib/k8s/helm/Release.js +80 -0
- package/lib/k8s/html5-apps-deployer/index.js +43 -0
- package/lib/k8s/html5-apps-deployer/templates/_html5_deployment_helpers.tpl +16 -0
- package/lib/k8s/html5-apps-deployer/templates/html5-apps-deployer-job.yaml +53 -0
- package/lib/k8s/html5-apps-repo-host/index.js +26 -0
- package/lib/k8s/html5-apps-repo-host/lib.js +16 -0
- package/lib/k8s/html5-apps-repo-host/templates/html5-apps-repo-host.yaml +1 -0
- package/lib/k8s/index.js +153 -0
- package/lib/k8s/modules.js +36 -0
- package/lib/k8s/multitenancy/index.js +24 -0
- package/lib/k8s/multitenancy/lib.js +25 -0
- package/lib/k8s/multitenancy/templates/saas-registry.yaml +19 -0
- package/lib/k8s/readProject.js +14 -0
- package/lib/k8s/resources/helm/chart/templates/NOTES.txt +11 -0
- package/lib/k8s/resources/helm/chart/templates/_deployment_helpers.tpl +221 -0
- package/lib/k8s/resources/helm/chart/templates/_helpers.tpl +167 -0
- package/lib/k8s/resources/helm/chart/templates/_sapcp_helpers.tpl +103 -0
- package/lib/k8s/util.js +186 -0
- package/lib/k8s/wrapFeature.js +55 -0
- package/lib/k8s/xsuaa/index.js +64 -0
- package/lib/k8s/xsuaa/lib.js +14 -0
- package/lib/k8s/xsuaa/templates/xsuaa.yaml +1 -0
- package/lib/util/exec.js +1 -1
- package/lib/util/write.js +1 -1
- package/lib/watch/node-watch.js +43 -14
- package/npm-shrinkwrap.json +1272 -2792
- package/package.json +5 -5
- package/lib/import/index.js +0 -235
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
@@ -0,0 +1,521 @@
|
|
|
1
|
+
const {
|
|
2
|
+
cdsName,
|
|
3
|
+
nameFromPath,
|
|
4
|
+
pathAndMethod,
|
|
5
|
+
serviceName,
|
|
6
|
+
} = require("./utilities");
|
|
7
|
+
|
|
8
|
+
module.exports = { importOpenAPI };
|
|
9
|
+
|
|
10
|
+
// we are not interested (yet) in HEAD, OPTIONS, TRACE
|
|
11
|
+
const IS_METHOD = {
|
|
12
|
+
delete: true,
|
|
13
|
+
get: true,
|
|
14
|
+
patch: true,
|
|
15
|
+
post: true,
|
|
16
|
+
put: true,
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const STANDARD_HEADERS = [
|
|
20
|
+
"accept",
|
|
21
|
+
"accept-encoding",
|
|
22
|
+
"accept-language",
|
|
23
|
+
"authorization",
|
|
24
|
+
"content-type",
|
|
25
|
+
"if-match",
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
function importOpenAPI(input) {
|
|
29
|
+
const csn = {
|
|
30
|
+
definitions: {},
|
|
31
|
+
meta: { creator: "cds-import-openapi" },
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
const context = {
|
|
35
|
+
serviceName: serviceName(input.info?.title || "TODO.service"),
|
|
36
|
+
oasVersion: input.openapi || input.swagger,
|
|
37
|
+
v3: !!input.openapi,
|
|
38
|
+
schemas: input.openapi ? input.components?.schemas : input.definitions,
|
|
39
|
+
anonymous: [],
|
|
40
|
+
JSON: false,
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
//TODO: complain if neither swagger nor openapi3, i.e. context.oasVersion is undefined
|
|
44
|
+
const service = {
|
|
45
|
+
kind: "service",
|
|
46
|
+
"@Capabilities.BatchSupported": false,
|
|
47
|
+
"@Capabilities.KeyAsSegmentSupported": true,
|
|
48
|
+
};
|
|
49
|
+
if (input.info?.title) service["@Core.Description"] = input.info.title;
|
|
50
|
+
if (input.info?.version) service["@Core.SchemaVersion"] = input.info.version;
|
|
51
|
+
if (input.info?.description)
|
|
52
|
+
service["@Core.LongDescription"] = input.info.description;
|
|
53
|
+
csn.definitions[context.serviceName] = service;
|
|
54
|
+
|
|
55
|
+
operations(context, csn, input);
|
|
56
|
+
|
|
57
|
+
reuseTypes(context, csn);
|
|
58
|
+
|
|
59
|
+
anonymousTypes(context, csn);
|
|
60
|
+
|
|
61
|
+
return csn;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function reuseTypes(context, csn) {
|
|
65
|
+
for (const [name, schema] of Object.entries(context.schemas || {})) {
|
|
66
|
+
const typeName = `${context.serviceName}_types.${cdsName(name)}`;
|
|
67
|
+
csn.definitions[typeName] = cdsType(context, schema, false, true);
|
|
68
|
+
csn.definitions[typeName].kind = "type";
|
|
69
|
+
delete csn.definitions[typeName].notNull;
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function anonymousTypes(context, csn) {
|
|
74
|
+
for (const a of context.anonymous) {
|
|
75
|
+
csn.definitions[a.name] = a.type;
|
|
76
|
+
csn.definitions[a.name].kind = "type";
|
|
77
|
+
}
|
|
78
|
+
if (context.JSON) {
|
|
79
|
+
csn.definitions["common.JSON"] = { kind: "type", type: "cds.LargeString" };
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function operations(context, csn, input) {
|
|
84
|
+
const reuseParameters = context.v3
|
|
85
|
+
? input.components?.parameters
|
|
86
|
+
: input.parameters;
|
|
87
|
+
const reuseResponses = context.v3
|
|
88
|
+
? input.components?.responses
|
|
89
|
+
: input.responses;
|
|
90
|
+
for (const [path, item] of Object.entries(input.paths || {})) {
|
|
91
|
+
const globalParameters = item.parameters || [];
|
|
92
|
+
for (const [method, operation] of Object.entries(item)) {
|
|
93
|
+
if (!IS_METHOD[method]) continue;
|
|
94
|
+
const cdsOperation = {
|
|
95
|
+
kind: method === "get" ? "function" : "action",
|
|
96
|
+
params: {},
|
|
97
|
+
};
|
|
98
|
+
if (operation.tags) cdsOperation["@Common.Label"] = operation.tags[0];
|
|
99
|
+
if (operation.summary)
|
|
100
|
+
cdsOperation["@Core.Description"] = operation.summary;
|
|
101
|
+
if (operation.description)
|
|
102
|
+
cdsOperation["@Core.LongDescription"] = operation.description;
|
|
103
|
+
if (cdsOperation.kind === "action" && method !== "post")
|
|
104
|
+
cdsOperation["@openapi.method"] = method.toUpperCase();
|
|
105
|
+
cdsOperation["@openapi.path"] = path;
|
|
106
|
+
|
|
107
|
+
for (let param of globalParameters.concat(operation.parameters || [])) {
|
|
108
|
+
if (param.$ref) {
|
|
109
|
+
// resolve reuse parameter
|
|
110
|
+
const expectedPrefix = context.v3
|
|
111
|
+
? "#/components/parameters/"
|
|
112
|
+
: "#/parameters/";
|
|
113
|
+
if (!param.$ref.startsWith(expectedPrefix)) {
|
|
114
|
+
throw new Error(`TODO: unexpected reference ${param.$ref} in parameter for ${method} of ${path}`);
|
|
115
|
+
}
|
|
116
|
+
param = reuseParameters[param.$ref.substring(expectedPrefix.length)];
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
if (param.in === "body") continue;
|
|
120
|
+
if (STANDARD_HEADERS.includes(param.name.toLowerCase())) continue;
|
|
121
|
+
|
|
122
|
+
const cdsParam = cdsType(
|
|
123
|
+
context,
|
|
124
|
+
context.v3 ? param?.schema : param,
|
|
125
|
+
false,
|
|
126
|
+
false,
|
|
127
|
+
true
|
|
128
|
+
);
|
|
129
|
+
if (param.description) cdsParam["@description"] = param.description;
|
|
130
|
+
cdsParam["@openapi.in"] = param.in;
|
|
131
|
+
|
|
132
|
+
if ((param.in === "path" && param.style && param.style !== "simple") ||
|
|
133
|
+
(param.in === "query" && param.style && param.style !== "form")
|
|
134
|
+
)
|
|
135
|
+
cdsParam["@openapi.style"] = param.style;
|
|
136
|
+
|
|
137
|
+
if (param.explode && param.style !== "form")
|
|
138
|
+
cdsParam["@openapi.explode"] = true;
|
|
139
|
+
|
|
140
|
+
if (param.explode === false &&
|
|
141
|
+
(param.style === "form" || (!param.style && param.in === "query"))
|
|
142
|
+
)
|
|
143
|
+
cdsParam["@openapi.explode"] = false;
|
|
144
|
+
|
|
145
|
+
if (param.in === "query" && param.collectionFormat === "csv")
|
|
146
|
+
cdsParam["@openapi.explode"] = false;
|
|
147
|
+
if (param.in === "query" && param.collectionFormat === "ssv")
|
|
148
|
+
cdsParam["@openapi.style"] = "spaceDelimited";
|
|
149
|
+
if (param.in === "query" && param.collectionFormat === "pipes")
|
|
150
|
+
cdsParam["@openapi.style"] = "pipeDelimited";
|
|
151
|
+
if (param.in === "query" && param.allowReserved === true)
|
|
152
|
+
cdsParam["@openapi.allowReserved"] = true;
|
|
153
|
+
if (param.required && param.in !== "path")
|
|
154
|
+
cdsParam["@openapi.required"] = true;
|
|
155
|
+
if (!param.required)
|
|
156
|
+
delete cdsParam.notNull;
|
|
157
|
+
|
|
158
|
+
const name = cdsName(param.name);
|
|
159
|
+
if (name !== param.name) cdsParam["@openapi.name"] = param.name;
|
|
160
|
+
cdsOperation.params[name] = cdsParam;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const schema = requestBodySchema(context, operation);
|
|
164
|
+
if (schema) {
|
|
165
|
+
cdsOperation.params.body = cdsType(context, schema);
|
|
166
|
+
cdsOperation.params.body["@openapi.in"] = "body";
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (!operation.responses)
|
|
170
|
+
throw new Error(`TODO: no responses for path ${path}, method ${method}`);
|
|
171
|
+
|
|
172
|
+
const successCode = Object.keys(operation.responses || {}).find((r) =>
|
|
173
|
+
r.startsWith("2")
|
|
174
|
+
);
|
|
175
|
+
if (successCode) {
|
|
176
|
+
let response = operation.responses[successCode];
|
|
177
|
+
if (response.$ref) {
|
|
178
|
+
// resolve reuse response
|
|
179
|
+
const expectedPrefix = context.v3
|
|
180
|
+
? "#/components/responses/"
|
|
181
|
+
: "#/responses/";
|
|
182
|
+
if (!response.$ref.startsWith(expectedPrefix))
|
|
183
|
+
throw new Error(`TODO: unexpected reference ${response.$ref} in ${successCode} response for method ${method} of path ${path}`);
|
|
184
|
+
else
|
|
185
|
+
response =
|
|
186
|
+
reuseResponses[response.$ref.substring(expectedPrefix.length)];
|
|
187
|
+
}
|
|
188
|
+
const responseSchema = context.v3
|
|
189
|
+
? response.content?.["application/json"]?.schema
|
|
190
|
+
: response.schema;
|
|
191
|
+
if (responseSchema)
|
|
192
|
+
cdsOperation.returns = cdsType(context, responseSchema);
|
|
193
|
+
else if (method === "get")
|
|
194
|
+
cdsOperation.returns = { type: "cds.Boolean" };
|
|
195
|
+
} else if (method === "get")
|
|
196
|
+
cdsOperation.returns = { type: "cds.Boolean" };
|
|
197
|
+
|
|
198
|
+
const operationName = `${context.serviceName}.${nameFromPath(
|
|
199
|
+
path,
|
|
200
|
+
method
|
|
201
|
+
)}`;
|
|
202
|
+
|
|
203
|
+
if (csn.definitions[operationName]) {
|
|
204
|
+
const existing = pathAndMethod(csn.definitions[operationName]);
|
|
205
|
+
throw new Error(`Name collision: same name ${operationName} for method ${method} of path ${path} and method ${existing.method.toLowerCase()} of path ${existing.path}`);
|
|
206
|
+
}
|
|
207
|
+
csn.definitions[operationName] = cdsOperation;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function requestBodySchema(context, operation) {
|
|
213
|
+
const requestBody = context.v3
|
|
214
|
+
? operation.requestBody
|
|
215
|
+
: operation.parameters?.find((p) => p.in === "body");
|
|
216
|
+
return context.v3
|
|
217
|
+
? requestBody?.content["application/json"]?.schema
|
|
218
|
+
: requestBody?.schema;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function cdsType(
|
|
222
|
+
context,
|
|
223
|
+
schema,
|
|
224
|
+
arrayItem = false,
|
|
225
|
+
namedType = false,
|
|
226
|
+
forParameter = false
|
|
227
|
+
) {
|
|
228
|
+
let type = {};
|
|
229
|
+
let hasIncludes = false;
|
|
230
|
+
|
|
231
|
+
if (!arrayItem) if (schema.title) type["@title"] = schema.title;
|
|
232
|
+
if (schema.description) type["@description"] = schema.description;
|
|
233
|
+
|
|
234
|
+
if (schema.$ref) {
|
|
235
|
+
type.type = referencedType(context, schema.$ref).name;
|
|
236
|
+
return type;
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
let schemaType = normalizeSchemaType(schema);
|
|
240
|
+
|
|
241
|
+
switch (schemaType) {
|
|
242
|
+
case "array":
|
|
243
|
+
//TODO: complain if "xml"
|
|
244
|
+
{
|
|
245
|
+
const itemsType = cdsType(
|
|
246
|
+
context,
|
|
247
|
+
schema.items,
|
|
248
|
+
true,
|
|
249
|
+
false,
|
|
250
|
+
forParameter
|
|
251
|
+
);
|
|
252
|
+
const annotations = Object.keys(itemsType).filter((k) =>
|
|
253
|
+
k.startsWith("@")
|
|
254
|
+
);
|
|
255
|
+
// make anonymous type for item if item has annotations
|
|
256
|
+
if (annotations.length > 0) {
|
|
257
|
+
type.items = anonymousType(context, itemsType);
|
|
258
|
+
} else {
|
|
259
|
+
type.items = itemsType;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
break;
|
|
263
|
+
|
|
264
|
+
case "boolean":
|
|
265
|
+
type.type = "cds.Boolean";
|
|
266
|
+
addDefault(type, schema, forParameter);
|
|
267
|
+
break;
|
|
268
|
+
|
|
269
|
+
case "file":
|
|
270
|
+
type.type = "cds.String";
|
|
271
|
+
break;
|
|
272
|
+
|
|
273
|
+
case "integer":
|
|
274
|
+
type.type = "cds.Integer";
|
|
275
|
+
if (schema.format === "int64") type.type = "cds.Integer64";
|
|
276
|
+
addDefault(type, schema, forParameter);
|
|
277
|
+
addPrimitiveExample(type, schema);
|
|
278
|
+
break;
|
|
279
|
+
|
|
280
|
+
case "number":
|
|
281
|
+
type.type = "cds.Decimal";
|
|
282
|
+
if (schema.format === "double" || schema.format === "float")
|
|
283
|
+
type.type = "cds.Double";
|
|
284
|
+
addDefault(type, schema, forParameter);
|
|
285
|
+
addPrimitiveExample(type, schema);
|
|
286
|
+
break;
|
|
287
|
+
|
|
288
|
+
case "object":
|
|
289
|
+
type.elements = {};
|
|
290
|
+
//TODO: discriminator(context, type, schema);
|
|
291
|
+
if (schema.allOf || schema.anyOf || schema.oneOf) {
|
|
292
|
+
const xOf = (schema.allOf || []).concat(
|
|
293
|
+
schema.anyOf || [],
|
|
294
|
+
schema.oneOf || []
|
|
295
|
+
);
|
|
296
|
+
for (const subSchema of xOf) {
|
|
297
|
+
if (subSchema.$ref) {
|
|
298
|
+
hasIncludes = true;
|
|
299
|
+
const refType = referencedType(context, subSchema.$ref);
|
|
300
|
+
if (!type.includes) type.includes = [];
|
|
301
|
+
type.includes.push(refType.name);
|
|
302
|
+
} else if (subSchema.type === "object" || subSchema.properties) {
|
|
303
|
+
//TODO: what if subSchema has allOf/...? Better recurse here and "merge" the types?
|
|
304
|
+
structElements(context, type, subSchema);
|
|
305
|
+
} else {
|
|
306
|
+
// should not get here
|
|
307
|
+
throw new Error(`Error: object with non-object sub-schema`);
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
structElements(context, type, schema);
|
|
313
|
+
|
|
314
|
+
if (!namedType && hasIncludes) {
|
|
315
|
+
type = anonymousType(context, type);
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
break;
|
|
319
|
+
|
|
320
|
+
case "string":
|
|
321
|
+
switch (schema.format) {
|
|
322
|
+
case "binary":
|
|
323
|
+
type.type = "cds.LargeBinary";
|
|
324
|
+
if (schema.maxLength) type.length = schema.maxLength;
|
|
325
|
+
break;
|
|
326
|
+
case "date":
|
|
327
|
+
type.type = "cds.Date";
|
|
328
|
+
break;
|
|
329
|
+
case "date-time":
|
|
330
|
+
type.type = "cds.Timestamp";
|
|
331
|
+
break;
|
|
332
|
+
case "time":
|
|
333
|
+
type.type = "cds.Time";
|
|
334
|
+
break;
|
|
335
|
+
case "uuid":
|
|
336
|
+
type.type = "cds.UUID";
|
|
337
|
+
break;
|
|
338
|
+
default:
|
|
339
|
+
type.type = "cds.String";
|
|
340
|
+
if (schema.maxLength) type.length = schema.maxLength;
|
|
341
|
+
}
|
|
342
|
+
if (Array.isArray(schema.enum)) {
|
|
343
|
+
type["@assert.range"] = true;
|
|
344
|
+
type.enum = Object.fromEntries(
|
|
345
|
+
schema.enum.map((val) => {
|
|
346
|
+
const key = cdsName(val);
|
|
347
|
+
return key === val ? [val, {}] : [key, { val }];
|
|
348
|
+
})
|
|
349
|
+
);
|
|
350
|
+
}
|
|
351
|
+
addDefault(type, schema, forParameter);
|
|
352
|
+
addPrimitiveExample(type, schema);
|
|
353
|
+
break;
|
|
354
|
+
|
|
355
|
+
case undefined:
|
|
356
|
+
schemaType = bestMatchingType(context, schema);
|
|
357
|
+
if (schemaType) {
|
|
358
|
+
schema.type = schemaType;
|
|
359
|
+
type = cdsType(context, schema, arrayItem, namedType);
|
|
360
|
+
break;
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
// last resort
|
|
364
|
+
// eslint-disable-next-line no-fallthrough
|
|
365
|
+
default:
|
|
366
|
+
{
|
|
367
|
+
context.JSON = true;
|
|
368
|
+
const jsonSchema = minimalSchema(schema);
|
|
369
|
+
if (arrayItem && jsonSchema !== "{}") {
|
|
370
|
+
type = anonymousType(context, {
|
|
371
|
+
type: "common.JSON",
|
|
372
|
+
"@openapi.schema": jsonSchema,
|
|
373
|
+
});
|
|
374
|
+
} else {
|
|
375
|
+
type.type = "common.JSON";
|
|
376
|
+
if (jsonSchema !== "{}") type["@openapi.schema"] = jsonSchema;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
break;
|
|
380
|
+
}
|
|
381
|
+
return type;
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
function addDefault(type, schema, forParameter) {
|
|
385
|
+
if (forParameter) return;
|
|
386
|
+
if (schema.default) type.default = { val: schema.default };
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
function addPrimitiveExample(type, schema) {
|
|
390
|
+
const example = schema.examples?.[0] || schema.example;
|
|
391
|
+
if (!example || example.$ref) return;
|
|
392
|
+
type["@Core.Example.$Type"] = "Core.PrimitiveExampleValue";
|
|
393
|
+
type["@Core.Example.Value"] = example;
|
|
394
|
+
}
|
|
395
|
+
|
|
396
|
+
function minimalSchema(schema) {
|
|
397
|
+
const s = { ...schema };
|
|
398
|
+
delete s.title;
|
|
399
|
+
delete s.description;
|
|
400
|
+
return JSON.stringify(s);
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
function anonymousType(context, type) {
|
|
404
|
+
//TODO: construct newName from path leading here instead of using a counter
|
|
405
|
+
// pro: makes imported models more stable
|
|
406
|
+
// con: may cause name clashes
|
|
407
|
+
const newName = `${context.serviceName}.anonymous.type${context.anonymous.length}`;
|
|
408
|
+
context.anonymous.push({ name: newName, type: type });
|
|
409
|
+
return { type: newName };
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
function bestMatchingType(context, schema) {
|
|
413
|
+
if (schema.type) return schema.type;
|
|
414
|
+
let type;
|
|
415
|
+
if (schema.allOf || schema.anyOf || schema.oneOf) {
|
|
416
|
+
const xOf = (schema.allOf || []).concat(
|
|
417
|
+
schema.anyOf || [],
|
|
418
|
+
schema.oneOf || []
|
|
419
|
+
);
|
|
420
|
+
for (const subSchema of xOf) {
|
|
421
|
+
// determine overall type of this construct
|
|
422
|
+
if (subSchema.$ref) {
|
|
423
|
+
let refType = { schema: subSchema };
|
|
424
|
+
let limit = 10;
|
|
425
|
+
while (refType.schema.$ref && limit-- > 0)
|
|
426
|
+
refType = referencedType(context, refType.schema.$ref);
|
|
427
|
+
if (
|
|
428
|
+
refType.schema.allOf ||
|
|
429
|
+
refType.schema.anyOf ||
|
|
430
|
+
refType.schema.oneOf
|
|
431
|
+
) {
|
|
432
|
+
const subType = bestMatchingType(context, refType.schema);
|
|
433
|
+
type = betterType(type, { type: subType });
|
|
434
|
+
} else type = betterType(type, refType.schema);
|
|
435
|
+
} else {
|
|
436
|
+
//TODO: nested xOf - not yet encountered
|
|
437
|
+
type = betterType(type, subSchema);
|
|
438
|
+
}
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
return type;
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
function referencedType(context, ref) {
|
|
445
|
+
const expectedPrefix = context.v3
|
|
446
|
+
? "#/components/schemas/"
|
|
447
|
+
: "#/definitions/";
|
|
448
|
+
if (!ref.startsWith(expectedPrefix)) {
|
|
449
|
+
throw new Error(`TODO: unexpected reference ${ref} in schema.`);
|
|
450
|
+
}
|
|
451
|
+
const schemaName = ref.substring(expectedPrefix.length);
|
|
452
|
+
const schema = context.schemas[schemaName];
|
|
453
|
+
return {
|
|
454
|
+
name: `${context.serviceName}_types.${cdsName(schemaName)}`,
|
|
455
|
+
schema,
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
function normalizeSchemaType(schema) {
|
|
460
|
+
if (!schema.type && schema.items) return "array";
|
|
461
|
+
|
|
462
|
+
if (
|
|
463
|
+
!schema.type &&
|
|
464
|
+
(schema.properties ||
|
|
465
|
+
schema.patternProperties ||
|
|
466
|
+
schema.additionalProperties ||
|
|
467
|
+
schema.discriminator)
|
|
468
|
+
)
|
|
469
|
+
return "object";
|
|
470
|
+
|
|
471
|
+
let schemaType = schema.type;
|
|
472
|
+
if (Array.isArray(schemaType)) {
|
|
473
|
+
schemaType = schemaType.sort();
|
|
474
|
+
if (schemaType.includes("null")) {
|
|
475
|
+
const index = schemaType.indexOf("null");
|
|
476
|
+
schemaType.splice(index, 1);
|
|
477
|
+
}
|
|
478
|
+
if (schemaType.length === 1) schemaType = schemaType[0];
|
|
479
|
+
}
|
|
480
|
+
return schemaType;
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
//TODO: better name :-)
|
|
484
|
+
function betterType(currentType, schema) {
|
|
485
|
+
const schemaType = normalizeSchemaType(schema);
|
|
486
|
+
if (currentType === undefined) return schemaType;
|
|
487
|
+
if (currentType === schemaType) return currentType;
|
|
488
|
+
if (currentType === "string" && ["integer", "number"].includes(schemaType))
|
|
489
|
+
return "string";
|
|
490
|
+
if (["integer", "number"].includes(currentType) && schemaType === "string")
|
|
491
|
+
return "string";
|
|
492
|
+
//TODO: more cases here?
|
|
493
|
+
return null;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
// function discriminator(context, type, schema) {
|
|
497
|
+
// if (!schema.discriminator) return;
|
|
498
|
+
// console.log("hi");
|
|
499
|
+
// const propertyName =
|
|
500
|
+
// schema.discriminator.propertyName || schema.discriminator;
|
|
501
|
+
// type["@openapi.discriminator"] = { propertyName };
|
|
502
|
+
// if (schema.discriminator.mapping) {
|
|
503
|
+
// const mapping = {};
|
|
504
|
+
// for (const [value, reference] of Object.entries(
|
|
505
|
+
// schema.discriminator.mapping
|
|
506
|
+
// )) {
|
|
507
|
+
// mapping[value] = referencedType(context, reference).name;
|
|
508
|
+
// }
|
|
509
|
+
// type["@openapi.discriminator"].mapping = mapping;
|
|
510
|
+
// }
|
|
511
|
+
// }
|
|
512
|
+
|
|
513
|
+
function structElements(context, type, schema) {
|
|
514
|
+
//TODO: interpret "required"
|
|
515
|
+
for (const [prop, propSchema] of Object.entries(schema.properties || {})) {
|
|
516
|
+
const name = cdsName(prop);
|
|
517
|
+
//TODO: check if property "name" already exists and has identical cdsType, warn otherwise
|
|
518
|
+
type.elements[name] = cdsType(context, propSchema);
|
|
519
|
+
if (name !== prop) type.elements[name]["@openapi.name"] = prop;
|
|
520
|
+
}
|
|
521
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"use strict"
|
|
2
|
+
|
|
3
|
+
const { importOpenAPI } = require("./importOpenAPI");
|
|
4
|
+
let messages = require("../message").getMessages();
|
|
5
|
+
|
|
6
|
+
function openAPI2csn(source) {
|
|
7
|
+
let csn = {};
|
|
8
|
+
try {
|
|
9
|
+
const fileContentToJSON = JSON.parse(source);
|
|
10
|
+
csn = importOpenAPI(fileContentToJSON);
|
|
11
|
+
} catch (e) {
|
|
12
|
+
throw new Error(messages.INVALID_OPENAPI_FILE);
|
|
13
|
+
}
|
|
14
|
+
return csn;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
module.exports = {
|
|
18
|
+
openAPI2csn
|
|
19
|
+
}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
module.exports = { cdsName, nameFromPath, pathAndMethod, serviceName };
|
|
2
|
+
|
|
3
|
+
function cdsName(oasName) {
|
|
4
|
+
if (oasName === "") return "_";
|
|
5
|
+
let name = oasName;
|
|
6
|
+
if (name.match(/^(\p{Nd}|\p{Mn}|\p{Mc}|\p{Pc}|\p{Cf})/u)) name = "_" + name;
|
|
7
|
+
return name.replace(/[^_\p{L}\p{Nl}\p{Nd}\p{Mn}\p{Mc}\p{Pc}\p{Cf}]/gu, "_");
|
|
8
|
+
//TODO: shorten if longer than 128 characters
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function nameFromPath(path, method) {
|
|
12
|
+
let cleanPath = path
|
|
13
|
+
.substring(1)
|
|
14
|
+
.replace(/\/$/, "")
|
|
15
|
+
.replace(/{[^}]+}/g, ".")
|
|
16
|
+
.replace(/\/\./g, "_")
|
|
17
|
+
.replace(/[^_\p{L}\p{Nl}\p{Nd}\p{Mn}\p{Mc}\p{Pc}\p{Cf}]/gu, "_");
|
|
18
|
+
if (cleanPath === "") cleanPath = "_";
|
|
19
|
+
//TODO: shorten if longer than 128 characters
|
|
20
|
+
return method === "get" ? cleanPath : `${cleanPath}_${method}`;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function pathAndMethod(operation) {
|
|
24
|
+
const path = operation["@openapi.path"];
|
|
25
|
+
const method =
|
|
26
|
+
operation.kind === "function"
|
|
27
|
+
? "GET"
|
|
28
|
+
: operation["@openapi.method"] || "POST";
|
|
29
|
+
return { path, method };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function serviceName(title) {
|
|
33
|
+
return title
|
|
34
|
+
.trim()
|
|
35
|
+
.replace(/(\s|-)+/g, ".")
|
|
36
|
+
.replace(/[^._\p{L}\p{Nl}\p{Nd}\p{Mn}\p{Mc}\p{Pc}\p{Cf}]/gu, "_");
|
|
37
|
+
//TODO: shorten if longer than 511-129=382 characters
|
|
38
|
+
}
|
package/lib/index.js
CHANGED
package/lib/init/constants.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
const os = require('os');
|
|
2
2
|
|
|
3
|
-
const MAVEN_ARCHETYPE_VERSION = '1.
|
|
3
|
+
const MAVEN_ARCHETYPE_VERSION = '1.25.0';
|
|
4
4
|
|
|
5
5
|
const constants = {
|
|
6
6
|
IS_WIN: (os.platform() === 'win32'),
|
|
@@ -22,12 +22,15 @@ const constants = {
|
|
|
22
22
|
OPTION_CF_MANIFEST: 'cf-manifest',
|
|
23
23
|
OPTION_PIPELINE: 'pipeline',
|
|
24
24
|
OPTION_SAMPLES: 'samples',
|
|
25
|
+
OPTION_DATA: 'data',
|
|
25
26
|
OPTION_NOTEBOOK: 'notebook',
|
|
26
27
|
OPTION_LINT: 'lint',
|
|
27
28
|
OPTION_LINT_GLOBAL: 'lint:global',
|
|
28
29
|
OPTION_LINT_DEV: 'lint:dev',
|
|
29
30
|
OPTION_JAVA_PACKAGE: 'java:package',
|
|
30
31
|
OPTION_JAVA_MVN: 'java:mvn',
|
|
32
|
+
OPTION_HELM: 'helm',
|
|
33
|
+
OPTION_HELM_UPDATE: 'helm/update',
|
|
31
34
|
|
|
32
35
|
// node enum
|
|
33
36
|
PROJECT_TYPE: Object.freeze({ nodejs: 1, java: 2, unknown: 3 }),
|
|
@@ -71,11 +74,13 @@ constants.OPTIONS_PRIORITY = [
|
|
|
71
74
|
constants.OPTION_AUDITLOG,
|
|
72
75
|
constants.OPTION_APPROUTER,
|
|
73
76
|
constants.OPTION_SAMPLES,
|
|
77
|
+
constants.OPTION_DATA,
|
|
74
78
|
constants.OPTION_KIBANA_LOGGING,
|
|
75
79
|
constants.OPTION_NOTEBOOK,
|
|
76
80
|
constants.OPTION_LINT,
|
|
77
81
|
constants.OPTION_LINT_CUSTOM,
|
|
78
|
-
constants.OPTION_LINT_EXAMPLE
|
|
82
|
+
constants.OPTION_LINT_EXAMPLE,
|
|
83
|
+
constants.OPTION_HELM
|
|
79
84
|
];
|
|
80
85
|
|
|
81
86
|
module.exports = constants;
|
package/lib/init/index.js
CHANGED
|
@@ -8,7 +8,7 @@ const Spinner = require('./util/spinner');
|
|
|
8
8
|
const { OPTIONS_PRIORITY, COMMAND_ADD, COMMAND_INIT,
|
|
9
9
|
// eslint-disable-next-line no-unused-vars
|
|
10
10
|
OPTION_XSUAA, OPTION_AUDITLOG, OPTION_HANA, OPTION_MTA, OPTION_MTX, OPTION_APPROUTER, OPTION_KIBANA_LOGGING,
|
|
11
|
-
OPTION_NODEJS, OPTION_JAVA,
|
|
11
|
+
OPTION_NODEJS, OPTION_JAVA, OPTION_HELM,
|
|
12
12
|
OPTION_PIPELINE, OPTION_SAMPLES, OPTION_CF_MANIFEST, PROJECT_FILES, REGEX_PROJECT_NAME } = require('./constants');
|
|
13
13
|
|
|
14
14
|
module.exports = class CDSGenerator {
|
|
@@ -86,6 +86,9 @@ module.exports = class CDSGenerator {
|
|
|
86
86
|
guiOptions: {
|
|
87
87
|
hint: 'Speed up your ramp-up phase with some sample files in your project.'
|
|
88
88
|
}
|
|
89
|
+
},
|
|
90
|
+
{
|
|
91
|
+
// OPTION_HELM this.config
|
|
89
92
|
}
|
|
90
93
|
]
|
|
91
94
|
|
|
@@ -106,7 +109,9 @@ module.exports = class CDSGenerator {
|
|
|
106
109
|
OPTION_CF_MANIFEST,
|
|
107
110
|
OPTION_MTA,
|
|
108
111
|
OPTION_SAMPLES,
|
|
109
|
-
OPTION_KIBANA_LOGGING
|
|
112
|
+
OPTION_KIBANA_LOGGING,
|
|
113
|
+
OPTION_HELM,
|
|
114
|
+
OPTION_SAMPLES
|
|
110
115
|
],
|
|
111
116
|
multiple: true,
|
|
112
117
|
promptOrder: 10 // used to identify ui prompts
|
|
@@ -209,7 +214,9 @@ module.exports = class CDSGenerator {
|
|
|
209
214
|
|
|
210
215
|
for (const template of this.templateList) {
|
|
211
216
|
const msg = `Adding feature '${template.name}'...`;
|
|
212
|
-
if (
|
|
217
|
+
if (template.dontAnnounce) {
|
|
218
|
+
await template.run();
|
|
219
|
+
} else if (this.logger.isDebug) {
|
|
213
220
|
this.logger.log(msg);
|
|
214
221
|
await template.run();
|
|
215
222
|
} else {
|