@cap-js/ord 1.8.0 → 1.9.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/README.md +10 -5
- package/lib/auth/authentication.js +30 -33
- package/lib/auth/cf-mtls.js +5 -5
- package/lib/build.js +1 -3
- package/lib/common/placeholders.js +6 -0
- package/lib/common/slice.js +72 -0
- package/lib/common/utils.js +190 -0
- package/lib/configuration.js +33 -88
- package/lib/constants.js +2 -32
- package/lib/defaults.js +27 -128
- package/lib/extend-ord-with-custom.js +82 -51
- package/lib/interop-csn.js +23 -11
- package/lib/logger.js +1 -3
- package/lib/meta-data.js +14 -26
- package/lib/ord.js +59 -122
- package/lib/protocol-resolver.js +26 -83
- package/lib/services/ord-service.js +30 -6
- package/lib/templates/api-resource.js +283 -0
- package/lib/templates/entity-type.js +121 -0
- package/lib/templates/event-resource.js +161 -0
- package/lib/templates/group.js +50 -0
- package/lib/templates/integration-dependency.js +98 -0
- package/lib/templates/package.js +91 -0
- package/lib/templates/product.js +57 -0
- package/package.json +6 -4
- package/data/well-known.json +0 -14
- package/lib/access-strategies.js +0 -172
- package/lib/date.js +0 -17
- package/lib/integration-dependency.js +0 -126
- package/lib/templates.js +0 -625
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
const { createPackages } = require("./package");
|
|
2
|
+
const placeholders = require("../common/placeholders");
|
|
3
|
+
const { readORDExtensions, isExternalDataProduct } = require("../common/utils");
|
|
4
|
+
const { RESOURCE_VISIBILITY, EXTERNAL_DP_ORD_ID_ANNOTATION } = require("../constants");
|
|
5
|
+
const _ = require("lodash");
|
|
6
|
+
|
|
7
|
+
const RESOLVERS = Object.freeze({
|
|
8
|
+
ordId: (appConfig) => {
|
|
9
|
+
const major = RESOLVERS.version(appConfig).split(".")[0];
|
|
10
|
+
|
|
11
|
+
return (
|
|
12
|
+
placeholders.replace(appConfig.env?.integrationDependency?.ordId, {
|
|
13
|
+
type: "integrationDependency",
|
|
14
|
+
namespace: appConfig.ordNamespace,
|
|
15
|
+
}) ?? `${appConfig.ordNamespace}:integrationDependency:externalDependencies:v${major}`
|
|
16
|
+
);
|
|
17
|
+
},
|
|
18
|
+
version: (appConfig) => {
|
|
19
|
+
return appConfig.env?.integrationDependency?.version || "1.0.0";
|
|
20
|
+
},
|
|
21
|
+
aspects: (appConfig) => {
|
|
22
|
+
return (
|
|
23
|
+
appConfig.env?.integrationDependency?.aspects ??
|
|
24
|
+
Object.values(appConfig.csn.definitions)
|
|
25
|
+
.filter((definition) => isExternalDataProduct(definition))
|
|
26
|
+
.filter((service) => "apiResource" === service[EXTERNAL_DP_ORD_ID_ANNOTATION].split(":")[1])
|
|
27
|
+
.map((service) => {
|
|
28
|
+
const version = service[EXTERNAL_DP_ORD_ID_ANNOTATION].split(":")[3] ?? "v1";
|
|
29
|
+
|
|
30
|
+
return {
|
|
31
|
+
title: service.name,
|
|
32
|
+
mandatory: false,
|
|
33
|
+
apiResources: [
|
|
34
|
+
{
|
|
35
|
+
ordId: service[EXTERNAL_DP_ORD_ID_ANNOTATION],
|
|
36
|
+
minVersion: version.replace("v", "") + ".0.0",
|
|
37
|
+
},
|
|
38
|
+
],
|
|
39
|
+
|
|
40
|
+
...readORDExtensions(service), // Allow customization via @ORD.Extensions on the service
|
|
41
|
+
};
|
|
42
|
+
})
|
|
43
|
+
);
|
|
44
|
+
},
|
|
45
|
+
visibility: (appConfig) => {
|
|
46
|
+
return appConfig.env?.integrationDependency?.visibility || RESOURCE_VISIBILITY.public;
|
|
47
|
+
},
|
|
48
|
+
partOfPackage: (appConfig) => {
|
|
49
|
+
const visibility = RESOLVERS.visibility(appConfig);
|
|
50
|
+
const name = appConfig.appName?.replace(/[^a-zA-Z0-9]/g, "");
|
|
51
|
+
const packages = createPackages(appConfig).map((pkg) => pkg.ordId);
|
|
52
|
+
const suffix = visibility === RESOURCE_VISIBILITY.public ? "" : `-${visibility}`;
|
|
53
|
+
|
|
54
|
+
return (
|
|
55
|
+
placeholders.replace(appConfig.env?.integrationDependency?.partOfPackage, {
|
|
56
|
+
type: "package",
|
|
57
|
+
namespace: appConfig.ordNamespace,
|
|
58
|
+
}) ??
|
|
59
|
+
[
|
|
60
|
+
`${appConfig.ordNamespace}:package:${name}-integrationDependency${suffix}:v1`,
|
|
61
|
+
`${appConfig.ordNamespace}:package:${name}:v1`,
|
|
62
|
+
].find((candidate) => packages.includes(candidate))
|
|
63
|
+
);
|
|
64
|
+
},
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Creates a single IntegrationDependency with one aspect per external service.
|
|
69
|
+
* @param {Object} appConfig - The application configuration
|
|
70
|
+
* @returns {Object} IntegrationDependency object
|
|
71
|
+
*/
|
|
72
|
+
function createIntegrationDependency(appConfig) {
|
|
73
|
+
return {
|
|
74
|
+
mandatory: false,
|
|
75
|
+
releaseStatus: "active",
|
|
76
|
+
title: "External Dependencies",
|
|
77
|
+
|
|
78
|
+
ordId: RESOLVERS.ordId(appConfig),
|
|
79
|
+
version: RESOLVERS.version(appConfig),
|
|
80
|
+
aspects: RESOLVERS.aspects(appConfig),
|
|
81
|
+
visibility: RESOLVERS.visibility(appConfig),
|
|
82
|
+
partOfPackage: RESOLVERS.partOfPackage(appConfig),
|
|
83
|
+
|
|
84
|
+
// Allow customization via cdsrc
|
|
85
|
+
..._.omit(appConfig.env?.integrationDependency ?? {}, Object.keys(RESOLVERS)),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function createIntegrationDependencies(appConfig) {
|
|
90
|
+
return [createIntegrationDependency(appConfig)] //
|
|
91
|
+
.filter((dependency) => dependency.aspects?.length > 0);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
module.exports = {
|
|
95
|
+
createIntegrationDependencies,
|
|
96
|
+
createIntegrationDependency,
|
|
97
|
+
RESOLVERS,
|
|
98
|
+
};
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
const _ = require("lodash");
|
|
2
|
+
|
|
3
|
+
const { prune } = require("../common/utils");
|
|
4
|
+
const { createProducts } = require("./product");
|
|
5
|
+
const placeholders = require("../common/placeholders");
|
|
6
|
+
const { ORD_RESOURCE_TYPE, RESOURCE_VISIBILITY } = require("../constants");
|
|
7
|
+
|
|
8
|
+
const RESOLVERS = Object.freeze({
|
|
9
|
+
title: (appConfig) => {
|
|
10
|
+
return appConfig?.env?.packages?.[0]?.title ?? appConfig.appName.replace(/[^a-zA-Z0-9]/g, " ").trim();
|
|
11
|
+
},
|
|
12
|
+
vendor: (appConfig) => {
|
|
13
|
+
return appConfig?.env?.packages?.[0]?.vendor ?? "customer:vendor:Customer:";
|
|
14
|
+
},
|
|
15
|
+
version: (appConfig) => {
|
|
16
|
+
return appConfig?.env?.packages?.[0]?.version ?? "1.0.0";
|
|
17
|
+
},
|
|
18
|
+
partOfProducts: (appConfig, products) => {
|
|
19
|
+
return appConfig?.env?.packages?.[0]?.partOfProducts ?? products;
|
|
20
|
+
},
|
|
21
|
+
description: (appConfig, label, visibility) => {
|
|
22
|
+
return (
|
|
23
|
+
appConfig?.env?.packages?.[0]?.description ??
|
|
24
|
+
`This package contains ${visibility} ${label} for ${RESOLVERS.title(appConfig)}.`
|
|
25
|
+
);
|
|
26
|
+
},
|
|
27
|
+
shortDescription: (appConfig, label, visibility) => {
|
|
28
|
+
return appConfig?.env?.packages?.[0]?.shortDescription ?? `Package containing ${visibility} ${label}`;
|
|
29
|
+
},
|
|
30
|
+
ordId: (appConfig, label, visibility, resourceType) => {
|
|
31
|
+
const tag = !resourceType ? "" : `-${resourceType}`;
|
|
32
|
+
const technicalName = appConfig.appName.replace(/[^a-zA-Z0-9]/g, "");
|
|
33
|
+
const suffix = visibility === RESOURCE_VISIBILITY.public ? "" : `-${visibility}`;
|
|
34
|
+
|
|
35
|
+
return (
|
|
36
|
+
placeholders.replace(appConfig?.env?.packages?.[0]?.ordId, {
|
|
37
|
+
type: "package",
|
|
38
|
+
namespace: appConfig.ordNamespace,
|
|
39
|
+
}) ?? `${appConfig.ordNamespace}:package:${technicalName}${tag}${suffix}:v1`
|
|
40
|
+
);
|
|
41
|
+
},
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
function createPackage(appConfig, { label, visibility, products, resourceType }) {
|
|
45
|
+
return prune({
|
|
46
|
+
title: RESOLVERS.title(appConfig),
|
|
47
|
+
vendor: RESOLVERS.vendor(appConfig),
|
|
48
|
+
version: RESOLVERS.version(appConfig),
|
|
49
|
+
partOfProducts: RESOLVERS.partOfProducts(appConfig, products),
|
|
50
|
+
description: RESOLVERS.description(appConfig, label, visibility),
|
|
51
|
+
ordId: RESOLVERS.ordId(appConfig, label, visibility, resourceType),
|
|
52
|
+
shortDescription: RESOLVERS.shortDescription(appConfig, label, visibility),
|
|
53
|
+
|
|
54
|
+
..._.omit(appConfig?.env?.packages?.[0], Object.keys(RESOLVERS)),
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function createPackages(appConfig) {
|
|
59
|
+
const products = appConfig.existingProductORDId
|
|
60
|
+
? [appConfig.existingProductORDId]
|
|
61
|
+
: createProducts(appConfig)?.map((p) => p.ordId);
|
|
62
|
+
const visibilities = !appConfig.hasSAPPolicyLevel
|
|
63
|
+
? [RESOURCE_VISIBILITY.public]
|
|
64
|
+
: Object.values(RESOURCE_VISIBILITY);
|
|
65
|
+
const packageTypes = !appConfig.hasSAPPolicyLevel
|
|
66
|
+
? [{ label: "General" }]
|
|
67
|
+
: [
|
|
68
|
+
{ label: "APIs", resourceType: ORD_RESOURCE_TYPE.api },
|
|
69
|
+
{ label: "Events", resourceType: ORD_RESOURCE_TYPE.event },
|
|
70
|
+
{ label: "Entity Types", resourceType: ORD_RESOURCE_TYPE.entityType },
|
|
71
|
+
{ label: "Data Products", resourceType: ORD_RESOURCE_TYPE.dataProduct },
|
|
72
|
+
{ label: "Integration Dependencies", resourceType: ORD_RESOURCE_TYPE.integrationDependency },
|
|
73
|
+
];
|
|
74
|
+
|
|
75
|
+
return packageTypes.flatMap(({ label, resourceType }) =>
|
|
76
|
+
visibilities.map((visibility) =>
|
|
77
|
+
createPackage(appConfig, {
|
|
78
|
+
label,
|
|
79
|
+
visibility,
|
|
80
|
+
products,
|
|
81
|
+
resourceType,
|
|
82
|
+
}),
|
|
83
|
+
),
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
module.exports = {
|
|
88
|
+
createPackages,
|
|
89
|
+
createPackage,
|
|
90
|
+
RESOLVERS,
|
|
91
|
+
};
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
const _ = require("lodash");
|
|
2
|
+
|
|
3
|
+
const Logger = require("../logger");
|
|
4
|
+
const placeholders = require("../common/placeholders");
|
|
5
|
+
|
|
6
|
+
const RESOLVERS = Object.freeze({
|
|
7
|
+
ordId: (appConfig, overrides) => {
|
|
8
|
+
const name = appConfig.packageName
|
|
9
|
+
.split(/[^a-zA-Z0-9]/)
|
|
10
|
+
.filter(Boolean)
|
|
11
|
+
.join(".");
|
|
12
|
+
|
|
13
|
+
return (
|
|
14
|
+
placeholders.replace(overrides?.ordId, { type: "product", namespace: appConfig.ordNamespace }) ??
|
|
15
|
+
`customer:product:${name}:`
|
|
16
|
+
);
|
|
17
|
+
},
|
|
18
|
+
title: (appConfig, overrides) => {
|
|
19
|
+
return overrides?.title ?? appConfig.packageName.replace(/[^a-zA-Z0-9]/g, " ").trim();
|
|
20
|
+
},
|
|
21
|
+
vendor: (appConfig, overrides) => {
|
|
22
|
+
return overrides?.vendor ?? "customer:vendor:Customer:";
|
|
23
|
+
},
|
|
24
|
+
shortDescription: (appConfig, overrides) => {
|
|
25
|
+
return overrides?.shortDescription ?? `Short description of ${RESOLVERS.title(appConfig, overrides)}`;
|
|
26
|
+
},
|
|
27
|
+
});
|
|
28
|
+
|
|
29
|
+
function createProduct(appConfig) {
|
|
30
|
+
let overrides = appConfig?.env?.products?.[0] ?? {};
|
|
31
|
+
|
|
32
|
+
if (overrides?.ordId?.toLowerCase().startsWith("sap")) {
|
|
33
|
+
overrides = {}; // disable overrides, misconfiguration detected
|
|
34
|
+
Logger.error(
|
|
35
|
+
"Detected sap product ordId, which should not be defined for custom products, use default value instead. Please check ORD global registry.",
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return {
|
|
40
|
+
title: RESOLVERS.title(appConfig, overrides),
|
|
41
|
+
ordId: RESOLVERS.ordId(appConfig, overrides),
|
|
42
|
+
vendor: RESOLVERS.vendor(appConfig, overrides),
|
|
43
|
+
shortDescription: RESOLVERS.shortDescription(appConfig, overrides),
|
|
44
|
+
|
|
45
|
+
..._.omit(overrides, Object.keys(RESOLVERS)),
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function createProducts(appConfig) {
|
|
50
|
+
return appConfig.existingProductORDId ? [] : [createProduct(appConfig)];
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
module.exports = {
|
|
54
|
+
createProducts,
|
|
55
|
+
createProduct,
|
|
56
|
+
RESOLVERS,
|
|
57
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@cap-js/ord",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.9.0",
|
|
4
4
|
"description": "CAP Plugin for generating ORD document.",
|
|
5
5
|
"repository": "cap-js/ord",
|
|
6
6
|
"author": "SAP SE (https://www.sap.com)",
|
|
@@ -22,20 +22,22 @@
|
|
|
22
22
|
"lint": "npx eslint .",
|
|
23
23
|
"test": "jest __tests__/unit --ci --collectCoverage",
|
|
24
24
|
"test:integration:basic": "jest __tests__/integration/basic-auth.test.js --testPathIgnorePatterns=/node_modules/ --forceExit",
|
|
25
|
-
"test:integration:mtls": "jest __tests__/integration/mtls-auth
|
|
25
|
+
"test:integration:mtls": "jest __tests__/integration/mtls-auth-*.test.js --testPathIgnorePatterns=/node_modules/ --forceExit",
|
|
26
26
|
"test:integration:build": "jest __tests__/integration/cds-build.test.js --testPathIgnorePatterns=/node_modules/ --forceExit",
|
|
27
27
|
"update-snapshot": "jest --ci --updateSnapshot",
|
|
28
28
|
"cds:version": "cds v -i",
|
|
29
29
|
"test:all": "npm run test && npm run test:integration:basic && npm run test:integration:mtls && npm run test:integration:build"
|
|
30
30
|
},
|
|
31
31
|
"devDependencies": {
|
|
32
|
+
"@cap-js/cds-test": "0.4.1",
|
|
32
33
|
"@cap-js/graphql": "^0.14.0",
|
|
33
34
|
"@cap-js/sqlite": "^2",
|
|
35
|
+
"@open-resource-discovery/specification": "1.16.1",
|
|
34
36
|
"@sap/cds-dk": ">=8.9.5",
|
|
35
37
|
"eslint": "^10.0.0",
|
|
36
38
|
"express": "^5.0.0",
|
|
37
39
|
"jest": "^30.0.0",
|
|
38
|
-
"prettier": "3.8.
|
|
40
|
+
"prettier": "3.8.4",
|
|
39
41
|
"supertest": "^7.0.0"
|
|
40
42
|
},
|
|
41
43
|
"peerDependencies": {
|
|
@@ -53,7 +55,7 @@
|
|
|
53
55
|
"node": ">=20 <25"
|
|
54
56
|
},
|
|
55
57
|
"overrides": {
|
|
56
|
-
"@sap/cds-compiler": "6.9.
|
|
58
|
+
"@sap/cds-compiler": "6.9.3"
|
|
57
59
|
},
|
|
58
60
|
"cds": {
|
|
59
61
|
"requires": {
|
package/data/well-known.json
DELETED
package/lib/access-strategies.js
DELETED
|
@@ -1,172 +0,0 @@
|
|
|
1
|
-
const cds = require("@sap/cds");
|
|
2
|
-
const { AUTHENTICATION_TYPE, ORD_ACCESS_STRATEGY } = require("./constants");
|
|
3
|
-
const Logger = require("./logger");
|
|
4
|
-
|
|
5
|
-
/**
|
|
6
|
-
* Mapping from internal authentication types to ORD access strategy values.
|
|
7
|
-
* This is the single source of truth for auth type to ORD document mapping.
|
|
8
|
-
*
|
|
9
|
-
* @private
|
|
10
|
-
*/
|
|
11
|
-
const AUTH_TYPE_ORD_ACCESS_STRATEGY_MAP = Object.freeze({
|
|
12
|
-
[AUTHENTICATION_TYPE.Open]: ORD_ACCESS_STRATEGY.Open,
|
|
13
|
-
[AUTHENTICATION_TYPE.Basic]: ORD_ACCESS_STRATEGY.Basic,
|
|
14
|
-
[AUTHENTICATION_TYPE.CfMtls]: ORD_ACCESS_STRATEGY.CfMtls,
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
/**
|
|
18
|
-
* Derives ORD access strategies from authentication configuration.
|
|
19
|
-
* This function is the main entry point for converting auth config to ORD document format.
|
|
20
|
-
*
|
|
21
|
-
* @param {Object} authConfig - Authentication configuration object
|
|
22
|
-
* @param {string[]} authConfig.types - Array of authentication types (from AUTHENTICATION_TYPE)
|
|
23
|
-
* @returns {Array<{type: string}>} Array of access strategy objects for ORD document
|
|
24
|
-
*
|
|
25
|
-
* @example
|
|
26
|
-
* // With Basic auth configured
|
|
27
|
-
* const authConfig = { types: ['basic'] };
|
|
28
|
-
* const strategies = getAccessStrategiesFromAuthConfig(authConfig);
|
|
29
|
-
* // Returns: [{ type: 'basic-auth' }]
|
|
30
|
-
*
|
|
31
|
-
* @example
|
|
32
|
-
* // With multiple auth types
|
|
33
|
-
* const authConfig = { types: ['basic', 'cf-mtls'] };
|
|
34
|
-
* const strategies = getAccessStrategiesFromAuthConfig(authConfig);
|
|
35
|
-
* // Returns: [{ type: 'basic-auth' }, { type: 'sap:cmp-mtls:v1' }]
|
|
36
|
-
*/
|
|
37
|
-
function getAccessStrategiesFromAuthConfig(authConfig) {
|
|
38
|
-
if (!authConfig || !Array.isArray(authConfig.types)) {
|
|
39
|
-
Logger.warn("getAccessStrategiesFromAuthConfig:", "Invalid authConfig, defaulting to 'open'");
|
|
40
|
-
return [{ type: ORD_ACCESS_STRATEGY.Open }];
|
|
41
|
-
}
|
|
42
|
-
|
|
43
|
-
const strategies = authConfig.types
|
|
44
|
-
.map((type) => {
|
|
45
|
-
const ordType = AUTH_TYPE_ORD_ACCESS_STRATEGY_MAP[type];
|
|
46
|
-
if (!ordType) {
|
|
47
|
-
Logger.warn("getAccessStrategiesFromAuthConfig:", `Unknown auth type '${type}', skipping`);
|
|
48
|
-
return null;
|
|
49
|
-
}
|
|
50
|
-
return { type: ordType };
|
|
51
|
-
})
|
|
52
|
-
.filter(Boolean); // Remove null entries
|
|
53
|
-
|
|
54
|
-
// If no valid strategies found, default to open
|
|
55
|
-
if (strategies.length === 0) {
|
|
56
|
-
return [{ type: ORD_ACCESS_STRATEGY.Open }];
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
return strategies;
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
/**
|
|
63
|
-
* Checks if access strategies contain any non-open strategies.
|
|
64
|
-
*
|
|
65
|
-
* @param {Array<{type: string}>} accessStrategies - Array of access strategy objects
|
|
66
|
-
* @returns {boolean} True if any non-open strategy is present
|
|
67
|
-
*/
|
|
68
|
-
function hasNonOpenStrategies(accessStrategies) {
|
|
69
|
-
if (!Array.isArray(accessStrategies)) {
|
|
70
|
-
return false;
|
|
71
|
-
}
|
|
72
|
-
return accessStrategies.some((s) => s.type !== ORD_ACCESS_STRATEGY.Open);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
/**
|
|
76
|
-
* Validates that 'open' strategy does not coexist with non-open strategies.
|
|
77
|
-
* According to ORD specification, 'open' should not be mixed with authenticated strategies.
|
|
78
|
-
*
|
|
79
|
-
* @param {Array<{type: string}>} accessStrategies - Array of access strategy objects
|
|
80
|
-
* @throws {Error} If 'open' coexists with non-open strategies
|
|
81
|
-
*/
|
|
82
|
-
function ensureNoOpenWhenNonOpenPresent(accessStrategies) {
|
|
83
|
-
if (!Array.isArray(accessStrategies) || accessStrategies.length === 0) {
|
|
84
|
-
return;
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
const hasOpen = accessStrategies.some((s) => s.type === ORD_ACCESS_STRATEGY.Open);
|
|
88
|
-
const hasNonOpen = hasNonOpenStrategies(accessStrategies);
|
|
89
|
-
|
|
90
|
-
if (hasOpen && hasNonOpen) {
|
|
91
|
-
throw new Error(
|
|
92
|
-
"Invalid access strategies: 'open' cannot coexist with authenticated strategies (basic-auth, sap:cmp-mtls:v1)",
|
|
93
|
-
);
|
|
94
|
-
}
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
/**
|
|
98
|
-
* Ensures access strategies are valid and present, with configurable strict mode.
|
|
99
|
-
* In non-strict mode (default), missing/empty strategies fallback to 'open' with error log.
|
|
100
|
-
* In strict mode, missing/empty strategies throw an error.
|
|
101
|
-
*
|
|
102
|
-
* @param {Array<{type: string}>|undefined} accessStrategies - Array of access strategy objects
|
|
103
|
-
* @param {Object} options - Validation options
|
|
104
|
-
* @param {string} [options.resourceName] - Name of the resource (for error messages)
|
|
105
|
-
* @param {boolean} [options.strict] - If true, throw error instead of fallback (default: reads from cds.env.ord.strictAccessStrategies)
|
|
106
|
-
* @returns {Array<{type: string}>} Validated access strategies array
|
|
107
|
-
* @throws {Error} In strict mode, if accessStrategies is missing or empty
|
|
108
|
-
*
|
|
109
|
-
* @example
|
|
110
|
-
* // Non-strict mode (default) - fallback to open
|
|
111
|
-
* const strategies = ensureAccessStrategies(undefined, { resourceName: 'MyAPI' });
|
|
112
|
-
* // Logs error and returns: [{ type: 'open' }]
|
|
113
|
-
*
|
|
114
|
-
* @example
|
|
115
|
-
* // Strict mode - throws error
|
|
116
|
-
* const strategies = ensureAccessStrategies(undefined, {
|
|
117
|
-
* resourceName: 'MyAPI',
|
|
118
|
-
* strict: true
|
|
119
|
-
* });
|
|
120
|
-
* // Throws: Error with message about missing accessStrategies
|
|
121
|
-
*/
|
|
122
|
-
function ensureAccessStrategies(accessStrategies, options = {}) {
|
|
123
|
-
const { resourceName = "unknown resource", strict } = options;
|
|
124
|
-
|
|
125
|
-
// Determine strict mode: explicit parameter > config > default false
|
|
126
|
-
const isStrict = strict !== undefined ? strict : cds.env.ord?.strictAccessStrategies === true;
|
|
127
|
-
|
|
128
|
-
if (!Array.isArray(accessStrategies) || accessStrategies.length === 0) {
|
|
129
|
-
const message = `[ORD] accessStrategies missing or empty for resource "${resourceName}"`;
|
|
130
|
-
|
|
131
|
-
if (isStrict) {
|
|
132
|
-
throw new Error(`${message}. Strict mode is enabled.`);
|
|
133
|
-
} else {
|
|
134
|
-
Logger.error("ensureAccessStrategies:", `${message}. Falling back to 'open'.`);
|
|
135
|
-
return [{ type: ORD_ACCESS_STRATEGY.Open }];
|
|
136
|
-
}
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// Validate no mixing of 'open' with non-open strategies
|
|
140
|
-
ensureNoOpenWhenNonOpenPresent(accessStrategies);
|
|
141
|
-
|
|
142
|
-
return accessStrategies;
|
|
143
|
-
}
|
|
144
|
-
|
|
145
|
-
/**
|
|
146
|
-
* Validates that access strategies array contains only known ORD access strategy types.
|
|
147
|
-
*
|
|
148
|
-
* @param {Array<{type: string}>} accessStrategies - Array of access strategy objects
|
|
149
|
-
* @returns {boolean} True if all strategies are valid
|
|
150
|
-
*/
|
|
151
|
-
function isValidAccessStrategies(accessStrategies) {
|
|
152
|
-
if (!Array.isArray(accessStrategies) || accessStrategies.length === 0) {
|
|
153
|
-
return false;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
|
-
const validTypes = Object.values(ORD_ACCESS_STRATEGY);
|
|
157
|
-
return accessStrategies.every((s) => s.type && validTypes.includes(s.type));
|
|
158
|
-
}
|
|
159
|
-
|
|
160
|
-
module.exports = {
|
|
161
|
-
// Main API
|
|
162
|
-
getAccessStrategiesFromAuthConfig,
|
|
163
|
-
ensureAccessStrategies,
|
|
164
|
-
|
|
165
|
-
// Helper functions
|
|
166
|
-
hasNonOpenStrategies,
|
|
167
|
-
ensureNoOpenWhenNonOpenPresent,
|
|
168
|
-
isValidAccessStrategies,
|
|
169
|
-
|
|
170
|
-
// Constants (re-exported for convenience)
|
|
171
|
-
AUTH_TYPE_ORD_ACCESS_STRATEGY_MAP,
|
|
172
|
-
};
|
package/lib/date.js
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
function getRFC3339Date() {
|
|
2
|
-
const now = new Date();
|
|
3
|
-
const year = now.getUTCFullYear();
|
|
4
|
-
const month = String(now.getUTCMonth() + 1).padStart(2, "0");
|
|
5
|
-
const day = String(now.getUTCDate()).padStart(2, "0");
|
|
6
|
-
const hours = String(now.getUTCHours()).padStart(2, "0");
|
|
7
|
-
const minutes = String(now.getUTCMinutes()).padStart(2, "0");
|
|
8
|
-
const seconds = String(now.getUTCSeconds()).padStart(2, "0");
|
|
9
|
-
const offsetHours = "01";
|
|
10
|
-
const offsetMinutes = "00";
|
|
11
|
-
const offsetSign = "+";
|
|
12
|
-
return `${year}-${month}-${day}T${hours}:${minutes}:${seconds}${offsetSign}${offsetHours}:${offsetMinutes}`;
|
|
13
|
-
}
|
|
14
|
-
|
|
15
|
-
module.exports = {
|
|
16
|
-
getRFC3339Date,
|
|
17
|
-
};
|
|
@@ -1,126 +0,0 @@
|
|
|
1
|
-
const {
|
|
2
|
-
DATA_PRODUCT_SIMPLE_ANNOTATION,
|
|
3
|
-
EXTERNAL_DP_ORD_ID_ANNOTATION,
|
|
4
|
-
EXTERNAL_SERVICE_ANNOTATION,
|
|
5
|
-
INTEGRATION_DEPENDENCY_RESOURCE_NAME,
|
|
6
|
-
ORD_RESOURCE_TYPE,
|
|
7
|
-
RESOURCE_VISIBILITY,
|
|
8
|
-
} = require("./constants");
|
|
9
|
-
const { readORDExtensions, _getPackageID } = require("./templates");
|
|
10
|
-
|
|
11
|
-
/**
|
|
12
|
-
* Parses @cds.dp.ordId annotation to extract resource information.
|
|
13
|
-
* @param {string} ordId - e.g., "sap.sai:apiResource:Supplier:v1"
|
|
14
|
-
* @returns {Object} { namespace, resourceType, resourceName, version }
|
|
15
|
-
*/
|
|
16
|
-
function parseDataProductOrdId(ordId) {
|
|
17
|
-
const [namespace, resourceType, resourceName, version = "v1"] = ordId.split(":");
|
|
18
|
-
return { namespace, resourceType, resourceName, version };
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
/**
|
|
22
|
-
* Checks if a CSN definition is an external Data Product.
|
|
23
|
-
* @param {Object} definition - CSN definition object
|
|
24
|
-
* @returns {boolean}
|
|
25
|
-
*/
|
|
26
|
-
function isExternalDataProduct(definition) {
|
|
27
|
-
return !!(
|
|
28
|
-
definition.kind === "service" &&
|
|
29
|
-
definition[EXTERNAL_SERVICE_ANNOTATION] &&
|
|
30
|
-
definition[DATA_PRODUCT_SIMPLE_ANNOTATION] &&
|
|
31
|
-
definition[EXTERNAL_DP_ORD_ID_ANNOTATION]
|
|
32
|
-
);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
/**
|
|
36
|
-
* Collects external services from CSN definitions.
|
|
37
|
-
* Returns a flat list of external services (one per service, no namespace grouping).
|
|
38
|
-
* @param {Object} csn - The CSN definitions object
|
|
39
|
-
* @returns {Array} Array of external service objects
|
|
40
|
-
*/
|
|
41
|
-
function collectExternalServices(csn) {
|
|
42
|
-
const externalServices = [];
|
|
43
|
-
|
|
44
|
-
for (const [serviceName, definition] of Object.entries(csn.definitions)) {
|
|
45
|
-
if (!isExternalDataProduct(definition)) continue;
|
|
46
|
-
|
|
47
|
-
const dpOrdId = definition[EXTERNAL_DP_ORD_ID_ANNOTATION];
|
|
48
|
-
const { resourceType, version } = parseDataProductOrdId(dpOrdId);
|
|
49
|
-
|
|
50
|
-
// Only support apiResource for now
|
|
51
|
-
if (resourceType !== "apiResource") continue;
|
|
52
|
-
|
|
53
|
-
externalServices.push({
|
|
54
|
-
serviceName,
|
|
55
|
-
ordId: dpOrdId,
|
|
56
|
-
minVersion: version.replace("v", "") + ".0.0",
|
|
57
|
-
definition,
|
|
58
|
-
});
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
return externalServices;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Creates a single IntegrationDependency with one aspect per external service.
|
|
66
|
-
* @param {Array} externalServices - Array of external service objects
|
|
67
|
-
* @param {Object} appConfig - The application configuration
|
|
68
|
-
* @param {Array} packageIds - The available package identifiers
|
|
69
|
-
* @returns {Object} IntegrationDependency object
|
|
70
|
-
*/
|
|
71
|
-
function createIntegrationDependency(externalServices, appConfig, packageIds) {
|
|
72
|
-
const packageId = _getPackageID(
|
|
73
|
-
appConfig.ordNamespace,
|
|
74
|
-
packageIds,
|
|
75
|
-
ORD_RESOURCE_TYPE.integrationDependency,
|
|
76
|
-
RESOURCE_VISIBILITY.public,
|
|
77
|
-
);
|
|
78
|
-
|
|
79
|
-
// Create one aspect per external service
|
|
80
|
-
const aspects = externalServices.map((service) => {
|
|
81
|
-
const ordExtensions = readORDExtensions(service.definition || {});
|
|
82
|
-
return {
|
|
83
|
-
title: service.serviceName,
|
|
84
|
-
mandatory: false,
|
|
85
|
-
apiResources: [{ ordId: service.ordId, minVersion: service.minVersion }],
|
|
86
|
-
...ordExtensions, // Allow customization via @ORD.Extensions on the service
|
|
87
|
-
};
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
// Read IntegrationDependency level config from cdsrc
|
|
91
|
-
const integrationDepConfig = appConfig.env?.integrationDependency || {};
|
|
92
|
-
const version = integrationDepConfig.version || "1.0.0";
|
|
93
|
-
|
|
94
|
-
return {
|
|
95
|
-
ordId: `${appConfig.ordNamespace}:${ORD_RESOURCE_TYPE.integrationDependency}:${INTEGRATION_DEPENDENCY_RESOURCE_NAME}:v${version.split(".")[0]}`,
|
|
96
|
-
title: "External Dependencies",
|
|
97
|
-
version: version,
|
|
98
|
-
releaseStatus: "active",
|
|
99
|
-
visibility: RESOURCE_VISIBILITY.public,
|
|
100
|
-
mandatory: false,
|
|
101
|
-
partOfPackage: packageId,
|
|
102
|
-
aspects,
|
|
103
|
-
...integrationDepConfig, // Allow customization via cdsrc
|
|
104
|
-
};
|
|
105
|
-
}
|
|
106
|
-
|
|
107
|
-
/**
|
|
108
|
-
* Generates a single IntegrationDependency for all external services.
|
|
109
|
-
* @param {Object} csn - The CSN definitions object
|
|
110
|
-
* @param {Object} appConfig - The application configuration
|
|
111
|
-
* @param {Array} packageIds - The available package identifiers
|
|
112
|
-
* @returns {Array} Array containing single IntegrationDependency or empty array
|
|
113
|
-
*/
|
|
114
|
-
function getIntegrationDependencies(csn, appConfig, packageIds) {
|
|
115
|
-
const externalServices = collectExternalServices(csn);
|
|
116
|
-
if (externalServices.length === 0) return [];
|
|
117
|
-
|
|
118
|
-
return [createIntegrationDependency(externalServices, appConfig, packageIds)];
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
module.exports = {
|
|
122
|
-
getIntegrationDependencies,
|
|
123
|
-
// Exported for testing
|
|
124
|
-
collectExternalServices,
|
|
125
|
-
createIntegrationDependency,
|
|
126
|
-
};
|