@mandujs/core 0.53.2 โ 0.54.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +654 -654
- package/package.json +2 -2
- package/src/a11y/__tests__/run-audit.test.ts +333 -333
- package/src/a11y/fix-hints.ts +76 -76
- package/src/a11y/index.ts +18 -18
- package/src/a11y/run-audit.ts +394 -394
- package/src/a11y/types.ts +125 -125
- package/src/auth/__tests__/login.test.ts +1 -1
- package/src/auth/__tests__/password.test.ts +122 -122
- package/src/auth/__tests__/tokens.test.ts +274 -274
- package/src/auth/__tests__/verification.test.ts +274 -274
- package/src/auth/index.ts +76 -76
- package/src/auth/login.ts +225 -225
- package/src/auth/password.ts +120 -120
- package/src/auth/reset.ts +243 -243
- package/src/auth/tokens.ts +612 -612
- package/src/auth/verification.ts +253 -253
- package/src/brain/__tests__/redactor.test.ts +94 -94
- package/src/brain/adapters/__tests__/_helpers.ts +83 -83
- package/src/brain/adapters/__tests__/anthropic-oauth.test.ts +196 -196
- package/src/brain/adapters/__tests__/chatgpt-auth.test.ts +193 -193
- package/src/brain/adapters/__tests__/openai-oauth.test.ts +209 -209
- package/src/brain/adapters/__tests__/resolver.test.ts +143 -143
- package/src/brain/adapters/anthropic-oauth.ts +1 -1
- package/src/brain/adapters/chatgpt-auth.ts +300 -300
- package/src/brain/adapters/index.ts +319 -319
- package/src/brain/adapters/oauth-flow.ts +439 -439
- package/src/brain/consent.ts +240 -240
- package/src/brain/credentials.ts +396 -396
- package/src/bundler/__tests__/build-runner.ts +113 -113
- package/src/bundler/__tests__/dev-reliability.test.ts +619 -619
- package/src/bundler/__tests__/extended-watch.test.ts +711 -711
- package/src/bundler/__tests__/fast-refresh.test.ts +10 -10
- package/src/bundler/__tests__/hdr.test.ts +353 -353
- package/src/bundler/__tests__/hmr-client.test.ts +532 -532
- package/src/bundler/__tests__/manifest-schema.test.ts +266 -266
- package/src/bundler/__tests__/prod-smoke.test.ts +138 -138
- package/src/bundler/__tests__/reverse-import-graph.test.ts +519 -519
- package/src/bundler/__tests__/slot-dispatch.test.ts +573 -573
- package/src/bundler/__tests__/url-cap-and-slot-regex.test.ts +286 -286
- package/src/bundler/__tests__/vendor-cache.test.ts +455 -455
- package/src/bundler/budget.ts +404 -404
- package/src/bundler/build.test.ts +179 -179
- package/src/bundler/build.ts +55 -55
- package/src/bundler/dev.ts +42 -42
- package/src/bundler/manifest-schema.ts +301 -301
- package/src/bundler/plugins/__tests__/block-generated-imports.test.ts +263 -263
- package/src/bundler/plugins/__tests__/react-compiler-config.test.ts +83 -83
- package/src/bundler/plugins/__tests__/react-compiler-lint.test.ts +110 -110
- package/src/bundler/plugins/block-generated-imports.ts +155 -155
- package/src/bundler/plugins/index.ts +83 -83
- package/src/bundler/plugins/react-compiler-config.ts +108 -108
- package/src/bundler/plugins/react-compiler-lint.ts +253 -253
- package/src/bundler/plugins/react-compiler.ts +162 -162
- package/src/bundler/reverse-import-graph.ts +339 -339
- package/src/bundler/safe-build.test.ts +201 -201
- package/src/bundler/safe-build.ts +103 -103
- package/src/bundler/scenario-matrix.ts +229 -229
- package/src/bundler/types.ts +10 -10
- package/src/bundler/vendor-cache-types.ts +130 -130
- package/src/bundler/vendor-cache.ts +526 -526
- package/src/change/snapshot.ts +18 -18
- package/src/client/Form.tsx +105 -105
- package/src/client/Link.tsx +9 -9
- package/src/client/__tests__/use-sse.test.ts +153 -153
- package/src/client/globals.ts +1 -1
- package/src/client/hooks.ts +362 -362
- package/src/client/hydrate.ts +340 -340
- package/src/client/prefetch-helper.ts +55 -55
- package/src/client/router.ts +11 -11
- package/src/client/runtime.ts +47 -47
- package/src/client/serialize.ts +404 -404
- package/src/client/use-fetch.ts +6 -6
- package/src/client/use-head.ts +197 -197
- package/src/client/use-sse.ts +378 -378
- package/src/client/window-state.ts +101 -101
- package/src/components/Image.tsx +162 -162
- package/src/config/validate.ts +3 -3
- package/src/config/watcher.ts +311 -311
- package/src/constants.ts +40 -40
- package/src/content/collection.ts +8 -8
- package/src/content/content-layer.ts +7 -7
- package/src/content/data-store.ts +245 -245
- package/src/content/frontmatter.ts +189 -189
- package/src/content/loader-context.ts +171 -171
- package/src/content/loaders/api.ts +216 -216
- package/src/content/loaders/file.ts +172 -172
- package/src/content/loaders/glob.ts +253 -253
- package/src/content/loaders/index.ts +34 -34
- package/src/content/loaders/types.ts +137 -137
- package/src/content/meta-store.ts +209 -209
- package/src/content/prebuild.test.ts +571 -571
- package/src/content/prebuild.ts +636 -636
- package/src/content/schema.ts +20 -20
- package/src/content/sidebar.ts +630 -630
- package/src/content/slug.ts +110 -110
- package/src/content/types.ts +282 -282
- package/src/content/watcher.ts +135 -135
- package/src/contract/client-safe.test.ts +42 -42
- package/src/contract/client-safe.ts +114 -114
- package/src/contract/define.ts +11 -11
- package/src/contract/index.ts +1 -1
- package/src/contract/normalize.test.ts +276 -276
- package/src/contract/normalize.ts +410 -410
- package/src/contract/registry.test.ts +206 -206
- package/src/contract/route-helpers.ts +1 -1
- package/src/contract/rpc.ts +443 -443
- package/src/contract/schema.ts +48 -48
- package/src/contract/types.ts +58 -58
- package/src/db/__tests__/db.test.ts +482 -482
- package/src/db/index.ts +138 -138
- package/src/db/migrations/history-table.ts +345 -345
- package/src/db/migrations/index.ts +3 -3
- package/src/db/migrations/lock.ts +324 -324
- package/src/db/migrations/runner.ts +650 -650
- package/src/deploy/cache.ts +140 -140
- package/src/deploy/compile/vercel.ts +344 -344
- package/src/deploy/index.ts +87 -87
- package/src/deploy/inference/brain.ts +268 -268
- package/src/deploy/inference/context.ts +182 -182
- package/src/deploy/inference/filling-extract.ts +245 -245
- package/src/deploy/inference/heuristic.ts +182 -182
- package/src/deploy/intent.ts +173 -173
- package/src/deploy/plan.ts +178 -178
- package/src/design/__tests__/agents-link.test.ts +109 -109
- package/src/design/__tests__/extract-patch-diff.test.ts +265 -265
- package/src/design/__tests__/lint.test.ts +110 -110
- package/src/design/__tests__/parser.test.ts +195 -195
- package/src/design/__tests__/tailwind-theme.test.ts +229 -229
- package/src/design/agents-link.ts +165 -165
- package/src/design/diff.ts +138 -138
- package/src/design/extract.ts +285 -285
- package/src/design/index.ts +102 -102
- package/src/design/lint.ts +209 -209
- package/src/design/parser.ts +555 -555
- package/src/design/patch.ts +241 -241
- package/src/design/scaffold.ts +147 -147
- package/src/design/tailwind-theme.ts +441 -441
- package/src/design/types.ts +210 -210
- package/src/desktop/__tests__/webview-fallback.test.ts +254 -254
- package/src/desktop/__tests__/window.test.ts +248 -248
- package/src/desktop/__tests__/worker.test.ts +266 -266
- package/src/desktop/index.ts +43 -43
- package/src/desktop/types.ts +158 -158
- package/src/desktop/worker.ts +180 -180
- package/src/dev-error-overlay/__tests__/overlay-injector.test.ts +241 -241
- package/src/dev-error-overlay/index.ts +30 -30
- package/src/dev-error-overlay/overlay-injector.ts +243 -243
- package/src/dev-error-overlay/overlay-styles.ts +52 -52
- package/src/dev-error-overlay/types.ts +66 -66
- package/src/devtools/ai/context-builder.ts +375 -375
- package/src/devtools/ai/index.ts +25 -25
- package/src/devtools/ai/mcp-connector.ts +25 -25
- package/src/devtools/client/catchers/error-catcher.ts +344 -344
- package/src/devtools/client/catchers/index.ts +18 -18
- package/src/devtools/client/components/index.ts +39 -39
- package/src/devtools/client/components/mandu-character.tsx +331 -331
- package/src/devtools/client/components/overlay.tsx +368 -368
- package/src/devtools/client/components/panel/errors-panel.tsx +259 -259
- package/src/devtools/client/components/panel/guard-panel.tsx +30 -30
- package/src/devtools/client/components/panel/islands-panel.tsx +320 -320
- package/src/devtools/client/components/panel/network-panel.tsx +291 -291
- package/src/devtools/client/components/panel/panel-container.tsx +500 -500
- package/src/devtools/client/components/panel/preview-panel.tsx +46 -46
- package/src/devtools/client/filters/context-filters.ts +282 -282
- package/src/devtools/client/filters/index.ts +16 -16
- package/src/devtools/client/index.ts +63 -63
- package/src/devtools/client/persistence.ts +335 -335
- package/src/devtools/hook/create-hook.ts +207 -207
- package/src/devtools/hook/index.ts +13 -13
- package/src/devtools/index.ts +439 -439
- package/src/devtools/init.ts +265 -265
- package/src/devtools/protocol.ts +237 -237
- package/src/devtools/server/index.ts +17 -17
- package/src/devtools/server/source-context.ts +450 -450
- package/src/devtools/types.ts +35 -35
- package/src/devtools/worker/index.ts +25 -25
- package/src/devtools/worker/redaction-worker.ts +233 -233
- package/src/devtools/worker/worker-manager.ts +410 -410
- package/src/diagnose/__tests__/checks.test.ts +451 -380
- package/src/diagnose/checks.ts +832 -720
- package/src/diagnose/index.ts +17 -16
- package/src/diagnose/run.ts +93 -91
- package/src/diagnose/types.ts +53 -53
- package/src/email/__tests__/email.test.ts +355 -355
- package/src/email/index.ts +282 -282
- package/src/email/smtp.ts +64 -64
- package/src/error/domains.ts +265 -265
- package/src/error/result.ts +60 -60
- package/src/error/types.ts +6 -6
- package/src/errors/extractor.ts +409 -409
- package/src/errors/index.ts +19 -19
- package/src/filling/__tests__/session-sqlite.test.ts +454 -454
- package/src/filling/auth.ts +308 -308
- package/src/filling/cookie-codec.ts +299 -299
- package/src/filling/deps.ts +265 -265
- package/src/filling/session-sqlite.ts +617 -617
- package/src/filling/sse.ts +5 -5
- package/src/filling/ws.ts +78 -78
- package/src/generator/index.ts +3 -3
- package/src/guard/__tests__/design-inline-class.test.ts +219 -219
- package/src/guard/__tests__/tsgolint-bridge.test.ts +347 -347
- package/src/guard/analyzer.ts +360 -360
- package/src/guard/auto-correct.ts +1 -1
- package/src/guard/contract-guard.ts +9 -9
- package/src/guard/define-rule.ts +243 -243
- package/src/guard/design-inline-class.ts +393 -393
- package/src/guard/file-type.test.ts +24 -24
- package/src/guard/graph.ts +1 -1
- package/src/guard/healing.ts +36 -36
- package/src/guard/presets/atomic.ts +70 -70
- package/src/guard/presets/clean.ts +77 -77
- package/src/guard/presets/fsd.ts +79 -79
- package/src/guard/presets/hexagonal.ts +68 -68
- package/src/guard/reporter.ts +442 -442
- package/src/guard/rule-presets.ts +379 -379
- package/src/guard/semantic-slots.ts +1 -1
- package/src/guard/suggestions.ts +358 -358
- package/src/guard/tsgolint-bridge.ts +512 -512
- package/src/guard/types.ts +348 -348
- package/src/guard/watcher.ts +405 -405
- package/src/i18n/define.ts +126 -126
- package/src/i18n/index.ts +52 -52
- package/src/i18n/message-registry.ts +173 -173
- package/src/i18n/types.ts +112 -112
- package/src/id/index.ts +105 -105
- package/src/index.ts +2 -2
- package/src/kitchen/api/agent-devtools-api.ts +779 -544
- package/src/kitchen/api/errors-grouping.ts +126 -0
- package/src/kitchen/kitchen-handler.ts +192 -63
- package/src/kitchen/kitchen-ui.ts +842 -464
- package/src/logging/index.ts +22 -22
- package/src/logging/transports.ts +365 -365
- package/src/middleware/bridge.ts +147 -147
- package/src/middleware/compose.ts +134 -134
- package/src/middleware/compress.ts +62 -62
- package/src/middleware/cors.ts +47 -47
- package/src/middleware/csrf.ts +328 -328
- package/src/middleware/define.ts +132 -132
- package/src/middleware/jwt.ts +134 -134
- package/src/middleware/logger.ts +58 -58
- package/src/middleware/oauth/__tests__/oauth.test.ts +1 -1
- package/src/middleware/oauth/index.ts +505 -505
- package/src/middleware/oauth/providers.ts +115 -115
- package/src/middleware/rate-limit/index.ts +522 -522
- package/src/middleware/rate-limit/sqlite-store.ts +382 -382
- package/src/middleware/scheduler-cron.ts +96 -96
- package/src/middleware/secure/__tests__/secure.test.ts +360 -360
- package/src/middleware/secure/csp.ts +193 -193
- package/src/middleware/session.ts +174 -174
- package/src/middleware/timeout.ts +55 -55
- package/src/observability/logger-adapter.ts +36 -36
- package/src/observability/sqlite-store.ts +254 -254
- package/src/openapi/generator.ts +1 -1
- package/src/openapi/openapi.test.ts +43 -43
- package/src/perf/__tests__/user-marks.test.ts +354 -354
- package/src/perf/index.ts +133 -133
- package/src/plugins/__tests__/lifecycle-integration.test.ts +272 -272
- package/src/plugins/__tests__/runner.test.ts +409 -409
- package/src/plugins/define.ts +124 -124
- package/src/plugins/examples/dep-check-plugin.ts +80 -80
- package/src/plugins/examples/prerender-cache-plugin.ts +111 -111
- package/src/plugins/examples/sitemap-plugin.ts +65 -65
- package/src/plugins/runner.ts +361 -361
- package/src/plugins/types.ts +368 -368
- package/src/report/index.ts +1 -1
- package/src/resource/__tests__/generator.test.ts +32 -32
- package/src/resource/ddl/__tests__/diff.test.ts +639 -639
- package/src/resource/ddl/__tests__/emit.test.ts +823 -823
- package/src/resource/ddl/__tests__/snapshot.test.ts +499 -499
- package/src/resource/ddl/emit.ts +559 -559
- package/src/resource/ddl/persistence-types.ts +218 -218
- package/src/resource/ddl/type-map.ts +223 -223
- package/src/resource/ddl/types.ts +232 -232
- package/src/resource/generator-repo.ts +630 -630
- package/src/resource/schema.ts +1 -1
- package/src/router/fs-patterns.test.ts +96 -96
- package/src/router/fs-routes.ts +6 -6
- package/src/routes/index.ts +74 -74
- package/src/routes/metadata-routes.ts +427 -427
- package/src/routes/types.ts +341 -341
- package/src/runtime/__tests__/error-boundary-redaction.test.ts +141 -141
- package/src/runtime/__tests__/hdr-client.test.ts +223 -223
- package/src/runtime/__tests__/http-errors.test.ts +117 -117
- package/src/runtime/__tests__/not-found.test.ts +152 -152
- package/src/runtime/adapter.ts +47 -47
- package/src/runtime/boundary.tsx +252 -252
- package/src/runtime/cache.ts +494 -494
- package/src/runtime/compose.ts +222 -222
- package/src/runtime/fast-refresh-runtime.ts +322 -322
- package/src/runtime/handler.ts +65 -65
- package/src/runtime/handlers.ts +300 -300
- package/src/runtime/http-errors.ts +113 -113
- package/src/runtime/image-handler.ts +1 -1
- package/src/runtime/lifecycle.ts +381 -381
- package/src/runtime/logger.test.ts +345 -345
- package/src/runtime/middleware.ts +264 -264
- package/src/runtime/not-found.ts +93 -93
- package/src/runtime/openapi-endpoint.ts +236 -236
- package/src/runtime/ppr.ts +74 -74
- package/src/runtime/registry.ts +171 -171
- package/src/runtime/router.ts +105 -105
- package/src/runtime/server.ts +67 -67
- package/src/runtime/shims.ts +48 -48
- package/src/runtime/ssr.ts +24 -31
- package/src/runtime/streaming-ssr.ts +32 -37
- package/src/runtime/trace.ts +144 -144
- package/src/scheduler/validate.ts +169 -169
- package/src/seo/index.ts +219 -219
- package/src/seo/integration/ssr.ts +306 -306
- package/src/seo/render/basic.ts +435 -435
- package/src/seo/render/index.ts +143 -143
- package/src/seo/render/jsonld.ts +539 -539
- package/src/seo/render/opengraph.ts +197 -197
- package/src/seo/render/robots.ts +116 -116
- package/src/seo/render/sitemap.ts +137 -137
- package/src/seo/render/twitter.ts +127 -127
- package/src/seo/resolve/opengraph.ts +143 -143
- package/src/seo/resolve/robots.ts +73 -73
- package/src/seo/resolve/title.ts +94 -94
- package/src/seo/resolve/twitter.ts +73 -73
- package/src/seo/resolve/url.ts +104 -104
- package/src/seo/routes/index.ts +290 -290
- package/src/seo/types.ts +588 -588
- package/src/slot/validator.ts +39 -39
- package/src/storage/s3/__tests__/s3.test.ts +479 -479
- package/src/storage/s3/index.ts +412 -412
- package/src/testing/__tests__/assertions.test.ts +632 -632
- package/src/testing/__tests__/reporter.test.ts +454 -454
- package/src/testing/assertions.ts +986 -986
- package/src/testing/db.ts +157 -157
- package/src/testing/mocks.ts +203 -203
- package/src/testing/session.ts +190 -190
- package/src/types/branded.ts +56 -56
- package/src/types/index.ts +1 -1
- package/src/utils/safe-io.ts +188 -188
- package/src/utils/string-safe.ts +298 -298
- package/src/watcher/watcher.ts +18 -18
package/src/runtime/handlers.ts
CHANGED
|
@@ -1,300 +1,300 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Manifest โ runtime registry wiring.
|
|
3
|
-
*
|
|
4
|
-
* Lives in `@mandujs/core/runtime` (not in `@mandujs/cli`) because it
|
|
5
|
-
* runs at *server* boot, not at build time. Deploy adapters generate
|
|
6
|
-
* SSR entry files that need to call this โ having it here means the
|
|
7
|
-
* generated entry imports a single, public package (`@mandujs/core`)
|
|
8
|
-
* instead of reaching into a CLI subpath that has no `exports` map.
|
|
9
|
-
*
|
|
10
|
-
* @module core/runtime/handlers
|
|
11
|
-
*/
|
|
12
|
-
import fs from "node:fs/promises";
|
|
13
|
-
import path from "node:path";
|
|
14
|
-
|
|
15
|
-
import {
|
|
16
|
-
registerApiHandler,
|
|
17
|
-
registerPageLoader,
|
|
18
|
-
registerPageHandler,
|
|
19
|
-
registerLayoutLoader,
|
|
20
|
-
registerNotFoundHandler,
|
|
21
|
-
registerMetadataHandler,
|
|
22
|
-
registerWSHandler,
|
|
23
|
-
type PageRegistration,
|
|
24
|
-
} from "./server";
|
|
25
|
-
import { registerManifest } from "./registry";
|
|
26
|
-
import { needsHydration, type RoutesManifest } from "../spec/schema";
|
|
27
|
-
|
|
28
|
-
type RouteModule = Record<string, unknown>;
|
|
29
|
-
|
|
30
|
-
/**
|
|
31
|
-
* Structural shape of a `ManduFilling` instance as seen by the registrar.
|
|
32
|
-
* Mirrors the subset of the public API we depend on (handle/hasWS/getWSHandlers)
|
|
33
|
-
* without importing the concrete class โ that would pull in runtime deps
|
|
34
|
-
* the CLI shouldn't need at import time.
|
|
35
|
-
*/
|
|
36
|
-
interface FillingInstance {
|
|
37
|
-
handle: (req: Request, params?: Record<string, string>) => Response | Promise<Response>;
|
|
38
|
-
hasWS?: () => boolean;
|
|
39
|
-
getWSHandlers?: () => Parameters<typeof registerWSHandler>[1];
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
function isFillingInstance(value: unknown): value is FillingInstance {
|
|
43
|
-
return (
|
|
44
|
-
typeof value === "object" &&
|
|
45
|
-
value !== null &&
|
|
46
|
-
typeof (value as { handle?: unknown }).handle === "function"
|
|
47
|
-
);
|
|
48
|
-
}
|
|
49
|
-
|
|
50
|
-
const HTTP_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"] as const;
|
|
51
|
-
|
|
52
|
-
type HttpMethod = (typeof HTTP_METHODS)[number];
|
|
53
|
-
|
|
54
|
-
function isHttpMethod(method: string): method is HttpMethod {
|
|
55
|
-
return (HTTP_METHODS as readonly string[]).includes(method);
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
function hasHttpMethodHandlers(module: RouteModule): boolean {
|
|
59
|
-
return HTTP_METHODS.some((method) => typeof module[method] === "function");
|
|
60
|
-
}
|
|
61
|
-
|
|
62
|
-
function createMethodDispatcher(module: RouteModule, routeId: string) {
|
|
63
|
-
return async (req: Request, params: Record<string, string> = {}) => {
|
|
64
|
-
const method = req.method.toUpperCase();
|
|
65
|
-
const handler = (isHttpMethod(method) ? module[method] : undefined) as
|
|
66
|
-
| ((request: Request, context?: { params: Record<string, string> }) => Response | Promise<Response>)
|
|
67
|
-
| undefined;
|
|
68
|
-
|
|
69
|
-
if (!handler) {
|
|
70
|
-
return Response.json(
|
|
71
|
-
{
|
|
72
|
-
error: `Method ${method} not allowed for route ${routeId}`,
|
|
73
|
-
},
|
|
74
|
-
{
|
|
75
|
-
status: 405,
|
|
76
|
-
headers: {
|
|
77
|
-
Allow: HTTP_METHODS.filter((m) => typeof module[m] === "function").join(", "),
|
|
78
|
-
},
|
|
79
|
-
}
|
|
80
|
-
);
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
return handler(req, { params });
|
|
84
|
-
};
|
|
85
|
-
}
|
|
86
|
-
|
|
87
|
-
export interface RegisterHandlersOptions {
|
|
88
|
-
/**
|
|
89
|
-
* Module import function (dev: importFresh, start: standard import).
|
|
90
|
-
* The optional `opts.changedFile` is forwarded into Phase 7.0 B5's
|
|
91
|
-
* incremental bundled-import: when the changed file is not in the
|
|
92
|
-
* module's import graph, `importFn` returns the cached bundle in ~0.1 ms
|
|
93
|
-
* instead of re-running Bun.build.
|
|
94
|
-
*/
|
|
95
|
-
importFn: (modulePath: string, opts?: { changedFile?: string }) => Promise<unknown>;
|
|
96
|
-
/** Set for tracking already registered layout paths */
|
|
97
|
-
registeredLayouts: Set<string>;
|
|
98
|
-
/** Clear layout cache on reload */
|
|
99
|
-
isReload?: boolean;
|
|
100
|
-
/**
|
|
101
|
-
* Phase 7.0 B5 wire-up โ on a live SSR reload, the changed file that
|
|
102
|
-
* triggered the reload. Omit for cold boot / wildcard (full
|
|
103
|
-
* invalidation). Forwarded to `importFn` so the incremental
|
|
104
|
-
* `bundledImport` can skip rebuilds for modules the file isn't part of.
|
|
105
|
-
*/
|
|
106
|
-
changedFile?: string;
|
|
107
|
-
}
|
|
108
|
-
|
|
109
|
-
/**
|
|
110
|
-
* Register manifest routes as server handlers.
|
|
111
|
-
* Shared between dev/build/start commands and deploy-target SSR entries.
|
|
112
|
-
*/
|
|
113
|
-
export async function registerManifestHandlers(
|
|
114
|
-
manifest: RoutesManifest,
|
|
115
|
-
rootDir: string,
|
|
116
|
-
options: RegisterHandlersOptions
|
|
117
|
-
): Promise<void> {
|
|
118
|
-
const { importFn, registeredLayouts, isReload = false, changedFile } = options;
|
|
119
|
-
const importOpts: { changedFile?: string } | undefined =
|
|
120
|
-
changedFile !== undefined ? { changedFile } : undefined;
|
|
121
|
-
|
|
122
|
-
if (isReload) {
|
|
123
|
-
registeredLayouts.clear();
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
// Expose the live manifest through the runtime registry so user code can
|
|
127
|
-
// read generated artifacts via `getGenerated("routes")` / `getManifest()`
|
|
128
|
-
// โ the only official path. See `packages/core/src/runtime/registry.ts`.
|
|
129
|
-
registerManifest("routes", manifest);
|
|
130
|
-
|
|
131
|
-
for (const route of manifest.routes) {
|
|
132
|
-
// Issue #206: metadata routes (sitemap/robots/llms.txt/manifest)
|
|
133
|
-
// register a thunk that lazily imports the user module. The
|
|
134
|
-
// runtime dispatcher invokes the default export on each request
|
|
135
|
-
// so HMR reloads pick up edits automatically (same pattern as
|
|
136
|
-
// API routes below).
|
|
137
|
-
if (route.kind === "metadata") {
|
|
138
|
-
const modulePath = path.resolve(rootDir, route.module);
|
|
139
|
-
registerMetadataHandler(route.id, async () => {
|
|
140
|
-
return importFn(modulePath, importOpts);
|
|
141
|
-
});
|
|
142
|
-
console.log(` ๐บ๏ธ Metadata: ${route.pattern} -> ${route.id}`);
|
|
143
|
-
continue;
|
|
144
|
-
}
|
|
145
|
-
|
|
146
|
-
if (route.kind === "api") {
|
|
147
|
-
const modulePath = path.resolve(rootDir, route.module);
|
|
148
|
-
try {
|
|
149
|
-
const module = (await importFn(modulePath, importOpts)) as RouteModule;
|
|
150
|
-
let handler: unknown = module.default ?? module.handler ?? module;
|
|
151
|
-
|
|
152
|
-
// 1) ManduFilling instance
|
|
153
|
-
if (isFillingInstance(handler)) {
|
|
154
|
-
console.log(` ๐ ManduFilling wrapped: ${route.id}`);
|
|
155
|
-
const filling = handler;
|
|
156
|
-
|
|
157
|
-
// WebSocket ํธ๋ค๋ฌ ๋ฑ๋ก
|
|
158
|
-
if (typeof filling.hasWS === "function" && filling.hasWS() && filling.getWSHandlers) {
|
|
159
|
-
registerWSHandler(route.id, filling.getWSHandlers());
|
|
160
|
-
console.log(` ๐ WebSocket: ${route.pattern} -> ${route.id}`);
|
|
161
|
-
}
|
|
162
|
-
|
|
163
|
-
handler = async (req: Request, params?: Record<string, string>) => {
|
|
164
|
-
return filling.handle(req, params);
|
|
165
|
-
};
|
|
166
|
-
}
|
|
167
|
-
// 2) Route module with HTTP method exports (GET/POST/...)
|
|
168
|
-
else if (handler && typeof handler === "object" && hasHttpMethodHandlers(handler as RouteModule)) {
|
|
169
|
-
handler = createMethodDispatcher(handler as RouteModule, route.id);
|
|
170
|
-
}
|
|
171
|
-
|
|
172
|
-
if (typeof handler !== "function") {
|
|
173
|
-
console.warn(` โ ๏ธ API handler conversion failed: ${route.id} (type: ${typeof handler})`);
|
|
174
|
-
continue;
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
registerApiHandler(route.id, handler as (req: Request, params?: Record<string, string>) => Response | Promise<Response>);
|
|
178
|
-
console.log(` ๐ก API: ${route.pattern} -> ${route.id}`);
|
|
179
|
-
} catch (error) {
|
|
180
|
-
console.error(` โ Failed to load API handler: ${route.id}`, error);
|
|
181
|
-
}
|
|
182
|
-
} else if (route.kind === "page" && route.componentModule) {
|
|
183
|
-
const componentPath = path.resolve(rootDir, route.componentModule);
|
|
184
|
-
const isIsland = needsHydration(route);
|
|
185
|
-
const hasLayout = route.layoutChain && route.layoutChain.length > 0;
|
|
186
|
-
|
|
187
|
-
// Register layout loaders
|
|
188
|
-
if (route.layoutChain) {
|
|
189
|
-
for (const layoutPath of route.layoutChain) {
|
|
190
|
-
if (!registeredLayouts.has(layoutPath)) {
|
|
191
|
-
const absLayoutPath = path.resolve(rootDir, layoutPath);
|
|
192
|
-
registerLayoutLoader(layoutPath, (async () => {
|
|
193
|
-
// Layout modules must export a default component. Runtime
|
|
194
|
-
// validation in `renderToHTML` / page-loader asserts this โ
|
|
195
|
-
// so casting the unknown `importFn` result is safe here.
|
|
196
|
-
return importFn(absLayoutPath, importOpts);
|
|
197
|
-
}) as Parameters<typeof registerLayoutLoader>[1]);
|
|
198
|
-
registeredLayouts.add(layoutPath);
|
|
199
|
-
console.log(` ๐จ Layout: ${layoutPath}`);
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
// Use PageHandler if slotModule exists (filling.loader support)
|
|
205
|
-
if (route.slotModule) {
|
|
206
|
-
registerPageHandler(route.id, async () => {
|
|
207
|
-
const mod = (await importFn(componentPath, importOpts)) as Record<string, unknown>;
|
|
208
|
-
// Normalize the page module shape. Users write pages in two styles:
|
|
209
|
-
// (a) `export default function Page() {โฆ}` + `export const filling = โฆ`
|
|
210
|
-
// (b) `export default { component: โฆ, filling: โฆ }`
|
|
211
|
-
// Spreading a function default drops the component silently (you get
|
|
212
|
-
// the function's own props like `name`/`length`, not the function).
|
|
213
|
-
// Auto-promote form (a) to form (b) so both work without surprises.
|
|
214
|
-
const rawDefault = mod.default as unknown;
|
|
215
|
-
|
|
216
|
-
let registration: PageRegistration;
|
|
217
|
-
if (typeof rawDefault === "function") {
|
|
218
|
-
registration = {
|
|
219
|
-
component: rawDefault as PageRegistration["component"],
|
|
220
|
-
filling: mod.filling as PageRegistration["filling"],
|
|
221
|
-
};
|
|
222
|
-
} else if (typeof rawDefault === "object" && rawDefault !== null) {
|
|
223
|
-
registration = { ...(rawDefault as unknown as PageRegistration) };
|
|
224
|
-
} else {
|
|
225
|
-
throw new Error(
|
|
226
|
-
`[Mandu] Page module '${route.id}' has no default export. ` +
|
|
227
|
-
`Expected a React component or { component, filling } object.`,
|
|
228
|
-
);
|
|
229
|
-
}
|
|
230
|
-
|
|
231
|
-
// #186: page ๋ชจ๋์ metadata / generateMetadata ๋ฅผ registration์ ์ค์ด์
|
|
232
|
-
// ensurePageRouteMetadata๊ฐ registry ์บ์์ ์ ์ฅํ ์ ์๊ฒ ์ ๋ฌ.
|
|
233
|
-
if (mod.metadata && typeof mod.metadata === "object") {
|
|
234
|
-
registration.metadata = mod.metadata as PageRegistration["metadata"];
|
|
235
|
-
}
|
|
236
|
-
if (typeof mod.generateMetadata === "function") {
|
|
237
|
-
registration.generateMetadata = mod.generateMetadata as PageRegistration["generateMetadata"];
|
|
238
|
-
}
|
|
239
|
-
return registration;
|
|
240
|
-
});
|
|
241
|
-
console.log(
|
|
242
|
-
` ๐ Page: ${route.pattern} -> ${route.id} (with loader)${isIsland ? " ๐๏ธ" : ""}${hasLayout ? " ๐จ" : ""}`
|
|
243
|
-
);
|
|
244
|
-
} else {
|
|
245
|
-
registerPageLoader(route.id, (() => importFn(componentPath, importOpts)) as Parameters<typeof registerPageLoader>[1]);
|
|
246
|
-
console.log(
|
|
247
|
-
` ๐ Page: ${route.pattern} -> ${route.id}${isIsland ? " ๐๏ธ" : ""}${hasLayout ? " ๐จ" : ""}`
|
|
248
|
-
);
|
|
249
|
-
}
|
|
250
|
-
}
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
// Phase 6.3: register `app/not-found.tsx` if it exists. Global, one per
|
|
254
|
-
// app โ the server falls through to the built-in 404 if unregistered.
|
|
255
|
-
await registerAppNotFound(rootDir, importFn, importOpts);
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
/**
|
|
259
|
-
* Phase 6.3: look for `app/not-found.tsx` (or its variants) at the
|
|
260
|
-
* project root and register it as the app-level 404 handler. Silent
|
|
261
|
-
* no-op if no file exists โ the server's built-in 404 covers that case.
|
|
262
|
-
*/
|
|
263
|
-
async function registerAppNotFound(
|
|
264
|
-
rootDir: string,
|
|
265
|
-
importFn: (modulePath: string, opts?: { changedFile?: string }) => Promise<unknown>,
|
|
266
|
-
importOpts?: { changedFile?: string },
|
|
267
|
-
): Promise<void> {
|
|
268
|
-
const candidates = [
|
|
269
|
-
"app/not-found.tsx",
|
|
270
|
-
"app/not-found.ts",
|
|
271
|
-
"app/not-found.jsx",
|
|
272
|
-
"app/not-found.js",
|
|
273
|
-
];
|
|
274
|
-
for (const rel of candidates) {
|
|
275
|
-
const abs = path.resolve(rootDir, rel);
|
|
276
|
-
try {
|
|
277
|
-
await fs.access(abs);
|
|
278
|
-
} catch {
|
|
279
|
-
continue;
|
|
280
|
-
}
|
|
281
|
-
registerNotFoundHandler(async () => {
|
|
282
|
-
const module = (await importFn(abs, importOpts)) as Record<string, unknown>;
|
|
283
|
-
const rawDefault = module.default as unknown;
|
|
284
|
-
if (typeof rawDefault === "function") {
|
|
285
|
-
return {
|
|
286
|
-
component: rawDefault as PageRegistration["component"],
|
|
287
|
-
filling: module.filling as PageRegistration["filling"],
|
|
288
|
-
};
|
|
289
|
-
}
|
|
290
|
-
if (typeof rawDefault === "object" && rawDefault !== null) {
|
|
291
|
-
return { ...(rawDefault as PageRegistration) };
|
|
292
|
-
}
|
|
293
|
-
throw new Error(
|
|
294
|
-
`[Mandu] app/not-found.tsx has no valid default export (type: ${typeof rawDefault})`,
|
|
295
|
-
);
|
|
296
|
-
});
|
|
297
|
-
console.log(` ๐ซ Not-Found: ${rel}`);
|
|
298
|
-
return;
|
|
299
|
-
}
|
|
300
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Manifest โ runtime registry wiring.
|
|
3
|
+
*
|
|
4
|
+
* Lives in `@mandujs/core/runtime` (not in `@mandujs/cli`) because it
|
|
5
|
+
* runs at *server* boot, not at build time. Deploy adapters generate
|
|
6
|
+
* SSR entry files that need to call this โ having it here means the
|
|
7
|
+
* generated entry imports a single, public package (`@mandujs/core`)
|
|
8
|
+
* instead of reaching into a CLI subpath that has no `exports` map.
|
|
9
|
+
*
|
|
10
|
+
* @module core/runtime/handlers
|
|
11
|
+
*/
|
|
12
|
+
import fs from "node:fs/promises";
|
|
13
|
+
import path from "node:path";
|
|
14
|
+
|
|
15
|
+
import {
|
|
16
|
+
registerApiHandler,
|
|
17
|
+
registerPageLoader,
|
|
18
|
+
registerPageHandler,
|
|
19
|
+
registerLayoutLoader,
|
|
20
|
+
registerNotFoundHandler,
|
|
21
|
+
registerMetadataHandler,
|
|
22
|
+
registerWSHandler,
|
|
23
|
+
type PageRegistration,
|
|
24
|
+
} from "./server";
|
|
25
|
+
import { registerManifest } from "./registry";
|
|
26
|
+
import { needsHydration, type RoutesManifest } from "../spec/schema";
|
|
27
|
+
|
|
28
|
+
type RouteModule = Record<string, unknown>;
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Structural shape of a `ManduFilling` instance as seen by the registrar.
|
|
32
|
+
* Mirrors the subset of the public API we depend on (handle/hasWS/getWSHandlers)
|
|
33
|
+
* without importing the concrete class โ that would pull in runtime deps
|
|
34
|
+
* the CLI shouldn't need at import time.
|
|
35
|
+
*/
|
|
36
|
+
interface FillingInstance {
|
|
37
|
+
handle: (req: Request, params?: Record<string, string>) => Response | Promise<Response>;
|
|
38
|
+
hasWS?: () => boolean;
|
|
39
|
+
getWSHandlers?: () => Parameters<typeof registerWSHandler>[1];
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
function isFillingInstance(value: unknown): value is FillingInstance {
|
|
43
|
+
return (
|
|
44
|
+
typeof value === "object" &&
|
|
45
|
+
value !== null &&
|
|
46
|
+
typeof (value as { handle?: unknown }).handle === "function"
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
const HTTP_METHODS = ["GET", "POST", "PUT", "PATCH", "DELETE", "HEAD", "OPTIONS"] as const;
|
|
51
|
+
|
|
52
|
+
type HttpMethod = (typeof HTTP_METHODS)[number];
|
|
53
|
+
|
|
54
|
+
function isHttpMethod(method: string): method is HttpMethod {
|
|
55
|
+
return (HTTP_METHODS as readonly string[]).includes(method);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function hasHttpMethodHandlers(module: RouteModule): boolean {
|
|
59
|
+
return HTTP_METHODS.some((method) => typeof module[method] === "function");
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function createMethodDispatcher(module: RouteModule, routeId: string) {
|
|
63
|
+
return async (req: Request, params: Record<string, string> = {}) => {
|
|
64
|
+
const method = req.method.toUpperCase();
|
|
65
|
+
const handler = (isHttpMethod(method) ? module[method] : undefined) as
|
|
66
|
+
| ((request: Request, context?: { params: Record<string, string> }) => Response | Promise<Response>)
|
|
67
|
+
| undefined;
|
|
68
|
+
|
|
69
|
+
if (!handler) {
|
|
70
|
+
return Response.json(
|
|
71
|
+
{
|
|
72
|
+
error: `Method ${method} not allowed for route ${routeId}`,
|
|
73
|
+
},
|
|
74
|
+
{
|
|
75
|
+
status: 405,
|
|
76
|
+
headers: {
|
|
77
|
+
Allow: HTTP_METHODS.filter((m) => typeof module[m] === "function").join(", "),
|
|
78
|
+
},
|
|
79
|
+
}
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
return handler(req, { params });
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
export interface RegisterHandlersOptions {
|
|
88
|
+
/**
|
|
89
|
+
* Module import function (dev: importFresh, start: standard import).
|
|
90
|
+
* The optional `opts.changedFile` is forwarded into Phase 7.0 B5's
|
|
91
|
+
* incremental bundled-import: when the changed file is not in the
|
|
92
|
+
* module's import graph, `importFn` returns the cached bundle in ~0.1 ms
|
|
93
|
+
* instead of re-running Bun.build.
|
|
94
|
+
*/
|
|
95
|
+
importFn: (modulePath: string, opts?: { changedFile?: string }) => Promise<unknown>;
|
|
96
|
+
/** Set for tracking already registered layout paths */
|
|
97
|
+
registeredLayouts: Set<string>;
|
|
98
|
+
/** Clear layout cache on reload */
|
|
99
|
+
isReload?: boolean;
|
|
100
|
+
/**
|
|
101
|
+
* Phase 7.0 B5 wire-up โ on a live SSR reload, the changed file that
|
|
102
|
+
* triggered the reload. Omit for cold boot / wildcard (full
|
|
103
|
+
* invalidation). Forwarded to `importFn` so the incremental
|
|
104
|
+
* `bundledImport` can skip rebuilds for modules the file isn't part of.
|
|
105
|
+
*/
|
|
106
|
+
changedFile?: string;
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Register manifest routes as server handlers.
|
|
111
|
+
* Shared between dev/build/start commands and deploy-target SSR entries.
|
|
112
|
+
*/
|
|
113
|
+
export async function registerManifestHandlers(
|
|
114
|
+
manifest: RoutesManifest,
|
|
115
|
+
rootDir: string,
|
|
116
|
+
options: RegisterHandlersOptions
|
|
117
|
+
): Promise<void> {
|
|
118
|
+
const { importFn, registeredLayouts, isReload = false, changedFile } = options;
|
|
119
|
+
const importOpts: { changedFile?: string } | undefined =
|
|
120
|
+
changedFile !== undefined ? { changedFile } : undefined;
|
|
121
|
+
|
|
122
|
+
if (isReload) {
|
|
123
|
+
registeredLayouts.clear();
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// Expose the live manifest through the runtime registry so user code can
|
|
127
|
+
// read generated artifacts via `getGenerated("routes")` / `getManifest()`
|
|
128
|
+
// โ the only official path. See `packages/core/src/runtime/registry.ts`.
|
|
129
|
+
registerManifest("routes", manifest);
|
|
130
|
+
|
|
131
|
+
for (const route of manifest.routes) {
|
|
132
|
+
// Issue #206: metadata routes (sitemap/robots/llms.txt/manifest)
|
|
133
|
+
// register a thunk that lazily imports the user module. The
|
|
134
|
+
// runtime dispatcher invokes the default export on each request
|
|
135
|
+
// so HMR reloads pick up edits automatically (same pattern as
|
|
136
|
+
// API routes below).
|
|
137
|
+
if (route.kind === "metadata") {
|
|
138
|
+
const modulePath = path.resolve(rootDir, route.module);
|
|
139
|
+
registerMetadataHandler(route.id, async () => {
|
|
140
|
+
return importFn(modulePath, importOpts);
|
|
141
|
+
});
|
|
142
|
+
console.log(` ๐บ๏ธ Metadata: ${route.pattern} -> ${route.id}`);
|
|
143
|
+
continue;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (route.kind === "api") {
|
|
147
|
+
const modulePath = path.resolve(rootDir, route.module);
|
|
148
|
+
try {
|
|
149
|
+
const module = (await importFn(modulePath, importOpts)) as RouteModule;
|
|
150
|
+
let handler: unknown = module.default ?? module.handler ?? module;
|
|
151
|
+
|
|
152
|
+
// 1) ManduFilling instance
|
|
153
|
+
if (isFillingInstance(handler)) {
|
|
154
|
+
console.log(` ๐ ManduFilling wrapped: ${route.id}`);
|
|
155
|
+
const filling = handler;
|
|
156
|
+
|
|
157
|
+
// WebSocket ํธ๋ค๋ฌ ๋ฑ๋ก
|
|
158
|
+
if (typeof filling.hasWS === "function" && filling.hasWS() && filling.getWSHandlers) {
|
|
159
|
+
registerWSHandler(route.id, filling.getWSHandlers());
|
|
160
|
+
console.log(` ๐ WebSocket: ${route.pattern} -> ${route.id}`);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
handler = async (req: Request, params?: Record<string, string>) => {
|
|
164
|
+
return filling.handle(req, params);
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
// 2) Route module with HTTP method exports (GET/POST/...)
|
|
168
|
+
else if (handler && typeof handler === "object" && hasHttpMethodHandlers(handler as RouteModule)) {
|
|
169
|
+
handler = createMethodDispatcher(handler as RouteModule, route.id);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
if (typeof handler !== "function") {
|
|
173
|
+
console.warn(` โ ๏ธ API handler conversion failed: ${route.id} (type: ${typeof handler})`);
|
|
174
|
+
continue;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
registerApiHandler(route.id, handler as (req: Request, params?: Record<string, string>) => Response | Promise<Response>);
|
|
178
|
+
console.log(` ๐ก API: ${route.pattern} -> ${route.id}`);
|
|
179
|
+
} catch (error) {
|
|
180
|
+
console.error(` โ Failed to load API handler: ${route.id}`, error);
|
|
181
|
+
}
|
|
182
|
+
} else if (route.kind === "page" && route.componentModule) {
|
|
183
|
+
const componentPath = path.resolve(rootDir, route.componentModule);
|
|
184
|
+
const isIsland = needsHydration(route);
|
|
185
|
+
const hasLayout = route.layoutChain && route.layoutChain.length > 0;
|
|
186
|
+
|
|
187
|
+
// Register layout loaders
|
|
188
|
+
if (route.layoutChain) {
|
|
189
|
+
for (const layoutPath of route.layoutChain) {
|
|
190
|
+
if (!registeredLayouts.has(layoutPath)) {
|
|
191
|
+
const absLayoutPath = path.resolve(rootDir, layoutPath);
|
|
192
|
+
registerLayoutLoader(layoutPath, (async () => {
|
|
193
|
+
// Layout modules must export a default component. Runtime
|
|
194
|
+
// validation in `renderToHTML` / page-loader asserts this โ
|
|
195
|
+
// so casting the unknown `importFn` result is safe here.
|
|
196
|
+
return importFn(absLayoutPath, importOpts);
|
|
197
|
+
}) as Parameters<typeof registerLayoutLoader>[1]);
|
|
198
|
+
registeredLayouts.add(layoutPath);
|
|
199
|
+
console.log(` ๐จ Layout: ${layoutPath}`);
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Use PageHandler if slotModule exists (filling.loader support)
|
|
205
|
+
if (route.slotModule) {
|
|
206
|
+
registerPageHandler(route.id, async () => {
|
|
207
|
+
const mod = (await importFn(componentPath, importOpts)) as Record<string, unknown>;
|
|
208
|
+
// Normalize the page module shape. Users write pages in two styles:
|
|
209
|
+
// (a) `export default function Page() {โฆ}` + `export const filling = โฆ`
|
|
210
|
+
// (b) `export default { component: โฆ, filling: โฆ }`
|
|
211
|
+
// Spreading a function default drops the component silently (you get
|
|
212
|
+
// the function's own props like `name`/`length`, not the function).
|
|
213
|
+
// Auto-promote form (a) to form (b) so both work without surprises.
|
|
214
|
+
const rawDefault = mod.default as unknown;
|
|
215
|
+
|
|
216
|
+
let registration: PageRegistration;
|
|
217
|
+
if (typeof rawDefault === "function") {
|
|
218
|
+
registration = {
|
|
219
|
+
component: rawDefault as PageRegistration["component"],
|
|
220
|
+
filling: mod.filling as PageRegistration["filling"],
|
|
221
|
+
};
|
|
222
|
+
} else if (typeof rawDefault === "object" && rawDefault !== null) {
|
|
223
|
+
registration = { ...(rawDefault as unknown as PageRegistration) };
|
|
224
|
+
} else {
|
|
225
|
+
throw new Error(
|
|
226
|
+
`[Mandu] Page module '${route.id}' has no default export. ` +
|
|
227
|
+
`Expected a React component or { component, filling } object.`,
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// #186: page ๋ชจ๋์ metadata / generateMetadata ๋ฅผ registration์ ์ค์ด์
|
|
232
|
+
// ensurePageRouteMetadata๊ฐ registry ์บ์์ ์ ์ฅํ ์ ์๊ฒ ์ ๋ฌ.
|
|
233
|
+
if (mod.metadata && typeof mod.metadata === "object") {
|
|
234
|
+
registration.metadata = mod.metadata as PageRegistration["metadata"];
|
|
235
|
+
}
|
|
236
|
+
if (typeof mod.generateMetadata === "function") {
|
|
237
|
+
registration.generateMetadata = mod.generateMetadata as PageRegistration["generateMetadata"];
|
|
238
|
+
}
|
|
239
|
+
return registration;
|
|
240
|
+
});
|
|
241
|
+
console.log(
|
|
242
|
+
` ๐ Page: ${route.pattern} -> ${route.id} (with loader)${isIsland ? " ๐๏ธ" : ""}${hasLayout ? " ๐จ" : ""}`
|
|
243
|
+
);
|
|
244
|
+
} else {
|
|
245
|
+
registerPageLoader(route.id, (() => importFn(componentPath, importOpts)) as Parameters<typeof registerPageLoader>[1]);
|
|
246
|
+
console.log(
|
|
247
|
+
` ๐ Page: ${route.pattern} -> ${route.id}${isIsland ? " ๐๏ธ" : ""}${hasLayout ? " ๐จ" : ""}`
|
|
248
|
+
);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Phase 6.3: register `app/not-found.tsx` if it exists. Global, one per
|
|
254
|
+
// app โ the server falls through to the built-in 404 if unregistered.
|
|
255
|
+
await registerAppNotFound(rootDir, importFn, importOpts);
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Phase 6.3: look for `app/not-found.tsx` (or its variants) at the
|
|
260
|
+
* project root and register it as the app-level 404 handler. Silent
|
|
261
|
+
* no-op if no file exists โ the server's built-in 404 covers that case.
|
|
262
|
+
*/
|
|
263
|
+
async function registerAppNotFound(
|
|
264
|
+
rootDir: string,
|
|
265
|
+
importFn: (modulePath: string, opts?: { changedFile?: string }) => Promise<unknown>,
|
|
266
|
+
importOpts?: { changedFile?: string },
|
|
267
|
+
): Promise<void> {
|
|
268
|
+
const candidates = [
|
|
269
|
+
"app/not-found.tsx",
|
|
270
|
+
"app/not-found.ts",
|
|
271
|
+
"app/not-found.jsx",
|
|
272
|
+
"app/not-found.js",
|
|
273
|
+
];
|
|
274
|
+
for (const rel of candidates) {
|
|
275
|
+
const abs = path.resolve(rootDir, rel);
|
|
276
|
+
try {
|
|
277
|
+
await fs.access(abs);
|
|
278
|
+
} catch {
|
|
279
|
+
continue;
|
|
280
|
+
}
|
|
281
|
+
registerNotFoundHandler(async () => {
|
|
282
|
+
const module = (await importFn(abs, importOpts)) as Record<string, unknown>;
|
|
283
|
+
const rawDefault = module.default as unknown;
|
|
284
|
+
if (typeof rawDefault === "function") {
|
|
285
|
+
return {
|
|
286
|
+
component: rawDefault as PageRegistration["component"],
|
|
287
|
+
filling: module.filling as PageRegistration["filling"],
|
|
288
|
+
};
|
|
289
|
+
}
|
|
290
|
+
if (typeof rawDefault === "object" && rawDefault !== null) {
|
|
291
|
+
return { ...(rawDefault as PageRegistration) };
|
|
292
|
+
}
|
|
293
|
+
throw new Error(
|
|
294
|
+
`[Mandu] app/not-found.tsx has no valid default export (type: ${typeof rawDefault})`,
|
|
295
|
+
);
|
|
296
|
+
});
|
|
297
|
+
console.log(` ๐ซ Not-Found: ${rel}`);
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
}
|