@mandujs/core 0.53.3 → 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 -451
- package/src/diagnose/checks.ts +832 -832
- package/src/diagnose/index.ts +17 -17
- package/src/diagnose/run.ts +93 -93
- 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/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
|
@@ -1,409 +1,409 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Phase 18.τ — canonical plugin runner tests.
|
|
3
|
-
*
|
|
4
|
-
* Covers every hook type and merge-semantics edge case the runner owns.
|
|
5
|
-
* Real `ManduPlugin` objects, no mocks; the runner is pure so we can
|
|
6
|
-
* assert outputs directly.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import { describe, it, expect } from "bun:test";
|
|
10
|
-
import type { BunPlugin } from "bun";
|
|
11
|
-
import type { Middleware } from "../../middleware/define";
|
|
12
|
-
import type { RouteSpec, RoutesManifest } from "../../spec/schema";
|
|
13
|
-
import type { BundleStats } from "../../bundler/types";
|
|
14
|
-
import type { ManduPlugin } from "../hooks";
|
|
15
|
-
import {
|
|
16
|
-
runOnRouteRegistered,
|
|
17
|
-
runOnBundleComplete,
|
|
18
|
-
runDefinePrerenderHook,
|
|
19
|
-
runOnManifestBuilt,
|
|
20
|
-
runDefineBundlerPlugin,
|
|
21
|
-
runDefineMiddlewareChain,
|
|
22
|
-
runDefineTestTransform,
|
|
23
|
-
resolvePluginMiddleware,
|
|
24
|
-
formatHookErrors,
|
|
25
|
-
} from "../runner";
|
|
26
|
-
|
|
27
|
-
// ───── Fixtures ─────
|
|
28
|
-
|
|
29
|
-
const sampleRoute: RouteSpec = {
|
|
30
|
-
id: "sample",
|
|
31
|
-
kind: "page",
|
|
32
|
-
pattern: "/",
|
|
33
|
-
module: "app/page.tsx",
|
|
34
|
-
componentModule: "app/page.tsx",
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
const sampleManifest: RoutesManifest = {
|
|
38
|
-
version: 1,
|
|
39
|
-
routes: [sampleRoute],
|
|
40
|
-
};
|
|
41
|
-
|
|
42
|
-
const sampleStats: BundleStats = {
|
|
43
|
-
totalSize: 1024,
|
|
44
|
-
totalGzipSize: 512,
|
|
45
|
-
largestBundle: { routeId: "sample", size: 1024 },
|
|
46
|
-
buildTime: 50,
|
|
47
|
-
bundleCount: 1,
|
|
48
|
-
};
|
|
49
|
-
|
|
50
|
-
const mkPluginContext = () => ({
|
|
51
|
-
rootDir: "/tmp/project",
|
|
52
|
-
mode: "production" as const,
|
|
53
|
-
logger: {
|
|
54
|
-
debug: () => {},
|
|
55
|
-
info: () => {},
|
|
56
|
-
warn: () => {},
|
|
57
|
-
error: () => {},
|
|
58
|
-
},
|
|
59
|
-
});
|
|
60
|
-
|
|
61
|
-
// ═══════════════════════════════════════════════════════════════════════
|
|
62
|
-
describe("runner — dispatch order", () => {
|
|
63
|
-
it("runs config hook BEFORE plugin hooks", async () => {
|
|
64
|
-
const log: string[] = [];
|
|
65
|
-
const plugins: ManduPlugin[] = [
|
|
66
|
-
{
|
|
67
|
-
name: "p1",
|
|
68
|
-
hooks: {
|
|
69
|
-
onRouteRegistered: () => {
|
|
70
|
-
log.push("p1");
|
|
71
|
-
},
|
|
72
|
-
},
|
|
73
|
-
},
|
|
74
|
-
];
|
|
75
|
-
const configHooks = {
|
|
76
|
-
onRouteRegistered: () => {
|
|
77
|
-
log.push("config");
|
|
78
|
-
},
|
|
79
|
-
};
|
|
80
|
-
await runOnRouteRegistered(sampleRoute, { plugins, configHooks });
|
|
81
|
-
expect(log).toEqual(["config", "p1"]);
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
it("runs plugin hooks in declaration order", async () => {
|
|
85
|
-
const log: string[] = [];
|
|
86
|
-
const plugins: ManduPlugin[] = [
|
|
87
|
-
{ name: "a", hooks: { onRouteRegistered: () => { log.push("a"); } } },
|
|
88
|
-
{ name: "b", hooks: { onRouteRegistered: () => { log.push("b"); } } },
|
|
89
|
-
{ name: "c", hooks: { onRouteRegistered: () => { log.push("c"); } } },
|
|
90
|
-
];
|
|
91
|
-
await runOnRouteRegistered(sampleRoute, { plugins });
|
|
92
|
-
expect(log).toEqual(["a", "b", "c"]);
|
|
93
|
-
});
|
|
94
|
-
|
|
95
|
-
it("skips undefined hooks silently", async () => {
|
|
96
|
-
const plugins: ManduPlugin[] = [
|
|
97
|
-
{ name: "empty" },
|
|
98
|
-
{ name: "also-empty", hooks: {} },
|
|
99
|
-
];
|
|
100
|
-
const report = await runOnRouteRegistered(sampleRoute, { plugins });
|
|
101
|
-
expect(report.errors).toEqual([]);
|
|
102
|
-
});
|
|
103
|
-
|
|
104
|
-
it("handles empty plugin array gracefully", async () => {
|
|
105
|
-
const report = await runOnBundleComplete(sampleStats, { plugins: [] });
|
|
106
|
-
expect(report.errors).toEqual([]);
|
|
107
|
-
expect(report.result).toBeUndefined();
|
|
108
|
-
});
|
|
109
|
-
});
|
|
110
|
-
|
|
111
|
-
// ═══════════════════════════════════════════════════════════════════════
|
|
112
|
-
describe("runner — error isolation", () => {
|
|
113
|
-
it("captures errors without stopping sibling plugins", async () => {
|
|
114
|
-
const log: string[] = [];
|
|
115
|
-
const plugins: ManduPlugin[] = [
|
|
116
|
-
{
|
|
117
|
-
name: "ok1",
|
|
118
|
-
hooks: { onRouteRegistered: () => { log.push("ok1"); } },
|
|
119
|
-
},
|
|
120
|
-
{
|
|
121
|
-
name: "fails",
|
|
122
|
-
hooks: {
|
|
123
|
-
onRouteRegistered: () => {
|
|
124
|
-
throw new Error("boom");
|
|
125
|
-
},
|
|
126
|
-
},
|
|
127
|
-
},
|
|
128
|
-
{
|
|
129
|
-
name: "ok2",
|
|
130
|
-
hooks: { onRouteRegistered: () => { log.push("ok2"); } },
|
|
131
|
-
},
|
|
132
|
-
];
|
|
133
|
-
const report = await runOnRouteRegistered(sampleRoute, { plugins });
|
|
134
|
-
expect(log).toEqual(["ok1", "ok2"]);
|
|
135
|
-
expect(report.errors).toHaveLength(1);
|
|
136
|
-
expect(report.errors[0].source).toBe("fails");
|
|
137
|
-
expect(report.errors[0].hook).toBe("onRouteRegistered");
|
|
138
|
-
expect(report.errors[0].error.message).toBe("boom");
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
it("handles async errors", async () => {
|
|
142
|
-
const plugins: ManduPlugin[] = [
|
|
143
|
-
{
|
|
144
|
-
name: "async-fails",
|
|
145
|
-
hooks: {
|
|
146
|
-
async onRouteRegistered() {
|
|
147
|
-
await Promise.resolve();
|
|
148
|
-
throw new Error("async boom");
|
|
149
|
-
},
|
|
150
|
-
},
|
|
151
|
-
},
|
|
152
|
-
];
|
|
153
|
-
const report = await runOnRouteRegistered(sampleRoute, { plugins });
|
|
154
|
-
expect(report.errors[0].error.message).toBe("async boom");
|
|
155
|
-
});
|
|
156
|
-
|
|
157
|
-
it("stringifies non-Error throws", async () => {
|
|
158
|
-
const plugins: ManduPlugin[] = [
|
|
159
|
-
{
|
|
160
|
-
name: "weird",
|
|
161
|
-
hooks: {
|
|
162
|
-
onRouteRegistered: () => {
|
|
163
|
-
throw "just a string";
|
|
164
|
-
},
|
|
165
|
-
},
|
|
166
|
-
},
|
|
167
|
-
];
|
|
168
|
-
const report = await runOnRouteRegistered(sampleRoute, { plugins });
|
|
169
|
-
expect(report.errors[0].error.message).toBe("just a string");
|
|
170
|
-
});
|
|
171
|
-
});
|
|
172
|
-
|
|
173
|
-
// ═══════════════════════════════════════════════════════════════════════
|
|
174
|
-
describe("runner — definePrerenderHook merge", () => {
|
|
175
|
-
it("spreads returns across plugins (last write wins)", async () => {
|
|
176
|
-
const plugins: ManduPlugin[] = [
|
|
177
|
-
{
|
|
178
|
-
name: "first",
|
|
179
|
-
hooks: {
|
|
180
|
-
definePrerenderHook: () => ({ html: "<p>first</p>", skip: false }),
|
|
181
|
-
},
|
|
182
|
-
},
|
|
183
|
-
{
|
|
184
|
-
name: "second",
|
|
185
|
-
hooks: {
|
|
186
|
-
definePrerenderHook: () => ({ html: "<p>second</p>" }),
|
|
187
|
-
},
|
|
188
|
-
},
|
|
189
|
-
];
|
|
190
|
-
const report = await runDefinePrerenderHook(
|
|
191
|
-
{
|
|
192
|
-
...mkPluginContext(),
|
|
193
|
-
pathname: "/",
|
|
194
|
-
html: "<p>original</p>",
|
|
195
|
-
},
|
|
196
|
-
{ plugins },
|
|
197
|
-
);
|
|
198
|
-
expect(report.result.html).toBe("<p>second</p>");
|
|
199
|
-
expect(report.result.skip).toBe(false);
|
|
200
|
-
});
|
|
201
|
-
|
|
202
|
-
it("treats void returns as no-change", async () => {
|
|
203
|
-
const plugins: ManduPlugin[] = [
|
|
204
|
-
{ name: "noop", hooks: { definePrerenderHook: () => {} } },
|
|
205
|
-
{
|
|
206
|
-
name: "sets",
|
|
207
|
-
hooks: { definePrerenderHook: () => ({ skip: true }) },
|
|
208
|
-
},
|
|
209
|
-
];
|
|
210
|
-
const report = await runDefinePrerenderHook(
|
|
211
|
-
{ ...mkPluginContext(), pathname: "/", html: "x" },
|
|
212
|
-
{ plugins },
|
|
213
|
-
);
|
|
214
|
-
expect(report.result.skip).toBe(true);
|
|
215
|
-
});
|
|
216
|
-
});
|
|
217
|
-
|
|
218
|
-
// ═══════════════════════════════════════════════════════════════════════
|
|
219
|
-
describe("runner — onManifestBuilt pipe", () => {
|
|
220
|
-
it("pipes manifest across plugins (each sees prev output)", async () => {
|
|
221
|
-
const plugins: ManduPlugin[] = [
|
|
222
|
-
{
|
|
223
|
-
name: "add-version",
|
|
224
|
-
hooks: {
|
|
225
|
-
onManifestBuilt: (m) => ({ ...m, version: 2 }) as RoutesManifest,
|
|
226
|
-
},
|
|
227
|
-
},
|
|
228
|
-
{
|
|
229
|
-
name: "inspect",
|
|
230
|
-
hooks: {
|
|
231
|
-
onManifestBuilt: (m) => {
|
|
232
|
-
expect(m.version).toBe(2);
|
|
233
|
-
return m;
|
|
234
|
-
},
|
|
235
|
-
},
|
|
236
|
-
},
|
|
237
|
-
];
|
|
238
|
-
const report = await runOnManifestBuilt(sampleManifest, { plugins });
|
|
239
|
-
expect(report.result.version).toBe(2);
|
|
240
|
-
});
|
|
241
|
-
|
|
242
|
-
it("void return passes through unchanged", async () => {
|
|
243
|
-
const plugins: ManduPlugin[] = [
|
|
244
|
-
{ name: "noop", hooks: { onManifestBuilt: () => {} } },
|
|
245
|
-
];
|
|
246
|
-
const report = await runOnManifestBuilt(sampleManifest, { plugins });
|
|
247
|
-
expect(report.result).toBe(sampleManifest);
|
|
248
|
-
});
|
|
249
|
-
});
|
|
250
|
-
|
|
251
|
-
// ═══════════════════════════════════════════════════════════════════════
|
|
252
|
-
describe("runner — defineBundlerPlugin concat", () => {
|
|
253
|
-
const mkPlugin = (name: string): BunPlugin => ({
|
|
254
|
-
name,
|
|
255
|
-
setup() {},
|
|
256
|
-
});
|
|
257
|
-
|
|
258
|
-
it("concatenates scalar + array returns", async () => {
|
|
259
|
-
const plugins: ManduPlugin[] = [
|
|
260
|
-
{
|
|
261
|
-
name: "single",
|
|
262
|
-
hooks: { defineBundlerPlugin: () => mkPlugin("one") },
|
|
263
|
-
},
|
|
264
|
-
{
|
|
265
|
-
name: "multi",
|
|
266
|
-
hooks: {
|
|
267
|
-
defineBundlerPlugin: () => [mkPlugin("two"), mkPlugin("three")],
|
|
268
|
-
},
|
|
269
|
-
},
|
|
270
|
-
];
|
|
271
|
-
const report = await runDefineBundlerPlugin({ plugins });
|
|
272
|
-
expect(report.result.map((p) => p.name)).toEqual(["one", "two", "three"]);
|
|
273
|
-
});
|
|
274
|
-
|
|
275
|
-
it("skips undefined returns", async () => {
|
|
276
|
-
const plugins: ManduPlugin[] = [
|
|
277
|
-
{ name: "nil", hooks: { defineBundlerPlugin: () => undefined } as any },
|
|
278
|
-
{ name: "one", hooks: { defineBundlerPlugin: () => mkPlugin("x") } },
|
|
279
|
-
];
|
|
280
|
-
const report = await runDefineBundlerPlugin({ plugins });
|
|
281
|
-
expect(report.result.map((p) => p.name)).toEqual(["x"]);
|
|
282
|
-
});
|
|
283
|
-
});
|
|
284
|
-
|
|
285
|
-
// ═══════════════════════════════════════════════════════════════════════
|
|
286
|
-
describe("runner — defineMiddlewareChain concat", () => {
|
|
287
|
-
const mkMw = (name: string): Middleware => ({
|
|
288
|
-
name,
|
|
289
|
-
handler: async (_req, next) => next(),
|
|
290
|
-
});
|
|
291
|
-
|
|
292
|
-
it("concatenates middleware across plugins in order", async () => {
|
|
293
|
-
const plugins: ManduPlugin[] = [
|
|
294
|
-
{ name: "p1", hooks: { defineMiddlewareChain: () => [mkMw("a"), mkMw("b")] } },
|
|
295
|
-
{ name: "p2", hooks: { defineMiddlewareChain: () => [mkMw("c")] } },
|
|
296
|
-
];
|
|
297
|
-
const report = await runDefineMiddlewareChain(mkPluginContext(), { plugins });
|
|
298
|
-
expect(report.result.map((m) => m.name)).toEqual(["a", "b", "c"]);
|
|
299
|
-
});
|
|
300
|
-
|
|
301
|
-
it("resolvePluginMiddleware returns flattened list", async () => {
|
|
302
|
-
const plugins: ManduPlugin[] = [
|
|
303
|
-
{ name: "p", hooks: { defineMiddlewareChain: () => [mkMw("only")] } },
|
|
304
|
-
];
|
|
305
|
-
const mw = await resolvePluginMiddleware({
|
|
306
|
-
plugins,
|
|
307
|
-
rootDir: "/tmp",
|
|
308
|
-
mode: "production",
|
|
309
|
-
});
|
|
310
|
-
expect(mw.map((m) => m.name)).toEqual(["only"]);
|
|
311
|
-
});
|
|
312
|
-
});
|
|
313
|
-
|
|
314
|
-
// ═══════════════════════════════════════════════════════════════════════
|
|
315
|
-
describe("runner — defineTestTransform pipe", () => {
|
|
316
|
-
it("pipes each plugin's output into the next", async () => {
|
|
317
|
-
const plugins: ManduPlugin[] = [
|
|
318
|
-
{
|
|
319
|
-
name: "upper",
|
|
320
|
-
hooks: {
|
|
321
|
-
defineTestTransform: (ctx) => ctx.source.toUpperCase(),
|
|
322
|
-
},
|
|
323
|
-
},
|
|
324
|
-
{
|
|
325
|
-
name: "suffix",
|
|
326
|
-
hooks: {
|
|
327
|
-
defineTestTransform: (ctx) => ctx.source + "!",
|
|
328
|
-
},
|
|
329
|
-
},
|
|
330
|
-
];
|
|
331
|
-
const report = await runDefineTestTransform(
|
|
332
|
-
{ testFile: "foo.test.ts", source: "hello" },
|
|
333
|
-
{ plugins },
|
|
334
|
-
);
|
|
335
|
-
expect(report.result).toBe("HELLO!");
|
|
336
|
-
});
|
|
337
|
-
|
|
338
|
-
it("preserves source when a plugin throws", async () => {
|
|
339
|
-
const plugins: ManduPlugin[] = [
|
|
340
|
-
{
|
|
341
|
-
name: "bad",
|
|
342
|
-
hooks: {
|
|
343
|
-
defineTestTransform: () => { throw new Error("oops"); },
|
|
344
|
-
},
|
|
345
|
-
},
|
|
346
|
-
{
|
|
347
|
-
name: "good",
|
|
348
|
-
hooks: {
|
|
349
|
-
defineTestTransform: (ctx) => ctx.source + ":ok",
|
|
350
|
-
},
|
|
351
|
-
},
|
|
352
|
-
];
|
|
353
|
-
const report = await runDefineTestTransform(
|
|
354
|
-
{ testFile: "foo.test.ts", source: "base" },
|
|
355
|
-
{ plugins },
|
|
356
|
-
);
|
|
357
|
-
expect(report.result).toBe("base:ok");
|
|
358
|
-
expect(report.errors).toHaveLength(1);
|
|
359
|
-
});
|
|
360
|
-
});
|
|
361
|
-
|
|
362
|
-
// ═══════════════════════════════════════════════════════════════════════
|
|
363
|
-
describe("runner — async hook timing", () => {
|
|
364
|
-
it("awaits async hooks serially", async () => {
|
|
365
|
-
const log: string[] = [];
|
|
366
|
-
const plugins: ManduPlugin[] = [
|
|
367
|
-
{
|
|
368
|
-
name: "slow",
|
|
369
|
-
hooks: {
|
|
370
|
-
async onRouteRegistered() {
|
|
371
|
-
await new Promise((r) => setTimeout(r, 10));
|
|
372
|
-
log.push("slow");
|
|
373
|
-
},
|
|
374
|
-
},
|
|
375
|
-
},
|
|
376
|
-
{
|
|
377
|
-
name: "fast",
|
|
378
|
-
hooks: {
|
|
379
|
-
onRouteRegistered: () => {
|
|
380
|
-
log.push("fast");
|
|
381
|
-
},
|
|
382
|
-
},
|
|
383
|
-
},
|
|
384
|
-
];
|
|
385
|
-
await runOnRouteRegistered(sampleRoute, { plugins });
|
|
386
|
-
expect(log).toEqual(["slow", "fast"]);
|
|
387
|
-
});
|
|
388
|
-
});
|
|
389
|
-
|
|
390
|
-
// ═══════════════════════════════════════════════════════════════════════
|
|
391
|
-
describe("runner — formatHookErrors", () => {
|
|
392
|
-
it("returns null when the report is clean", () => {
|
|
393
|
-
expect(
|
|
394
|
-
formatHookErrors({ result: undefined, errors: [] }),
|
|
395
|
-
).toBeNull();
|
|
396
|
-
});
|
|
397
|
-
|
|
398
|
-
it("formats error rollup", () => {
|
|
399
|
-
const formatted = formatHookErrors({
|
|
400
|
-
result: undefined,
|
|
401
|
-
errors: [
|
|
402
|
-
{ hook: "onRouteRegistered", source: "bad-plugin", error: new Error("boom") },
|
|
403
|
-
],
|
|
404
|
-
});
|
|
405
|
-
expect(formatted).toContain("1 hook failure(s)");
|
|
406
|
-
expect(formatted).toContain("bad-plugin");
|
|
407
|
-
expect(formatted).toContain("boom");
|
|
408
|
-
});
|
|
409
|
-
});
|
|
1
|
+
/**
|
|
2
|
+
* Phase 18.τ — canonical plugin runner tests.
|
|
3
|
+
*
|
|
4
|
+
* Covers every hook type and merge-semantics edge case the runner owns.
|
|
5
|
+
* Real `ManduPlugin` objects, no mocks; the runner is pure so we can
|
|
6
|
+
* assert outputs directly.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { describe, it, expect } from "bun:test";
|
|
10
|
+
import type { BunPlugin } from "bun";
|
|
11
|
+
import type { Middleware } from "../../middleware/define";
|
|
12
|
+
import type { RouteSpec, RoutesManifest } from "../../spec/schema";
|
|
13
|
+
import type { BundleStats } from "../../bundler/types";
|
|
14
|
+
import type { ManduPlugin } from "../hooks";
|
|
15
|
+
import {
|
|
16
|
+
runOnRouteRegistered,
|
|
17
|
+
runOnBundleComplete,
|
|
18
|
+
runDefinePrerenderHook,
|
|
19
|
+
runOnManifestBuilt,
|
|
20
|
+
runDefineBundlerPlugin,
|
|
21
|
+
runDefineMiddlewareChain,
|
|
22
|
+
runDefineTestTransform,
|
|
23
|
+
resolvePluginMiddleware,
|
|
24
|
+
formatHookErrors,
|
|
25
|
+
} from "../runner";
|
|
26
|
+
|
|
27
|
+
// ───── Fixtures ─────
|
|
28
|
+
|
|
29
|
+
const sampleRoute: RouteSpec = {
|
|
30
|
+
id: "sample",
|
|
31
|
+
kind: "page",
|
|
32
|
+
pattern: "/",
|
|
33
|
+
module: "app/page.tsx",
|
|
34
|
+
componentModule: "app/page.tsx",
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
const sampleManifest: RoutesManifest = {
|
|
38
|
+
version: 1,
|
|
39
|
+
routes: [sampleRoute],
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
const sampleStats: BundleStats = {
|
|
43
|
+
totalSize: 1024,
|
|
44
|
+
totalGzipSize: 512,
|
|
45
|
+
largestBundle: { routeId: "sample", size: 1024 },
|
|
46
|
+
buildTime: 50,
|
|
47
|
+
bundleCount: 1,
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const mkPluginContext = () => ({
|
|
51
|
+
rootDir: "/tmp/project",
|
|
52
|
+
mode: "production" as const,
|
|
53
|
+
logger: {
|
|
54
|
+
debug: () => {},
|
|
55
|
+
info: () => {},
|
|
56
|
+
warn: () => {},
|
|
57
|
+
error: () => {},
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
62
|
+
describe("runner — dispatch order", () => {
|
|
63
|
+
it("runs config hook BEFORE plugin hooks", async () => {
|
|
64
|
+
const log: string[] = [];
|
|
65
|
+
const plugins: ManduPlugin[] = [
|
|
66
|
+
{
|
|
67
|
+
name: "p1",
|
|
68
|
+
hooks: {
|
|
69
|
+
onRouteRegistered: () => {
|
|
70
|
+
log.push("p1");
|
|
71
|
+
},
|
|
72
|
+
},
|
|
73
|
+
},
|
|
74
|
+
];
|
|
75
|
+
const configHooks = {
|
|
76
|
+
onRouteRegistered: () => {
|
|
77
|
+
log.push("config");
|
|
78
|
+
},
|
|
79
|
+
};
|
|
80
|
+
await runOnRouteRegistered(sampleRoute, { plugins, configHooks });
|
|
81
|
+
expect(log).toEqual(["config", "p1"]);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
it("runs plugin hooks in declaration order", async () => {
|
|
85
|
+
const log: string[] = [];
|
|
86
|
+
const plugins: ManduPlugin[] = [
|
|
87
|
+
{ name: "a", hooks: { onRouteRegistered: () => { log.push("a"); } } },
|
|
88
|
+
{ name: "b", hooks: { onRouteRegistered: () => { log.push("b"); } } },
|
|
89
|
+
{ name: "c", hooks: { onRouteRegistered: () => { log.push("c"); } } },
|
|
90
|
+
];
|
|
91
|
+
await runOnRouteRegistered(sampleRoute, { plugins });
|
|
92
|
+
expect(log).toEqual(["a", "b", "c"]);
|
|
93
|
+
});
|
|
94
|
+
|
|
95
|
+
it("skips undefined hooks silently", async () => {
|
|
96
|
+
const plugins: ManduPlugin[] = [
|
|
97
|
+
{ name: "empty" },
|
|
98
|
+
{ name: "also-empty", hooks: {} },
|
|
99
|
+
];
|
|
100
|
+
const report = await runOnRouteRegistered(sampleRoute, { plugins });
|
|
101
|
+
expect(report.errors).toEqual([]);
|
|
102
|
+
});
|
|
103
|
+
|
|
104
|
+
it("handles empty plugin array gracefully", async () => {
|
|
105
|
+
const report = await runOnBundleComplete(sampleStats, { plugins: [] });
|
|
106
|
+
expect(report.errors).toEqual([]);
|
|
107
|
+
expect(report.result).toBeUndefined();
|
|
108
|
+
});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
112
|
+
describe("runner — error isolation", () => {
|
|
113
|
+
it("captures errors without stopping sibling plugins", async () => {
|
|
114
|
+
const log: string[] = [];
|
|
115
|
+
const plugins: ManduPlugin[] = [
|
|
116
|
+
{
|
|
117
|
+
name: "ok1",
|
|
118
|
+
hooks: { onRouteRegistered: () => { log.push("ok1"); } },
|
|
119
|
+
},
|
|
120
|
+
{
|
|
121
|
+
name: "fails",
|
|
122
|
+
hooks: {
|
|
123
|
+
onRouteRegistered: () => {
|
|
124
|
+
throw new Error("boom");
|
|
125
|
+
},
|
|
126
|
+
},
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
name: "ok2",
|
|
130
|
+
hooks: { onRouteRegistered: () => { log.push("ok2"); } },
|
|
131
|
+
},
|
|
132
|
+
];
|
|
133
|
+
const report = await runOnRouteRegistered(sampleRoute, { plugins });
|
|
134
|
+
expect(log).toEqual(["ok1", "ok2"]);
|
|
135
|
+
expect(report.errors).toHaveLength(1);
|
|
136
|
+
expect(report.errors[0].source).toBe("fails");
|
|
137
|
+
expect(report.errors[0].hook).toBe("onRouteRegistered");
|
|
138
|
+
expect(report.errors[0].error.message).toBe("boom");
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it("handles async errors", async () => {
|
|
142
|
+
const plugins: ManduPlugin[] = [
|
|
143
|
+
{
|
|
144
|
+
name: "async-fails",
|
|
145
|
+
hooks: {
|
|
146
|
+
async onRouteRegistered() {
|
|
147
|
+
await Promise.resolve();
|
|
148
|
+
throw new Error("async boom");
|
|
149
|
+
},
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
];
|
|
153
|
+
const report = await runOnRouteRegistered(sampleRoute, { plugins });
|
|
154
|
+
expect(report.errors[0].error.message).toBe("async boom");
|
|
155
|
+
});
|
|
156
|
+
|
|
157
|
+
it("stringifies non-Error throws", async () => {
|
|
158
|
+
const plugins: ManduPlugin[] = [
|
|
159
|
+
{
|
|
160
|
+
name: "weird",
|
|
161
|
+
hooks: {
|
|
162
|
+
onRouteRegistered: () => {
|
|
163
|
+
throw "just a string";
|
|
164
|
+
},
|
|
165
|
+
},
|
|
166
|
+
},
|
|
167
|
+
];
|
|
168
|
+
const report = await runOnRouteRegistered(sampleRoute, { plugins });
|
|
169
|
+
expect(report.errors[0].error.message).toBe("just a string");
|
|
170
|
+
});
|
|
171
|
+
});
|
|
172
|
+
|
|
173
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
174
|
+
describe("runner — definePrerenderHook merge", () => {
|
|
175
|
+
it("spreads returns across plugins (last write wins)", async () => {
|
|
176
|
+
const plugins: ManduPlugin[] = [
|
|
177
|
+
{
|
|
178
|
+
name: "first",
|
|
179
|
+
hooks: {
|
|
180
|
+
definePrerenderHook: () => ({ html: "<p>first</p>", skip: false }),
|
|
181
|
+
},
|
|
182
|
+
},
|
|
183
|
+
{
|
|
184
|
+
name: "second",
|
|
185
|
+
hooks: {
|
|
186
|
+
definePrerenderHook: () => ({ html: "<p>second</p>" }),
|
|
187
|
+
},
|
|
188
|
+
},
|
|
189
|
+
];
|
|
190
|
+
const report = await runDefinePrerenderHook(
|
|
191
|
+
{
|
|
192
|
+
...mkPluginContext(),
|
|
193
|
+
pathname: "/",
|
|
194
|
+
html: "<p>original</p>",
|
|
195
|
+
},
|
|
196
|
+
{ plugins },
|
|
197
|
+
);
|
|
198
|
+
expect(report.result.html).toBe("<p>second</p>");
|
|
199
|
+
expect(report.result.skip).toBe(false);
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
it("treats void returns as no-change", async () => {
|
|
203
|
+
const plugins: ManduPlugin[] = [
|
|
204
|
+
{ name: "noop", hooks: { definePrerenderHook: () => {} } },
|
|
205
|
+
{
|
|
206
|
+
name: "sets",
|
|
207
|
+
hooks: { definePrerenderHook: () => ({ skip: true }) },
|
|
208
|
+
},
|
|
209
|
+
];
|
|
210
|
+
const report = await runDefinePrerenderHook(
|
|
211
|
+
{ ...mkPluginContext(), pathname: "/", html: "x" },
|
|
212
|
+
{ plugins },
|
|
213
|
+
);
|
|
214
|
+
expect(report.result.skip).toBe(true);
|
|
215
|
+
});
|
|
216
|
+
});
|
|
217
|
+
|
|
218
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
219
|
+
describe("runner — onManifestBuilt pipe", () => {
|
|
220
|
+
it("pipes manifest across plugins (each sees prev output)", async () => {
|
|
221
|
+
const plugins: ManduPlugin[] = [
|
|
222
|
+
{
|
|
223
|
+
name: "add-version",
|
|
224
|
+
hooks: {
|
|
225
|
+
onManifestBuilt: (m) => ({ ...m, version: 2 }) as RoutesManifest,
|
|
226
|
+
},
|
|
227
|
+
},
|
|
228
|
+
{
|
|
229
|
+
name: "inspect",
|
|
230
|
+
hooks: {
|
|
231
|
+
onManifestBuilt: (m) => {
|
|
232
|
+
expect(m.version).toBe(2);
|
|
233
|
+
return m;
|
|
234
|
+
},
|
|
235
|
+
},
|
|
236
|
+
},
|
|
237
|
+
];
|
|
238
|
+
const report = await runOnManifestBuilt(sampleManifest, { plugins });
|
|
239
|
+
expect(report.result.version).toBe(2);
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
it("void return passes through unchanged", async () => {
|
|
243
|
+
const plugins: ManduPlugin[] = [
|
|
244
|
+
{ name: "noop", hooks: { onManifestBuilt: () => {} } },
|
|
245
|
+
];
|
|
246
|
+
const report = await runOnManifestBuilt(sampleManifest, { plugins });
|
|
247
|
+
expect(report.result).toBe(sampleManifest);
|
|
248
|
+
});
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
252
|
+
describe("runner — defineBundlerPlugin concat", () => {
|
|
253
|
+
const mkPlugin = (name: string): BunPlugin => ({
|
|
254
|
+
name,
|
|
255
|
+
setup() {},
|
|
256
|
+
});
|
|
257
|
+
|
|
258
|
+
it("concatenates scalar + array returns", async () => {
|
|
259
|
+
const plugins: ManduPlugin[] = [
|
|
260
|
+
{
|
|
261
|
+
name: "single",
|
|
262
|
+
hooks: { defineBundlerPlugin: () => mkPlugin("one") },
|
|
263
|
+
},
|
|
264
|
+
{
|
|
265
|
+
name: "multi",
|
|
266
|
+
hooks: {
|
|
267
|
+
defineBundlerPlugin: () => [mkPlugin("two"), mkPlugin("three")],
|
|
268
|
+
},
|
|
269
|
+
},
|
|
270
|
+
];
|
|
271
|
+
const report = await runDefineBundlerPlugin({ plugins });
|
|
272
|
+
expect(report.result.map((p) => p.name)).toEqual(["one", "two", "three"]);
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
it("skips undefined returns", async () => {
|
|
276
|
+
const plugins: ManduPlugin[] = [
|
|
277
|
+
{ name: "nil", hooks: { defineBundlerPlugin: () => undefined } as any },
|
|
278
|
+
{ name: "one", hooks: { defineBundlerPlugin: () => mkPlugin("x") } },
|
|
279
|
+
];
|
|
280
|
+
const report = await runDefineBundlerPlugin({ plugins });
|
|
281
|
+
expect(report.result.map((p) => p.name)).toEqual(["x"]);
|
|
282
|
+
});
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
286
|
+
describe("runner — defineMiddlewareChain concat", () => {
|
|
287
|
+
const mkMw = (name: string): Middleware => ({
|
|
288
|
+
name,
|
|
289
|
+
handler: async (_req, next) => next(),
|
|
290
|
+
});
|
|
291
|
+
|
|
292
|
+
it("concatenates middleware across plugins in order", async () => {
|
|
293
|
+
const plugins: ManduPlugin[] = [
|
|
294
|
+
{ name: "p1", hooks: { defineMiddlewareChain: () => [mkMw("a"), mkMw("b")] } },
|
|
295
|
+
{ name: "p2", hooks: { defineMiddlewareChain: () => [mkMw("c")] } },
|
|
296
|
+
];
|
|
297
|
+
const report = await runDefineMiddlewareChain(mkPluginContext(), { plugins });
|
|
298
|
+
expect(report.result.map((m) => m.name)).toEqual(["a", "b", "c"]);
|
|
299
|
+
});
|
|
300
|
+
|
|
301
|
+
it("resolvePluginMiddleware returns flattened list", async () => {
|
|
302
|
+
const plugins: ManduPlugin[] = [
|
|
303
|
+
{ name: "p", hooks: { defineMiddlewareChain: () => [mkMw("only")] } },
|
|
304
|
+
];
|
|
305
|
+
const mw = await resolvePluginMiddleware({
|
|
306
|
+
plugins,
|
|
307
|
+
rootDir: "/tmp",
|
|
308
|
+
mode: "production",
|
|
309
|
+
});
|
|
310
|
+
expect(mw.map((m) => m.name)).toEqual(["only"]);
|
|
311
|
+
});
|
|
312
|
+
});
|
|
313
|
+
|
|
314
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
315
|
+
describe("runner — defineTestTransform pipe", () => {
|
|
316
|
+
it("pipes each plugin's output into the next", async () => {
|
|
317
|
+
const plugins: ManduPlugin[] = [
|
|
318
|
+
{
|
|
319
|
+
name: "upper",
|
|
320
|
+
hooks: {
|
|
321
|
+
defineTestTransform: (ctx) => ctx.source.toUpperCase(),
|
|
322
|
+
},
|
|
323
|
+
},
|
|
324
|
+
{
|
|
325
|
+
name: "suffix",
|
|
326
|
+
hooks: {
|
|
327
|
+
defineTestTransform: (ctx) => ctx.source + "!",
|
|
328
|
+
},
|
|
329
|
+
},
|
|
330
|
+
];
|
|
331
|
+
const report = await runDefineTestTransform(
|
|
332
|
+
{ testFile: "foo.test.ts", source: "hello" },
|
|
333
|
+
{ plugins },
|
|
334
|
+
);
|
|
335
|
+
expect(report.result).toBe("HELLO!");
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
it("preserves source when a plugin throws", async () => {
|
|
339
|
+
const plugins: ManduPlugin[] = [
|
|
340
|
+
{
|
|
341
|
+
name: "bad",
|
|
342
|
+
hooks: {
|
|
343
|
+
defineTestTransform: () => { throw new Error("oops"); },
|
|
344
|
+
},
|
|
345
|
+
},
|
|
346
|
+
{
|
|
347
|
+
name: "good",
|
|
348
|
+
hooks: {
|
|
349
|
+
defineTestTransform: (ctx) => ctx.source + ":ok",
|
|
350
|
+
},
|
|
351
|
+
},
|
|
352
|
+
];
|
|
353
|
+
const report = await runDefineTestTransform(
|
|
354
|
+
{ testFile: "foo.test.ts", source: "base" },
|
|
355
|
+
{ plugins },
|
|
356
|
+
);
|
|
357
|
+
expect(report.result).toBe("base:ok");
|
|
358
|
+
expect(report.errors).toHaveLength(1);
|
|
359
|
+
});
|
|
360
|
+
});
|
|
361
|
+
|
|
362
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
363
|
+
describe("runner — async hook timing", () => {
|
|
364
|
+
it("awaits async hooks serially", async () => {
|
|
365
|
+
const log: string[] = [];
|
|
366
|
+
const plugins: ManduPlugin[] = [
|
|
367
|
+
{
|
|
368
|
+
name: "slow",
|
|
369
|
+
hooks: {
|
|
370
|
+
async onRouteRegistered() {
|
|
371
|
+
await new Promise((r) => setTimeout(r, 10));
|
|
372
|
+
log.push("slow");
|
|
373
|
+
},
|
|
374
|
+
},
|
|
375
|
+
},
|
|
376
|
+
{
|
|
377
|
+
name: "fast",
|
|
378
|
+
hooks: {
|
|
379
|
+
onRouteRegistered: () => {
|
|
380
|
+
log.push("fast");
|
|
381
|
+
},
|
|
382
|
+
},
|
|
383
|
+
},
|
|
384
|
+
];
|
|
385
|
+
await runOnRouteRegistered(sampleRoute, { plugins });
|
|
386
|
+
expect(log).toEqual(["slow", "fast"]);
|
|
387
|
+
});
|
|
388
|
+
});
|
|
389
|
+
|
|
390
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
391
|
+
describe("runner — formatHookErrors", () => {
|
|
392
|
+
it("returns null when the report is clean", () => {
|
|
393
|
+
expect(
|
|
394
|
+
formatHookErrors({ result: undefined, errors: [] }),
|
|
395
|
+
).toBeNull();
|
|
396
|
+
});
|
|
397
|
+
|
|
398
|
+
it("formats error rollup", () => {
|
|
399
|
+
const formatted = formatHookErrors({
|
|
400
|
+
result: undefined,
|
|
401
|
+
errors: [
|
|
402
|
+
{ hook: "onRouteRegistered", source: "bad-plugin", error: new Error("boom") },
|
|
403
|
+
],
|
|
404
|
+
});
|
|
405
|
+
expect(formatted).toContain("1 hook failure(s)");
|
|
406
|
+
expect(formatted).toContain("bad-plugin");
|
|
407
|
+
expect(formatted).toContain("boom");
|
|
408
|
+
});
|
|
409
|
+
});
|