@mandujs/core 0.54.17 → 0.54.19
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/package.json +3 -1
- package/src/agent/__tests__/context.test.ts +94 -25
- package/src/agent/context.ts +17 -0
- package/src/agent/types.ts +32 -12
- package/src/agent/verify.ts +55 -24
- package/src/bundler/__snapshots__/build.test.ts.snap +5 -0
- package/src/bundler/__tests__/build-runner.ts +130 -17
- package/src/bundler/__tests__/client-boundary-transform.test.ts +524 -0
- package/src/bundler/__tests__/reverse-import-graph.test.ts +42 -33
- package/src/bundler/build.test.ts +478 -9
- package/src/bundler/build.ts +424 -746
- package/src/bundler/client-boundary-transform.ts +977 -0
- package/src/bundler/dev.ts +39 -112
- package/src/bundler/fast-refresh-preamble.ts +47 -0
- package/src/bundler/index.ts +3 -2
- package/src/bundler/manifest-schema.ts +10 -0
- package/src/bundler/types.ts +20 -2
- package/src/client/__tests__/props-serialization.test.ts +37 -0
- package/src/client/hydrate.ts +2 -2
- package/src/client/index.ts +1 -1
- package/src/client/props-serialization.ts +233 -0
- package/src/client/runtime-entry.ts +567 -0
- package/src/client/runtime.ts +1 -1
- package/src/client/serialize.ts +50 -404
- package/src/diagnose/__tests__/checks.test.ts +132 -17
- package/src/diagnose/checks.ts +184 -3
- package/src/diagnose/run.ts +10 -8
- package/src/generator/templates.test.ts +48 -5
- package/src/generator/templates.ts +10 -1
- package/src/internal/client-boundary.ts +266 -0
- package/src/internal/index.ts +2 -1
- package/src/router/client-entry.test.ts +154 -29
- package/src/router/client-entry.ts +111 -313
- package/src/router/fs-routes.test.ts +443 -1
- package/src/router/fs-routes.ts +16 -3
- package/src/router/fs-scanner.ts +176 -57
- package/src/router/fs-types.ts +11 -2
- package/src/router/route-source-analyzer.ts +521 -0
- package/src/runtime/__tests__/inline-client-hydration.test.ts +104 -1
- package/src/runtime/__tests__/page-render-response.test.ts +218 -0
- package/src/runtime/handlers.ts +50 -26
- package/src/runtime/page-render-response.ts +24 -1
- package/src/runtime/server.ts +14 -0
- package/src/runtime/ssr.ts +16 -5
- package/src/runtime/streaming-ssr.ts +119 -76
- package/src/spec/schema.ts +31 -5
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mandujs/core",
|
|
3
|
-
"version": "0.54.
|
|
3
|
+
"version": "0.54.19",
|
|
4
4
|
"description": "Mandu Framework Core - Spec, Generator, Guard, Runtime",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.ts",
|
|
@@ -65,6 +65,7 @@
|
|
|
65
65
|
"./i18n": "./src/i18n/index.ts",
|
|
66
66
|
"./id": "./src/id/index.ts",
|
|
67
67
|
"./internal": "./src/internal/index.ts",
|
|
68
|
+
"./internal/client-boundary": "./src/internal/client-boundary.ts",
|
|
68
69
|
"./observability": "./src/observability/index.ts",
|
|
69
70
|
"./openapi/generator": "./src/openapi/generator.ts",
|
|
70
71
|
"./perf": "./src/perf/index.ts",
|
|
@@ -203,6 +204,7 @@
|
|
|
203
204
|
"fast-glob": "^3.3.2",
|
|
204
205
|
"glob": "^13.0.0",
|
|
205
206
|
"minimatch": "^10.1.1",
|
|
207
|
+
"typescript": ">=6.0.0",
|
|
206
208
|
"zod": "^3.23.8"
|
|
207
209
|
}
|
|
208
210
|
}
|
|
@@ -64,14 +64,35 @@ describe("agent context", () => {
|
|
|
64
64
|
await writeFile(root, ".mandu/routes.manifest.json", JSON.stringify({
|
|
65
65
|
version: 1,
|
|
66
66
|
routes: [
|
|
67
|
-
{
|
|
68
|
-
id: "home",
|
|
69
|
-
pattern: "/",
|
|
70
|
-
kind: "page",
|
|
71
|
-
module: "app/page.tsx",
|
|
72
|
-
componentModule: "app/page.tsx",
|
|
73
|
-
layoutChain: [],
|
|
74
|
-
|
|
67
|
+
{
|
|
68
|
+
id: "home",
|
|
69
|
+
pattern: "/",
|
|
70
|
+
kind: "page",
|
|
71
|
+
module: "app/page.tsx",
|
|
72
|
+
componentModule: "app/page.tsx",
|
|
73
|
+
layoutChain: [],
|
|
74
|
+
hydration: { strategy: "island", priority: "visible", preload: false },
|
|
75
|
+
boundaries: [
|
|
76
|
+
{
|
|
77
|
+
id: "home--0",
|
|
78
|
+
routeId: "home",
|
|
79
|
+
module: "src/client/HomeWidget.client.tsx",
|
|
80
|
+
importSpecifier: "@/client/HomeWidget.client",
|
|
81
|
+
exportName: "HomeWidget",
|
|
82
|
+
localName: "HomeWidget",
|
|
83
|
+
hydrate: "visible",
|
|
84
|
+
ordinal: 0,
|
|
85
|
+
propsSource: "inline",
|
|
86
|
+
propsKeys: ["user"],
|
|
87
|
+
hasSpreadProps: false,
|
|
88
|
+
source: {
|
|
89
|
+
file: "app/page.tsx",
|
|
90
|
+
line: 5,
|
|
91
|
+
column: 12,
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
],
|
|
95
|
+
},
|
|
75
96
|
{
|
|
76
97
|
id: "api-ping",
|
|
77
98
|
pattern: "/api/ping",
|
|
@@ -102,10 +123,26 @@ describe("agent context", () => {
|
|
|
102
123
|
expect(context.framework).toBe("mandu");
|
|
103
124
|
expect(context.project.name).toBe("agent-app");
|
|
104
125
|
expect(context.project.packageManager).toBe("bun@1.3.14");
|
|
105
|
-
expect(context.routeSource).toBe("manifest");
|
|
106
|
-
expect(context.routes).toHaveLength(2);
|
|
107
|
-
expect(context.pages.map((r) => r.id)).toEqual(["home"]);
|
|
108
|
-
expect(context.
|
|
126
|
+
expect(context.routeSource).toBe("manifest");
|
|
127
|
+
expect(context.routes).toHaveLength(2);
|
|
128
|
+
expect(context.pages.map((r) => r.id)).toEqual(["home"]);
|
|
129
|
+
expect(context.pages[0]?.boundaryCount).toBe(1);
|
|
130
|
+
expect(context.pages[0]?.boundaries?.[0]).toMatchObject({
|
|
131
|
+
id: "home--0",
|
|
132
|
+
module: "src/client/HomeWidget.client.tsx",
|
|
133
|
+
importSpecifier: "@/client/HomeWidget.client",
|
|
134
|
+
exportName: "HomeWidget",
|
|
135
|
+
localName: "HomeWidget",
|
|
136
|
+
ordinal: 0,
|
|
137
|
+
hydrate: "visible",
|
|
138
|
+
propsKeys: ["user"],
|
|
139
|
+
source: {
|
|
140
|
+
file: "app/page.tsx",
|
|
141
|
+
line: 5,
|
|
142
|
+
column: 12,
|
|
143
|
+
},
|
|
144
|
+
});
|
|
145
|
+
expect(context.apis.map((r) => r.id)).toEqual(["api-ping"]);
|
|
109
146
|
expect(context.apis[0]?.hasContractModule).toBe(true);
|
|
110
147
|
expect(context.partials.map((p) => p.path)).toEqual(["app/dashboard/counter.partial.tsx"]);
|
|
111
148
|
expect(context.islands.map((p) => p.path)).toEqual(["app/dashboard/chart.island.tsx"]);
|
|
@@ -125,10 +162,11 @@ describe("agent context", () => {
|
|
|
125
162
|
|
|
126
163
|
expect(result.path).toBe(agentManifestPath(root));
|
|
127
164
|
const manifest = await readAgentManifest(root);
|
|
128
|
-
expect(manifest?.schemaVersion).toBe(1);
|
|
129
|
-
expect(manifest?.project.name).toBe("agent-app");
|
|
130
|
-
expect(manifest?.
|
|
131
|
-
|
|
165
|
+
expect(manifest?.schemaVersion).toBe(1);
|
|
166
|
+
expect(manifest?.project.name).toBe("agent-app");
|
|
167
|
+
expect(manifest?.routes[0]?.boundaries?.[0]?.id).toBe("home--0");
|
|
168
|
+
expect(manifest?.agentWorkflow.canonical).toEqual(["context", "plan", "apply", "verify", "repair"]);
|
|
169
|
+
});
|
|
132
170
|
|
|
133
171
|
it("builds a deterministic agent plan from intent", () => {
|
|
134
172
|
const plan = buildAgentPlan({
|
|
@@ -202,9 +240,9 @@ describe("agent context", () => {
|
|
|
202
240
|
expect(parsed.project.name).toBe("agent-app");
|
|
203
241
|
});
|
|
204
242
|
|
|
205
|
-
it("records changed file reasons and warns on internal API edits", async () => {
|
|
206
|
-
const version = await runGit(root, ["--version"]);
|
|
207
|
-
if (!version.ok) return;
|
|
243
|
+
it("records changed file reasons and warns on internal API edits", async () => {
|
|
244
|
+
const version = await runGit(root, ["--version"]);
|
|
245
|
+
if (!version.ok) return;
|
|
208
246
|
|
|
209
247
|
expect((await runGit(root, ["init"])).ok).toBe(true);
|
|
210
248
|
await runGit(root, ["config", "user.email", "agent@example.com"]);
|
|
@@ -226,12 +264,43 @@ describe("agent context", () => {
|
|
|
226
264
|
expect(reason?.internalApi).toBe(true);
|
|
227
265
|
expect(reason?.recommendedChecks).toContain("bun run check:public-api && bun run check:target-boundaries");
|
|
228
266
|
expect(report.diagnostics.some((diag) => diag.code === "MANDU_VERIFY_INTERNAL_API_EDIT")).toBe(true);
|
|
229
|
-
expect(report.suggestedCommands.map((cmd) => cmd.command)).toContain(
|
|
230
|
-
"bun run check:public-api && bun run check:target-boundaries",
|
|
231
|
-
);
|
|
232
|
-
});
|
|
233
|
-
|
|
234
|
-
it("
|
|
267
|
+
expect(report.suggestedCommands.map((cmd) => cmd.command)).toContain(
|
|
268
|
+
"bun run check:public-api && bun run check:target-boundaries",
|
|
269
|
+
);
|
|
270
|
+
});
|
|
271
|
+
|
|
272
|
+
it("recommends hydration gates for hydration-sensitive runtime edits", async () => {
|
|
273
|
+
const version = await runGit(root, ["--version"]);
|
|
274
|
+
if (!version.ok) return;
|
|
275
|
+
|
|
276
|
+
expect((await runGit(root, ["init"])).ok).toBe(true);
|
|
277
|
+
await runGit(root, ["config", "user.email", "agent@example.com"]);
|
|
278
|
+
await runGit(root, ["config", "user.name", "Agent"]);
|
|
279
|
+
expect((await runGit(root, ["add", "."])).ok).toBe(true);
|
|
280
|
+
expect((await runGit(root, ["commit", "-m", "initial"])).ok).toBe(true);
|
|
281
|
+
|
|
282
|
+
await writeFile(root, "packages/core/src/bundler/build.ts", "export const changed = true;\n");
|
|
283
|
+
|
|
284
|
+
const report = await buildAgentVerifyReport(root, {
|
|
285
|
+
includeDiagnose: false,
|
|
286
|
+
includeGuard: false,
|
|
287
|
+
includeContract: false,
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
const reason = report.changedFileReasons.find(
|
|
291
|
+
(entry) => entry.file === "packages/core/src/bundler/build.ts",
|
|
292
|
+
);
|
|
293
|
+
expect(reason?.recommendedChecks).toContain("bun run test:hydration-boundary");
|
|
294
|
+
expect(reason?.recommendedChecks).toContain("bun run test:hydration-e2e");
|
|
295
|
+
expect(reason?.recommendedChecks).toContain("bun run check:publish");
|
|
296
|
+
|
|
297
|
+
const suggestedCommands = report.suggestedCommands.map((cmd) => cmd.command);
|
|
298
|
+
expect(suggestedCommands).toContain("bun run test:hydration-boundary");
|
|
299
|
+
expect(suggestedCommands).toContain("bun run test:hydration-e2e");
|
|
300
|
+
expect(suggestedCommands).toContain("bun run check:publish");
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
it("turns a verify report into repair actions", async () => {
|
|
235
304
|
await writeAgentVerifyReport(root, {
|
|
236
305
|
schemaVersion: 1,
|
|
237
306
|
framework: "mandu",
|
package/src/agent/context.ts
CHANGED
|
@@ -119,6 +119,7 @@ function summarizeRoute(route: RouteSpec | {
|
|
|
119
119
|
methods?: string[];
|
|
120
120
|
hydration?: { strategy?: string; priority?: string };
|
|
121
121
|
clientModule?: string;
|
|
122
|
+
boundaries?: RouteSpec["boundaries"];
|
|
122
123
|
contractModule?: string;
|
|
123
124
|
layoutChain?: string[];
|
|
124
125
|
metadataKind?: string;
|
|
@@ -127,6 +128,21 @@ function summarizeRoute(route: RouteSpec | {
|
|
|
127
128
|
"metadataKind" in route && typeof route.metadataKind === "string"
|
|
128
129
|
? route.metadataKind
|
|
129
130
|
: undefined;
|
|
131
|
+
const boundaries = Array.isArray(route.boundaries)
|
|
132
|
+
? route.boundaries.map((boundary) => ({
|
|
133
|
+
id: boundary.id,
|
|
134
|
+
module: boundary.module,
|
|
135
|
+
...(boundary.importSpecifier ? { importSpecifier: boundary.importSpecifier } : {}),
|
|
136
|
+
exportName: boundary.exportName,
|
|
137
|
+
localName: boundary.localName,
|
|
138
|
+
ordinal: boundary.ordinal,
|
|
139
|
+
hydrate: boundary.hydrate,
|
|
140
|
+
propsSource: boundary.propsSource,
|
|
141
|
+
propsKeys: boundary.propsKeys ?? [],
|
|
142
|
+
hasSpreadProps: !!boundary.hasSpreadProps,
|
|
143
|
+
source: boundary.source,
|
|
144
|
+
}))
|
|
145
|
+
: [];
|
|
130
146
|
return {
|
|
131
147
|
id: route.id,
|
|
132
148
|
pattern: route.pattern,
|
|
@@ -144,6 +160,7 @@ function summarizeRoute(route: RouteSpec | {
|
|
|
144
160
|
hasClientModule: typeof route.clientModule === "string" && route.clientModule.length > 0,
|
|
145
161
|
hasContractModule:
|
|
146
162
|
typeof route.contractModule === "string" && route.contractModule.length > 0,
|
|
163
|
+
...(boundaries.length > 0 ? { boundaryCount: boundaries.length, boundaries } : {}),
|
|
147
164
|
layoutDepth: Array.isArray(route.layoutChain) ? route.layoutChain.length : 0,
|
|
148
165
|
...(metadataKind ? { metadataKind } : {}),
|
|
149
166
|
};
|
package/src/agent/types.ts
CHANGED
|
@@ -32,23 +32,43 @@ export interface AgentProjectSummary {
|
|
|
32
32
|
configFile: string | null;
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
export interface AgentRouteSummary {
|
|
36
|
-
id: string;
|
|
37
|
-
pattern: string;
|
|
38
|
-
kind: "page" | "api" | "metadata" | string;
|
|
35
|
+
export interface AgentRouteSummary {
|
|
36
|
+
id: string;
|
|
37
|
+
pattern: string;
|
|
38
|
+
kind: "page" | "api" | "metadata" | string;
|
|
39
39
|
module: string;
|
|
40
40
|
methods?: string[];
|
|
41
41
|
hydration?: {
|
|
42
42
|
strategy?: string;
|
|
43
43
|
priority?: string;
|
|
44
|
-
};
|
|
45
|
-
hasClientModule: boolean;
|
|
46
|
-
hasContractModule: boolean;
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
44
|
+
};
|
|
45
|
+
hasClientModule: boolean;
|
|
46
|
+
hasContractModule: boolean;
|
|
47
|
+
boundaryCount?: number;
|
|
48
|
+
boundaries?: AgentRouteBoundarySummary[];
|
|
49
|
+
layoutDepth: number;
|
|
50
|
+
metadataKind?: string;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
export interface AgentRouteBoundarySummary {
|
|
54
|
+
id: string;
|
|
55
|
+
module: string;
|
|
56
|
+
importSpecifier?: string;
|
|
57
|
+
exportName: string;
|
|
58
|
+
localName: string;
|
|
59
|
+
ordinal: number;
|
|
60
|
+
hydrate: string;
|
|
61
|
+
propsSource: string;
|
|
62
|
+
propsKeys: string[];
|
|
63
|
+
hasSpreadProps: boolean;
|
|
64
|
+
source: {
|
|
65
|
+
file: string;
|
|
66
|
+
line: number;
|
|
67
|
+
column: number;
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export interface AgentArtifactSummary {
|
|
52
72
|
path: string;
|
|
53
73
|
kind: "partial" | "island" | "slot" | "contract";
|
|
54
74
|
}
|
package/src/agent/verify.ts
CHANGED
|
@@ -245,12 +245,17 @@ function commandSuggestions(
|
|
|
245
245
|
if (files.some((file) => file.startsWith("packages/mcp/"))) {
|
|
246
246
|
add("bun test packages/mcp/tests", "MCP package files changed.", true);
|
|
247
247
|
}
|
|
248
|
-
if (files.some((file) => file.includes("package.json") || file === "bun.lock")) {
|
|
249
|
-
add("bun run check:publish", "Package metadata or lockfile changed.", true);
|
|
250
|
-
}
|
|
251
|
-
if (
|
|
252
|
-
add("bun run
|
|
253
|
-
|
|
248
|
+
if (files.some((file) => file.includes("package.json") || file === "bun.lock")) {
|
|
249
|
+
add("bun run check:publish", "Package metadata or lockfile changed.", true);
|
|
250
|
+
}
|
|
251
|
+
if (files.some(isHydrationSensitiveEdit)) {
|
|
252
|
+
add("bun run test:hydration-boundary", "Hydration-sensitive runtime/router/bundler files changed.", true);
|
|
253
|
+
add("bun run test:hydration-e2e", "Hydration-sensitive changes need browser-level validation.", true);
|
|
254
|
+
add("bun run check:publish", "Pre-publish hydration gates should pass before release.", true);
|
|
255
|
+
}
|
|
256
|
+
if (changedFileReasons.some((entry) => entry.internalApi)) {
|
|
257
|
+
add("bun run check:public-api && bun run check:target-boundaries", "Internal framework boundaries changed.", true);
|
|
258
|
+
}
|
|
254
259
|
if (diagnostics.some((d) => d.severity === "error" || d.severity === "fatal")) {
|
|
255
260
|
add("mandu agent repair --from .mandu/agent-verify.json", "Verification produced repairable diagnostics.", false);
|
|
256
261
|
}
|
|
@@ -258,22 +263,40 @@ function commandSuggestions(
|
|
|
258
263
|
return out;
|
|
259
264
|
}
|
|
260
265
|
|
|
261
|
-
function isInternalApiEdit(file: string): boolean {
|
|
262
|
-
return [
|
|
263
|
-
"packages/core/src/runtime/",
|
|
264
|
-
"packages/core/src/bundler/",
|
|
266
|
+
function isInternalApiEdit(file: string): boolean {
|
|
267
|
+
return [
|
|
268
|
+
"packages/core/src/runtime/",
|
|
269
|
+
"packages/core/src/bundler/",
|
|
265
270
|
"packages/core/src/server/",
|
|
266
271
|
"packages/core/src/guard/",
|
|
267
272
|
"packages/core/src/spec/",
|
|
268
273
|
"packages/core/src/router/",
|
|
269
274
|
"packages/core/src/internal/",
|
|
270
|
-
].some((prefix) => file.startsWith(prefix));
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
function
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
275
|
+
].some((prefix) => file.startsWith(prefix));
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
function isHydrationSensitiveEdit(file: string): boolean {
|
|
279
|
+
return [
|
|
280
|
+
"packages/core/src/runtime/page-render-response.ts",
|
|
281
|
+
"packages/core/src/runtime/server.ts",
|
|
282
|
+
"packages/core/src/runtime/ssr.ts",
|
|
283
|
+
"packages/core/src/runtime/streaming-ssr.ts",
|
|
284
|
+
"packages/core/src/internal/client-boundary.ts",
|
|
285
|
+
"packages/core/src/bundler/build.ts",
|
|
286
|
+
"packages/core/src/bundler/client-boundary-transform.ts",
|
|
287
|
+
"packages/core/src/router/client-entry.ts",
|
|
288
|
+
"packages/core/src/router/fs-routes.ts",
|
|
289
|
+
"packages/core/src/router/fs-scanner.ts",
|
|
290
|
+
"packages/core/src/client/serialize.ts",
|
|
291
|
+
"packages/core/src/client/props-serialization.ts",
|
|
292
|
+
"packages/core/src/client/runtime-entry.ts",
|
|
293
|
+
].includes(file);
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
function changedFileReason(file: string): AgentChangedFileReason {
|
|
297
|
+
const normalized = normalizePath(file) ?? file;
|
|
298
|
+
const reasons: string[] = [];
|
|
299
|
+
const recommendedChecks: string[] = [];
|
|
277
300
|
|
|
278
301
|
if (/\.(ts|tsx|js|jsx)$/.test(normalized)) {
|
|
279
302
|
reasons.push("Source code changed.");
|
|
@@ -295,13 +318,21 @@ function changedFileReason(file: string): AgentChangedFileReason {
|
|
|
295
318
|
reasons.push("User-facing documentation changed.");
|
|
296
319
|
recommendedChecks.push("bun run check:docs-drift");
|
|
297
320
|
}
|
|
298
|
-
if (normalized === "package.json" || normalized === "bun.lock" || normalized.endsWith("/package.json")) {
|
|
299
|
-
reasons.push("Package metadata or dependency graph changed.");
|
|
300
|
-
recommendedChecks.push("bun run check:publish");
|
|
301
|
-
}
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
321
|
+
if (normalized === "package.json" || normalized === "bun.lock" || normalized.endsWith("/package.json")) {
|
|
322
|
+
reasons.push("Package metadata or dependency graph changed.");
|
|
323
|
+
recommendedChecks.push("bun run check:publish");
|
|
324
|
+
}
|
|
325
|
+
if (isHydrationSensitiveEdit(normalized)) {
|
|
326
|
+
reasons.push("Hydration/runtime/client boundary behavior changed.");
|
|
327
|
+
recommendedChecks.push(
|
|
328
|
+
"bun run test:hydration-boundary",
|
|
329
|
+
"bun run test:hydration-e2e",
|
|
330
|
+
"bun run check:publish",
|
|
331
|
+
);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
const internalApi = isInternalApiEdit(normalized);
|
|
335
|
+
if (internalApi) {
|
|
305
336
|
reasons.push("Framework internal API changed.");
|
|
306
337
|
recommendedChecks.push("bun run check:public-api && bun run check:target-boundaries");
|
|
307
338
|
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
{
|
|
2
|
+
"snapshots": {
|
|
3
|
+
"client boundary manifest metadata": "{\n \"bundleBoundaries\": {\n \"boundary-demo--0\": {\n \"exportName\": \"BoundaryWidget\",\n \"hydrate\": \"visible\",\n \"js\": \"/.mandu/client/boundary-demo--0.boundary.js\",\n \"module\": \"src/client/BoundaryWidget.client.tsx\",\n \"priority\": \"visible\",\n \"route\": \"boundary-demo\"\n }\n },\n \"routes\": [\n {\n \"boundaries\": [\n {\n \"exportName\": \"BoundaryWidget\",\n \"hasSpreadProps\": false,\n \"hydrate\": \"visible\",\n \"id\": \"boundary-demo--0\",\n \"importSpecifier\": \"@/client/BoundaryWidget.client\",\n \"localName\": \"BoundaryWidget\",\n \"module\": \"src/client/BoundaryWidget.client.tsx\",\n \"ordinal\": 0,\n \"propsKeys\": [\n \"label\"\n ],\n \"propsSource\": \"inline\",\n \"routeId\": \"boundary-demo\",\n \"source\": {\n \"column\": 12,\n \"file\": \"app/boundary/page.tsx\",\n \"line\": 5\n }\n }\n ],\n \"componentModule\": \"app/boundary/page.tsx\",\n \"id\": \"boundary-demo\",\n \"module\": \"app/boundary/page.tsx\"\n }\n ]\n}\n"
|
|
4
|
+
}
|
|
5
|
+
}
|
|
@@ -51,8 +51,11 @@
|
|
|
51
51
|
* clean tmpdir with no prior `.mandu/vendor-cache/`.
|
|
52
52
|
*/
|
|
53
53
|
|
|
54
|
-
import { buildClientBundles } from "../build";
|
|
55
|
-
import type { RoutesManifest } from "../../spec/schema";
|
|
54
|
+
import { buildClientBundles } from "../build";
|
|
55
|
+
import type { RoutesManifest } from "../../spec/schema";
|
|
56
|
+
import { mkdir } from "fs/promises";
|
|
57
|
+
import path from "path";
|
|
58
|
+
import { generateManifest } from "../../router/fs-routes";
|
|
56
59
|
|
|
57
60
|
const rootDir = process.argv[2];
|
|
58
61
|
if (!rootDir) {
|
|
@@ -61,7 +64,9 @@ if (!rootDir) {
|
|
|
61
64
|
}
|
|
62
65
|
const mode = process.argv[3] ?? "default";
|
|
63
66
|
|
|
64
|
-
const manifest: RoutesManifest = mode === "
|
|
67
|
+
const manifest: RoutesManifest = mode === "client-boundary-generated-transitive"
|
|
68
|
+
? (await generateManifest(rootDir)).manifest
|
|
69
|
+
: mode === "server-page-client-module"
|
|
65
70
|
? {
|
|
66
71
|
version: 1,
|
|
67
72
|
routes: [
|
|
@@ -118,6 +123,83 @@ const manifest: RoutesManifest = mode === "server-page-client-module"
|
|
|
118
123
|
},
|
|
119
124
|
],
|
|
120
125
|
}
|
|
126
|
+
: mode === "client-boundary"
|
|
127
|
+
|| mode === "client-boundary-target"
|
|
128
|
+
|| mode === "client-boundary-skip-framework"
|
|
129
|
+
|| mode === "client-boundary-duplicate-id"
|
|
130
|
+
? {
|
|
131
|
+
version: 1,
|
|
132
|
+
routes: [
|
|
133
|
+
{
|
|
134
|
+
id: "boundary-demo",
|
|
135
|
+
kind: "page",
|
|
136
|
+
pattern: "/boundary",
|
|
137
|
+
module: "app/boundary/page.tsx",
|
|
138
|
+
componentModule: "app/boundary/page.tsx",
|
|
139
|
+
hydration: {
|
|
140
|
+
strategy: "island",
|
|
141
|
+
priority: "visible",
|
|
142
|
+
preload: false,
|
|
143
|
+
},
|
|
144
|
+
boundaries: [
|
|
145
|
+
{
|
|
146
|
+
id: "boundary-demo--0",
|
|
147
|
+
routeId: "boundary-demo",
|
|
148
|
+
module: "src/client/BoundaryWidget.client.tsx",
|
|
149
|
+
importSpecifier: "@/client/BoundaryWidget.client",
|
|
150
|
+
exportName: "BoundaryWidget",
|
|
151
|
+
localName: "BoundaryWidget",
|
|
152
|
+
hydrate: "visible",
|
|
153
|
+
ordinal: 0,
|
|
154
|
+
propsSource: "inline",
|
|
155
|
+
propsKeys: ["label"],
|
|
156
|
+
hasSpreadProps: false,
|
|
157
|
+
source: {
|
|
158
|
+
file: "app/boundary/page.tsx",
|
|
159
|
+
line: 5,
|
|
160
|
+
column: 12,
|
|
161
|
+
},
|
|
162
|
+
},
|
|
163
|
+
],
|
|
164
|
+
},
|
|
165
|
+
...(mode === "client-boundary-duplicate-id"
|
|
166
|
+
? [
|
|
167
|
+
{
|
|
168
|
+
id: "boundary-copy",
|
|
169
|
+
kind: "page" as const,
|
|
170
|
+
pattern: "/boundary-copy",
|
|
171
|
+
module: "app/boundary/page.tsx",
|
|
172
|
+
componentModule: "app/boundary/page.tsx",
|
|
173
|
+
hydration: {
|
|
174
|
+
strategy: "island" as const,
|
|
175
|
+
priority: "visible" as const,
|
|
176
|
+
preload: false,
|
|
177
|
+
},
|
|
178
|
+
boundaries: [
|
|
179
|
+
{
|
|
180
|
+
id: "boundary-demo--0",
|
|
181
|
+
routeId: "boundary-copy",
|
|
182
|
+
module: "src/client/BoundaryWidget.client.tsx",
|
|
183
|
+
importSpecifier: "@/client/BoundaryWidget.client",
|
|
184
|
+
exportName: "BoundaryWidget",
|
|
185
|
+
localName: "BoundaryWidget",
|
|
186
|
+
hydrate: "visible" as const,
|
|
187
|
+
ordinal: 0,
|
|
188
|
+
propsSource: "inline" as const,
|
|
189
|
+
propsKeys: ["label"],
|
|
190
|
+
hasSpreadProps: false,
|
|
191
|
+
source: {
|
|
192
|
+
file: "app/boundary/page.tsx",
|
|
193
|
+
line: 8,
|
|
194
|
+
column: 12,
|
|
195
|
+
},
|
|
196
|
+
},
|
|
197
|
+
],
|
|
198
|
+
},
|
|
199
|
+
]
|
|
200
|
+
: []),
|
|
201
|
+
],
|
|
202
|
+
}
|
|
121
203
|
: mode === "i18n-locale-route-id"
|
|
122
204
|
? {
|
|
123
205
|
version: 1,
|
|
@@ -156,27 +238,58 @@ const manifest: RoutesManifest = mode === "server-page-client-module"
|
|
|
156
238
|
],
|
|
157
239
|
};
|
|
158
240
|
|
|
159
|
-
try {
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
241
|
+
try {
|
|
242
|
+
if (mode === "client-boundary-target" || mode === "client-boundary-skip-framework") {
|
|
243
|
+
await mkdir(path.join(rootDir, ".mandu"), { recursive: true });
|
|
244
|
+
await Bun.write(
|
|
245
|
+
path.join(rootDir, ".mandu", "manifest.json"),
|
|
246
|
+
JSON.stringify(
|
|
247
|
+
{
|
|
248
|
+
version: 1,
|
|
249
|
+
buildTime: "2026-05-23T00:00:00.000Z",
|
|
250
|
+
env: "development",
|
|
251
|
+
bundles: {},
|
|
252
|
+
shared: {
|
|
253
|
+
runtime: "/.mandu/client/_runtime.js",
|
|
254
|
+
vendor: "/.mandu/client/_react.js",
|
|
255
|
+
router: "/.mandu/client/_router.js",
|
|
256
|
+
},
|
|
257
|
+
},
|
|
258
|
+
null,
|
|
259
|
+
2,
|
|
260
|
+
),
|
|
261
|
+
);
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
const result = await buildClientBundles(manifest, rootDir, {
|
|
265
|
+
minify: false,
|
|
266
|
+
sourcemap: false,
|
|
267
|
+
splitting: false,
|
|
268
|
+
...(mode === "client-boundary-target" ? { targetRouteIds: ["boundary-demo"] } : {}),
|
|
269
|
+
...(mode === "client-boundary-skip-framework" ? { skipFrameworkBundles: true } : {}),
|
|
270
|
+
});
|
|
165
271
|
process.stdout.write(
|
|
166
272
|
JSON.stringify({
|
|
167
|
-
success: result.success,
|
|
168
|
-
errors: result.errors,
|
|
169
|
-
manifest: {
|
|
170
|
-
|
|
273
|
+
success: result.success,
|
|
274
|
+
errors: result.errors,
|
|
275
|
+
manifest: {
|
|
276
|
+
routes: manifest.routes.map((route) => ({
|
|
277
|
+
id: route.id,
|
|
278
|
+
module: route.module,
|
|
279
|
+
componentModule: route.componentModule,
|
|
280
|
+
boundaries: route.kind === "page" ? route.boundaries ?? [] : [],
|
|
281
|
+
})),
|
|
282
|
+
shared: {
|
|
171
283
|
runtime: result.manifest.shared?.runtime ?? null,
|
|
172
284
|
vendor: result.manifest.shared?.vendor ?? null,
|
|
173
285
|
router: result.manifest.shared?.router ?? null,
|
|
174
286
|
fastRefresh: result.manifest.shared?.fastRefresh ?? null,
|
|
175
287
|
},
|
|
176
|
-
bundles: result.manifest.bundles ?? {},
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
288
|
+
bundles: result.manifest.bundles ?? {},
|
|
289
|
+
boundaries: result.manifest.boundaries ?? {},
|
|
290
|
+
},
|
|
291
|
+
}) + "\n",
|
|
292
|
+
);
|
|
180
293
|
process.exit(result.success ? 0 : 1);
|
|
181
294
|
} catch (err) {
|
|
182
295
|
process.stdout.write(
|