veryfront 0.1.80 → 0.1.82
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/esm/deno.js +1 -1
- package/esm/src/mcp/server.d.ts.map +1 -1
- package/esm/src/mcp/server.js +45 -9
- package/esm/src/proxy/handler.d.ts.map +1 -1
- package/esm/src/proxy/handler.js +54 -3
- package/esm/src/rendering/orchestrator/module-loader/index.d.ts.map +1 -1
- package/esm/src/rendering/orchestrator/module-loader/index.js +7 -20
- package/esm/src/transforms/esm/bundle-manifest.d.ts +3 -1
- package/esm/src/transforms/esm/bundle-manifest.d.ts.map +1 -1
- package/esm/src/transforms/esm/bundle-manifest.js +2 -2
- package/esm/src/transforms/esm/cached-bundle-validation.d.ts +9 -0
- package/esm/src/transforms/esm/cached-bundle-validation.d.ts.map +1 -0
- package/esm/src/transforms/esm/cached-bundle-validation.js +25 -0
- package/esm/src/transforms/mdx/esm-module-loader/module-fetcher/distributed-cache.d.ts.map +1 -1
- package/esm/src/transforms/mdx/esm-module-loader/module-fetcher/distributed-cache.js +9 -21
- package/esm/src/transforms/pipeline/index.d.ts.map +1 -1
- package/esm/src/transforms/pipeline/index.js +8 -24
- package/package.json +1 -1
- package/src/deno.js +1 -1
- package/src/src/mcp/server.ts +57 -9
- package/src/src/proxy/handler.ts +63 -2
- package/src/src/rendering/orchestrator/module-loader/index.ts +11 -19
- package/src/src/transforms/esm/bundle-manifest.ts +6 -3
- package/src/src/transforms/esm/cached-bundle-validation.ts +40 -0
- package/src/src/transforms/mdx/esm-module-loader/module-fetcher/distributed-cache.ts +13 -24
- package/src/src/transforms/pipeline/index.ts +8 -26
package/esm/deno.js
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../../src/src/mcp/server.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,OAAO,MAAM,qBAAqB,CAAC;AAG/C,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAI7D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAMlD,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,0BAA0B,CAAC;
|
|
1
|
+
{"version":3,"file":"server.d.ts","sourceRoot":"","sources":["../../../src/src/mcp/server.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,OAAO,MAAM,qBAAqB,CAAC;AAG/C,OAAO,KAAK,EAAE,oBAAoB,EAAE,MAAM,kBAAkB,CAAC;AAI7D,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAMlD,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,0BAA0B,CAAC;AAOzE,KAAK,aAAa,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAAG,OAAO,EAAE,CAAC;AAOzD,UAAU,cAAc;IACtB,OAAO,EAAE,KAAK,CAAC;IACf,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,CAAC,EAAE,aAAa,CAAC;CACxB;AAED,UAAU,eAAe;IACvB,OAAO,EAAE,KAAK,CAAC;IACf,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,CAAC;IACrB,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,KAAK,CAAC,EAAE;QACN,IAAI,EAAE,MAAM,CAAC;QACb,OAAO,EAAE,MAAM,CAAC;QAChB,IAAI,CAAC,EAAE,OAAO,CAAC;KAChB,CAAC;CACH;AAED,MAAM,WAAW,uBAAuB;IACtC,YAAY,EAAE,MAAM,CAAC,MAAM,EAAE,wBAAwB,GAAG,SAAS,CAAC,CAAC;IACnE,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,qBAAa,SAAS;IACpB,OAAO,CAAC,MAAM,CAAkB;IAChC,OAAO,CAAC,iBAAiB,CAAC,CAA0B;IACpD,OAAO,CAAC,kBAAkB,CAAS;gBAEvB,MAAM,EAAE,eAAe;IAQnC;;;OAGG;IACH,oBAAoB,CAAC,MAAM,EAAE,uBAAuB,GAAG,IAAI;IAK3D,aAAa,CAAC,OAAO,EAAE,cAAc,EAAE,OAAO,CAAC,EAAE,oBAAoB,GAAG,OAAO,CAAC,eAAe,CAAC;IAsBhG,OAAO,CAAC,QAAQ;IA8BhB,OAAO,CAAC,UAAU;YAYJ,SAAS;IA2BvB,OAAO,CAAC,QAAQ;IAmChB,OAAO,CAAC,aAAa;IAgBrB,OAAO,CAAC,YAAY;IA6CpB,OAAO,CAAC,WAAW;IAcnB,OAAO,CAAC,SAAS;IAuCjB,iBAAiB,IAAI,CAAC,OAAO,EAAE,OAAO,CAAC,OAAO,KAAK,OAAO,CAAC,OAAO,CAAC,QAAQ,CAAC;IA+E5E,OAAO,CAAC,qBAAqB;YAkBf,YAAY;IAoB1B,OAAO,CAAC,UAAU;IAIlB,OAAO,CAAC,cAAc;YAqBR,oBAAoB;CA0BnC;AAED,wBAAgB,eAAe,CAAC,MAAM,EAAE,eAAe,GAAG,SAAS,CAElE"}
|
package/esm/src/mcp/server.js
CHANGED
|
@@ -9,6 +9,9 @@ import { withSpan } from "../observability/tracing/otlp-setup.js";
|
|
|
9
9
|
import { VERSION } from "../utils/version.js";
|
|
10
10
|
import { validateContentType } from "../security/input-validation/limits.js";
|
|
11
11
|
import { VeryfrontError } from "../security/input-validation/errors.js";
|
|
12
|
+
import { logger as baseLogger } from "../utils/index.js";
|
|
13
|
+
const logger = baseLogger.component("mcp-server");
|
|
14
|
+
const MAX_REQUEST_BODY_SIZE = 1_048_576; // 1 MB
|
|
12
15
|
function asParamsRecord(params) {
|
|
13
16
|
if (!params || Array.isArray(params))
|
|
14
17
|
return {};
|
|
@@ -20,6 +23,9 @@ export class MCPServer {
|
|
|
20
23
|
integrationsLoaded = false;
|
|
21
24
|
constructor(config) {
|
|
22
25
|
this.config = config;
|
|
26
|
+
if (!config.auth || config.auth.type === "none") {
|
|
27
|
+
logger.warn("MCP server has no authentication configured — all requests will be accepted");
|
|
28
|
+
}
|
|
23
29
|
}
|
|
24
30
|
/**
|
|
25
31
|
* Configure integration tools to be lazily loaded on first tools/list call.
|
|
@@ -207,13 +213,23 @@ export class MCPServer {
|
|
|
207
213
|
}
|
|
208
214
|
createHTTPHandler() {
|
|
209
215
|
return async (request) => {
|
|
216
|
+
const requestOrigin = request.headers.get("Origin");
|
|
210
217
|
if (request.method === "OPTIONS")
|
|
211
|
-
return this.handleCORS();
|
|
218
|
+
return this.handleCORS(requestOrigin);
|
|
212
219
|
if (this.config.auth?.type && this.config.auth.type !== "none") {
|
|
213
220
|
const authorized = await this.validateAuth(request);
|
|
214
221
|
if (!authorized)
|
|
215
222
|
return new dntShim.Response("Unauthorized", { status: 401 });
|
|
216
223
|
}
|
|
224
|
+
// Enforce request body size limit (fast path via Content-Length header)
|
|
225
|
+
const contentLength = request.headers.get("content-length");
|
|
226
|
+
if (contentLength && Number(contentLength) > MAX_REQUEST_BODY_SIZE) {
|
|
227
|
+
return new dntShim.Response(JSON.stringify({
|
|
228
|
+
jsonrpc: "2.0",
|
|
229
|
+
id: null,
|
|
230
|
+
error: { code: -32600, message: "Request body too large" },
|
|
231
|
+
}), { status: 413, headers: { "Content-Type": "application/json" } });
|
|
232
|
+
}
|
|
217
233
|
try {
|
|
218
234
|
validateContentType(request, "application/json");
|
|
219
235
|
}
|
|
@@ -227,7 +243,15 @@ export class MCPServer {
|
|
|
227
243
|
}
|
|
228
244
|
let rpcRequest;
|
|
229
245
|
try {
|
|
230
|
-
|
|
246
|
+
const bodyText = await request.text();
|
|
247
|
+
if (bodyText.length > MAX_REQUEST_BODY_SIZE) {
|
|
248
|
+
return new dntShim.Response(JSON.stringify({
|
|
249
|
+
jsonrpc: "2.0",
|
|
250
|
+
id: null,
|
|
251
|
+
error: { code: -32600, message: "Request body too large" },
|
|
252
|
+
}), { status: 413, headers: { "Content-Type": "application/json" } });
|
|
253
|
+
}
|
|
254
|
+
rpcRequest = JSON.parse(bodyText);
|
|
231
255
|
}
|
|
232
256
|
catch (_) {
|
|
233
257
|
// expected: malformed JSON in request body
|
|
@@ -246,7 +270,7 @@ export class MCPServer {
|
|
|
246
270
|
return new dntShim.Response(JSON.stringify(rpcResponse), {
|
|
247
271
|
headers: {
|
|
248
272
|
"Content-Type": "application/json",
|
|
249
|
-
...this.getCORSHeaders(),
|
|
273
|
+
...this.getCORSHeaders(requestOrigin),
|
|
250
274
|
},
|
|
251
275
|
});
|
|
252
276
|
};
|
|
@@ -275,21 +299,33 @@ export class MCPServer {
|
|
|
275
299
|
if (auth.type !== "bearer")
|
|
276
300
|
return false;
|
|
277
301
|
const token = authHeader.replace("Bearer ", "");
|
|
278
|
-
|
|
302
|
+
// When bearer auth is configured without a validate function, reject all requests
|
|
303
|
+
if (!auth.validate) {
|
|
304
|
+
logger.warn("Bearer auth configured without validate function — rejecting request");
|
|
279
305
|
return false;
|
|
306
|
+
}
|
|
280
307
|
return await auth.validate(token);
|
|
281
308
|
}
|
|
282
|
-
handleCORS() {
|
|
283
|
-
return new dntShim.Response(null, { status: 204, headers: this.getCORSHeaders() });
|
|
309
|
+
handleCORS(requestOrigin) {
|
|
310
|
+
return new dntShim.Response(null, { status: 204, headers: this.getCORSHeaders(requestOrigin) });
|
|
284
311
|
}
|
|
285
|
-
getCORSHeaders() {
|
|
312
|
+
getCORSHeaders(requestOrigin) {
|
|
286
313
|
if (!this.config.cors?.enabled)
|
|
287
314
|
return {};
|
|
288
|
-
const
|
|
315
|
+
const origins = this.config.cors.origins;
|
|
316
|
+
if (!origins || origins.length === 0)
|
|
317
|
+
return {};
|
|
318
|
+
// Match request origin against the configured origins list
|
|
319
|
+
const matchedOrigin = requestOrigin && origins.includes(requestOrigin)
|
|
320
|
+
? requestOrigin
|
|
321
|
+
: undefined;
|
|
322
|
+
if (!matchedOrigin)
|
|
323
|
+
return {};
|
|
289
324
|
return {
|
|
290
|
-
"Access-Control-Allow-Origin":
|
|
325
|
+
"Access-Control-Allow-Origin": matchedOrigin,
|
|
291
326
|
"Access-Control-Allow-Methods": "POST, OPTIONS",
|
|
292
327
|
"Access-Control-Allow-Headers": "Content-Type, Authorization, X-End-User-Id, X-Project-Id",
|
|
328
|
+
"Vary": "Origin",
|
|
293
329
|
};
|
|
294
330
|
}
|
|
295
331
|
async loadIntegrationTools(config) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../../src/src/proxy/handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,OAAO,MAAM,qBAAqB,CAAC;AAE/C,OAAO,EAAE,KAAK,YAAY,EAAsB,MAAM,kCAAkC,CAAC;AACzF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAQnD,eAAO,MAAM,sBAAsB,0MAYzB,CAAC;
|
|
1
|
+
{"version":3,"file":"handler.d.ts","sourceRoot":"","sources":["../../../src/src/proxy/handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,OAAO,MAAM,qBAAqB,CAAC;AAE/C,OAAO,EAAE,KAAK,YAAY,EAAsB,MAAM,kCAAkC,CAAC;AACzF,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,kBAAkB,CAAC;AAQnD,eAAO,MAAM,sBAAsB,0MAYzB,CAAC;AAoGX,MAAM,WAAW,WAAW;IAC1B,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;IACxB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,sBAAsB,EAAE,MAAM,CAAC;IAC/B,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,aAAa,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACxC;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,WAAW,EAAE,SAAS,GAAG,YAAY,CAAC;IACtC,eAAe,EAAE,MAAM,CAAC;IACxB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,MAAM,CAAC;IACb,YAAY,EAAE,YAAY,CAAC;IAC3B,cAAc,EAAE,OAAO,CAAC;IACxB,KAAK,CAAC,EAAE;QACN,MAAM,EAAE,MAAM,CAAC;QACf,OAAO,EAAE,MAAM,CAAC;QAChB,IAAI,CAAC,EAAE,MAAM,CAAC;QACd,WAAW,CAAC,EAAE,MAAM,CAAC;KACtB,CAAC;CACH;AAED,MAAM,WAAW,WAAW;IAC1B,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;IAC9D,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;IAC7D,IAAI,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;IAC7D,KAAK,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,KAAK,EAAE,KAAK,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,IAAI,CAAC;CAC9E;AAED,MAAM,WAAW,mBAAmB;IAClC,MAAM,EAAE,WAAW,CAAC;IACpB,KAAK,CAAC,EAAE,UAAU,CAAC;IACnB,MAAM,CAAC,EAAE,WAAW,CAAC;CACtB;AA8ED,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,mBAAmB;0BAiL1B,OAAO,CAAC,OAAO,KAAG,OAAO,CAAC,YAAY,CAAC;0BA+NvC,OAAO,CAAC,OAAO,KAAG,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC;;;;;;;;0BArVrD,MAAM,EAAE;;EAmYpC;AAED,MAAM,MAAM,YAAY,GAAG,UAAU,CAAC,OAAO,kBAAkB,CAAC,CAAC;AAEjE,wBAAgB,oBAAoB,CAAC,GAAG,EAAE,OAAO,CAAC,OAAO,EAAE,GAAG,EAAE,YAAY,GAAG,OAAO,CAAC,OAAO,CAwB7F"}
|
package/esm/src/proxy/handler.js
CHANGED
|
@@ -6,7 +6,7 @@ import { cwd, getEnv } from "../platform/compat/process.js";
|
|
|
6
6
|
import { join } from "../platform/compat/path/index.js";
|
|
7
7
|
import { injectContext, ProxySpanNames, withSpan } from "./tracing.js";
|
|
8
8
|
import { computeContentSourceId } from "../cache/keys.js";
|
|
9
|
-
import { jwtVerify } from "jose";
|
|
9
|
+
import { createRemoteJWKSet, decodeProtectedHeader, jwtVerify } from "jose";
|
|
10
10
|
export const INTERNAL_PROXY_HEADERS = [
|
|
11
11
|
"x-token",
|
|
12
12
|
"x-project-slug",
|
|
@@ -20,6 +20,26 @@ export const INTERNAL_PROXY_HEADERS = [
|
|
|
20
20
|
"x-branch-id",
|
|
21
21
|
"x-branch-name",
|
|
22
22
|
];
|
|
23
|
+
const remoteJwksByUrl = new Map();
|
|
24
|
+
function getApiJwks(apiBaseUrl, logger) {
|
|
25
|
+
try {
|
|
26
|
+
const normalizedBaseUrl = apiBaseUrl.endsWith("/") ? apiBaseUrl : `${apiBaseUrl}/`;
|
|
27
|
+
const jwksUrl = new URL(".well-known/jwks.json", normalizedBaseUrl);
|
|
28
|
+
const cacheKey = jwksUrl.toString();
|
|
29
|
+
let jwks = remoteJwksByUrl.get(cacheKey);
|
|
30
|
+
if (!jwks) {
|
|
31
|
+
jwks = createRemoteJWKSet(jwksUrl);
|
|
32
|
+
remoteJwksByUrl.set(cacheKey, jwks);
|
|
33
|
+
}
|
|
34
|
+
return jwks;
|
|
35
|
+
}
|
|
36
|
+
catch (error) {
|
|
37
|
+
logger?.error("Invalid API base URL for JWKS lookup", error, {
|
|
38
|
+
apiBaseUrl,
|
|
39
|
+
});
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
23
43
|
async function lookupProjectByDomain(domain, apiBaseUrl, token, logger) {
|
|
24
44
|
return withSpan(ProxySpanNames.PROXY_DOMAIN_LOOKUP, async () => {
|
|
25
45
|
const domainWithoutPort = domain.replace(/:\d+$/, "");
|
|
@@ -70,7 +90,38 @@ function extractUserToken(cookieHeader) {
|
|
|
70
90
|
const match = cookieHeader.match(/(?:^|;\s*)authToken=([^;]+)/);
|
|
71
91
|
return match?.[1] ? decodeURIComponent(match[1]) : undefined;
|
|
72
92
|
}
|
|
73
|
-
async function extractUserIdFromToken(token, log) {
|
|
93
|
+
async function extractUserIdFromToken(token, apiBaseUrl, log) {
|
|
94
|
+
let algorithm;
|
|
95
|
+
try {
|
|
96
|
+
algorithm = decodeProtectedHeader(token).alg;
|
|
97
|
+
}
|
|
98
|
+
catch (error) {
|
|
99
|
+
log?.debug("Failed to decode JWT header", {
|
|
100
|
+
error: error instanceof Error ? error.message : String(error),
|
|
101
|
+
});
|
|
102
|
+
return undefined;
|
|
103
|
+
}
|
|
104
|
+
if (algorithm === "RS256") {
|
|
105
|
+
const jwks = getApiJwks(apiBaseUrl, log);
|
|
106
|
+
if (!jwks)
|
|
107
|
+
return undefined;
|
|
108
|
+
try {
|
|
109
|
+
const { payload } = await jwtVerify(token, jwks, {
|
|
110
|
+
algorithms: ["RS256"],
|
|
111
|
+
});
|
|
112
|
+
return payload.userId;
|
|
113
|
+
}
|
|
114
|
+
catch (error) {
|
|
115
|
+
log?.debug("RS256 JWT verification failed", {
|
|
116
|
+
error: error instanceof Error ? error.message : String(error),
|
|
117
|
+
});
|
|
118
|
+
return undefined;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
if (algorithm !== "HS256") {
|
|
122
|
+
log?.debug("Unsupported JWT algorithm", { algorithm: algorithm ?? null });
|
|
123
|
+
return undefined;
|
|
124
|
+
}
|
|
74
125
|
const jwtSecret = getEnv("JWT_SECRET");
|
|
75
126
|
if (!jwtSecret) {
|
|
76
127
|
log?.warn("JWT_SECRET not configured — cannot verify user token");
|
|
@@ -184,7 +235,7 @@ export function createProxyHandler(options) {
|
|
|
184
235
|
});
|
|
185
236
|
return { status: 302, message: "Authentication required", redirectUrl };
|
|
186
237
|
}
|
|
187
|
-
const userId = await extractUserIdFromToken(userToken, logger);
|
|
238
|
+
const userId = await extractUserIdFromToken(userToken, config.apiBaseUrl, logger);
|
|
188
239
|
if (!userId) {
|
|
189
240
|
const redirectUrl = makeAuthRedirectUrl(req);
|
|
190
241
|
logger?.info("Could not extract userId from token", {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/src/rendering/orchestrator/module-loader/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../../src/src/rendering/orchestrator/module-loader/index.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,oCAAoC,CAAC;AA2BzE,OAAO,EAAE,cAAc,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC7E,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAkJpE,wBAAsB,uBAAuB,CAC3C,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,MAAM,EACd,YAAY,EAAE,cAAc,EAC5B,MAAM,EAAE,kBAAkB,EAC1B,eAAe,UAAQ,GACtB,OAAO,CAAC,MAAM,CAAC,CA4RjB;AAED,MAAM,WAAW,kBAAkB;IACjC,UAAU,EAAE,MAAM,CAAC;IACnB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,eAAe,CAAC,EAAE,MAAM,CAAC;IACzB,OAAO,EAAE,cAAc,CAAC;IACxB,IAAI,EAAE,aAAa,GAAG,YAAY,CAAC;IACnC,WAAW,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IACjC,QAAQ,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC9B,yDAAyD;IACzD,YAAY,CAAC,EAAE,MAAM,CAAC;CACvB;AAyBD;;;;;;GAMG;AACH,wBAAsB,UAAU,CAC9B,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,kBAAkB,GACzB,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAqClC"}
|
|
@@ -14,14 +14,12 @@ import { createFileSystem } from "../../../platform/compat/fs.js";
|
|
|
14
14
|
import { getProjectTmpDir } from "../../../modules/react-loader/index.js";
|
|
15
15
|
import { generateCacheKey as generateTransformCacheKey, getOrComputeTransform, initializeTransformCache, setCachedTransformAsync, } from "../../../transforms/esm/transform-cache.js";
|
|
16
16
|
import { TRANSFORM_DISTRIBUTED_TTL_SEC } from "../../../utils/constants/cache.js";
|
|
17
|
-
import {
|
|
18
|
-
import { validateBundleGroup } from "../../../transforms/esm/bundle-manifest.js";
|
|
17
|
+
import { validateCachedBundlesByManifestOrCode } from "../../../transforms/esm/cached-bundle-validation.js";
|
|
19
18
|
import { getHttpBundleCacheDir, getMdxEsmCacheDir } from "../../../utils/cache-dir.js";
|
|
20
19
|
import { dirname, join, normalize } from "../../../platform/compat/path/index.js";
|
|
21
20
|
import { hashCodeHex } from "../../../utils/hash-utils.js";
|
|
22
21
|
import { VERSION } from "../../../utils/version.js";
|
|
23
22
|
import { getModulePathCache, lookupMdxEsmCache, saveModulePathCache, } from "../../../transforms/mdx/esm-module-loader/cache/index.js";
|
|
24
|
-
import { extractHttpBundlePaths } from "../../../modules/react-loader/ssr-module-loader/http-bundle-helpers.js";
|
|
25
23
|
const logger = rendererLogger.component("module-loader");
|
|
26
24
|
// Re-export utilities
|
|
27
25
|
export { createEsmCache, createModuleCache, generateHash } from "./cache.js";
|
|
@@ -243,30 +241,19 @@ export async function transformModuleWithDeps(filePath, tmpDir, localAdapter, co
|
|
|
243
241
|
let transformedCode = transformResult.code;
|
|
244
242
|
const cacheDir = getHttpBundleCacheDir();
|
|
245
243
|
let bundlesValid = true;
|
|
246
|
-
if (transformResult.cacheHit
|
|
247
|
-
const validation = await
|
|
244
|
+
if (transformResult.cacheHit) {
|
|
245
|
+
const validation = await validateCachedBundlesByManifestOrCode(transformedCode, transformResult.bundleManifestId, cacheDir);
|
|
248
246
|
if (!validation.valid) {
|
|
249
|
-
logger.warn("
|
|
247
|
+
logger.warn("Cached HTTP bundle validation failed, re-transforming", {
|
|
250
248
|
filePath,
|
|
251
|
-
manifestId: transformResult.bundleManifestId
|
|
249
|
+
manifestId: transformResult.bundleManifestId?.slice(0, 12),
|
|
252
250
|
failedHashes: validation.failedHashes,
|
|
251
|
+
reason: validation.reason,
|
|
252
|
+
source: validation.source,
|
|
253
253
|
});
|
|
254
254
|
bundlesValid = false;
|
|
255
255
|
}
|
|
256
256
|
}
|
|
257
|
-
else {
|
|
258
|
-
const bundlePaths = extractHttpBundlePaths(transformedCode);
|
|
259
|
-
if (bundlePaths.length > 0) {
|
|
260
|
-
const failed = await ensureHttpBundlesExist(bundlePaths, cacheDir);
|
|
261
|
-
if (failed.length > 0) {
|
|
262
|
-
logger.warn("HTTP bundle recovery failed, re-transforming", {
|
|
263
|
-
filePath,
|
|
264
|
-
failed,
|
|
265
|
-
});
|
|
266
|
-
bundlesValid = false;
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
}
|
|
270
257
|
if (!bundlesValid) {
|
|
271
258
|
transformedCode = await transformToESM(fileContent, filePath, projectDir, adapter, {
|
|
272
259
|
projectId: effectiveProjectId,
|
|
@@ -20,10 +20,12 @@ interface BundleManifest {
|
|
|
20
20
|
createdAt: number;
|
|
21
21
|
ttlSeconds: number;
|
|
22
22
|
}
|
|
23
|
+
export type ManifestValidationReason = "manifest_missing" | "bundle_missing";
|
|
23
24
|
/** Result of manifest validation. */
|
|
24
|
-
interface ManifestValidationResult {
|
|
25
|
+
export interface ManifestValidationResult {
|
|
25
26
|
valid: boolean;
|
|
26
27
|
failedHashes: string[];
|
|
28
|
+
reason?: ManifestValidationReason;
|
|
27
29
|
}
|
|
28
30
|
/**
|
|
29
31
|
* Compute a deterministic manifest ID from bundle hashes.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bundle-manifest.d.ts","sourceRoot":"","sources":["../../../../src/src/transforms/esm/bundle-manifest.ts"],"names":[],"mappings":"AAAA;;;;;;;;4BAQ4B;AAiB5B,gDAAgD;AAChD,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,oEAAoE;AACpE,UAAU,cAAc;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,WAAW,EAAE,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,qCAAqC;AACrC,
|
|
1
|
+
{"version":3,"file":"bundle-manifest.d.ts","sourceRoot":"","sources":["../../../../src/src/transforms/esm/bundle-manifest.ts"],"names":[],"mappings":"AAAA;;;;;;;;4BAQ4B;AAiB5B,gDAAgD;AAChD,MAAM,WAAW,WAAW;IAC1B,IAAI,EAAE,MAAM,CAAC;IACb,GAAG,EAAE,MAAM,CAAC;IACZ,SAAS,EAAE,MAAM,CAAC;CACnB;AAED,oEAAoE;AACpE,UAAU,cAAc;IACtB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,WAAW,EAAE,CAAC;IACvB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,MAAM,wBAAwB,GAAG,kBAAkB,GAAG,gBAAgB,CAAC;AAE7E,qCAAqC;AACrC,MAAM,WAAW,wBAAwB;IACvC,KAAK,EAAE,OAAO,CAAC;IACf,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,MAAM,CAAC,EAAE,wBAAwB,CAAC;CACnC;AAgBD;;;GAGG;AACH,wBAAsB,iBAAiB,CAAC,MAAM,EAAE,MAAM,EAAE,GAAG,OAAO,CAAC,MAAM,CAAC,CAEzE;AAED;;GAEG;AACH,wBAAsB,oBAAoB,CAAC,OAAO,EAAE,WAAW,EAAE,GAAG,OAAO,CAAC,cAAc,CAAC,CAc1F;AAED;;GAEG;AACH,wBAAsB,mBAAmB,CAAC,QAAQ,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAqBjF;AAuBD;;;;;;GAMG;AACH,wBAAsB,mBAAmB,CACvC,UAAU,EAAE,MAAM,EAClB,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,wBAAwB,CAAC,CA6CnC;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,IAAI,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAErE;AAED;;GAEG;AACH,wBAAsB,kBAAkB,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAgB1E"}
|
|
@@ -106,7 +106,7 @@ export async function validateBundleGroup(manifestId, cacheDir) {
|
|
|
106
106
|
logger.debug(`${LOG_PREFIX} Manifest not found in distributed cache`, {
|
|
107
107
|
manifestId: manifestId.slice(0, 12),
|
|
108
108
|
});
|
|
109
|
-
return { valid: false, failedHashes: [] };
|
|
109
|
+
return { valid: false, failedHashes: [], reason: "manifest_missing" };
|
|
110
110
|
}
|
|
111
111
|
const missingBundles = [];
|
|
112
112
|
await Promise.all(manifest.bundles.map(async ({ hash }) => {
|
|
@@ -132,7 +132,7 @@ export async function validateBundleGroup(manifestId, cacheDir) {
|
|
|
132
132
|
manifestId: manifestId.slice(0, 12),
|
|
133
133
|
unrecoverable: unrecoverableHashes,
|
|
134
134
|
});
|
|
135
|
-
return { valid: false, failedHashes: unrecoverableHashes };
|
|
135
|
+
return { valid: false, failedHashes: unrecoverableHashes, reason: "bundle_missing" };
|
|
136
136
|
}
|
|
137
137
|
logger.info(`${LOG_PREFIX} All missing bundles recovered successfully`, {
|
|
138
138
|
manifestId: manifestId.slice(0, 12),
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { type ManifestValidationReason } from "./bundle-manifest.js";
|
|
2
|
+
export interface CachedBundleValidationResult {
|
|
3
|
+
valid: boolean;
|
|
4
|
+
failedHashes: string[];
|
|
5
|
+
reason?: ManifestValidationReason;
|
|
6
|
+
source: "manifest" | "code";
|
|
7
|
+
}
|
|
8
|
+
export declare function validateCachedBundlesByManifestOrCode(code: string, bundleManifestId: string | undefined, cacheDir: string): Promise<CachedBundleValidationResult>;
|
|
9
|
+
//# sourceMappingURL=cached-bundle-validation.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"cached-bundle-validation.d.ts","sourceRoot":"","sources":["../../../../src/src/transforms/esm/cached-bundle-validation.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,KAAK,wBAAwB,EAAuB,MAAM,sBAAsB,CAAC;AAE1F,MAAM,WAAW,4BAA4B;IAC3C,KAAK,EAAE,OAAO,CAAC;IACf,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,MAAM,CAAC,EAAE,wBAAwB,CAAC;IAClC,MAAM,EAAE,UAAU,GAAG,MAAM,CAAC;CAC7B;AAED,wBAAsB,qCAAqC,CACzD,IAAI,EAAE,MAAM,EACZ,gBAAgB,EAAE,MAAM,GAAG,SAAS,EACpC,QAAQ,EAAE,MAAM,GACf,OAAO,CAAC,4BAA4B,CAAC,CAwBvC"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { extractHttpBundlePaths } from "../../modules/react-loader/ssr-module-loader/http-bundle-helpers.js";
|
|
2
|
+
import { ensureHttpBundlesExist } from "./http-cache.js";
|
|
3
|
+
import { validateBundleGroup } from "./bundle-manifest.js";
|
|
4
|
+
export async function validateCachedBundlesByManifestOrCode(code, bundleManifestId, cacheDir) {
|
|
5
|
+
if (bundleManifestId) {
|
|
6
|
+
const validation = await validateBundleGroup(bundleManifestId, cacheDir);
|
|
7
|
+
if (validation.valid || validation.reason === "bundle_missing") {
|
|
8
|
+
return { ...validation, source: "manifest" };
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
const bundlePaths = extractHttpBundlePaths(code);
|
|
12
|
+
if (bundlePaths.length === 0) {
|
|
13
|
+
return { valid: true, failedHashes: [], source: "code" };
|
|
14
|
+
}
|
|
15
|
+
const failedHashes = await ensureHttpBundlesExist(bundlePaths, cacheDir);
|
|
16
|
+
if (failedHashes.length === 0) {
|
|
17
|
+
return { valid: true, failedHashes: [], source: "code" };
|
|
18
|
+
}
|
|
19
|
+
return {
|
|
20
|
+
valid: false,
|
|
21
|
+
failedHashes,
|
|
22
|
+
reason: "bundle_missing",
|
|
23
|
+
source: "code",
|
|
24
|
+
};
|
|
25
|
+
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"distributed-cache.d.ts","sourceRoot":"","sources":["../../../../../../src/src/transforms/mdx/esm-module-loader/module-fetcher/distributed-cache.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,oCAAoC,CAAC;
|
|
1
|
+
{"version":3,"file":"distributed-cache.d.ts","sourceRoot":"","sources":["../../../../../../src/src/transforms/mdx/esm-module-loader/module-fetcher/distributed-cache.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,oCAAoC,CAAC;AASjE,OAAO,EAAE,8BAA8B,EAAE,MAAM,iCAAiC,CAAC;AAWjF,qDAAqD;AACrD,KAAK,gBAAgB,GAAG,WAAW,CAAC,OAAO,CAAC,UAAU,CAAC,OAAO,8BAA8B,CAAC,CAAC,CAAC,CAAC;AAEhG;;;;;GAKG;AACH,UAAU,0BAA0B;IAClC,4EAA4E;IAC5E,IAAI,EAAE,MAAM,GAAG,IAAI,CAAC;IACpB,4DAA4D;IAC5D,gBAAgB,EAAE,gBAAgB,CAAC;CACpC;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAsB,oBAAoB,CACxC,iBAAiB,EAAE,MAAM,EACzB,cAAc,EAAE,MAAM,EACtB,WAAW,EAAE,MAAM,EACnB,UAAU,EAAE,MAAM,EAClB,YAAY,EAAE,MAAM,GAAG,SAAS,EAChC,GAAG,EAAE,MAAM,GACV,OAAO,CAAC,0BAA0B,GAAG,IAAI,CAAC,CAoG5C;AAED;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CACnC,gBAAgB,EAAE,gBAAgB,EAClC,iBAAiB,EAAE,MAAM,EACzB,UAAU,EAAE,MAAM,EAClB,cAAc,EAAE,MAAM,EACtB,GAAG,EAAE,MAAM,GACV,IAAI,CAqCN"}
|
|
@@ -7,10 +7,11 @@
|
|
|
7
7
|
* @module transforms/mdx/esm-module-loader/module-fetcher/distributed-cache
|
|
8
8
|
*/
|
|
9
9
|
import { detokenizeAllCachePaths, tokenizeAllVeryFrontPaths } from "../../../../cache/index.js";
|
|
10
|
-
import { cacheHttpImportsToLocal
|
|
10
|
+
import { cacheHttpImportsToLocal } from "../../../esm/http-cache.js";
|
|
11
11
|
import { loadImportMap } from "../../../../modules/import-map/index.js";
|
|
12
12
|
import { extractHttpBundlePaths } from "../../../../modules/react-loader/ssr-module-loader/http-bundle-helpers.js";
|
|
13
|
-
import { createBundleManifest, storeBundleManifest
|
|
13
|
+
import { createBundleManifest, storeBundleManifest } from "../../../esm/bundle-manifest.js";
|
|
14
|
+
import { validateCachedBundlesByManifestOrCode } from "../../../esm/cached-bundle-validation.js";
|
|
14
15
|
import { getHttpBundleCacheDir } from "../../../../utils/cache-dir.js";
|
|
15
16
|
import { FRAMEWORK_ROOT, LOG_PREFIX_MDX_LOADER } from "../constants.js";
|
|
16
17
|
import { getDistributedTransformBackend } from "../../../esm/transform-cache.js";
|
|
@@ -50,33 +51,20 @@ export async function readDistributedCache(transformCacheKey, normalizedPath, pr
|
|
|
50
51
|
});
|
|
51
52
|
const bundleManifestKey = `${transformCacheKey}:bm`;
|
|
52
53
|
const manifestId = await distributedCache.get(bundleManifestKey).catch(() => null);
|
|
53
|
-
if (
|
|
54
|
+
if (moduleCode) {
|
|
54
55
|
const cacheDir = getHttpBundleCacheDir();
|
|
55
|
-
const validation = await
|
|
56
|
+
const validation = await validateCachedBundlesByManifestOrCode(moduleCode, manifestId ?? undefined, cacheDir);
|
|
56
57
|
if (!validation.valid) {
|
|
57
|
-
log.warn(`${LOG_PREFIX_MDX_LOADER}
|
|
58
|
+
log.warn(`${LOG_PREFIX_MDX_LOADER} Cached HTTP bundle validation failed`, {
|
|
58
59
|
normalizedPath,
|
|
59
|
-
manifestId: manifestId
|
|
60
|
+
manifestId: manifestId?.slice(0, 12),
|
|
60
61
|
failedHashes: validation.failedHashes,
|
|
62
|
+
reason: validation.reason,
|
|
63
|
+
source: validation.source,
|
|
61
64
|
});
|
|
62
65
|
moduleCode = null;
|
|
63
66
|
}
|
|
64
67
|
}
|
|
65
|
-
else {
|
|
66
|
-
// Use detokenized code for bundle path extraction
|
|
67
|
-
const bundlePaths = extractHttpBundlePaths(moduleCode);
|
|
68
|
-
if (bundlePaths.length > 0) {
|
|
69
|
-
const cacheDir = getHttpBundleCacheDir();
|
|
70
|
-
const failed = await ensureHttpBundlesExist(bundlePaths, cacheDir);
|
|
71
|
-
if (failed.length > 0) {
|
|
72
|
-
log.warn(`${LOG_PREFIX_MDX_LOADER} Some HTTP bundles could not be recovered`, {
|
|
73
|
-
normalizedPath,
|
|
74
|
-
failed,
|
|
75
|
-
});
|
|
76
|
-
moduleCode = null;
|
|
77
|
-
}
|
|
78
|
-
}
|
|
79
|
-
}
|
|
80
68
|
// Use detokenized code for framework path checks
|
|
81
69
|
if (moduleCode && await hasIncompatibleFrameworkPaths(moduleCode, log)) {
|
|
82
70
|
log.warn(`${LOG_PREFIX_MDX_LOADER} Cached code has incompatible framework paths`, {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/src/transforms/pipeline/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAYH,OAAO,KAAK,EACV,cAAc,EACd,gBAAgB,EAEhB,eAAe,EAChB,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../../src/src/transforms/pipeline/index.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAYH,OAAO,KAAK,EACV,cAAc,EACd,gBAAgB,EAEhB,eAAe,EAChB,MAAM,YAAY,CAAC;AA0HpB,wBAAgB,WAAW,CACzB,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,gBAAgB,EACzB,MAAM,CAAC,EAAE,cAAc,GACtB,OAAO,CAAC,eAAe,CAAC,CAoI1B;AAoBD,wBAAsB,cAAc,CAClC,MAAM,EAAE,MAAM,EACd,QAAQ,EAAE,MAAM,EAChB,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,OAAO,EAChB,OAAO,EAAE,gBAAgB,GACxB,OAAO,CAAC,MAAM,CAAC,CASjB;AAmCD,YAAY,EACV,cAAc,EACd,gBAAgB,EAChB,gBAAgB,EAChB,eAAe,EACf,eAAe,EACf,eAAe,GAChB,MAAM,YAAY,CAAC;AAEpB,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAE5C,OAAO,EACL,sBAAsB,EACtB,0BAA0B,EAC1B,SAAS,EACT,KAAK,EACL,KAAK,EACL,YAAY,GACb,MAAM,cAAc,CAAC"}
|
|
@@ -11,10 +11,8 @@ import { computeConfigHash } from "../../cache/config-hash.js";
|
|
|
11
11
|
import { computeDepsHash } from "../../cache/dependency-graph.js";
|
|
12
12
|
import { compilePlugin, cssStripPlugin, finalizePlugin, parsePlugin, resolveImportsPlugin, ssrHttpCachePlugin, ssrHttpStubPlugin, ssrVfModulesPlugin, } from "./stages/index.js";
|
|
13
13
|
import { createFileSystem, exists } from "../../platform/compat/fs.js";
|
|
14
|
-
import { ensureHttpBundlesExist } from "../esm/http-cache.js";
|
|
15
|
-
import { extractHttpBundlePaths } from "../../modules/react-loader/ssr-module-loader/http-bundle-helpers.js";
|
|
16
14
|
import { getHttpBundleCacheDir } from "../../utils/cache-dir.js";
|
|
17
|
-
import {
|
|
15
|
+
import { validateCachedBundlesByManifestOrCode } from "../esm/cached-bundle-validation.js";
|
|
18
16
|
import { extractFrameworkBundlePaths } from "../shared/framework-bundle-paths.js";
|
|
19
17
|
const SSR_PIPELINE = [
|
|
20
18
|
parsePlugin,
|
|
@@ -96,29 +94,15 @@ async function validateFrameworkBundles(code, cacheKey) {
|
|
|
96
94
|
*/
|
|
97
95
|
async function validateCachedBundles(code, bundleManifestId, cacheKey) {
|
|
98
96
|
const cacheDir = getHttpBundleCacheDir();
|
|
99
|
-
|
|
100
|
-
if (
|
|
101
|
-
const validation = await validateBundleGroup(bundleManifestId, cacheDir);
|
|
102
|
-
if (validation.valid)
|
|
103
|
-
return true;
|
|
104
|
-
logger.debug("Bundle manifest validation failed", {
|
|
105
|
-
cacheKey: cacheKey.slice(-40),
|
|
106
|
-
manifestId: bundleManifestId.slice(0, 12),
|
|
107
|
-
failedCount: validation.failedHashes.length,
|
|
108
|
-
});
|
|
109
|
-
return false;
|
|
110
|
-
}
|
|
111
|
-
// Fall back to extracting bundle paths from code and validating each
|
|
112
|
-
const bundlePaths = extractHttpBundlePaths(code);
|
|
113
|
-
if (bundlePaths.length === 0)
|
|
114
|
-
return true;
|
|
115
|
-
const failed = await ensureHttpBundlesExist(bundlePaths, cacheDir);
|
|
116
|
-
if (failed.length === 0)
|
|
97
|
+
const validation = await validateCachedBundlesByManifestOrCode(code, bundleManifestId, cacheDir);
|
|
98
|
+
if (validation.valid)
|
|
117
99
|
return true;
|
|
118
|
-
logger.debug("HTTP bundle validation failed", {
|
|
100
|
+
logger.debug("Cached HTTP bundle validation failed", {
|
|
119
101
|
cacheKey: cacheKey.slice(-40),
|
|
120
|
-
|
|
121
|
-
|
|
102
|
+
manifestId: bundleManifestId?.slice(0, 12),
|
|
103
|
+
failedCount: validation.failedHashes.length,
|
|
104
|
+
reason: validation.reason,
|
|
105
|
+
source: validation.source,
|
|
122
106
|
});
|
|
123
107
|
return false;
|
|
124
108
|
}
|
package/package.json
CHANGED
package/src/deno.js
CHANGED
package/src/src/mcp/server.ts
CHANGED
|
@@ -12,6 +12,11 @@ import { VERSION } from "../utils/version.js";
|
|
|
12
12
|
import { validateContentType } from "../security/input-validation/limits.js";
|
|
13
13
|
import { VeryfrontError } from "../security/input-validation/errors.js";
|
|
14
14
|
import type { IntegrationRuntimeConfig } from "../integrations/types.js";
|
|
15
|
+
import { logger as baseLogger } from "../utils/index.js";
|
|
16
|
+
|
|
17
|
+
const logger = baseLogger.component("mcp-server");
|
|
18
|
+
|
|
19
|
+
const MAX_REQUEST_BODY_SIZE = 1_048_576; // 1 MB
|
|
15
20
|
|
|
16
21
|
type JSONRPCParams = Record<string, unknown> | unknown[];
|
|
17
22
|
|
|
@@ -51,6 +56,10 @@ export class MCPServer {
|
|
|
51
56
|
|
|
52
57
|
constructor(config: MCPServerConfig) {
|
|
53
58
|
this.config = config;
|
|
59
|
+
|
|
60
|
+
if (!config.auth || config.auth.type === "none") {
|
|
61
|
+
logger.warn("MCP server has no authentication configured — all requests will be accepted");
|
|
62
|
+
}
|
|
54
63
|
}
|
|
55
64
|
|
|
56
65
|
/**
|
|
@@ -304,13 +313,27 @@ export class MCPServer {
|
|
|
304
313
|
|
|
305
314
|
createHTTPHandler(): (request: dntShim.Request) => Promise<dntShim.Response> {
|
|
306
315
|
return async (request: dntShim.Request) => {
|
|
307
|
-
|
|
316
|
+
const requestOrigin = request.headers.get("Origin");
|
|
317
|
+
if (request.method === "OPTIONS") return this.handleCORS(requestOrigin);
|
|
308
318
|
|
|
309
319
|
if (this.config.auth?.type && this.config.auth.type !== "none") {
|
|
310
320
|
const authorized = await this.validateAuth(request);
|
|
311
321
|
if (!authorized) return new dntShim.Response("Unauthorized", { status: 401 });
|
|
312
322
|
}
|
|
313
323
|
|
|
324
|
+
// Enforce request body size limit (fast path via Content-Length header)
|
|
325
|
+
const contentLength = request.headers.get("content-length");
|
|
326
|
+
if (contentLength && Number(contentLength) > MAX_REQUEST_BODY_SIZE) {
|
|
327
|
+
return new dntShim.Response(
|
|
328
|
+
JSON.stringify({
|
|
329
|
+
jsonrpc: "2.0",
|
|
330
|
+
id: null,
|
|
331
|
+
error: { code: -32600, message: "Request body too large" },
|
|
332
|
+
}),
|
|
333
|
+
{ status: 413, headers: { "Content-Type": "application/json" } },
|
|
334
|
+
);
|
|
335
|
+
}
|
|
336
|
+
|
|
314
337
|
try {
|
|
315
338
|
validateContentType(request, "application/json");
|
|
316
339
|
} catch (error) {
|
|
@@ -327,7 +350,18 @@ export class MCPServer {
|
|
|
327
350
|
|
|
328
351
|
let rpcRequest: JSONRPCRequest;
|
|
329
352
|
try {
|
|
330
|
-
|
|
353
|
+
const bodyText = await request.text();
|
|
354
|
+
if (bodyText.length > MAX_REQUEST_BODY_SIZE) {
|
|
355
|
+
return new dntShim.Response(
|
|
356
|
+
JSON.stringify({
|
|
357
|
+
jsonrpc: "2.0",
|
|
358
|
+
id: null,
|
|
359
|
+
error: { code: -32600, message: "Request body too large" },
|
|
360
|
+
}),
|
|
361
|
+
{ status: 413, headers: { "Content-Type": "application/json" } },
|
|
362
|
+
);
|
|
363
|
+
}
|
|
364
|
+
rpcRequest = JSON.parse(bodyText) as JSONRPCRequest;
|
|
331
365
|
} catch (_) {
|
|
332
366
|
// expected: malformed JSON in request body
|
|
333
367
|
return new dntShim.Response(
|
|
@@ -350,7 +384,7 @@ export class MCPServer {
|
|
|
350
384
|
return new dntShim.Response(JSON.stringify(rpcResponse), {
|
|
351
385
|
headers: {
|
|
352
386
|
"Content-Type": "application/json",
|
|
353
|
-
...this.getCORSHeaders(),
|
|
387
|
+
...this.getCORSHeaders(requestOrigin),
|
|
354
388
|
},
|
|
355
389
|
});
|
|
356
390
|
};
|
|
@@ -384,24 +418,38 @@ export class MCPServer {
|
|
|
384
418
|
if (auth.type !== "bearer") return false;
|
|
385
419
|
|
|
386
420
|
const token = authHeader.replace("Bearer ", "");
|
|
387
|
-
|
|
421
|
+
|
|
422
|
+
// When bearer auth is configured without a validate function, reject all requests
|
|
423
|
+
if (!auth.validate) {
|
|
424
|
+
logger.warn("Bearer auth configured without validate function — rejecting request");
|
|
425
|
+
return false;
|
|
426
|
+
}
|
|
388
427
|
|
|
389
428
|
return await auth.validate(token);
|
|
390
429
|
}
|
|
391
430
|
|
|
392
|
-
private handleCORS(): dntShim.Response {
|
|
393
|
-
return new dntShim.Response(null, { status: 204, headers: this.getCORSHeaders() });
|
|
431
|
+
private handleCORS(requestOrigin?: string | null): dntShim.Response {
|
|
432
|
+
return new dntShim.Response(null, { status: 204, headers: this.getCORSHeaders(requestOrigin) });
|
|
394
433
|
}
|
|
395
434
|
|
|
396
|
-
private getCORSHeaders(): Record<string, string> {
|
|
435
|
+
private getCORSHeaders(requestOrigin?: string | null): Record<string, string> {
|
|
397
436
|
if (!this.config.cors?.enabled) return {};
|
|
398
437
|
|
|
399
|
-
const
|
|
438
|
+
const origins = this.config.cors.origins;
|
|
439
|
+
if (!origins || origins.length === 0) return {};
|
|
440
|
+
|
|
441
|
+
// Match request origin against the configured origins list
|
|
442
|
+
const matchedOrigin = requestOrigin && origins.includes(requestOrigin)
|
|
443
|
+
? requestOrigin
|
|
444
|
+
: undefined;
|
|
445
|
+
|
|
446
|
+
if (!matchedOrigin) return {};
|
|
400
447
|
|
|
401
448
|
return {
|
|
402
|
-
"Access-Control-Allow-Origin":
|
|
449
|
+
"Access-Control-Allow-Origin": matchedOrigin,
|
|
403
450
|
"Access-Control-Allow-Methods": "POST, OPTIONS",
|
|
404
451
|
"Access-Control-Allow-Headers": "Content-Type, Authorization, X-End-User-Id, X-Project-Id",
|
|
452
|
+
"Vary": "Origin",
|
|
405
453
|
};
|
|
406
454
|
}
|
|
407
455
|
|
package/src/src/proxy/handler.ts
CHANGED
|
@@ -7,7 +7,7 @@ import { cwd, getEnv } from "../platform/compat/process.js";
|
|
|
7
7
|
import { join } from "../platform/compat/path/index.js";
|
|
8
8
|
import { injectContext, ProxySpanNames, withSpan } from "./tracing.js";
|
|
9
9
|
import { computeContentSourceId } from "../cache/keys.js";
|
|
10
|
-
import { jwtVerify } from "jose";
|
|
10
|
+
import { createRemoteJWKSet, decodeProtectedHeader, jwtVerify } from "jose";
|
|
11
11
|
|
|
12
12
|
export const INTERNAL_PROXY_HEADERS = [
|
|
13
13
|
"x-token",
|
|
@@ -37,6 +37,29 @@ interface DomainLookupResult {
|
|
|
37
37
|
}>;
|
|
38
38
|
}
|
|
39
39
|
|
|
40
|
+
const remoteJwksByUrl = new Map<string, ReturnType<typeof createRemoteJWKSet>>();
|
|
41
|
+
|
|
42
|
+
function getApiJwks(apiBaseUrl: string, logger?: ProxyLogger) {
|
|
43
|
+
try {
|
|
44
|
+
const normalizedBaseUrl = apiBaseUrl.endsWith("/") ? apiBaseUrl : `${apiBaseUrl}/`;
|
|
45
|
+
const jwksUrl = new URL(".well-known/jwks.json", normalizedBaseUrl);
|
|
46
|
+
const cacheKey = jwksUrl.toString();
|
|
47
|
+
let jwks = remoteJwksByUrl.get(cacheKey);
|
|
48
|
+
|
|
49
|
+
if (!jwks) {
|
|
50
|
+
jwks = createRemoteJWKSet(jwksUrl);
|
|
51
|
+
remoteJwksByUrl.set(cacheKey, jwks);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return jwks;
|
|
55
|
+
} catch (error) {
|
|
56
|
+
logger?.error("Invalid API base URL for JWKS lookup", error as Error, {
|
|
57
|
+
apiBaseUrl,
|
|
58
|
+
});
|
|
59
|
+
return undefined;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
40
63
|
async function lookupProjectByDomain(
|
|
41
64
|
domain: string,
|
|
42
65
|
apiBaseUrl: string,
|
|
@@ -154,8 +177,42 @@ function extractUserToken(cookieHeader: string): string | undefined {
|
|
|
154
177
|
|
|
155
178
|
async function extractUserIdFromToken(
|
|
156
179
|
token: string,
|
|
180
|
+
apiBaseUrl: string,
|
|
157
181
|
log?: ProxyLogger,
|
|
158
182
|
): Promise<string | undefined> {
|
|
183
|
+
let algorithm: string | undefined;
|
|
184
|
+
|
|
185
|
+
try {
|
|
186
|
+
algorithm = decodeProtectedHeader(token).alg;
|
|
187
|
+
} catch (error) {
|
|
188
|
+
log?.debug("Failed to decode JWT header", {
|
|
189
|
+
error: error instanceof Error ? error.message : String(error),
|
|
190
|
+
});
|
|
191
|
+
return undefined;
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
if (algorithm === "RS256") {
|
|
195
|
+
const jwks = getApiJwks(apiBaseUrl, log);
|
|
196
|
+
if (!jwks) return undefined;
|
|
197
|
+
|
|
198
|
+
try {
|
|
199
|
+
const { payload } = await jwtVerify(token, jwks, {
|
|
200
|
+
algorithms: ["RS256"],
|
|
201
|
+
});
|
|
202
|
+
return (payload as { userId?: string }).userId;
|
|
203
|
+
} catch (error) {
|
|
204
|
+
log?.debug("RS256 JWT verification failed", {
|
|
205
|
+
error: error instanceof Error ? error.message : String(error),
|
|
206
|
+
});
|
|
207
|
+
return undefined;
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
if (algorithm !== "HS256") {
|
|
212
|
+
log?.debug("Unsupported JWT algorithm", { algorithm: algorithm ?? null });
|
|
213
|
+
return undefined;
|
|
214
|
+
}
|
|
215
|
+
|
|
159
216
|
const jwtSecret = getEnv("JWT_SECRET");
|
|
160
217
|
|
|
161
218
|
if (!jwtSecret) {
|
|
@@ -304,7 +361,11 @@ export function createProxyHandler(options: ProxyHandlerOptions) {
|
|
|
304
361
|
return { status: 302, message: "Authentication required", redirectUrl };
|
|
305
362
|
}
|
|
306
363
|
|
|
307
|
-
const userId = await extractUserIdFromToken(
|
|
364
|
+
const userId = await extractUserIdFromToken(
|
|
365
|
+
userToken,
|
|
366
|
+
config.apiBaseUrl,
|
|
367
|
+
logger,
|
|
368
|
+
);
|
|
308
369
|
if (!userId) {
|
|
309
370
|
const redirectUrl = makeAuthRedirectUrl(req);
|
|
310
371
|
logger?.info("Could not extract userId from token", {
|
|
@@ -21,8 +21,7 @@ import {
|
|
|
21
21
|
setCachedTransformAsync,
|
|
22
22
|
} from "../../../transforms/esm/transform-cache.js";
|
|
23
23
|
import { TRANSFORM_DISTRIBUTED_TTL_SEC } from "../../../utils/constants/cache.js";
|
|
24
|
-
import {
|
|
25
|
-
import { validateBundleGroup } from "../../../transforms/esm/bundle-manifest.js";
|
|
24
|
+
import { validateCachedBundlesByManifestOrCode } from "../../../transforms/esm/cached-bundle-validation.js";
|
|
26
25
|
import { getHttpBundleCacheDir, getMdxEsmCacheDir } from "../../../utils/cache-dir.js";
|
|
27
26
|
import { dirname, join, normalize } from "../../../platform/compat/path/index.js";
|
|
28
27
|
import { hashCodeHex } from "../../../utils/hash-utils.js";
|
|
@@ -32,7 +31,6 @@ import {
|
|
|
32
31
|
lookupMdxEsmCache,
|
|
33
32
|
saveModulePathCache,
|
|
34
33
|
} from "../../../transforms/mdx/esm-module-loader/cache/index.js";
|
|
35
|
-
import { extractHttpBundlePaths } from "../../../modules/react-loader/ssr-module-loader/http-bundle-helpers.js";
|
|
36
34
|
|
|
37
35
|
const logger = rendererLogger.component("module-loader");
|
|
38
36
|
|
|
@@ -344,28 +342,22 @@ export async function transformModuleWithDeps(
|
|
|
344
342
|
const cacheDir = getHttpBundleCacheDir();
|
|
345
343
|
let bundlesValid = true;
|
|
346
344
|
|
|
347
|
-
if (transformResult.cacheHit
|
|
348
|
-
const validation = await
|
|
345
|
+
if (transformResult.cacheHit) {
|
|
346
|
+
const validation = await validateCachedBundlesByManifestOrCode(
|
|
347
|
+
transformedCode,
|
|
348
|
+
transformResult.bundleManifestId,
|
|
349
|
+
cacheDir,
|
|
350
|
+
);
|
|
349
351
|
if (!validation.valid) {
|
|
350
|
-
logger.warn("
|
|
352
|
+
logger.warn("Cached HTTP bundle validation failed, re-transforming", {
|
|
351
353
|
filePath,
|
|
352
|
-
manifestId: transformResult.bundleManifestId
|
|
354
|
+
manifestId: transformResult.bundleManifestId?.slice(0, 12),
|
|
353
355
|
failedHashes: validation.failedHashes,
|
|
356
|
+
reason: validation.reason,
|
|
357
|
+
source: validation.source,
|
|
354
358
|
});
|
|
355
359
|
bundlesValid = false;
|
|
356
360
|
}
|
|
357
|
-
} else {
|
|
358
|
-
const bundlePaths = extractHttpBundlePaths(transformedCode);
|
|
359
|
-
if (bundlePaths.length > 0) {
|
|
360
|
-
const failed = await ensureHttpBundlesExist(bundlePaths, cacheDir);
|
|
361
|
-
if (failed.length > 0) {
|
|
362
|
-
logger.warn("HTTP bundle recovery failed, re-transforming", {
|
|
363
|
-
filePath,
|
|
364
|
-
failed,
|
|
365
|
-
});
|
|
366
|
-
bundlesValid = false;
|
|
367
|
-
}
|
|
368
|
-
}
|
|
369
361
|
}
|
|
370
362
|
|
|
371
363
|
if (!bundlesValid) {
|
|
@@ -38,10 +38,13 @@ interface BundleManifest {
|
|
|
38
38
|
ttlSeconds: number;
|
|
39
39
|
}
|
|
40
40
|
|
|
41
|
+
export type ManifestValidationReason = "manifest_missing" | "bundle_missing";
|
|
42
|
+
|
|
41
43
|
/** Result of manifest validation. */
|
|
42
|
-
interface ManifestValidationResult {
|
|
44
|
+
export interface ManifestValidationResult {
|
|
43
45
|
valid: boolean;
|
|
44
46
|
failedHashes: string[];
|
|
47
|
+
reason?: ManifestValidationReason;
|
|
45
48
|
}
|
|
46
49
|
|
|
47
50
|
/**
|
|
@@ -148,7 +151,7 @@ export async function validateBundleGroup(
|
|
|
148
151
|
logger.debug(`${LOG_PREFIX} Manifest not found in distributed cache`, {
|
|
149
152
|
manifestId: manifestId.slice(0, 12),
|
|
150
153
|
});
|
|
151
|
-
return { valid: false, failedHashes: [] };
|
|
154
|
+
return { valid: false, failedHashes: [], reason: "manifest_missing" };
|
|
152
155
|
}
|
|
153
156
|
|
|
154
157
|
const missingBundles: Array<{ path: string; hash: string }> = [];
|
|
@@ -178,7 +181,7 @@ export async function validateBundleGroup(
|
|
|
178
181
|
manifestId: manifestId.slice(0, 12),
|
|
179
182
|
unrecoverable: unrecoverableHashes,
|
|
180
183
|
});
|
|
181
|
-
return { valid: false, failedHashes: unrecoverableHashes };
|
|
184
|
+
return { valid: false, failedHashes: unrecoverableHashes, reason: "bundle_missing" };
|
|
182
185
|
}
|
|
183
186
|
|
|
184
187
|
logger.info(`${LOG_PREFIX} All missing bundles recovered successfully`, {
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { extractHttpBundlePaths } from "../../modules/react-loader/ssr-module-loader/http-bundle-helpers.js";
|
|
2
|
+
import { ensureHttpBundlesExist } from "./http-cache.js";
|
|
3
|
+
import { type ManifestValidationReason, validateBundleGroup } from "./bundle-manifest.js";
|
|
4
|
+
|
|
5
|
+
export interface CachedBundleValidationResult {
|
|
6
|
+
valid: boolean;
|
|
7
|
+
failedHashes: string[];
|
|
8
|
+
reason?: ManifestValidationReason;
|
|
9
|
+
source: "manifest" | "code";
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
export async function validateCachedBundlesByManifestOrCode(
|
|
13
|
+
code: string,
|
|
14
|
+
bundleManifestId: string | undefined,
|
|
15
|
+
cacheDir: string,
|
|
16
|
+
): Promise<CachedBundleValidationResult> {
|
|
17
|
+
if (bundleManifestId) {
|
|
18
|
+
const validation = await validateBundleGroup(bundleManifestId, cacheDir);
|
|
19
|
+
if (validation.valid || validation.reason === "bundle_missing") {
|
|
20
|
+
return { ...validation, source: "manifest" };
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
const bundlePaths = extractHttpBundlePaths(code);
|
|
25
|
+
if (bundlePaths.length === 0) {
|
|
26
|
+
return { valid: true, failedHashes: [], source: "code" };
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
const failedHashes = await ensureHttpBundlesExist(bundlePaths, cacheDir);
|
|
30
|
+
if (failedHashes.length === 0) {
|
|
31
|
+
return { valid: true, failedHashes: [], source: "code" };
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
valid: false,
|
|
36
|
+
failedHashes,
|
|
37
|
+
reason: "bundle_missing",
|
|
38
|
+
source: "code",
|
|
39
|
+
};
|
|
40
|
+
}
|
|
@@ -9,14 +9,11 @@
|
|
|
9
9
|
|
|
10
10
|
import type { Logger } from "../../../../utils/logger/logger.js";
|
|
11
11
|
import { detokenizeAllCachePaths, tokenizeAllVeryFrontPaths } from "../../../../cache/index.js";
|
|
12
|
-
import { cacheHttpImportsToLocal
|
|
12
|
+
import { cacheHttpImportsToLocal } from "../../../esm/http-cache.js";
|
|
13
13
|
import { loadImportMap } from "../../../../modules/import-map/index.js";
|
|
14
14
|
import { extractHttpBundlePaths } from "../../../../modules/react-loader/ssr-module-loader/http-bundle-helpers.js";
|
|
15
|
-
import {
|
|
16
|
-
|
|
17
|
-
storeBundleManifest,
|
|
18
|
-
validateBundleGroup,
|
|
19
|
-
} from "../../../esm/bundle-manifest.js";
|
|
15
|
+
import { createBundleManifest, storeBundleManifest } from "../../../esm/bundle-manifest.js";
|
|
16
|
+
import { validateCachedBundlesByManifestOrCode } from "../../../esm/cached-bundle-validation.js";
|
|
20
17
|
import { getHttpBundleCacheDir } from "../../../../utils/cache-dir.js";
|
|
21
18
|
import { FRAMEWORK_ROOT, LOG_PREFIX_MDX_LOADER } from "../constants.js";
|
|
22
19
|
import { getDistributedTransformBackend } from "../../../esm/transform-cache.js";
|
|
@@ -86,31 +83,23 @@ export async function readDistributedCache(
|
|
|
86
83
|
const bundleManifestKey = `${transformCacheKey}:bm`;
|
|
87
84
|
const manifestId = await distributedCache.get(bundleManifestKey).catch(() => null);
|
|
88
85
|
|
|
89
|
-
if (
|
|
86
|
+
if (moduleCode) {
|
|
90
87
|
const cacheDir = getHttpBundleCacheDir();
|
|
91
|
-
const validation = await
|
|
88
|
+
const validation = await validateCachedBundlesByManifestOrCode(
|
|
89
|
+
moduleCode,
|
|
90
|
+
manifestId ?? undefined,
|
|
91
|
+
cacheDir,
|
|
92
|
+
);
|
|
92
93
|
if (!validation.valid) {
|
|
93
|
-
log.warn(`${LOG_PREFIX_MDX_LOADER}
|
|
94
|
+
log.warn(`${LOG_PREFIX_MDX_LOADER} Cached HTTP bundle validation failed`, {
|
|
94
95
|
normalizedPath,
|
|
95
|
-
manifestId: manifestId
|
|
96
|
+
manifestId: manifestId?.slice(0, 12),
|
|
96
97
|
failedHashes: validation.failedHashes,
|
|
98
|
+
reason: validation.reason,
|
|
99
|
+
source: validation.source,
|
|
97
100
|
});
|
|
98
101
|
moduleCode = null;
|
|
99
102
|
}
|
|
100
|
-
} else {
|
|
101
|
-
// Use detokenized code for bundle path extraction
|
|
102
|
-
const bundlePaths = extractHttpBundlePaths(moduleCode);
|
|
103
|
-
if (bundlePaths.length > 0) {
|
|
104
|
-
const cacheDir = getHttpBundleCacheDir();
|
|
105
|
-
const failed = await ensureHttpBundlesExist(bundlePaths, cacheDir);
|
|
106
|
-
if (failed.length > 0) {
|
|
107
|
-
log.warn(`${LOG_PREFIX_MDX_LOADER} Some HTTP bundles could not be recovered`, {
|
|
108
|
-
normalizedPath,
|
|
109
|
-
failed,
|
|
110
|
-
});
|
|
111
|
-
moduleCode = null;
|
|
112
|
-
}
|
|
113
|
-
}
|
|
114
103
|
}
|
|
115
104
|
|
|
116
105
|
// Use detokenized code for framework path checks
|
|
@@ -31,10 +31,8 @@ import {
|
|
|
31
31
|
ssrVfModulesPlugin,
|
|
32
32
|
} from "./stages/index.js";
|
|
33
33
|
import { createFileSystem, exists } from "../../platform/compat/fs.js";
|
|
34
|
-
import { ensureHttpBundlesExist } from "../esm/http-cache.js";
|
|
35
|
-
import { extractHttpBundlePaths } from "../../modules/react-loader/ssr-module-loader/http-bundle-helpers.js";
|
|
36
34
|
import { getHttpBundleCacheDir } from "../../utils/cache-dir.js";
|
|
37
|
-
import {
|
|
35
|
+
import { validateCachedBundlesByManifestOrCode } from "../esm/cached-bundle-validation.js";
|
|
38
36
|
import { extractFrameworkBundlePaths } from "../shared/framework-bundle-paths.js";
|
|
39
37
|
|
|
40
38
|
const SSR_PIPELINE: TransformPlugin[] = [
|
|
@@ -130,31 +128,15 @@ async function validateCachedBundles(
|
|
|
130
128
|
cacheKey: string,
|
|
131
129
|
): Promise<boolean> {
|
|
132
130
|
const cacheDir = getHttpBundleCacheDir();
|
|
131
|
+
const validation = await validateCachedBundlesByManifestOrCode(code, bundleManifestId, cacheDir);
|
|
132
|
+
if (validation.valid) return true;
|
|
133
133
|
|
|
134
|
-
|
|
135
|
-
if (bundleManifestId) {
|
|
136
|
-
const validation = await validateBundleGroup(bundleManifestId, cacheDir);
|
|
137
|
-
if (validation.valid) return true;
|
|
138
|
-
|
|
139
|
-
logger.debug("Bundle manifest validation failed", {
|
|
140
|
-
cacheKey: cacheKey.slice(-40),
|
|
141
|
-
manifestId: bundleManifestId.slice(0, 12),
|
|
142
|
-
failedCount: validation.failedHashes.length,
|
|
143
|
-
});
|
|
144
|
-
return false;
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
// Fall back to extracting bundle paths from code and validating each
|
|
148
|
-
const bundlePaths = extractHttpBundlePaths(code);
|
|
149
|
-
if (bundlePaths.length === 0) return true;
|
|
150
|
-
|
|
151
|
-
const failed = await ensureHttpBundlesExist(bundlePaths, cacheDir);
|
|
152
|
-
if (failed.length === 0) return true;
|
|
153
|
-
|
|
154
|
-
logger.debug("HTTP bundle validation failed", {
|
|
134
|
+
logger.debug("Cached HTTP bundle validation failed", {
|
|
155
135
|
cacheKey: cacheKey.slice(-40),
|
|
156
|
-
|
|
157
|
-
|
|
136
|
+
manifestId: bundleManifestId?.slice(0, 12),
|
|
137
|
+
failedCount: validation.failedHashes.length,
|
|
138
|
+
reason: validation.reason,
|
|
139
|
+
source: validation.source,
|
|
158
140
|
});
|
|
159
141
|
return false;
|
|
160
142
|
}
|