@cap-js/ord 1.1.0 → 1.3.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/LICENSE +1 -1
- package/README.md +98 -8
- package/cds-plugin.js +17 -8
- package/data/well-known.json +2 -2
- package/lib/authentication.js +153 -0
- package/lib/build.js +53 -0
- package/lib/constants.js +106 -16
- package/lib/date.js +17 -0
- package/lib/defaults.js +73 -46
- package/lib/es-module.mjs +6 -0
- package/lib/extendOrdWithCustom.js +23 -18
- package/lib/index.js +5 -3
- package/lib/logger.js +17 -0
- package/lib/metaData.js +28 -13
- package/lib/ord-service.cds +3 -0
- package/lib/ord-service.mjs +32 -0
- package/lib/ord.js +246 -104
- package/lib/templates.js +306 -81
- package/package.json +16 -7
- package/lib/plugin.js +0 -33
package/lib/ord.js
CHANGED
|
@@ -1,86 +1,175 @@
|
|
|
1
|
-
const
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
1
|
+
const {
|
|
2
|
+
BLOCKED_SERVICE_NAME,
|
|
3
|
+
CDS_ELEMENT_KIND,
|
|
4
|
+
CONTENT_MERGE_KEY,
|
|
5
|
+
ENTITY_RELATIONSHIP_ANNOTATION,
|
|
6
|
+
ORD_ODM_ENTITY_NAME_ANNOTATION,
|
|
7
|
+
} = require("./constants");
|
|
6
8
|
const {
|
|
7
9
|
createAPIResourceTemplate,
|
|
10
|
+
createEntityTypeTemplate,
|
|
11
|
+
createEntityTypeMappingsItemTemplate,
|
|
8
12
|
createEventResourceTemplate,
|
|
9
13
|
createGroupsTemplateForService,
|
|
10
|
-
|
|
11
|
-
} = require(
|
|
12
|
-
const { extendCustomORDContentIfExists } = require(
|
|
13
|
-
const {
|
|
14
|
+
_propagateORDVisibility,
|
|
15
|
+
} = require("./templates");
|
|
16
|
+
const { extendCustomORDContentIfExists } = require("./extendOrdWithCustom");
|
|
17
|
+
const { getRFC3339Date } = require("./date");
|
|
18
|
+
const { getAuthConfig } = require("./authentication");
|
|
14
19
|
|
|
15
|
-
const
|
|
20
|
+
const { Logger } = require("./logger");
|
|
21
|
+
const _ = require("lodash");
|
|
22
|
+
const cds = require("@sap/cds");
|
|
23
|
+
const defaults = require("./defaults");
|
|
24
|
+
const path = require("path");
|
|
16
25
|
|
|
17
26
|
const initializeAppConfig = (csn) => {
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
27
|
+
const packageJson = _loadPackageJson();
|
|
28
|
+
const packageName = packageJson.name;
|
|
29
|
+
const appName = _formatAppName(packageName);
|
|
30
|
+
const lastUpdate = getRFC3339Date();
|
|
31
|
+
|
|
32
|
+
const ordNamespace = _getORDNamespace(packageName);
|
|
33
|
+
const eventApplicationNamespace = cds.env?.export?.asyncapi?.applicationNamespace;
|
|
34
|
+
|
|
35
|
+
_validateNamespaces(ordNamespace, eventApplicationNamespace);
|
|
36
|
+
|
|
37
|
+
const { serviceNames, apiResourceNames, apiEndpoints, eventNames, entityTypeTargets } = _triageCsnDefinitions(csn);
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
env: cds.env["ord"],
|
|
41
|
+
lastUpdate,
|
|
42
|
+
appName,
|
|
43
|
+
apiEndpoints: Array.from(apiEndpoints),
|
|
44
|
+
eventNames,
|
|
45
|
+
serviceNames,
|
|
46
|
+
apiResourceNames,
|
|
47
|
+
entityTypeTargets: _.uniqBy(entityTypeTargets, CONTENT_MERGE_KEY),
|
|
48
|
+
ordNamespace,
|
|
49
|
+
eventApplicationNamespace,
|
|
50
|
+
packageName,
|
|
51
|
+
};
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
function _loadPackageJson() {
|
|
55
|
+
const packageJsonPath = path.join(cds.root, "package.json");
|
|
56
|
+
if (!cds.utils.exists(packageJsonPath)) {
|
|
23
57
|
throw new Error(`package.json not found in the project root directory`);
|
|
24
58
|
}
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
//if cdsrc.json does not have applicationNamespace, then use just the namespace
|
|
59
|
+
return require(packageJsonPath);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function _formatAppName(packageName) {
|
|
63
|
+
return packageName.replace(/^[@]/, "").replace(/[@/]/g, "-");
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function _getORDNamespace(packageName) {
|
|
34
67
|
const vendorNamespace = "customer";
|
|
35
|
-
|
|
36
|
-
|
|
68
|
+
return cds.env["ord"]?.namespace || `${vendorNamespace}.${packageName.replace(/[^a-zA-Z0-9]/g, "")}`;
|
|
69
|
+
}
|
|
37
70
|
|
|
71
|
+
function _validateNamespaces(ordNamespace, eventApplicationNamespace) {
|
|
38
72
|
if (eventApplicationNamespace && ordNamespace !== eventApplicationNamespace) {
|
|
39
|
-
|
|
73
|
+
Logger.warn("ORD and AsyncAPI namespaces should be the same.");
|
|
40
74
|
}
|
|
75
|
+
}
|
|
41
76
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
77
|
+
function _triageCsnDefinitions(csn) {
|
|
78
|
+
const pendingApiResourceNames = [];
|
|
79
|
+
const apiEndpoints = new Set();
|
|
80
|
+
const pendingEventNames = [];
|
|
81
|
+
const entityTypeTargets = [];
|
|
82
|
+
const pendingServiceNames = [];
|
|
83
|
+
|
|
84
|
+
for (const definitionKey of Object.keys(csn.definitions)) {
|
|
85
|
+
const definitionObj = csn.definitions[definitionKey];
|
|
86
|
+
if (definitionKey.includes(BLOCKED_SERVICE_NAME.MTXServices)) {
|
|
87
|
+
Logger.warn(`ORD service name "${definitionKey}" is blocked.`);
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
switch (definitionObj.kind) {
|
|
91
|
+
case CDS_ELEMENT_KIND.service: {
|
|
92
|
+
pendingServiceNames.push(definitionKey);
|
|
93
|
+
const apiResourceName = _handleApiResource(definitionKey, definitionObj);
|
|
94
|
+
if (apiResourceName) pendingApiResourceNames.push(apiResourceName);
|
|
49
95
|
break;
|
|
50
|
-
|
|
51
|
-
case
|
|
52
|
-
|
|
53
|
-
|
|
96
|
+
}
|
|
97
|
+
case CDS_ELEMENT_KIND.entity: {
|
|
98
|
+
const result = _handleEntity(definitionKey, definitionObj);
|
|
99
|
+
if (result) {
|
|
100
|
+
if (result.apiEndpoint) apiEndpoints.add(result.apiEndpoint);
|
|
101
|
+
if (result.entityTypeTarget) entityTypeTargets.push(result.entityTypeTarget);
|
|
54
102
|
}
|
|
55
103
|
break;
|
|
56
|
-
|
|
57
|
-
|
|
104
|
+
}
|
|
105
|
+
case CDS_ELEMENT_KIND.event: {
|
|
106
|
+
const event = _handleEvent(definitionKey);
|
|
107
|
+
if (event) pendingEventNames.push(event);
|
|
108
|
+
break;
|
|
109
|
+
}
|
|
110
|
+
case CDS_ELEMENT_KIND.action:
|
|
111
|
+
case CDS_ELEMENT_KIND.function: {
|
|
112
|
+
const apiEndpoint = _handleActionOrFunction(definitionKey);
|
|
113
|
+
if (apiEndpoint) apiEndpoints.add(apiEndpoint);
|
|
58
114
|
break;
|
|
115
|
+
}
|
|
59
116
|
}
|
|
60
117
|
}
|
|
61
118
|
|
|
62
|
-
odmEntity = _.uniqBy(odmEntity, "ordId");
|
|
63
|
-
|
|
64
119
|
return {
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
ordNamespace,
|
|
71
|
-
eventApplicationNamespace,
|
|
72
|
-
packageName,
|
|
120
|
+
serviceNames: pendingServiceNames,
|
|
121
|
+
apiResourceNames: pendingApiResourceNames,
|
|
122
|
+
apiEndpoints: Array.from(apiEndpoints),
|
|
123
|
+
eventNames: pendingEventNames,
|
|
124
|
+
entityTypeTargets,
|
|
73
125
|
};
|
|
74
|
-
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function _handleApiResource(apiResourceName, serviceDefinition) {
|
|
129
|
+
if (_shouldSkipIfServiceOnlyContainsEvents(serviceDefinition) || serviceDefinition["@cds.external"]) {
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
return apiResourceName;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
function _shouldSkipIfServiceOnlyContainsEvents(serviceDefinition) {
|
|
136
|
+
const isActionsNotContained = !serviceDefinition.actions || Object.keys(serviceDefinition.actions).length === 0;
|
|
137
|
+
const isFunctionsNotContained =
|
|
138
|
+
!serviceDefinition.functions || Object.keys(serviceDefinition.functions).length === 0;
|
|
139
|
+
const isEntitiesNotContained = !serviceDefinition.entities || Object.keys(serviceDefinition.entities).length === 0;
|
|
140
|
+
const isEventsContained = serviceDefinition.events && Object.keys(serviceDefinition.events).length > 0;
|
|
141
|
+
if (isActionsNotContained && isFunctionsNotContained && isEntitiesNotContained && isEventsContained) {
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
144
|
+
return false;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
function _handleEntity(key, keyDefinition) {
|
|
148
|
+
if (!key.includes(".texts")) {
|
|
149
|
+
const apiEndpoint = key;
|
|
150
|
+
const entityTypeTarget =
|
|
151
|
+
keyDefinition[ORD_ODM_ENTITY_NAME_ANNOTATION] || keyDefinition[ENTITY_RELATIONSHIP_ANNOTATION]
|
|
152
|
+
? createEntityTypeMappingsItemTemplate(keyDefinition)
|
|
153
|
+
: null;
|
|
154
|
+
return { apiEndpoint, entityTypeTarget };
|
|
155
|
+
}
|
|
156
|
+
return null;
|
|
157
|
+
}
|
|
75
158
|
|
|
76
|
-
|
|
77
|
-
|
|
159
|
+
function _handleEvent(key) {
|
|
160
|
+
return key;
|
|
161
|
+
}
|
|
78
162
|
|
|
79
|
-
|
|
80
|
-
|
|
163
|
+
function _handleActionOrFunction(key) {
|
|
164
|
+
return key;
|
|
165
|
+
}
|
|
81
166
|
|
|
82
|
-
const
|
|
83
|
-
appConfig.env?.
|
|
167
|
+
const _getPolicyLevels = (appConfig) =>
|
|
168
|
+
appConfig.env?.policyLevels ||
|
|
169
|
+
(appConfig.env?.policyLevel && [appConfig.env?.policyLevel]) ||
|
|
170
|
+
defaults.policyLevels;
|
|
171
|
+
|
|
172
|
+
const _getDescription = (appConfig) => appConfig.env?.description || defaults.description;
|
|
84
173
|
|
|
85
174
|
const _getGroups = (csn, appConfig) => {
|
|
86
175
|
return appConfig.serviceNames
|
|
@@ -88,79 +177,132 @@ const _getGroups = (csn, appConfig) => {
|
|
|
88
177
|
.filter((resource) => !!resource);
|
|
89
178
|
};
|
|
90
179
|
|
|
91
|
-
const _getPackages = (
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
: defaults
|
|
95
|
-
.packages(appConfig.appName, policyLevel, appConfig.ordNamespace)
|
|
96
|
-
.slice(0, 1);
|
|
180
|
+
const _getPackages = (policyLevels, appConfig) => {
|
|
181
|
+
return defaults.packages(appConfig, policyLevels);
|
|
182
|
+
};
|
|
97
183
|
|
|
98
|
-
const
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
184
|
+
const _getEntityTypes = (appConfig, packageIds) => {
|
|
185
|
+
if (!appConfig.entityTypeTargets?.length) return [];
|
|
186
|
+
|
|
187
|
+
return appConfig.entityTypeTargets.map((entity) => createEntityTypeTemplate(appConfig, packageIds, entity));
|
|
102
188
|
};
|
|
103
189
|
|
|
104
|
-
const
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
190
|
+
const _getAPIResources = (csn, appConfig, packageIds, accessStrategies) => {
|
|
191
|
+
return appConfig.apiResourceNames.flatMap((apiResourceName) =>
|
|
192
|
+
createAPIResourceTemplate(
|
|
193
|
+
apiResourceName,
|
|
194
|
+
csn.definitions[apiResourceName],
|
|
195
|
+
appConfig,
|
|
196
|
+
packageIds,
|
|
197
|
+
accessStrategies,
|
|
198
|
+
),
|
|
199
|
+
);
|
|
109
200
|
};
|
|
110
201
|
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
202
|
+
const _getEventResources = (csn, appConfig, packageIds, accessStrategies) => {
|
|
203
|
+
return appConfig.serviceNames.flatMap((serviceName) =>
|
|
204
|
+
createEventResourceTemplate(serviceName, csn.definitions[serviceName], appConfig, packageIds, accessStrategies),
|
|
205
|
+
);
|
|
206
|
+
};
|
|
207
|
+
|
|
208
|
+
function _getOpenResourceDiscovery(appConfig) {
|
|
209
|
+
return appConfig.env?.openResourceDiscovery || defaults.openResourceDiscovery;
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
function _getConsumptionBundles(appConfig) {
|
|
213
|
+
return appConfig.env?.consumptionBundles || defaults.consumptionBundles(appConfig);
|
|
123
214
|
}
|
|
124
215
|
|
|
216
|
+
const _getProducts = (appConfig) => {
|
|
217
|
+
const productsObj = defaults.products(appConfig.packageName);
|
|
218
|
+
if (appConfig.env?.products) {
|
|
219
|
+
const customProducts = appConfig.env.products[0];
|
|
220
|
+
if (customProducts?.ordId?.toLowerCase().startsWith("sap")) {
|
|
221
|
+
Logger.error(
|
|
222
|
+
"Detected sap product ordId, which should not be defined for custom products, use default value instead. Please check ord global registry.",
|
|
223
|
+
);
|
|
224
|
+
} else {
|
|
225
|
+
_.assign(productsObj[0], customProducts);
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
appConfig.products = productsObj;
|
|
229
|
+
return productsObj;
|
|
230
|
+
};
|
|
231
|
+
|
|
125
232
|
function createDefaultORDDocument(linkedCsn, appConfig) {
|
|
126
233
|
let ordDocument = {
|
|
127
|
-
$schema: "https://
|
|
128
|
-
openResourceDiscovery:
|
|
129
|
-
|
|
234
|
+
$schema: "https://open-resource-discovery.github.io/specification/spec-v1/interfaces/Document.schema.json",
|
|
235
|
+
openResourceDiscovery: _getOpenResourceDiscovery(appConfig),
|
|
236
|
+
policyLevels: _getPolicyLevels(appConfig),
|
|
130
237
|
description: _getDescription(appConfig),
|
|
131
|
-
|
|
132
|
-
groups: _getGroups(linkedCsn, appConfig),
|
|
238
|
+
consumptionBundles: _getConsumptionBundles(appConfig),
|
|
133
239
|
};
|
|
134
240
|
|
|
135
|
-
if (
|
|
136
|
-
ordDocument.
|
|
241
|
+
if (appConfig.serviceNames.length) {
|
|
242
|
+
ordDocument.groups = _getGroups(linkedCsn, appConfig);
|
|
137
243
|
}
|
|
244
|
+
|
|
245
|
+
if (appConfig.env?.existingProductORDId) {
|
|
246
|
+
appConfig.existingProductORDId = appConfig.env.existingProductORDId;
|
|
247
|
+
} else {
|
|
248
|
+
ordDocument.products = [_getProducts(appConfig)[0]];
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
ordDocument.packages = _getPackages(ordDocument.policyLevels, appConfig);
|
|
252
|
+
|
|
138
253
|
return ordDocument;
|
|
139
254
|
}
|
|
140
255
|
|
|
141
256
|
function extractPackageIds(ordDocument) {
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
return
|
|
257
|
+
return ordDocument.packages?.map((pkg) => pkg.ordId) || [];
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
function _filterUnusedPackages(ordDocument) {
|
|
261
|
+
if (!ordDocument.packages?.length) return [];
|
|
262
|
+
|
|
263
|
+
const usedPackageIds = new Set();
|
|
264
|
+
|
|
265
|
+
ordDocument.apiResources?.forEach((api) => usedPackageIds?.add(api.partOfPackage));
|
|
266
|
+
ordDocument.eventResources?.forEach((event) => usedPackageIds?.add(event.partOfPackage));
|
|
267
|
+
ordDocument.dataProducts?.forEach((dp) => usedPackageIds?.add(dp.partOfPackage));
|
|
268
|
+
ordDocument.entityTypes?.forEach((et) => {
|
|
269
|
+
if (et && et.partOfPackage) {
|
|
270
|
+
usedPackageIds.add(et.partOfPackage);
|
|
271
|
+
}
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
const filteredPackages = ordDocument.packages.filter((pkg) => usedPackageIds.has(pkg.ordId));
|
|
275
|
+
|
|
276
|
+
return filteredPackages;
|
|
147
277
|
}
|
|
148
278
|
|
|
149
279
|
module.exports = (csn) => {
|
|
150
|
-
const linkedCsn = cds.linked(csn);
|
|
280
|
+
const linkedCsn = _propagateORDVisibility(cds.linked(csn));
|
|
151
281
|
const appConfig = initializeAppConfig(linkedCsn);
|
|
152
|
-
|
|
282
|
+
const accessStrategies = getAuthConfig().accessStrategies;
|
|
153
283
|
|
|
154
284
|
let ordDocument = createDefaultORDDocument(linkedCsn, appConfig);
|
|
155
285
|
const packageIds = extractPackageIds(ordDocument);
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
ordDocument.eventResources = eventResources;
|
|
286
|
+
const entityTypes = _getEntityTypes(appConfig, packageIds);
|
|
287
|
+
|
|
288
|
+
if (entityTypes.length) {
|
|
289
|
+
ordDocument.entityTypes = entityTypes;
|
|
161
290
|
}
|
|
162
291
|
|
|
163
|
-
|
|
292
|
+
if (appConfig.apiResourceNames.length) {
|
|
293
|
+
const apiResources = _getAPIResources(linkedCsn, appConfig, packageIds, accessStrategies);
|
|
294
|
+
if (apiResources.length) {
|
|
295
|
+
ordDocument.apiResources = apiResources;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
if (appConfig.eventNames.length) {
|
|
299
|
+
const eventResources = _getEventResources(linkedCsn, appConfig, packageIds, accessStrategies);
|
|
300
|
+
if (eventResources.length) {
|
|
301
|
+
ordDocument.eventResources = eventResources;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
ordDocument = extendCustomORDContentIfExists(appConfig, ordDocument);
|
|
305
|
+
ordDocument.packages = _filterUnusedPackages(ordDocument);
|
|
164
306
|
|
|
165
307
|
return ordDocument;
|
|
166
308
|
};
|