@bleedingdev/modern-js-plugin-bff-extensions 3.9.0-ultramodern.11 → 3.9.0-ultramodern.12
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/dist/cjs/backend-federation/identity.js +23 -22
- package/dist/cjs/backend-federation-manifest/validation.js +11 -7
- package/dist/cjs/cross-project-policy/node.js +8 -28
- package/dist/cjs/effect-adapter/cross-project-policy.js +2 -2
- package/dist/cjs/effect-adapter/entry.js +8 -44
- package/dist/cjs/effect-adapter/index.js +10 -2
- package/dist/cjs/effect-source-loader/built-entry.js +4 -5
- package/dist/cjs/effect-source-loader/loader.js +2 -1
- package/dist/cjs/effect-source-loader/operation-contracts.js +8 -31
- package/dist/cjs/effect-source-loader/paths.js +38 -10
- package/dist/esm/backend-federation/identity.mjs +18 -23
- package/dist/esm/backend-federation-manifest/validation.mjs +12 -8
- package/dist/esm/cross-project-policy/node.mjs +9 -19
- package/dist/esm/effect-adapter/cross-project-policy.mjs +2 -2
- package/dist/esm/effect-adapter/entry.mjs +9 -45
- package/dist/esm/effect-adapter/index.mjs +10 -2
- package/dist/esm/effect-source-loader/built-entry.mjs +5 -6
- package/dist/esm/effect-source-loader/loader.mjs +2 -1
- package/dist/esm/effect-source-loader/operation-contracts.mjs +10 -33
- package/dist/esm/effect-source-loader/paths.mjs +30 -11
- package/dist/esm-node/backend-federation/identity.mjs +18 -23
- package/dist/esm-node/backend-federation-manifest/validation.mjs +12 -8
- package/dist/esm-node/cross-project-policy/node.mjs +9 -19
- package/dist/esm-node/effect-adapter/cross-project-policy.mjs +2 -2
- package/dist/esm-node/effect-adapter/entry.mjs +9 -45
- package/dist/esm-node/effect-adapter/index.mjs +10 -2
- package/dist/esm-node/effect-source-loader/built-entry.mjs +5 -6
- package/dist/esm-node/effect-source-loader/loader.mjs +2 -1
- package/dist/esm-node/effect-source-loader/operation-contracts.mjs +10 -33
- package/dist/esm-node/effect-source-loader/paths.mjs +30 -11
- package/dist/types/backend-federation/identity.d.ts +3 -6
- package/dist/types/cross-project-policy/node.d.ts +2 -2
- package/dist/types/effect-adapter/cross-project-policy.d.ts +2 -1
- package/dist/types/effect-source-loader/paths.d.ts +20 -2
- package/package.json +10 -10
- package/src/backend-federation/identity.ts +39 -47
- package/src/backend-federation-manifest/validation.ts +22 -29
- package/src/cross-project-policy/node.ts +7 -38
- package/src/effect-adapter/cross-project-policy.ts +7 -1
- package/src/effect-adapter/entry.ts +10 -88
- package/src/effect-adapter/index.ts +7 -1
- package/src/effect-source-loader/built-entry.ts +11 -9
- package/src/effect-source-loader/loader.ts +10 -5
- package/src/effect-source-loader/operation-contracts.ts +7 -41
- package/src/effect-source-loader/paths.ts +55 -21
|
@@ -10,16 +10,35 @@ const JS_OR_TS_EXTS = [
|
|
|
10
10
|
'.cjs',
|
|
11
11
|
'.cts'
|
|
12
12
|
];
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
const
|
|
16
|
-
|
|
17
|
-
|
|
13
|
+
const emittedEffectEntry = (entry)=>entry.replace(/\.(?:[cm]?ts|tsx|jsx)$/u, '.js');
|
|
14
|
+
function relativeEffectAppPath(appDir, entry) {
|
|
15
|
+
const relative = upath.relative(appDir, entry);
|
|
16
|
+
return '..' === relative || relative.startsWith('../') || relative.split('/').includes('node_modules') ? void 0 : relative;
|
|
17
|
+
}
|
|
18
|
+
function existingEntry(entry) {
|
|
19
|
+
return upath.extname(entry) ? fs.existsSync(entry) ? entry : void 0 : findExists(JS_OR_TS_EXTS.map((ext)=>`${entry}${ext}`)) || void 0;
|
|
20
|
+
}
|
|
21
|
+
function resolveEffectEntry(options) {
|
|
22
|
+
const { appDir, apiDir, effectEntry, distDir } = options;
|
|
23
|
+
const entry = upath.resolve(appDir, effectEntry || upath.join(apiDir, 'index'));
|
|
24
|
+
if (distDir && void 0 !== relativeEffectAppPath(distDir, entry)) return {
|
|
25
|
+
kind: 'built-output',
|
|
26
|
+
path: existingEntry(emittedEffectEntry(entry))
|
|
27
|
+
};
|
|
28
|
+
const relative = relativeEffectAppPath(appDir, entry);
|
|
29
|
+
if (void 0 === relative) return {
|
|
30
|
+
kind: 'external-sdk',
|
|
31
|
+
path: existingEntry(entry)
|
|
18
32
|
};
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
}
|
|
23
|
-
|
|
33
|
+
return distDir ? {
|
|
34
|
+
kind: 'built-output',
|
|
35
|
+
path: existingEntry(emittedEffectEntry(upath.resolve(distDir, relative)))
|
|
36
|
+
} : {
|
|
37
|
+
kind: 'app-source',
|
|
38
|
+
path: existingEntry(entry)
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
function resolveEffectEntryFile(options) {
|
|
42
|
+
return resolveEffectEntry(options).path;
|
|
24
43
|
}
|
|
25
|
-
export { resolveEffectEntryFile };
|
|
44
|
+
export { emittedEffectEntry, relativeEffectAppPath, resolveEffectEntry, resolveEffectEntryFile };
|
|
@@ -11,11 +11,8 @@ export type BackendFederationIdentityIssue = {
|
|
|
11
11
|
path: string;
|
|
12
12
|
message: string;
|
|
13
13
|
};
|
|
14
|
-
/**
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
* (`unitId`, `build`) against the consumer's expected delivery-unit identity.
|
|
18
|
-
* Missing identity metadata is an error.
|
|
19
|
-
*/
|
|
14
|
+
/** Decode independently at each received-manifest / executed-expose boundary. */
|
|
15
|
+
export declare function backendFederationCompatibility(loaded: unknown): Record<string, unknown> | undefined;
|
|
16
|
+
export declare function validateBackendFederationCompatibility(compatibility: Record<string, unknown>, expected: Record<string, string | undefined>): BackendFederationIdentityIssue[];
|
|
20
17
|
export declare function validateExpectedBackendFederationIdentity(loaded: unknown, expected: BackendFederationExpectedIdentity): BackendFederationIdentityIssue[];
|
|
21
18
|
export declare function formatBackendFederationIdentityIssues(issues: BackendFederationIdentityIssue[]): string;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import '@modern-js/server-runtime-extensions/server-config';
|
|
2
2
|
import type { ServerPluginAPI } from '@modern-js/server-core';
|
|
3
|
-
import { type OperationContractSource } from '@modern-js/server-runtime-extensions/bff-policy/node';
|
|
3
|
+
import { type OperationContractSource, resolveOperationProducer } from '@modern-js/server-runtime-extensions/bff-policy/node';
|
|
4
4
|
import type { ResolvedCrossProjectPolicy } from './evaluation.js';
|
|
5
|
-
export declare const resolveAdapterCrossProjectPolicy: (api: ServerPluginAPI, handlers: OperationContractSource[]) => ResolvedCrossProjectPolicy | undefined;
|
|
5
|
+
export declare const resolveAdapterCrossProjectPolicy: (api: ServerPluginAPI, handlers: OperationContractSource[], producer?: ReturnType<typeof resolveOperationProducer>) => ResolvedCrossProjectPolicy | undefined;
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { type EffectApiModule } from '@modern-js/bff-effect/effect';
|
|
2
2
|
import type { ServerPluginAPI } from '@modern-js/server-core';
|
|
3
|
+
import type { resolveOperationProducer } from '@modern-js/server-runtime-extensions/bff-policy/node';
|
|
3
4
|
import type { ResolvedCrossProjectPolicy } from '../cross-project-policy/evaluation.js';
|
|
4
|
-
export declare function resolveEffectAdapterCrossProjectPolicy(api: ServerPluginAPI, prefix: string, mod: EffectApiModule | null): Promise<ResolvedCrossProjectPolicy | undefined>;
|
|
5
|
+
export declare function resolveEffectAdapterCrossProjectPolicy(api: ServerPluginAPI, prefix: string, mod: EffectApiModule | null, producer?: ReturnType<typeof resolveOperationProducer>): Promise<ResolvedCrossProjectPolicy | undefined>;
|
|
@@ -1,5 +1,23 @@
|
|
|
1
|
-
export declare
|
|
1
|
+
export declare const emittedEffectEntry: (entry: string) => string;
|
|
2
|
+
export declare function relativeEffectAppPath(appDir: string, entry: string): string | undefined;
|
|
3
|
+
type EffectEntryOptions = {
|
|
2
4
|
appDir: string;
|
|
3
5
|
apiDir: string;
|
|
4
6
|
effectEntry?: string;
|
|
5
|
-
}
|
|
7
|
+
};
|
|
8
|
+
export type EffectEntry = {
|
|
9
|
+
kind: 'app-source';
|
|
10
|
+
path: string | undefined;
|
|
11
|
+
} | {
|
|
12
|
+
kind: 'built-output';
|
|
13
|
+
path: string | undefined;
|
|
14
|
+
} | {
|
|
15
|
+
kind: 'external-sdk';
|
|
16
|
+
path: string | undefined;
|
|
17
|
+
};
|
|
18
|
+
/** Classify ownership before resolving files: production works without source. */
|
|
19
|
+
export declare function resolveEffectEntry(options: EffectEntryOptions & {
|
|
20
|
+
distDir?: string;
|
|
21
|
+
}): EffectEntry;
|
|
22
|
+
export declare function resolveEffectEntryFile(options: EffectEntryOptions): string | undefined;
|
|
23
|
+
export {};
|
package/package.json
CHANGED
|
@@ -18,7 +18,7 @@
|
|
|
18
18
|
"modern.js",
|
|
19
19
|
"ultramodern.js"
|
|
20
20
|
],
|
|
21
|
-
"version": "3.9.0-ultramodern.
|
|
21
|
+
"version": "3.9.0-ultramodern.12",
|
|
22
22
|
"files": [
|
|
23
23
|
"dist",
|
|
24
24
|
"src"
|
|
@@ -193,14 +193,14 @@
|
|
|
193
193
|
"node": ">=26.7.0"
|
|
194
194
|
},
|
|
195
195
|
"dependencies": {
|
|
196
|
-
"@modern-js/backend-federation-contracts": "npm:@bleedingdev/modern-js-backend-federation-contracts@3.9.0-ultramodern.
|
|
197
|
-
"@modern-js/bff-core": "npm:@bleedingdev/modern-js-bff-core@3.9.0-ultramodern.
|
|
198
|
-
"@modern-js/bff-effect": "npm:@bleedingdev/modern-js-bff-effect@3.9.0-ultramodern.
|
|
199
|
-
"@modern-js/runtime-extensions": "npm:@bleedingdev/modern-js-runtime-extensions@3.9.0-ultramodern.
|
|
200
|
-
"@modern-js/server-core": "npm:@bleedingdev/modern-js-server-core@3.9.0-ultramodern.
|
|
201
|
-
"@modern-js/server-runtime-extensions": "npm:@bleedingdev/modern-js-server-runtime-extensions@3.9.0-ultramodern.
|
|
202
|
-
"@modern-js/types": "npm:@bleedingdev/modern-js-types@3.9.0-ultramodern.
|
|
203
|
-
"@modern-js/utils": "npm:@bleedingdev/modern-js-utils@3.9.0-ultramodern.
|
|
196
|
+
"@modern-js/backend-federation-contracts": "npm:@bleedingdev/modern-js-backend-federation-contracts@3.9.0-ultramodern.12",
|
|
197
|
+
"@modern-js/bff-core": "npm:@bleedingdev/modern-js-bff-core@3.9.0-ultramodern.12",
|
|
198
|
+
"@modern-js/bff-effect": "npm:@bleedingdev/modern-js-bff-effect@3.9.0-ultramodern.12",
|
|
199
|
+
"@modern-js/runtime-extensions": "npm:@bleedingdev/modern-js-runtime-extensions@3.9.0-ultramodern.12",
|
|
200
|
+
"@modern-js/server-core": "npm:@bleedingdev/modern-js-server-core@3.9.0-ultramodern.12",
|
|
201
|
+
"@modern-js/server-runtime-extensions": "npm:@bleedingdev/modern-js-server-runtime-extensions@3.9.0-ultramodern.12",
|
|
202
|
+
"@modern-js/types": "npm:@bleedingdev/modern-js-types@3.9.0-ultramodern.12",
|
|
203
|
+
"@modern-js/utils": "npm:@bleedingdev/modern-js-utils@3.9.0-ultramodern.12",
|
|
204
204
|
"@module-federation/runtime": "2.9.0",
|
|
205
205
|
"@swc/core": "1.16.2",
|
|
206
206
|
"esbuild": "^0.28.2"
|
|
@@ -219,7 +219,7 @@
|
|
|
219
219
|
},
|
|
220
220
|
"devDependencies": {
|
|
221
221
|
"@effect/opentelemetry": "4.0.0-rc.112",
|
|
222
|
-
"@modern-js/plugin": "npm:@bleedingdev/modern-js-plugin@3.9.0-ultramodern.
|
|
222
|
+
"@modern-js/plugin": "npm:@bleedingdev/modern-js-plugin@3.9.0-ultramodern.12",
|
|
223
223
|
"@rsbuild/core": "2.2.3",
|
|
224
224
|
"@rslib/core": "1.0.0",
|
|
225
225
|
"@scripts/rstest-config": "2.66.0",
|
|
@@ -17,59 +17,51 @@ export type BackendFederationIdentityIssue = {
|
|
|
17
17
|
message: string;
|
|
18
18
|
};
|
|
19
19
|
|
|
20
|
-
/**
|
|
21
|
-
|
|
22
|
-
* Compares the expose's `backendFederationContract.compatibility` identity
|
|
23
|
-
* (`unitId`, `build`) against the consumer's expected delivery-unit identity.
|
|
24
|
-
* Missing identity metadata is an error.
|
|
25
|
-
*/
|
|
26
|
-
export function validateExpectedBackendFederationIdentity(
|
|
27
|
-
loaded: unknown,
|
|
28
|
-
expected: BackendFederationExpectedIdentity,
|
|
29
|
-
): BackendFederationIdentityIssue[] {
|
|
30
|
-
const issues: BackendFederationIdentityIssue[] = [];
|
|
20
|
+
/** Decode independently at each received-manifest / executed-expose boundary. */
|
|
21
|
+
export function backendFederationCompatibility(loaded: unknown) {
|
|
31
22
|
const contract = isRecord(loaded)
|
|
32
23
|
? loaded.backendFederationContract
|
|
33
24
|
: undefined;
|
|
34
25
|
const compatibility = isRecord(contract) ? contract.compatibility : undefined;
|
|
26
|
+
return isRecord(compatibility) ? compatibility : undefined;
|
|
27
|
+
}
|
|
35
28
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
path: 'backendFederationContract.compatibility.unitId',
|
|
55
|
-
message: `expected ${expected.unitId}, received ${unitId}`,
|
|
56
|
-
});
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
const build = compatibility.build;
|
|
60
|
-
if (typeof build !== 'string' || build.length === 0) {
|
|
61
|
-
issues.push({
|
|
62
|
-
path: 'backendFederationContract.compatibility.build',
|
|
63
|
-
message: `missing build marker; expected ${expected.buildMarker}`,
|
|
64
|
-
});
|
|
65
|
-
} else if (build !== expected.buildMarker) {
|
|
66
|
-
issues.push({
|
|
67
|
-
path: 'backendFederationContract.compatibility.build',
|
|
68
|
-
message: `expected ${expected.buildMarker}, received ${build}`,
|
|
69
|
-
});
|
|
70
|
-
}
|
|
29
|
+
export function validateBackendFederationCompatibility(
|
|
30
|
+
compatibility: Record<string, unknown>,
|
|
31
|
+
expected: Record<string, string | undefined>,
|
|
32
|
+
): BackendFederationIdentityIssue[] {
|
|
33
|
+
return Object.entries(expected).flatMap(([field, value]) =>
|
|
34
|
+
value === undefined ||
|
|
35
|
+
(typeof compatibility[field] === 'string' &&
|
|
36
|
+
compatibility[field].length > 0 &&
|
|
37
|
+
compatibility[field] === value)
|
|
38
|
+
? []
|
|
39
|
+
: [
|
|
40
|
+
{
|
|
41
|
+
path: `backendFederationContract.compatibility.${field}`,
|
|
42
|
+
message: `expected ${value}, received ${typeof compatibility[field] === 'string' ? compatibility[field] : typeof compatibility[field]}`,
|
|
43
|
+
},
|
|
44
|
+
],
|
|
45
|
+
);
|
|
46
|
+
}
|
|
71
47
|
|
|
72
|
-
|
|
48
|
+
export function validateExpectedBackendFederationIdentity(
|
|
49
|
+
loaded: unknown,
|
|
50
|
+
expected: BackendFederationExpectedIdentity,
|
|
51
|
+
): BackendFederationIdentityIssue[] {
|
|
52
|
+
const compatibility = backendFederationCompatibility(loaded);
|
|
53
|
+
return compatibility
|
|
54
|
+
? validateBackendFederationCompatibility(compatibility, {
|
|
55
|
+
unitId: expected.unitId,
|
|
56
|
+
build: expected.buildMarker,
|
|
57
|
+
})
|
|
58
|
+
: [
|
|
59
|
+
{
|
|
60
|
+
path: 'backendFederationContract.compatibility',
|
|
61
|
+
message:
|
|
62
|
+
'loaded module declares no compatibility metadata; delivery-unit identity cannot be validated',
|
|
63
|
+
},
|
|
64
|
+
];
|
|
73
65
|
}
|
|
74
66
|
|
|
75
67
|
export function formatBackendFederationIdentityIssues(
|
|
@@ -10,6 +10,11 @@ import {
|
|
|
10
10
|
type BackendFederatedEffectApiModule,
|
|
11
11
|
type BackendFederationRemote,
|
|
12
12
|
} from '../backend-federation';
|
|
13
|
+
import {
|
|
14
|
+
backendFederationCompatibility,
|
|
15
|
+
formatBackendFederationIdentityIssues,
|
|
16
|
+
validateBackendFederationCompatibility,
|
|
17
|
+
} from '../backend-federation/identity';
|
|
13
18
|
import {
|
|
14
19
|
assertConsistentValue,
|
|
15
20
|
assertManifestAdapter,
|
|
@@ -19,7 +24,6 @@ import {
|
|
|
19
24
|
import {
|
|
20
25
|
backendFederationMetadata,
|
|
21
26
|
manifestDeliveryUnit,
|
|
22
|
-
recordField,
|
|
23
27
|
stringValue,
|
|
24
28
|
versionBoundaryMetadata,
|
|
25
29
|
} from './metadata';
|
|
@@ -166,7 +170,7 @@ export function validateLoadedBackendFederationContract(
|
|
|
166
170
|
const loadedContract = loaded.backendFederationContract;
|
|
167
171
|
const backendFederation = backendFederationMetadata(manifest);
|
|
168
172
|
const boundary = versionBoundaryMetadata(manifest);
|
|
169
|
-
const compatibility =
|
|
173
|
+
const compatibility = backendFederationCompatibility(loaded);
|
|
170
174
|
|
|
171
175
|
assertManifestAdapter(
|
|
172
176
|
compatibility,
|
|
@@ -186,26 +190,6 @@ export function validateLoadedBackendFederationContract(
|
|
|
186
190
|
remote.name,
|
|
187
191
|
'expose name',
|
|
188
192
|
);
|
|
189
|
-
assertVersionValue(
|
|
190
|
-
stringValue(compatibility.contractVersion),
|
|
191
|
-
stringValue(backendFederation?.contractVersion),
|
|
192
|
-
'expose contractVersion',
|
|
193
|
-
);
|
|
194
|
-
assertVersionValue(
|
|
195
|
-
stringValue(compatibility.nodeAdapterVersion),
|
|
196
|
-
stringValue(backendFederation?.nodeAdapterVersion),
|
|
197
|
-
'expose nodeAdapterVersion',
|
|
198
|
-
);
|
|
199
|
-
assertVersionValue(
|
|
200
|
-
stringValue(compatibility.packageName),
|
|
201
|
-
stringValue(boundary?.packageName),
|
|
202
|
-
'expose packageName',
|
|
203
|
-
);
|
|
204
|
-
assertVersionValue(
|
|
205
|
-
stringValue(compatibility.build),
|
|
206
|
-
stringValue(boundary?.buildVersion),
|
|
207
|
-
'expose buildVersion',
|
|
208
|
-
);
|
|
209
193
|
|
|
210
194
|
const deliveryUnit = manifestDeliveryUnit(backendFederation, boundary);
|
|
211
195
|
const manifestUnitId =
|
|
@@ -213,15 +197,24 @@ export function validateLoadedBackendFederationContract(
|
|
|
213
197
|
stringValue(deliveryUnit.top?.unitId);
|
|
214
198
|
const manifestBuildMarker = stringValue(deliveryUnit.boundary?.buildMarker);
|
|
215
199
|
|
|
216
|
-
|
|
217
|
-
stringValue(
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
200
|
+
const issues = validateBackendFederationCompatibility(compatibility, {
|
|
201
|
+
contractVersion: stringValue(backendFederation?.contractVersion),
|
|
202
|
+
nodeAdapterVersion: stringValue(backendFederation?.nodeAdapterVersion),
|
|
203
|
+
packageName: stringValue(boundary?.packageName),
|
|
204
|
+
unitId: manifestUnitId,
|
|
205
|
+
build: manifestBuildMarker ?? stringValue(boundary?.buildVersion),
|
|
206
|
+
});
|
|
207
|
+
// Manifest validation establishes buildVersion/buildMarker consistency; the
|
|
208
|
+
// executed expose is still checked independently against that trusted record.
|
|
221
209
|
assertVersionValue(
|
|
222
210
|
stringValue(compatibility.build),
|
|
223
|
-
|
|
224
|
-
'
|
|
211
|
+
stringValue(boundary?.buildVersion),
|
|
212
|
+
'expose buildVersion',
|
|
213
|
+
);
|
|
214
|
+
assertManifestAdapter(
|
|
215
|
+
issues.length === 0,
|
|
216
|
+
'version_mismatch',
|
|
217
|
+
`[BFF][Effect] ${formatBackendFederationIdentityIssues(issues)}.`,
|
|
225
218
|
);
|
|
226
219
|
}
|
|
227
220
|
|
|
@@ -1,48 +1,17 @@
|
|
|
1
|
-
import { readFileSync } from 'node:fs';
|
|
2
|
-
import path from 'node:path';
|
|
3
1
|
import '@modern-js/server-runtime-extensions/server-config';
|
|
4
2
|
import type { ServerPluginAPI } from '@modern-js/server-core';
|
|
5
3
|
import {
|
|
6
|
-
deriveOperationVersion,
|
|
7
4
|
type OperationContractSource,
|
|
8
5
|
resolveCrossProjectPolicy,
|
|
6
|
+
resolveOperationProducer,
|
|
9
7
|
} from '@modern-js/server-runtime-extensions/bff-policy/node';
|
|
10
8
|
|
|
11
9
|
import type { ResolvedCrossProjectPolicy } from './evaluation';
|
|
12
10
|
|
|
13
|
-
const readNearestPackageVersion = (
|
|
14
|
-
startDir: string | undefined,
|
|
15
|
-
): string | undefined => {
|
|
16
|
-
if (!startDir) {
|
|
17
|
-
return undefined;
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
let current = path.resolve(startDir);
|
|
21
|
-
for (let depth = 0; depth < 10; depth += 1) {
|
|
22
|
-
try {
|
|
23
|
-
const packageJson = JSON.parse(
|
|
24
|
-
readFileSync(path.join(current, 'package.json'), 'utf8'),
|
|
25
|
-
) as { version?: unknown };
|
|
26
|
-
if (typeof packageJson.version === 'string') {
|
|
27
|
-
return packageJson.version;
|
|
28
|
-
}
|
|
29
|
-
} catch {
|
|
30
|
-
// The producer package may be nested below its package.json.
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
const parent = path.dirname(current);
|
|
34
|
-
if (parent === current) {
|
|
35
|
-
return undefined;
|
|
36
|
-
}
|
|
37
|
-
current = parent;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
return undefined;
|
|
41
|
-
};
|
|
42
|
-
|
|
43
11
|
export const resolveAdapterCrossProjectPolicy = (
|
|
44
12
|
api: ServerPluginAPI,
|
|
45
13
|
handlers: OperationContractSource[],
|
|
14
|
+
producer?: ReturnType<typeof resolveOperationProducer>,
|
|
46
15
|
): ResolvedCrossProjectPolicy | undefined => {
|
|
47
16
|
const bff = api.getServerConfig()?.bff;
|
|
48
17
|
const { apiDirectory, appDirectory } = api.getServerContext() as {
|
|
@@ -53,11 +22,11 @@ export const resolveAdapterCrossProjectPolicy = (
|
|
|
53
22
|
return resolveCrossProjectPolicy({
|
|
54
23
|
crossProjectPolicy: bff?.crossProjectPolicy,
|
|
55
24
|
handlers,
|
|
56
|
-
requestId: bff?.requestId,
|
|
57
25
|
isCrossProjectServer: bff?.isCrossProjectServer,
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
26
|
+
...(producer ??
|
|
27
|
+
resolveOperationProducer({
|
|
28
|
+
directories: [apiDirectory, appDirectory],
|
|
29
|
+
requestId: bff?.requestId || 'default',
|
|
30
|
+
})),
|
|
62
31
|
});
|
|
63
32
|
};
|
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
toOperationContractSources,
|
|
7
7
|
} from '@modern-js/bff-effect/effect';
|
|
8
8
|
import type { ServerPluginAPI } from '@modern-js/server-core';
|
|
9
|
+
import type { resolveOperationProducer } from '@modern-js/server-runtime-extensions/bff-policy/node';
|
|
9
10
|
import { logger } from '@modern-js/utils';
|
|
10
11
|
import { HttpApi } from 'effect/unstable/httpapi';
|
|
11
12
|
|
|
@@ -16,6 +17,7 @@ export function resolveEffectAdapterCrossProjectPolicy(
|
|
|
16
17
|
api: ServerPluginAPI,
|
|
17
18
|
prefix: string,
|
|
18
19
|
mod: EffectApiModule | null,
|
|
20
|
+
producer?: ReturnType<typeof resolveOperationProducer>,
|
|
19
21
|
): Promise<ResolvedCrossProjectPolicy | undefined> {
|
|
20
22
|
const contractSourcesPromise =
|
|
21
23
|
mod === null
|
|
@@ -52,7 +54,11 @@ export function resolveEffectAdapterCrossProjectPolicy(
|
|
|
52
54
|
});
|
|
53
55
|
|
|
54
56
|
return contractSourcesPromise.then(contractSources => {
|
|
55
|
-
const policy = resolveAdapterCrossProjectPolicy(
|
|
57
|
+
const policy = resolveAdapterCrossProjectPolicy(
|
|
58
|
+
api,
|
|
59
|
+
contractSources,
|
|
60
|
+
producer,
|
|
61
|
+
);
|
|
56
62
|
if (policy?.enabled === true && contractSources.length === 0) {
|
|
57
63
|
logger.warn(
|
|
58
64
|
'[BFF][Effect] Cross-project policy enabled but no HttpApi endpoints could be reflected; requests fail operation-contract matching unless allowUnknownOperations is enabled.',
|
|
@@ -1,96 +1,18 @@
|
|
|
1
1
|
// @effect-diagnostics nodeBuiltinImport:off strictBooleanExpressions:off
|
|
2
|
-
|
|
3
2
|
import path from 'node:path';
|
|
4
3
|
import '@modern-js/server-runtime-extensions/server-config';
|
|
5
4
|
import type { ServerPluginAPI } from '@modern-js/server-core';
|
|
6
|
-
import { API_DIR,
|
|
7
|
-
|
|
8
|
-
const JS_OR_TS_EXTS = [
|
|
9
|
-
'.js',
|
|
10
|
-
'.jsx',
|
|
11
|
-
'.ts',
|
|
12
|
-
'.tsx',
|
|
13
|
-
'.mjs',
|
|
14
|
-
'.mts',
|
|
15
|
-
'.cjs',
|
|
16
|
-
'.cts',
|
|
17
|
-
] as const;
|
|
18
|
-
|
|
19
|
-
type JsOrTsExtension = (typeof JS_OR_TS_EXTS)[number];
|
|
20
|
-
|
|
21
|
-
function resolveJsOrTsEntry(entryWithoutOrWithExt: string) {
|
|
22
|
-
const extension = path.extname(entryWithoutOrWithExt) as JsOrTsExtension;
|
|
23
|
-
if (JS_OR_TS_EXTS.includes(extension)) {
|
|
24
|
-
return fs.existsSync(entryWithoutOrWithExt)
|
|
25
|
-
? entryWithoutOrWithExt
|
|
26
|
-
: undefined;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
return (
|
|
30
|
-
findExists(JS_OR_TS_EXTS.map(ext => `${entryWithoutOrWithExt}${ext}`)) ||
|
|
31
|
-
undefined
|
|
32
|
-
);
|
|
33
|
-
}
|
|
34
|
-
|
|
35
|
-
function builtEntryPath(entryPath: string) {
|
|
36
|
-
return entryPath.replace(/\.(?:[cm]?ts|tsx|jsx)$/u, '.js');
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
function relativeAppPath(appDirectory: string, entryPath: string) {
|
|
40
|
-
const relativePath = path.relative(appDirectory, entryPath);
|
|
41
|
-
return relativePath === '..' || relativePath.startsWith(`..${path.sep}`)
|
|
42
|
-
? undefined
|
|
43
|
-
: relativePath;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
function isDependencyPath(relativePath: string | undefined) {
|
|
47
|
-
return relativePath?.split(/[\\/]/u).includes('node_modules') ?? false;
|
|
48
|
-
}
|
|
5
|
+
import { API_DIR, isProd } from '@modern-js/utils';
|
|
6
|
+
import { resolveEffectEntry } from '../effect-source-loader/paths';
|
|
49
7
|
|
|
50
8
|
export function resolveEffectAdapterEntryFile(api: ServerPluginAPI) {
|
|
51
9
|
const { appDirectory, apiDirectory, distDirectory } = api.getServerContext();
|
|
52
|
-
const
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
: path.resolve(appRoot, configuredEntry);
|
|
61
|
-
if (productionRoot) {
|
|
62
|
-
const relativeEntry = relativeAppPath(appRoot, sourceEntry);
|
|
63
|
-
if (relativeEntry === undefined || isDependencyPath(relativeEntry)) {
|
|
64
|
-
return resolveJsOrTsEntry(sourceEntry);
|
|
65
|
-
}
|
|
66
|
-
return relativeEntry
|
|
67
|
-
? resolveJsOrTsEntry(
|
|
68
|
-
builtEntryPath(path.resolve(productionRoot, relativeEntry)),
|
|
69
|
-
)
|
|
70
|
-
: undefined;
|
|
71
|
-
}
|
|
72
|
-
return resolveJsOrTsEntry(sourceEntry);
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
const resolvedApiDirectory = path.resolve(appRoot, apiDirectory || API_DIR);
|
|
76
|
-
const productionApiDirectory = productionRoot
|
|
77
|
-
? relativeAppPath(productionRoot, resolvedApiDirectory) !== undefined
|
|
78
|
-
? resolvedApiDirectory
|
|
79
|
-
: (() => {
|
|
80
|
-
const relativeApiDirectory = relativeAppPath(
|
|
81
|
-
appRoot,
|
|
82
|
-
resolvedApiDirectory,
|
|
83
|
-
);
|
|
84
|
-
if (isDependencyPath(relativeApiDirectory)) {
|
|
85
|
-
return resolvedApiDirectory;
|
|
86
|
-
}
|
|
87
|
-
return relativeApiDirectory
|
|
88
|
-
? path.resolve(productionRoot, relativeApiDirectory)
|
|
89
|
-
: undefined;
|
|
90
|
-
})()
|
|
91
|
-
: resolvedApiDirectory;
|
|
92
|
-
|
|
93
|
-
return productionApiDirectory
|
|
94
|
-
? resolveJsOrTsEntry(path.resolve(productionApiDirectory, 'index'))
|
|
95
|
-
: undefined;
|
|
10
|
+
const appDir = path.resolve(appDirectory || process.cwd());
|
|
11
|
+
return resolveEffectEntry({
|
|
12
|
+
appDir,
|
|
13
|
+
apiDir: path.resolve(appDir, apiDirectory || API_DIR),
|
|
14
|
+
effectEntry: api.getServerConfig()?.bff?.effect?.entry,
|
|
15
|
+
distDir:
|
|
16
|
+
isProd() && distDirectory ? path.resolve(distDirectory) : undefined,
|
|
17
|
+
}).path;
|
|
96
18
|
}
|
|
@@ -10,6 +10,7 @@ import {
|
|
|
10
10
|
useEffectContext,
|
|
11
11
|
} from '@modern-js/bff-effect/effect';
|
|
12
12
|
import type { Context, Next, ServerPluginAPI } from '@modern-js/server-core';
|
|
13
|
+
import { resolveOperationProducer } from '@modern-js/server-runtime-extensions/bff-policy/node';
|
|
13
14
|
import {
|
|
14
15
|
createDisposableServerRuntimeHandle,
|
|
15
16
|
type DisposableServerRuntimeHandle,
|
|
@@ -161,9 +162,9 @@ export class EffectAdapter {
|
|
|
161
162
|
return;
|
|
162
163
|
}
|
|
163
164
|
|
|
165
|
+
const { appDirectory } = this.api.getServerContext();
|
|
164
166
|
let mod: EffectApiModule;
|
|
165
167
|
try {
|
|
166
|
-
const { appDirectory } = this.api.getServerContext();
|
|
167
168
|
mod = (await (isProd()
|
|
168
169
|
? loadEffectBuiltModule(entryFile)
|
|
169
170
|
: loadEffectSourceModule({
|
|
@@ -177,6 +178,10 @@ export class EffectAdapter {
|
|
|
177
178
|
throw error;
|
|
178
179
|
}
|
|
179
180
|
|
|
181
|
+
const producer = resolveOperationProducer({
|
|
182
|
+
directories: [path.dirname(entryFile), appDirectory],
|
|
183
|
+
requestId: this.api.getServerConfig()?.bff?.requestId,
|
|
184
|
+
});
|
|
180
185
|
const crossProjectPolicies = new Map(
|
|
181
186
|
await Promise.all(
|
|
182
187
|
this.prefixes.map(
|
|
@@ -187,6 +192,7 @@ export class EffectAdapter {
|
|
|
187
192
|
this.api,
|
|
188
193
|
prefix,
|
|
189
194
|
mod,
|
|
195
|
+
producer,
|
|
190
196
|
),
|
|
191
197
|
] as const,
|
|
192
198
|
),
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { fs, upath as path } from '@modern-js/utils';
|
|
2
2
|
|
|
3
|
-
import {
|
|
4
|
-
|
|
5
|
-
|
|
3
|
+
import {
|
|
4
|
+
emittedEffectEntry,
|
|
5
|
+
relativeEffectAppPath,
|
|
6
|
+
resolveEffectEntryFile,
|
|
7
|
+
} from './paths';
|
|
6
8
|
|
|
7
9
|
export function resolveBuiltEffectEntry(
|
|
8
10
|
appDirectory: string,
|
|
@@ -12,15 +14,15 @@ export function resolveBuiltEffectEntry(
|
|
|
12
14
|
if (sourceEntry === undefined || sourceEntry.length === 0) {
|
|
13
15
|
return undefined;
|
|
14
16
|
}
|
|
15
|
-
const relativeEntry =
|
|
16
|
-
if (relativeEntry ===
|
|
17
|
+
const relativeEntry = relativeEffectAppPath(appDirectory, sourceEntry);
|
|
18
|
+
if (relativeEntry === undefined) {
|
|
17
19
|
throw new Error(
|
|
18
20
|
`Effect BFF entry must be inside the application directory: ${sourceEntry}`,
|
|
19
21
|
);
|
|
20
22
|
}
|
|
21
|
-
const builtEntry =
|
|
22
|
-
.resolve(distDirectory, relativeEntry)
|
|
23
|
-
|
|
23
|
+
const builtEntry = emittedEffectEntry(
|
|
24
|
+
path.resolve(distDirectory, relativeEntry),
|
|
25
|
+
);
|
|
24
26
|
return fs.existsSync(builtEntry) ? builtEntry : undefined;
|
|
25
27
|
}
|
|
26
28
|
|
|
@@ -62,7 +64,7 @@ export function resolveEffectEntryPaths(options: {
|
|
|
62
64
|
});
|
|
63
65
|
const relativeEffectEntry =
|
|
64
66
|
sourceEffectEntry !== undefined && sourceEffectEntry.length > 0
|
|
65
|
-
? path.relative(appDir, sourceEffectEntry)
|
|
67
|
+
? emittedEffectEntry(path.relative(appDir, sourceEffectEntry))
|
|
66
68
|
: '';
|
|
67
69
|
return { sourceEffectEntry, relativeEffectEntry };
|
|
68
70
|
}
|
|
@@ -125,10 +125,9 @@ function isBareSpecifier(specifier: string) {
|
|
|
125
125
|
}
|
|
126
126
|
|
|
127
127
|
/**
|
|
128
|
-
* Keep installed
|
|
129
|
-
*
|
|
130
|
-
*
|
|
131
|
-
* generated entry is relocatable and directly executable by Node 26.7.
|
|
128
|
+
* Keep installed executable dependencies external to preserve runtime identity.
|
|
129
|
+
* Source requiring compilation is bundled even when pnpm injects a workspace
|
|
130
|
+
* package into node_modules: Node does not strip types inside that directory.
|
|
132
131
|
*/
|
|
133
132
|
function externalizeInstalledDependencies(options: {
|
|
134
133
|
runtimeResolveDir: string;
|
|
@@ -167,7 +166,13 @@ function externalizeInstalledDependencies(options: {
|
|
|
167
166
|
const realPath = await fs.promises
|
|
168
167
|
.realpath(resolution.path)
|
|
169
168
|
.catch(() => resolution.path);
|
|
170
|
-
|
|
169
|
+
const loader = SOURCE_LOADERS.get(path.extname(realPath).toLowerCase());
|
|
170
|
+
if (
|
|
171
|
+
realPath.includes(`${path.sep}node_modules${path.sep}`) &&
|
|
172
|
+
loader !== 'ts' &&
|
|
173
|
+
loader !== 'tsx' &&
|
|
174
|
+
loader !== 'jsx'
|
|
175
|
+
) {
|
|
171
176
|
const runtimeResolution = await buildApi.resolve(args.path, {
|
|
172
177
|
importer: '',
|
|
173
178
|
kind: args.kind,
|