@cap-js/ord 1.2.0 → 1.3.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/LICENSE +1 -1
- package/README.md +116 -8
- package/cds-plugin.js +17 -8
- package/data/well-known.json +2 -2
- package/lib/authentication.js +153 -0
- package/lib/build.js +85 -0
- package/lib/constants.js +80 -4
- package/lib/date.js +11 -16
- package/lib/defaults.js +68 -44
- package/lib/es-module.mjs +6 -0
- package/lib/extendOrdWithCustom.js +12 -6
- package/lib/index.js +5 -3
- package/lib/logger.js +9 -3
- package/lib/metaData.js +28 -14
- package/lib/ord-service.cds +3 -0
- package/lib/ord-service.mjs +32 -0
- package/lib/ord.js +233 -93
- package/lib/templates.js +278 -71
- package/package.json +21 -8
- package/lib/plugin.js +0 -33
package/lib/ord.js
CHANGED
|
@@ -1,85 +1,175 @@
|
|
|
1
|
-
const {
|
|
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");
|
|
2
8
|
const {
|
|
3
9
|
createAPIResourceTemplate,
|
|
4
10
|
createEntityTypeTemplate,
|
|
11
|
+
createEntityTypeMappingsItemTemplate,
|
|
5
12
|
createEventResourceTemplate,
|
|
6
|
-
createGroupsTemplateForService
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
const {
|
|
10
|
-
const {
|
|
13
|
+
createGroupsTemplateForService,
|
|
14
|
+
_propagateORDVisibility,
|
|
15
|
+
} = require("./templates");
|
|
16
|
+
const { extendCustomORDContentIfExists } = require("./extendOrdWithCustom");
|
|
17
|
+
const { getRFC3339Date } = require("./date");
|
|
18
|
+
const { getAuthConfig } = require("./authentication");
|
|
19
|
+
|
|
20
|
+
const { Logger } = require("./logger");
|
|
11
21
|
const _ = require("lodash");
|
|
12
22
|
const cds = require("@sap/cds");
|
|
13
23
|
const defaults = require("./defaults");
|
|
14
24
|
const path = require("path");
|
|
15
25
|
|
|
16
26
|
const initializeAppConfig = (csn) => {
|
|
17
|
-
|
|
18
|
-
let packageJson;
|
|
19
|
-
if (cds.utils.exists(packageJsonPath)) {
|
|
20
|
-
packageJson = require(packageJsonPath);
|
|
21
|
-
} else {
|
|
22
|
-
throw new Error(`package.json not found in the project root directory`);
|
|
23
|
-
}
|
|
27
|
+
const packageJson = _loadPackageJson();
|
|
24
28
|
const packageName = packageJson.name;
|
|
25
|
-
const appName =
|
|
26
|
-
const modelKeys = Object.keys(csn.definitions);
|
|
27
|
-
const events = [];
|
|
28
|
-
const serviceNames = [];
|
|
29
|
+
const appName = _formatAppName(packageName);
|
|
29
30
|
const lastUpdate = getRFC3339Date();
|
|
30
|
-
let odmEntity = [];
|
|
31
31
|
|
|
32
|
-
const
|
|
33
|
-
const ordNamespace = cds.env["ord"]?.namespace || `${vendorNamespace}.${packageName.replace(/[^a-zA-Z0-9]/g, "")}`;
|
|
32
|
+
const ordNamespace = _getORDNamespace(packageName);
|
|
34
33
|
const eventApplicationNamespace = cds.env?.export?.asyncapi?.applicationNamespace;
|
|
35
34
|
|
|
36
|
-
|
|
37
|
-
Logger.warn('ORD and AsyncAPI namespaces should be the same.');
|
|
38
|
-
}
|
|
35
|
+
_validateNamespaces(ordNamespace, eventApplicationNamespace);
|
|
39
36
|
|
|
40
|
-
|
|
41
|
-
const keyDefinition = csn.definitions[key];
|
|
42
|
-
switch (keyDefinition.kind) {
|
|
43
|
-
case ORD_RESOURCE_TYPE.service:
|
|
44
|
-
if (!keyDefinition["@cds.external"]) {
|
|
45
|
-
serviceNames.push(key);
|
|
46
|
-
}
|
|
47
|
-
break;
|
|
48
|
-
// TODO: should be rewritten
|
|
49
|
-
case ORD_RESOURCE_TYPE.entity:
|
|
50
|
-
if (!key.includes(".texts") && keyDefinition["@ODM.entityName"]) {
|
|
51
|
-
odmEntity.push(createEntityTypeTemplate(keyDefinition));
|
|
52
|
-
}
|
|
53
|
-
break;
|
|
54
|
-
case ORD_RESOURCE_TYPE.event:
|
|
55
|
-
events.push(key);
|
|
56
|
-
break;
|
|
57
|
-
}
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
odmEntity = _.uniqBy(odmEntity, "ordId");
|
|
37
|
+
const { serviceNames, apiResourceNames, apiEndpoints, eventNames, entityTypeTargets } = _triageCsnDefinitions(csn);
|
|
61
38
|
|
|
62
39
|
return {
|
|
63
40
|
env: cds.env["ord"],
|
|
64
41
|
lastUpdate,
|
|
65
42
|
appName,
|
|
66
|
-
|
|
43
|
+
apiEndpoints: Array.from(apiEndpoints),
|
|
44
|
+
eventNames,
|
|
67
45
|
serviceNames,
|
|
68
|
-
|
|
46
|
+
apiResourceNames,
|
|
47
|
+
entityTypeTargets: _.uniqBy(entityTypeTargets, CONTENT_MERGE_KEY),
|
|
69
48
|
ordNamespace,
|
|
70
49
|
eventApplicationNamespace,
|
|
71
50
|
packageName,
|
|
72
51
|
};
|
|
73
52
|
};
|
|
74
53
|
|
|
75
|
-
|
|
76
|
-
|
|
54
|
+
function _loadPackageJson() {
|
|
55
|
+
const packageJsonPath = path.join(cds.root, "package.json");
|
|
56
|
+
if (!cds.utils.exists(packageJsonPath)) {
|
|
57
|
+
throw new Error(`package.json not found in the project root directory`);
|
|
58
|
+
}
|
|
59
|
+
return require(packageJsonPath);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function _formatAppName(packageName) {
|
|
63
|
+
return packageName.replace(/^[@]/, "").replace(/[@/]/g, "-");
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
function _getORDNamespace(packageName) {
|
|
67
|
+
const vendorNamespace = "customer";
|
|
68
|
+
return cds.env["ord"]?.namespace || `${vendorNamespace}.${packageName.replace(/[^a-zA-Z0-9]/g, "")}`;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function _validateNamespaces(ordNamespace, eventApplicationNamespace) {
|
|
72
|
+
if (eventApplicationNamespace && ordNamespace !== eventApplicationNamespace) {
|
|
73
|
+
Logger.warn("ORD and AsyncAPI namespaces should be the same.");
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
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);
|
|
95
|
+
break;
|
|
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);
|
|
102
|
+
}
|
|
103
|
+
break;
|
|
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);
|
|
114
|
+
break;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
return {
|
|
120
|
+
serviceNames: pendingServiceNames,
|
|
121
|
+
apiResourceNames: pendingApiResourceNames,
|
|
122
|
+
apiEndpoints: Array.from(apiEndpoints),
|
|
123
|
+
eventNames: pendingEventNames,
|
|
124
|
+
entityTypeTargets,
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
function _handleApiResource(apiResourceName, serviceDefinition) {
|
|
129
|
+
if (_shouldSkipIfServiceOnlyContainsEvents(serviceDefinition) || serviceDefinition["@cds.external"]) {
|
|
130
|
+
return null;
|
|
131
|
+
}
|
|
132
|
+
return apiResourceName;
|
|
133
|
+
}
|
|
77
134
|
|
|
78
|
-
|
|
79
|
-
|
|
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
|
+
}
|
|
80
146
|
|
|
81
|
-
|
|
82
|
-
|
|
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
|
+
}
|
|
158
|
+
|
|
159
|
+
function _handleEvent(key) {
|
|
160
|
+
return key;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function _handleActionOrFunction(key) {
|
|
164
|
+
return key;
|
|
165
|
+
}
|
|
166
|
+
|
|
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;
|
|
83
173
|
|
|
84
174
|
const _getGroups = (csn, appConfig) => {
|
|
85
175
|
return appConfig.serviceNames
|
|
@@ -87,23 +177,32 @@ const _getGroups = (csn, appConfig) => {
|
|
|
87
177
|
.filter((resource) => !!resource);
|
|
88
178
|
};
|
|
89
179
|
|
|
90
|
-
const _getPackages = (
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
: defaults
|
|
94
|
-
.packages(appConfig.appName, policyLevel, appConfig.ordNamespace)
|
|
95
|
-
.slice(0, 1);
|
|
180
|
+
const _getPackages = (policyLevels, appConfig) => {
|
|
181
|
+
return defaults.packages(appConfig, policyLevels);
|
|
182
|
+
};
|
|
96
183
|
|
|
97
|
-
const
|
|
98
|
-
|
|
99
|
-
|
|
184
|
+
const _getEntityTypes = (appConfig, packageIds) => {
|
|
185
|
+
if (!appConfig.entityTypeTargets?.length) return [];
|
|
186
|
+
|
|
187
|
+
return appConfig.entityTypeTargets.map((entity) => createEntityTypeTemplate(appConfig, packageIds, entity));
|
|
100
188
|
};
|
|
101
189
|
|
|
102
|
-
const
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
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
|
+
);
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
const _getEventResources = (csn, appConfig, packageIds, accessStrategies) => {
|
|
203
|
+
return appConfig.serviceNames.flatMap((serviceName) =>
|
|
204
|
+
createEventResourceTemplate(serviceName, csn.definitions[serviceName], appConfig, packageIds, accessStrategies),
|
|
205
|
+
);
|
|
107
206
|
};
|
|
108
207
|
|
|
109
208
|
function _getOpenResourceDiscovery(appConfig) {
|
|
@@ -114,55 +213,96 @@ function _getConsumptionBundles(appConfig) {
|
|
|
114
213
|
return appConfig.env?.consumptionBundles || defaults.consumptionBundles(appConfig);
|
|
115
214
|
}
|
|
116
215
|
|
|
117
|
-
|
|
118
|
-
const
|
|
119
|
-
if (
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
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
|
+
}
|
|
128
227
|
}
|
|
129
|
-
|
|
228
|
+
appConfig.products = productsObj;
|
|
229
|
+
return productsObj;
|
|
230
|
+
};
|
|
130
231
|
|
|
131
232
|
function createDefaultORDDocument(linkedCsn, appConfig) {
|
|
132
233
|
let ordDocument = {
|
|
133
|
-
$schema: "https://
|
|
234
|
+
$schema: "https://open-resource-discovery.github.io/specification/spec-v1/interfaces/Document.schema.json",
|
|
134
235
|
openResourceDiscovery: _getOpenResourceDiscovery(appConfig),
|
|
135
|
-
|
|
236
|
+
policyLevels: _getPolicyLevels(appConfig),
|
|
136
237
|
description: _getDescription(appConfig),
|
|
137
|
-
products: _getProducts(appConfig),
|
|
138
|
-
groups: _getGroups(linkedCsn, appConfig),
|
|
139
238
|
consumptionBundles: _getConsumptionBundles(appConfig),
|
|
140
239
|
};
|
|
141
240
|
|
|
142
|
-
if (
|
|
143
|
-
ordDocument.
|
|
241
|
+
if (appConfig.serviceNames.length) {
|
|
242
|
+
ordDocument.groups = _getGroups(linkedCsn, appConfig);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
if (appConfig.env?.existingProductORDId) {
|
|
246
|
+
appConfig.existingProductORDId = appConfig.env.existingProductORDId;
|
|
247
|
+
} else {
|
|
248
|
+
ordDocument.products = [_getProducts(appConfig)[0]];
|
|
144
249
|
}
|
|
250
|
+
|
|
251
|
+
ordDocument.packages = _getPackages(ordDocument.policyLevels, appConfig);
|
|
252
|
+
|
|
145
253
|
return ordDocument;
|
|
146
254
|
}
|
|
147
255
|
|
|
148
256
|
function extractPackageIds(ordDocument) {
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
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;
|
|
154
277
|
}
|
|
155
278
|
|
|
156
279
|
module.exports = (csn) => {
|
|
157
|
-
const linkedCsn = cds.linked(csn);
|
|
280
|
+
const linkedCsn = _propagateORDVisibility(cds.linked(csn));
|
|
158
281
|
const appConfig = initializeAppConfig(linkedCsn);
|
|
159
|
-
|
|
282
|
+
const accessStrategies = getAuthConfig().accessStrategies;
|
|
160
283
|
|
|
161
284
|
let ordDocument = createDefaultORDDocument(linkedCsn, appConfig);
|
|
162
285
|
const packageIds = extractPackageIds(ordDocument);
|
|
163
|
-
|
|
164
|
-
|
|
286
|
+
const entityTypes = _getEntityTypes(appConfig, packageIds);
|
|
287
|
+
|
|
288
|
+
if (entityTypes.length) {
|
|
289
|
+
ordDocument.entityTypes = entityTypes;
|
|
290
|
+
}
|
|
291
|
+
|
|
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
|
+
}
|
|
165
304
|
ordDocument = extendCustomORDContentIfExists(appConfig, ordDocument);
|
|
305
|
+
ordDocument.packages = _filterUnusedPackages(ordDocument);
|
|
166
306
|
|
|
167
307
|
return ordDocument;
|
|
168
308
|
};
|