@mandujs/core 0.53.0 → 0.53.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +38 -2
- package/src/a11y/run-audit.ts +6 -6
- package/src/brain/adapters/oauth-flow.ts +1 -1
- package/src/brain/architecture/analyzer.ts +0 -2
- package/src/brain/credentials.ts +2 -2
- package/src/bundler/__tests__/build-runner.ts +13 -1
- package/src/bundler/__tests__/extended-watch.test.ts +3 -2
- package/src/bundler/__tests__/fast-refresh.test.ts +10 -12
- package/src/bundler/build.test.ts +30 -33
- package/src/bundler/build.ts +2 -1
- package/src/bundler/dev.ts +23 -22
- package/src/bundler/prerender.ts +63 -0
- package/src/bundler/safe-build.test.ts +2 -1
- package/src/bundler/types.ts +5 -3
- package/src/change/history.ts +1 -1
- package/src/change/snapshot.ts +18 -9
- package/src/client/Link.tsx +9 -9
- package/src/client/globals.ts +3 -2
- package/src/client/hooks.ts +0 -4
- package/src/client/hydrate.ts +1 -1
- package/src/client/router.ts +12 -12
- package/src/client/rpc.ts +1 -1
- package/src/client/use-fetch.ts +6 -6
- package/src/client/use-sse.ts +5 -5
- package/src/config/mcp-status.ts +0 -1
- package/src/config/validate.ts +1 -1
- package/src/config/watcher.ts +1 -1
- package/src/content/collection.ts +8 -12
- package/src/content/content-layer.ts +5 -5
- package/src/content/data-store.ts +2 -2
- package/src/content/loader-context.ts +0 -1
- package/src/content/loaders/file.ts +1 -1
- package/src/content/loaders/glob.ts +4 -4
- package/src/content/loaders/types.ts +1 -1
- package/src/content/meta-store.ts +1 -1
- package/src/content/types.ts +1 -1
- package/src/contract/define.ts +11 -12
- package/src/contract/index.ts +3 -3
- package/src/contract/protection.ts +2 -2
- package/src/contract/types.ts +1 -1
- package/src/contract/validator.ts +0 -1
- package/src/db/__tests__/db.test.ts +4 -7
- package/src/db/migrations/index.ts +3 -0
- package/src/db/migrations/lock.ts +11 -11
- package/src/deploy/cache.ts +1 -0
- package/src/design/extract.ts +2 -1
- package/src/design/patch.ts +0 -1
- package/src/devtools/ai/mcp-connector.ts +24 -24
- package/src/devtools/client/components/kitchen-root.tsx +1 -1
- package/src/devtools/client/components/panel/errors-panel.tsx +1 -1
- package/src/devtools/client/state-manager.ts +1 -1
- package/src/devtools/init.ts +2 -3
- package/src/devtools/server/source-context.ts +1 -1
- package/src/devtools/worker/redaction-worker.ts +8 -4
- package/src/devtools/worker/worker-manager.ts +4 -3
- package/src/error/classifier.ts +1 -1
- package/src/error/domains.ts +1 -1
- package/src/filling/sse.ts +5 -5
- package/src/generator/generate.ts +2 -2
- package/src/guard/analyzer.ts +1 -1
- package/src/guard/ast-analyzer.ts +7 -7
- package/src/guard/auto-correct.ts +2 -2
- package/src/guard/config-guard.ts +0 -2
- package/src/guard/contract-guard.ts +1 -1
- package/src/guard/decision-memory.ts +2 -2
- package/src/guard/design-inline-class.ts +2 -1
- package/src/guard/graph.ts +1 -1
- package/src/guard/healing.ts +36 -41
- package/src/guard/negotiation.ts +3 -3
- package/src/guard/reporter.ts +1 -4
- package/src/guard/semantic-slots.ts +2 -2
- package/src/guard/suggestions.ts +2 -2
- package/src/guard/watcher.ts +5 -4
- package/src/index.ts +5 -5
- package/src/intent/index.ts +1 -1
- package/src/kitchen/api/contract-api.ts +1 -1
- package/src/kitchen/api/file-api.ts +3 -3
- package/src/kitchen/kitchen-handler.ts +29 -21
- package/src/lockfile/validate.ts +0 -1
- package/src/openapi/generator.ts +2 -1
- package/src/openapi/openapi.test.ts +43 -14
- package/src/perf/user-marks.ts +1 -1
- package/src/plugins/registry.ts +1 -2
- package/src/plugins/types.ts +1 -1
- package/src/resource/ddl/diff.ts +0 -1
- package/src/resource/generator.ts +1 -1
- package/src/resource/generators/contract.ts +1 -1
- package/src/resource/schema.ts +2 -1
- package/src/router/fs-routes.ts +6 -6
- package/src/router/fs-scanner.ts +1 -1
- package/src/runtime/env.ts +1 -1
- package/src/runtime/image-handler.ts +2 -1
- package/src/runtime/middleware.ts +2 -1
- package/src/runtime/server.ts +67 -60
- package/src/runtime/ssr.ts +3 -3
- package/src/runtime/streaming-ssr.ts +25 -27
- package/src/scheduler/validate.ts +1 -1
- package/src/seo/integration/ssr.ts +2 -3
- package/src/seo/render/opengraph.ts +1 -1
- package/src/seo/resolve/index.ts +0 -4
- package/src/slot/corrector.ts +1 -1
- package/src/slot/validator.ts +4 -4
- package/src/watcher/reporter.ts +1 -1
- package/src/watcher/watcher.ts +19 -19
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mandujs/core",
|
|
3
|
-
"version": "0.53.
|
|
3
|
+
"version": "0.53.1",
|
|
4
4
|
"description": "Mandu Framework Core - Spec, Generator, Guard, Runtime",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./src/index.ts",
|
|
@@ -24,19 +24,36 @@
|
|
|
24
24
|
"./content/slug": "./src/content/slug.ts",
|
|
25
25
|
"./content/llms-txt": "./src/content/llms-txt.ts",
|
|
26
26
|
"./content/schema": "./src/content/schema.ts",
|
|
27
|
+
"./config": "./src/config/index.ts",
|
|
28
|
+
"./config/mandu": "./src/config/mandu.ts",
|
|
29
|
+
"./config/validate": "./src/config/validate.ts",
|
|
27
30
|
"./db": "./src/db/index.ts",
|
|
31
|
+
"./db/migrations": "./src/db/migrations/index.ts",
|
|
32
|
+
"./db/migrations/history-table": "./src/db/migrations/history-table.ts",
|
|
33
|
+
"./db/migrations/runner": "./src/db/migrations/runner.ts",
|
|
28
34
|
"./deploy": "./src/deploy/index.ts",
|
|
29
35
|
"./design": "./src/design/index.ts",
|
|
30
36
|
"./desktop": "./src/desktop/index.ts",
|
|
31
37
|
"./desktop/worker": "./src/desktop/worker.ts",
|
|
32
38
|
"./email": "./src/email/index.ts",
|
|
39
|
+
"./filling": "./src/filling/index.ts",
|
|
40
|
+
"./filling/cookie-codec": "./src/filling/cookie-codec.ts",
|
|
33
41
|
"./filling/session-sqlite": "./src/filling/session-sqlite.ts",
|
|
34
42
|
"./kitchen": "./src/kitchen/index.ts",
|
|
43
|
+
"./logging": "./src/logging/index.ts",
|
|
35
44
|
"./middleware": "./src/middleware/index.ts",
|
|
45
|
+
"./middleware/csrf": "./src/middleware/csrf.ts",
|
|
36
46
|
"./middleware/oauth": "./src/middleware/oauth/index.ts",
|
|
37
47
|
"./middleware/secure": "./src/middleware/secure/index.ts",
|
|
38
48
|
"./middleware/rate-limit": "./src/middleware/rate-limit/index.ts",
|
|
49
|
+
"./middleware/session": "./src/middleware/session.ts",
|
|
39
50
|
"./testing": "./src/testing/index.ts",
|
|
51
|
+
"./testing/db": "./src/testing/db.ts",
|
|
52
|
+
"./testing/mocks": "./src/testing/mocks.ts",
|
|
53
|
+
"./testing/reporter": "./src/testing/reporter.ts",
|
|
54
|
+
"./testing/server": "./src/testing/server.ts",
|
|
55
|
+
"./testing/session": "./src/testing/session.ts",
|
|
56
|
+
"./testing/snapshot": "./src/testing/snapshot.ts",
|
|
40
57
|
"./plugins": "./src/plugins/index.ts",
|
|
41
58
|
"./plugins/define": "./src/plugins/define.ts",
|
|
42
59
|
"./plugins/runner": "./src/plugins/runner.ts",
|
|
@@ -44,17 +61,36 @@
|
|
|
44
61
|
"./i18n": "./src/i18n/index.ts",
|
|
45
62
|
"./id": "./src/id/index.ts",
|
|
46
63
|
"./observability": "./src/observability/index.ts",
|
|
64
|
+
"./openapi/generator": "./src/openapi/generator.ts",
|
|
47
65
|
"./perf": "./src/perf/index.ts",
|
|
48
66
|
"./perf/hmr-markers": "./src/perf/hmr-markers.ts",
|
|
49
67
|
"./perf/user-marks": "./src/perf/user-marks.ts",
|
|
68
|
+
"./resource": "./src/resource/index.ts",
|
|
69
|
+
"./resource/ddl/diff": "./src/resource/ddl/diff.ts",
|
|
70
|
+
"./resource/ddl/emit": "./src/resource/ddl/emit.ts",
|
|
71
|
+
"./resource/ddl/snapshot": "./src/resource/ddl/snapshot.ts",
|
|
72
|
+
"./resource/ddl/type-map": "./src/resource/ddl/type-map.ts",
|
|
73
|
+
"./resource/ddl/types": "./src/resource/ddl/types.ts",
|
|
74
|
+
"./resource/generator-repo": "./src/resource/generator-repo.ts",
|
|
75
|
+
"./resource/parser": "./src/resource/parser.ts",
|
|
76
|
+
"./resource/schema": "./src/resource/schema.ts",
|
|
77
|
+
"./router": "./src/router/index.ts",
|
|
50
78
|
"./routes": "./src/routes/index.ts",
|
|
79
|
+
"./runtime": "./src/runtime/index.ts",
|
|
80
|
+
"./runtime/cache": "./src/runtime/cache.ts",
|
|
81
|
+
"./runtime/router": "./src/runtime/router.ts",
|
|
82
|
+
"./runtime/server": "./src/runtime/server.ts",
|
|
51
83
|
"./scheduler": "./src/scheduler/index.ts",
|
|
52
84
|
"./storage/s3": "./src/storage/s3/index.ts",
|
|
85
|
+
"./guard": "./src/guard/index.ts",
|
|
53
86
|
"./guard/define-rule": "./src/guard/define-rule.ts",
|
|
87
|
+
"./guard/design-inline-class": "./src/guard/design-inline-class.ts",
|
|
54
88
|
"./guard/rule-presets": "./src/guard/rule-presets.ts",
|
|
55
89
|
"./guard/tsgolint-bridge": "./src/guard/tsgolint-bridge.ts",
|
|
90
|
+
"./bundler": "./src/bundler/index.ts",
|
|
56
91
|
"./bundler/prerender": "./src/bundler/prerender.ts",
|
|
57
92
|
"./bundler/safe-build": "./src/bundler/safe-build.ts",
|
|
93
|
+
"./bundler/types": "./src/bundler/types.ts",
|
|
58
94
|
"./bundler/hmr-types": "./src/bundler/hmr-types.ts",
|
|
59
95
|
"./bundler/scenario-matrix": "./src/bundler/scenario-matrix.ts",
|
|
60
96
|
"./bundler/vendor-cache-types": "./src/bundler/vendor-cache-types.ts",
|
|
@@ -71,7 +107,7 @@
|
|
|
71
107
|
"./middleware/bridge": "./src/middleware/bridge.ts",
|
|
72
108
|
"./client/hydrate": "./src/client/hydrate.ts",
|
|
73
109
|
"./runtime/fast-refresh-types": "./src/runtime/fast-refresh-types.ts",
|
|
74
|
-
"
|
|
110
|
+
"./components/Image": "./src/components/Image.tsx"
|
|
75
111
|
},
|
|
76
112
|
"files": [
|
|
77
113
|
"src/**/*"
|
package/src/a11y/run-audit.ts
CHANGED
|
@@ -66,7 +66,7 @@ interface AxeRunResult {
|
|
|
66
66
|
/** Minimal structural typing for the DOM provider handle. */
|
|
67
67
|
interface DomProvider {
|
|
68
68
|
kind: "jsdom" | "happy-dom";
|
|
69
|
-
fromHtml(html: string, url: string): Promise<{ window: unknown; dispose: () => void }>;
|
|
69
|
+
fromHtml(html: string, url: string): Promise<{ window: unknown; dispose: () => Promise<void> }>;
|
|
70
70
|
}
|
|
71
71
|
|
|
72
72
|
const DEFAULT_MAX_FILES = 500;
|
|
@@ -139,7 +139,7 @@ async function resolveDomProvider(options: RunAuditOptions): Promise<DomProvider
|
|
|
139
139
|
const window = (instance as { window: unknown }).window;
|
|
140
140
|
return {
|
|
141
141
|
window,
|
|
142
|
-
dispose() {
|
|
142
|
+
async dispose() {
|
|
143
143
|
const w = window as { close?: () => void };
|
|
144
144
|
if (typeof w.close === "function") {
|
|
145
145
|
try { w.close(); } catch { /* no-op */ }
|
|
@@ -170,9 +170,9 @@ async function resolveDomProvider(options: RunAuditOptions): Promise<DomProvider
|
|
|
170
170
|
window.document.close();
|
|
171
171
|
return {
|
|
172
172
|
window,
|
|
173
|
-
dispose() {
|
|
173
|
+
async dispose() {
|
|
174
174
|
if (typeof window.close === "function") {
|
|
175
|
-
try { window.close(); } catch { /* no-op */ }
|
|
175
|
+
try { await window.close(); } catch { /* no-op */ }
|
|
176
176
|
}
|
|
177
177
|
},
|
|
178
178
|
};
|
|
@@ -235,7 +235,7 @@ async function auditFile(
|
|
|
235
235
|
}
|
|
236
236
|
|
|
237
237
|
const pageUrl = "file://" + absFile.replace(/\\/g, "/");
|
|
238
|
-
let handle: { window: unknown; dispose: () => void };
|
|
238
|
+
let handle: { window: unknown; dispose: () => void | Promise<void> };
|
|
239
239
|
try {
|
|
240
240
|
handle = await dom.fromHtml(html, pageUrl);
|
|
241
241
|
} catch {
|
|
@@ -265,7 +265,7 @@ async function auditFile(
|
|
|
265
265
|
} catch {
|
|
266
266
|
return null;
|
|
267
267
|
} finally {
|
|
268
|
-
handle.dispose();
|
|
268
|
+
await handle.dispose();
|
|
269
269
|
}
|
|
270
270
|
}
|
|
271
271
|
|
package/src/brain/credentials.ts
CHANGED
|
@@ -28,7 +28,7 @@
|
|
|
28
28
|
* expires_at) — `CredentialStore` serializes it as a JSON string.
|
|
29
29
|
*/
|
|
30
30
|
|
|
31
|
-
import { promises as fs
|
|
31
|
+
import { promises as fs } from "node:fs";
|
|
32
32
|
import os from "node:os";
|
|
33
33
|
import path from "node:path";
|
|
34
34
|
|
|
@@ -157,7 +157,7 @@ async function runWithStdin(
|
|
|
157
157
|
stdout: "pipe",
|
|
158
158
|
stderr: "pipe",
|
|
159
159
|
});
|
|
160
|
-
proc.stdin.write(stdin);
|
|
160
|
+
await proc.stdin.write(stdin);
|
|
161
161
|
await proc.stdin.end();
|
|
162
162
|
const [stdout, exitCode] = await Promise.all([
|
|
163
163
|
new Response(proc.stdout).text(),
|
|
@@ -30,7 +30,15 @@
|
|
|
30
30
|
* - stdout: JSON blob terminated by `\n`:
|
|
31
31
|
* { "success": boolean,
|
|
32
32
|
* "errors": string[],
|
|
33
|
-
* "manifest": {
|
|
33
|
+
* "manifest": {
|
|
34
|
+
* shared: {
|
|
35
|
+
* runtime?: string;
|
|
36
|
+
* vendor?: string;
|
|
37
|
+
* router?: string;
|
|
38
|
+
* fastRefresh?: { runtime: string; glue: string };
|
|
39
|
+
* };
|
|
40
|
+
* bundles: Record<string, unknown>;
|
|
41
|
+
* }
|
|
34
42
|
* }
|
|
35
43
|
* Only the fields tests consume are serialized; the in-memory manifest
|
|
36
44
|
* is also persisted at `<rootDir>/.mandu/manifest.json` by the build.
|
|
@@ -83,8 +91,12 @@ try {
|
|
|
83
91
|
errors: result.errors,
|
|
84
92
|
manifest: {
|
|
85
93
|
shared: {
|
|
94
|
+
runtime: result.manifest.shared?.runtime ?? null,
|
|
95
|
+
vendor: result.manifest.shared?.vendor ?? null,
|
|
96
|
+
router: result.manifest.shared?.router ?? null,
|
|
86
97
|
fastRefresh: result.manifest.shared?.fastRefresh ?? null,
|
|
87
98
|
},
|
|
99
|
+
bundles: result.manifest.bundles ?? {},
|
|
88
100
|
},
|
|
89
101
|
}) + "\n",
|
|
90
102
|
);
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type * as __ManduHmrTypesTypes0 from "../hmr-types";
|
|
1
2
|
/**
|
|
2
3
|
* Phase 7.0 R2 Agent D — Extended file watch tests.
|
|
3
4
|
*
|
|
@@ -702,8 +703,8 @@ describe("Phase 7.0 R2 Agent D — CoalescedChange kind surface", () => {
|
|
|
702
703
|
|
|
703
704
|
// Literal assignments — if the union shrinks, TS errors in this
|
|
704
705
|
// file before the suite even runs.
|
|
705
|
-
const a:
|
|
706
|
-
const b:
|
|
706
|
+
const a: __ManduHmrTypesTypes0.CoalescedChange["kind"] = "config-reload";
|
|
707
|
+
const b: __ManduHmrTypesTypes0.CoalescedChange["kind"] = "resource-regen";
|
|
707
708
|
expect(a).toBe("config-reload");
|
|
708
709
|
expect(b).toBe("resource-regen");
|
|
709
710
|
});
|
|
@@ -541,18 +541,16 @@ describe.skipIf(process.env.MANDU_SKIP_BUNDLER_TESTS === "1")(
|
|
|
541
541
|
// 7-parallel shim fan-out fail with `AggregateError: Bundle failed`
|
|
542
542
|
// ~100 % of the time for the affected shim(s). A subprocess has a
|
|
543
543
|
// clean module graph.
|
|
544
|
-
const
|
|
545
|
-
const
|
|
546
|
-
const
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
proc.on("close", () => resolve(buf));
|
|
555
|
-
});
|
|
544
|
+
const runner = path.join(import.meta.dir, "build-runner.ts");
|
|
545
|
+
const cwd = path.resolve(import.meta.dir, "..", "..", "..");
|
|
546
|
+
const proc = Bun.spawn([process.execPath, "run", runner, rootDir], {
|
|
547
|
+
cwd,
|
|
548
|
+
stdin: "ignore",
|
|
549
|
+
stdout: "pipe",
|
|
550
|
+
stderr: "inherit",
|
|
551
|
+
});
|
|
552
|
+
const out = await new Response(proc.stdout).text();
|
|
553
|
+
await proc.exited;
|
|
556
554
|
const jsonLine =
|
|
557
555
|
out
|
|
558
556
|
.split(/\r?\n/)
|
|
@@ -2,7 +2,6 @@ import { afterAll, beforeAll, describe, expect, test } from "bun:test";
|
|
|
2
2
|
import { mkdtemp, mkdir, readFile, rm, writeFile } from "fs/promises";
|
|
3
3
|
import path from "path";
|
|
4
4
|
import { pathToFileURL } from "url";
|
|
5
|
-
import { spawn } from "node:child_process";
|
|
6
5
|
|
|
7
6
|
// 모든 테스트가 하나의 빌드 결과를 공유 — 병렬 Bun.build 충돌 방지
|
|
8
7
|
let rootDir: string;
|
|
@@ -34,40 +33,38 @@ async function runBuildInSubprocess(root: string): Promise<{
|
|
|
34
33
|
"__tests__",
|
|
35
34
|
"build-runner.ts",
|
|
36
35
|
);
|
|
37
|
-
|
|
38
|
-
const proc = spawn(process.execPath,
|
|
36
|
+
try {
|
|
37
|
+
const proc = Bun.spawn([process.execPath, "run", runner, root], {
|
|
39
38
|
cwd: path.resolve(import.meta.dir, "..", ".."),
|
|
40
|
-
|
|
39
|
+
stdin: "ignore",
|
|
40
|
+
stdout: "pipe",
|
|
41
|
+
stderr: "inherit",
|
|
41
42
|
});
|
|
42
|
-
|
|
43
|
-
proc.
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
const
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
proc.on("error", (err) => {
|
|
68
|
-
resolve({ success: false, errors: [`spawn failed: ${String(err)}`] });
|
|
69
|
-
});
|
|
70
|
-
});
|
|
43
|
+
const out = await new Response(proc.stdout).text();
|
|
44
|
+
await proc.exited;
|
|
45
|
+
|
|
46
|
+
// Find the final JSON line — the runner may log Mandu dev banners
|
|
47
|
+
// (e.g. "[Mandu] DevTools …") before emitting the payload. The
|
|
48
|
+
// contract is: last non-empty line is the JSON blob.
|
|
49
|
+
const lines = out.split(/\r?\n/).filter((l) => l.trim().length > 0);
|
|
50
|
+
const last = lines[lines.length - 1] ?? "";
|
|
51
|
+
try {
|
|
52
|
+
const parsed = JSON.parse(last);
|
|
53
|
+
return {
|
|
54
|
+
success: parsed.success === true,
|
|
55
|
+
errors: Array.isArray(parsed.errors) ? parsed.errors : [],
|
|
56
|
+
};
|
|
57
|
+
} catch (e) {
|
|
58
|
+
return {
|
|
59
|
+
success: false,
|
|
60
|
+
errors: [
|
|
61
|
+
`build-runner output could not be parsed as JSON: ${String(e)}\nLast stdout line: ${last}`,
|
|
62
|
+
],
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
} catch (err) {
|
|
66
|
+
return { success: false, errors: [`spawn failed: ${String(err)}`] };
|
|
67
|
+
}
|
|
71
68
|
}
|
|
72
69
|
|
|
73
70
|
beforeAll(async () => {
|
package/src/bundler/build.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type * as __ManduPluginsReactCompilerTypes0 from "./plugins/react-compiler";
|
|
1
2
|
/**
|
|
2
3
|
* Mandu Client Bundler 📦
|
|
3
4
|
* Bun.build 기반 클라이언트 번들 빌드
|
|
@@ -69,7 +70,7 @@ function manduClientPlugins(options: BundlerOptions): BunPlugin[] {
|
|
|
69
70
|
if (options.reactCompiler?.enabled !== true) return base;
|
|
70
71
|
// Lazy import to avoid pulling the react-compiler module into every
|
|
71
72
|
// build graph — SSR / non-client paths never touch this branch.
|
|
72
|
-
const { reactCompiler } = require("./plugins/react-compiler") as typeof
|
|
73
|
+
const { reactCompiler } = require("./plugins/react-compiler") as typeof __ManduPluginsReactCompilerTypes0;
|
|
73
74
|
return [
|
|
74
75
|
...base,
|
|
75
76
|
reactCompiler({
|
package/src/bundler/dev.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import type * as __ManduContentCollectionTypes0 from "../content/collection";
|
|
1
2
|
/**
|
|
2
3
|
* Mandu Dev Bundler 🔥
|
|
3
4
|
* 개발 모드 번들링 + HMR (Hot Module Replacement)
|
|
4
5
|
*/
|
|
5
6
|
|
|
6
|
-
import type { RoutesManifest
|
|
7
|
+
import type { RoutesManifest } from "../spec/schema";
|
|
7
8
|
import { buildClientBundles } from "./build";
|
|
8
9
|
import type { BundleResult } from "./types";
|
|
9
10
|
import { PORTS, TIMEOUTS } from "../constants";
|
|
@@ -1086,7 +1087,7 @@ export async function startDevBundler(options: DevBundlerOptions): Promise<DevBu
|
|
|
1086
1087
|
const handleContentChange = async (files: readonly string[]): Promise<void> => {
|
|
1087
1088
|
if (files.length === 0) return;
|
|
1088
1089
|
try {
|
|
1089
|
-
const mod: typeof
|
|
1090
|
+
const mod: typeof __ManduContentCollectionTypes0 = await import("../content/collection");
|
|
1090
1091
|
if (typeof mod.invalidateAllCollections === "function") {
|
|
1091
1092
|
mod.invalidateAllCollections();
|
|
1092
1093
|
}
|
|
@@ -1447,19 +1448,19 @@ export async function startDevBundler(options: DevBundlerOptions): Promise<DevBu
|
|
|
1447
1448
|
// SSR 모듈 변경 감지 (page.tsx, layout.tsx) — #151
|
|
1448
1449
|
if (onSSRChange && serverModuleSet.has(normalizedPath)) {
|
|
1449
1450
|
console.log(`\n🔄 SSR file changed: ${path.basename(changedFile)}`);
|
|
1450
|
-
// Refresh the changed file's imports so the reverse graph
|
|
1451
|
-
// tracks refactors (a new import added to an SSR module).
|
|
1452
|
-
await refreshReverseGraphEdges(path.resolve(rootDir, changedFile));
|
|
1453
|
-
onSSRChange(normalizedPath);
|
|
1454
|
-
return;
|
|
1455
|
-
}
|
|
1451
|
+
// Refresh the changed file's imports so the reverse graph
|
|
1452
|
+
// tracks refactors (a new import added to an SSR module).
|
|
1453
|
+
await refreshReverseGraphEdges(path.resolve(rootDir, changedFile));
|
|
1454
|
+
await Promise.resolve(onSSRChange(normalizedPath));
|
|
1455
|
+
return;
|
|
1456
|
+
}
|
|
1456
1457
|
// API 모듈 변경 감지 (route.ts)
|
|
1457
1458
|
if (onAPIChange && apiModuleSet.has(normalizedPath)) {
|
|
1458
|
-
console.log(`\n🔄 API route changed: ${path.basename(changedFile)}`);
|
|
1459
|
-
await refreshReverseGraphEdges(path.resolve(rootDir, changedFile));
|
|
1460
|
-
onAPIChange(normalizedPath);
|
|
1461
|
-
return;
|
|
1462
|
-
}
|
|
1459
|
+
console.log(`\n🔄 API route changed: ${path.basename(changedFile)}`);
|
|
1460
|
+
await refreshReverseGraphEdges(path.resolve(rootDir, changedFile));
|
|
1461
|
+
await Promise.resolve(onAPIChange(normalizedPath));
|
|
1462
|
+
return;
|
|
1463
|
+
}
|
|
1463
1464
|
// Phase 7.0 R2 Agent D — route middleware change.
|
|
1464
1465
|
// `app/**/middleware.ts` isn't in apiModuleSet (not registered as a
|
|
1465
1466
|
// route handler) but reuses the API reload path: the underlying
|
|
@@ -1467,11 +1468,11 @@ export async function startDevBundler(options: DevBundlerOptions): Promise<DevBu
|
|
|
1467
1468
|
// import chain. Falls through to `onAPIChange` so the CLI can
|
|
1468
1469
|
// reuse its existing `handleAPIChange` plumbing.
|
|
1469
1470
|
if (onAPIChange && isRouteMiddlewareFile(normalizedPath)) {
|
|
1470
|
-
console.log(`\n🔄 Middleware changed: ${path.basename(changedFile)}`);
|
|
1471
|
-
await refreshReverseGraphEdges(path.resolve(rootDir, changedFile));
|
|
1472
|
-
onAPIChange(normalizedPath);
|
|
1473
|
-
return;
|
|
1474
|
-
}
|
|
1471
|
+
console.log(`\n🔄 Middleware changed: ${path.basename(changedFile)}`);
|
|
1472
|
+
await refreshReverseGraphEdges(path.resolve(rootDir, changedFile));
|
|
1473
|
+
await Promise.resolve(onAPIChange(normalizedPath));
|
|
1474
|
+
return;
|
|
1475
|
+
}
|
|
1475
1476
|
// #189 — reverse import-graph fallback.
|
|
1476
1477
|
//
|
|
1477
1478
|
// The changed file matched none of our direct dispatch sets.
|
|
@@ -2188,10 +2189,10 @@ export function createHMRServer(
|
|
|
2188
2189
|
} catch {
|
|
2189
2190
|
// 무시
|
|
2190
2191
|
}
|
|
2191
|
-
}
|
|
2192
|
-
clients.clear();
|
|
2193
|
-
server.stop();
|
|
2194
|
-
},
|
|
2192
|
+
}
|
|
2193
|
+
clients.clear();
|
|
2194
|
+
void server.stop();
|
|
2195
|
+
},
|
|
2195
2196
|
setRestartHandler: (handler: () => Promise<void>) => {
|
|
2196
2197
|
restartHandler = handler;
|
|
2197
2198
|
},
|
package/src/bundler/prerender.ts
CHANGED
|
@@ -555,6 +555,46 @@ export async function prerenderRoutes(
|
|
|
555
555
|
}
|
|
556
556
|
}
|
|
557
557
|
|
|
558
|
+
// 5.5. Issue #254 — emit `404.html` at the dist root when the project
|
|
559
|
+
// declares an `app/not-found.tsx`. Static hosts (Vercel, Netlify,
|
|
560
|
+
// Cloudflare Pages, Firebase Hosting) auto-serve `dist/404.html` for
|
|
561
|
+
// unmatched URLs; without this, visitors hit the platform's plain-
|
|
562
|
+
// text default.
|
|
563
|
+
//
|
|
564
|
+
// The probe path is multi-segment + sentinel-prefixed so it doesn't
|
|
565
|
+
// collide with single-segment dynamic routes (e.g. `[lang]`). Catch-
|
|
566
|
+
// all routes at the root (`[...slug]`) WILL still catch this — those
|
|
567
|
+
// projects own their own 404 surface anyway, so the probe falls
|
|
568
|
+
// through and we surface a clear warning instead of writing a wrong
|
|
569
|
+
// 404.html.
|
|
570
|
+
if (await hasAppNotFound(rootDir)) {
|
|
571
|
+
const probePath =
|
|
572
|
+
"/__mandu_not_found_probe_38f17a2c__/__inner_38f17a2c__";
|
|
573
|
+
try {
|
|
574
|
+
const request = new Request(`http://localhost${probePath}`);
|
|
575
|
+
const response = await fetchHandler(request);
|
|
576
|
+
if (response.status === 404) {
|
|
577
|
+
const html = await response.text();
|
|
578
|
+
const filePath = path.join(outputDir, "404.html");
|
|
579
|
+
await fs.writeFile(filePath, html, "utf-8");
|
|
580
|
+
pages.push({ path: "/404", size: html.length, duration: 0 });
|
|
581
|
+
// Not added to pageIndex — `/404` is a host-level fallback,
|
|
582
|
+
// not a routable page. The runtime keeps using its registered
|
|
583
|
+
// handler for live SSR.
|
|
584
|
+
} else {
|
|
585
|
+
// A catch-all route matched the probe. Skip the 404 emit and
|
|
586
|
+
// tell the user how to handle it themselves — emitting the
|
|
587
|
+
// catch-all body as 404.html would be wrong.
|
|
588
|
+
errors.push(
|
|
589
|
+
`[404.html] not emitted: probe path matched a catch-all route (HTTP ${response.status}). ` +
|
|
590
|
+
`Add an explicit 404 page or a sentinel route, or write \`dist/404.html\` from a postbuild script.`,
|
|
591
|
+
);
|
|
592
|
+
}
|
|
593
|
+
} catch (err) {
|
|
594
|
+
errors.push(`[404.html] ${describeError(err)}`);
|
|
595
|
+
}
|
|
596
|
+
}
|
|
597
|
+
|
|
558
598
|
// 6. Emit runtime index.
|
|
559
599
|
if (writeIndex) {
|
|
560
600
|
const indexContents: PrerenderIndex = {
|
|
@@ -874,3 +914,26 @@ function describeError(error: unknown): string {
|
|
|
874
914
|
if (error instanceof Error) return error.message;
|
|
875
915
|
return String(error);
|
|
876
916
|
}
|
|
917
|
+
|
|
918
|
+
/**
|
|
919
|
+
* Issue #254 — true when the project ships an `app/not-found.tsx`
|
|
920
|
+
* (or one of its non-TSX variants). Used by the prerender step to
|
|
921
|
+
* decide whether to emit a `404.html` fallback at the dist root.
|
|
922
|
+
*/
|
|
923
|
+
async function hasAppNotFound(rootDir: string): Promise<boolean> {
|
|
924
|
+
const candidates = [
|
|
925
|
+
"app/not-found.tsx",
|
|
926
|
+
"app/not-found.ts",
|
|
927
|
+
"app/not-found.jsx",
|
|
928
|
+
"app/not-found.js",
|
|
929
|
+
];
|
|
930
|
+
for (const rel of candidates) {
|
|
931
|
+
try {
|
|
932
|
+
await fs.access(path.join(rootDir, rel));
|
|
933
|
+
return true;
|
|
934
|
+
} catch {
|
|
935
|
+
// try next
|
|
936
|
+
}
|
|
937
|
+
}
|
|
938
|
+
return false;
|
|
939
|
+
}
|
|
@@ -163,7 +163,8 @@ describe("safeBuild", () => {
|
|
|
163
163
|
// of landing inside it). The strict assertion is still `peak <= max`.
|
|
164
164
|
let stop = false;
|
|
165
165
|
const sample = async () => {
|
|
166
|
-
|
|
166
|
+
for (;;) {
|
|
167
|
+
if (stop) break;
|
|
167
168
|
const { active } = _getConcurrencyState();
|
|
168
169
|
if (active > peak) peak = active;
|
|
169
170
|
samples++;
|
package/src/bundler/types.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type * as __ManduBunTypes0 from "bun";
|
|
2
|
+
import type * as __ManduPluginsHooksTypes1 from "../plugins/hooks";
|
|
1
3
|
/**
|
|
2
4
|
* Mandu Bundler Types
|
|
3
5
|
*/
|
|
@@ -187,13 +189,13 @@ export interface BundlerOptions {
|
|
|
187
189
|
* via `runDefineBundlerPlugin()` and fed into every `safeBuild(...)`
|
|
188
190
|
* call-site; library users may populate it directly.
|
|
189
191
|
*/
|
|
190
|
-
pluginBundlerPlugins?: readonly
|
|
192
|
+
pluginBundlerPlugins?: readonly __ManduBunTypes0.BunPlugin[];
|
|
191
193
|
|
|
192
194
|
/**
|
|
193
195
|
* Phase 18.τ — fire per-build `onBundleComplete(stats)` hook after
|
|
194
196
|
* `buildClientBundles()` finishes. Paired with `configHooks` for
|
|
195
197
|
* config-level hooks. Zero-overhead when both are omitted.
|
|
196
198
|
*/
|
|
197
|
-
plugins?: readonly
|
|
198
|
-
configHooks?: Partial<
|
|
199
|
+
plugins?: readonly __ManduPluginsHooksTypes1.ManduPlugin[];
|
|
200
|
+
configHooks?: Partial<__ManduPluginsHooksTypes1.ManduHooks>;
|
|
199
201
|
}
|
package/src/change/history.ts
CHANGED
|
@@ -114,7 +114,7 @@ export async function pruneHistory(
|
|
|
114
114
|
/**
|
|
115
115
|
* History 설정 로드 (향후 mandu.config.json에서 로드)
|
|
116
116
|
*/
|
|
117
|
-
export async function loadHistoryConfig(
|
|
117
|
+
export async function loadHistoryConfig(_rootDir: string): Promise<HistoryConfig> {
|
|
118
118
|
// 향후 mandu.config.json에서 로드하도록 확장
|
|
119
119
|
// 현재는 기본값 반환
|
|
120
120
|
return DEFAULT_HISTORY_CONFIG;
|
package/src/change/snapshot.ts
CHANGED
|
@@ -13,11 +13,19 @@ import { validateAndReport } from "../config";
|
|
|
13
13
|
const MANDU_DIR = ".mandu";
|
|
14
14
|
const SPEC_DIR = "spec";
|
|
15
15
|
const MANIFEST_FILE = "routes.manifest.json";
|
|
16
|
-
const SLOTS_DIR = "slots";
|
|
17
|
-
const HISTORY_DIR = "history";
|
|
18
|
-
const SNAPSHOTS_DIR = "snapshots";
|
|
19
|
-
|
|
20
|
-
|
|
16
|
+
const SLOTS_DIR = "slots";
|
|
17
|
+
const HISTORY_DIR = "history";
|
|
18
|
+
const SNAPSHOTS_DIR = "snapshots";
|
|
19
|
+
|
|
20
|
+
function reverseCopy<T>(items: readonly T[]): T[] {
|
|
21
|
+
const reversed: T[] = [];
|
|
22
|
+
for (let index = items.length - 1; index >= 0; index -= 1) {
|
|
23
|
+
reversed.push(items[index] as T);
|
|
24
|
+
}
|
|
25
|
+
return reversed;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/**
|
|
21
29
|
* 스냅샷 ID 생성 (YYYYMMDD-HHmmss-xxx)
|
|
22
30
|
*/
|
|
23
31
|
function generateSnapshotId(): string {
|
|
@@ -250,10 +258,11 @@ export async function listSnapshotIds(rootDir: string): Promise<string[]> {
|
|
|
250
258
|
})
|
|
251
259
|
);
|
|
252
260
|
|
|
253
|
-
return
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
261
|
+
return reverseCopy(
|
|
262
|
+
entries
|
|
263
|
+
.map((entry) => entry.replace(".snapshot.json", ""))
|
|
264
|
+
.sort()
|
|
265
|
+
); // 최신 순
|
|
257
266
|
} catch {
|
|
258
267
|
return [];
|
|
259
268
|
}
|
package/src/client/Link.tsx
CHANGED
|
@@ -88,10 +88,10 @@ export function Link({
|
|
|
88
88
|
} catch {
|
|
89
89
|
return;
|
|
90
90
|
}
|
|
91
|
-
|
|
92
|
-
// Client-side 네비게이션
|
|
93
|
-
event.preventDefault();
|
|
94
|
-
navigate(href, { replace, scroll });
|
|
91
|
+
|
|
92
|
+
// Client-side 네비게이션
|
|
93
|
+
event.preventDefault();
|
|
94
|
+
void navigate(href, { replace, scroll });
|
|
95
95
|
},
|
|
96
96
|
[href, replace, scroll, onClick]
|
|
97
97
|
);
|
|
@@ -101,11 +101,11 @@ export function Link({
|
|
|
101
101
|
if (!shouldPrefetch || prefetchedRef.current) return;
|
|
102
102
|
|
|
103
103
|
try {
|
|
104
|
-
const url = new URL(href, window.location.origin);
|
|
105
|
-
if (url.origin === window.location.origin) {
|
|
106
|
-
prefetch(href);
|
|
107
|
-
prefetchedRef.current = true;
|
|
108
|
-
}
|
|
104
|
+
const url = new URL(href, window.location.origin);
|
|
105
|
+
if (url.origin === window.location.origin) {
|
|
106
|
+
void prefetch(href);
|
|
107
|
+
prefetchedRef.current = true;
|
|
108
|
+
}
|
|
109
109
|
} catch {
|
|
110
110
|
// 무시
|
|
111
111
|
}
|
package/src/client/globals.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type * as __ManduReactTypes0 from "react";
|
|
1
2
|
/**
|
|
2
3
|
* Mandu 전역 타입 선언
|
|
3
4
|
* 클라이언트 측 전역 상태의 타입 정의
|
|
@@ -37,7 +38,7 @@ declare global {
|
|
|
37
38
|
__MANDU_ROOTS__?: Map<string, Root>;
|
|
38
39
|
|
|
39
40
|
/** React 인스턴스 공유 */
|
|
40
|
-
__MANDU_REACT__?: typeof
|
|
41
|
+
__MANDU_REACT__?: typeof __ManduReactTypes0;
|
|
41
42
|
|
|
42
43
|
/**
|
|
43
44
|
* Issue #193 — global SPA navigation toggle.
|
|
@@ -58,4 +59,4 @@ declare global {
|
|
|
58
59
|
}
|
|
59
60
|
}
|
|
60
61
|
|
|
61
|
-
|
|
62
|
+
|