@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/plugins/runner.ts
CHANGED
|
@@ -1,361 +1,361 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Canonical Plugin Hook Runner — Phase 18.τ.
|
|
3
|
-
*
|
|
4
|
-
* This module is the only place where plugin hook dispatch + merge
|
|
5
|
-
* semantics are implemented. Every integration point (bundler, prerender,
|
|
6
|
-
* router scanner, runtime server) imports from here so the rules are
|
|
7
|
-
* consistent and easy to audit.
|
|
8
|
-
*
|
|
9
|
-
* Dispatch order, common to every hook:
|
|
10
|
-
* 1. Config-level hooks (from `ManduConfig.hooks`) run FIRST.
|
|
11
|
-
* 2. Plugin hooks (from `ManduConfig.plugins[].hooks`) run in
|
|
12
|
-
* declaration order.
|
|
13
|
-
* 3. Each invocation is isolated in try/catch — one failure does not
|
|
14
|
-
* block subsequent hooks. Errors are collected in the returned
|
|
15
|
-
* {@link HookRunReport} so callers can surface a single rollup.
|
|
16
|
-
*
|
|
17
|
-
* Merge semantics (per hook type):
|
|
18
|
-
*
|
|
19
|
-
* | Hook | Return type | Merge rule |
|
|
20
|
-
* | ----------------------- | ---------------------- | ------------------- |
|
|
21
|
-
* | `onRouteRegistered` | void | — |
|
|
22
|
-
* | `onBundleComplete` | void | — |
|
|
23
|
-
* | `definePrerenderHook` | PrerenderOverride|void | Object spread |
|
|
24
|
-
* | `onManifestBuilt` | RoutesManifest|void | Pipe (last wins) |
|
|
25
|
-
* | `defineBundlerPlugin` | BunPlugin|BunPlugin[] | Concat |
|
|
26
|
-
* | `defineMiddlewareChain` | Middleware[] | Concat |
|
|
27
|
-
* | `defineTestTransform` | string | Pipe (each sees prev)|
|
|
28
|
-
*
|
|
29
|
-
* @see `docs/architect/plugin-api.md`
|
|
30
|
-
* @see `./hooks.ts` for the typed surface
|
|
31
|
-
*/
|
|
32
|
-
|
|
33
|
-
import type { BunPlugin } from "bun";
|
|
34
|
-
import type { Middleware } from "../middleware/define";
|
|
35
|
-
import type { BundleStats } from "../bundler/types";
|
|
36
|
-
import type { RouteSpec, RoutesManifest } from "../spec/schema";
|
|
37
|
-
import type {
|
|
38
|
-
ManduPlugin,
|
|
39
|
-
ManduHooks,
|
|
40
|
-
PluginContext,
|
|
41
|
-
PrerenderContext,
|
|
42
|
-
PrerenderOverride,
|
|
43
|
-
TestTransformContext,
|
|
44
|
-
} from "./hooks";
|
|
45
|
-
|
|
46
|
-
// ═══════════════════════════════════════════════════════════════════════
|
|
47
|
-
// Shared types
|
|
48
|
-
// ═══════════════════════════════════════════════════════════════════════
|
|
49
|
-
|
|
50
|
-
/**
|
|
51
|
-
* Per-hook-invocation error record. Attached to the returned report so
|
|
52
|
-
* callers can log "plugin X failed on hook Y" without swallowing the
|
|
53
|
-
* stack trace.
|
|
54
|
-
*/
|
|
55
|
-
export interface HookError {
|
|
56
|
-
/** Hook name that failed. */
|
|
57
|
-
hook: keyof ManduHooks;
|
|
58
|
-
/** Plugin label — either `"config"` or `plugin.name`. */
|
|
59
|
-
source: string;
|
|
60
|
-
/** Captured error. */
|
|
61
|
-
error: Error;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
export interface HookRunReport<T = unknown> {
|
|
65
|
-
/** Merged result (shape varies by hook — see merge table above). */
|
|
66
|
-
result: T;
|
|
67
|
-
/** Errors swallowed per hook invocation. Empty when all plugins succeeded. */
|
|
68
|
-
errors: HookError[];
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
/**
|
|
72
|
-
* Args common to every runner call.
|
|
73
|
-
*/
|
|
74
|
-
export interface RunnerArgs {
|
|
75
|
-
plugins: readonly ManduPlugin[];
|
|
76
|
-
configHooks?: Partial<ManduHooks>;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
// ═══════════════════════════════════════════════════════════════════════
|
|
80
|
-
// Internal helpers
|
|
81
|
-
// ═══════════════════════════════════════════════════════════════════════
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* Yield every `(source, fn)` pair for a given hook name, in the canonical
|
|
85
|
-
* dispatch order: config hook first, then plugin hooks by declaration.
|
|
86
|
-
*/
|
|
87
|
-
function* iterateHook<K extends keyof ManduHooks>(
|
|
88
|
-
hookName: K,
|
|
89
|
-
args: RunnerArgs,
|
|
90
|
-
): Generator<{ source: string; fn: NonNullable<ManduHooks[K]> }> {
|
|
91
|
-
const configFn = args.configHooks?.[hookName];
|
|
92
|
-
if (configFn) {
|
|
93
|
-
yield { source: "config", fn: configFn as NonNullable<ManduHooks[K]> };
|
|
94
|
-
}
|
|
95
|
-
for (const plugin of args.plugins) {
|
|
96
|
-
const pluginFn = plugin.hooks?.[hookName];
|
|
97
|
-
if (pluginFn) {
|
|
98
|
-
yield {
|
|
99
|
-
source: plugin.name,
|
|
100
|
-
fn: pluginFn as NonNullable<ManduHooks[K]>,
|
|
101
|
-
};
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
/**
|
|
107
|
-
* Invoke a hook function and surface any thrown error as a HookError so
|
|
108
|
-
* the caller can carry on with the next plugin. Returns the function's
|
|
109
|
-
* return value (or `undefined` on error).
|
|
110
|
-
*/
|
|
111
|
-
async function invokeSafely<R>(
|
|
112
|
-
hookName: keyof ManduHooks,
|
|
113
|
-
source: string,
|
|
114
|
-
thunk: () => R | Promise<R>,
|
|
115
|
-
errors: HookError[],
|
|
116
|
-
): Promise<R | undefined> {
|
|
117
|
-
try {
|
|
118
|
-
return await thunk();
|
|
119
|
-
} catch (raw) {
|
|
120
|
-
const error = raw instanceof Error ? raw : new Error(String(raw));
|
|
121
|
-
errors.push({ hook: hookName, source, error });
|
|
122
|
-
// Surface to stderr too — matches the legacy `runHook` behaviour so
|
|
123
|
-
// users see the failure even if the caller ignores the report.
|
|
124
|
-
console.error(`[plugin] ${hookName} failed in ${source}: ${error.message}`);
|
|
125
|
-
return undefined;
|
|
126
|
-
}
|
|
127
|
-
}
|
|
128
|
-
|
|
129
|
-
// ═══════════════════════════════════════════════════════════════════════
|
|
130
|
-
// Observer runners (void return — "fire and forget")
|
|
131
|
-
// ═══════════════════════════════════════════════════════════════════════
|
|
132
|
-
|
|
133
|
-
/**
|
|
134
|
-
* Fire `onRouteRegistered` for each scanned route. Errors in one plugin
|
|
135
|
-
* don't stop the scan, or even other plugins for the same route.
|
|
136
|
-
*/
|
|
137
|
-
export async function runOnRouteRegistered(
|
|
138
|
-
route: RouteSpec,
|
|
139
|
-
args: RunnerArgs,
|
|
140
|
-
): Promise<HookRunReport<void>> {
|
|
141
|
-
const errors: HookError[] = [];
|
|
142
|
-
for (const { source, fn } of iterateHook("onRouteRegistered", args)) {
|
|
143
|
-
await invokeSafely("onRouteRegistered", source, () => fn(route), errors);
|
|
144
|
-
}
|
|
145
|
-
return { result: undefined, errors };
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
/**
|
|
149
|
-
* Fire `onBundleComplete` with the final BundleStats.
|
|
150
|
-
*/
|
|
151
|
-
export async function runOnBundleComplete(
|
|
152
|
-
stats: BundleStats,
|
|
153
|
-
args: RunnerArgs,
|
|
154
|
-
): Promise<HookRunReport<void>> {
|
|
155
|
-
const errors: HookError[] = [];
|
|
156
|
-
for (const { source, fn } of iterateHook("onBundleComplete", args)) {
|
|
157
|
-
await invokeSafely("onBundleComplete", source, () => fn(stats), errors);
|
|
158
|
-
}
|
|
159
|
-
return { result: undefined, errors };
|
|
160
|
-
}
|
|
161
|
-
|
|
162
|
-
// ═══════════════════════════════════════════════════════════════════════
|
|
163
|
-
// Transformer runners (return-value pipe / spread)
|
|
164
|
-
// ═══════════════════════════════════════════════════════════════════════
|
|
165
|
-
|
|
166
|
-
/**
|
|
167
|
-
* Merge strategy for `definePrerenderHook`:
|
|
168
|
-
*
|
|
169
|
-
* - Each plugin's non-undefined return spreads onto the accumulator so
|
|
170
|
-
* later plugins can override earlier fields (`{ skip: true }` wins
|
|
171
|
-
* unless a later plugin sets `{ skip: false }` explicitly).
|
|
172
|
-
* - An undefined / void return is treated as "no change".
|
|
173
|
-
*/
|
|
174
|
-
export async function runDefinePrerenderHook(
|
|
175
|
-
ctx: PrerenderContext,
|
|
176
|
-
args: RunnerArgs,
|
|
177
|
-
): Promise<HookRunReport<PrerenderOverride>> {
|
|
178
|
-
const errors: HookError[] = [];
|
|
179
|
-
let merged: PrerenderOverride = {};
|
|
180
|
-
for (const { source, fn } of iterateHook("definePrerenderHook", args)) {
|
|
181
|
-
const out = await invokeSafely(
|
|
182
|
-
"definePrerenderHook",
|
|
183
|
-
source,
|
|
184
|
-
() => fn(ctx),
|
|
185
|
-
errors,
|
|
186
|
-
);
|
|
187
|
-
if (out && typeof out === "object") {
|
|
188
|
-
merged = { ...merged, ...out };
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
return { result: merged, errors };
|
|
192
|
-
}
|
|
193
|
-
|
|
194
|
-
/**
|
|
195
|
-
* Pipe `onManifestBuilt` across plugins. Each plugin receives the
|
|
196
|
-
* manifest the previous plugin returned. A `void` / `undefined` return
|
|
197
|
-
* means "no change, pass through".
|
|
198
|
-
*/
|
|
199
|
-
export async function runOnManifestBuilt(
|
|
200
|
-
manifest: RoutesManifest,
|
|
201
|
-
args: RunnerArgs,
|
|
202
|
-
): Promise<HookRunReport<RoutesManifest>> {
|
|
203
|
-
const errors: HookError[] = [];
|
|
204
|
-
let current: RoutesManifest = manifest;
|
|
205
|
-
for (const { source, fn } of iterateHook("onManifestBuilt", args)) {
|
|
206
|
-
const out = await invokeSafely(
|
|
207
|
-
"onManifestBuilt",
|
|
208
|
-
source,
|
|
209
|
-
() => fn(current),
|
|
210
|
-
errors,
|
|
211
|
-
);
|
|
212
|
-
if (out && typeof out === "object") {
|
|
213
|
-
current = out;
|
|
214
|
-
}
|
|
215
|
-
}
|
|
216
|
-
return { result: current, errors };
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
/**
|
|
220
|
-
* Collect BunPlugin contributions across every plugin. Scalar returns are
|
|
221
|
-
* wrapped into a single-element array; array returns are concatenated.
|
|
222
|
-
* `undefined` is skipped.
|
|
223
|
-
*/
|
|
224
|
-
export async function runDefineBundlerPlugin(
|
|
225
|
-
args: RunnerArgs,
|
|
226
|
-
): Promise<HookRunReport<BunPlugin[]>> {
|
|
227
|
-
const errors: HookError[] = [];
|
|
228
|
-
const collected: BunPlugin[] = [];
|
|
229
|
-
for (const { source, fn } of iterateHook("defineBundlerPlugin", args)) {
|
|
230
|
-
const out = await invokeSafely(
|
|
231
|
-
"defineBundlerPlugin",
|
|
232
|
-
source,
|
|
233
|
-
() => fn(),
|
|
234
|
-
errors,
|
|
235
|
-
);
|
|
236
|
-
if (!out) continue;
|
|
237
|
-
if (Array.isArray(out)) {
|
|
238
|
-
collected.push(...out);
|
|
239
|
-
} else {
|
|
240
|
-
collected.push(out);
|
|
241
|
-
}
|
|
242
|
-
}
|
|
243
|
-
return { result: collected, errors };
|
|
244
|
-
}
|
|
245
|
-
|
|
246
|
-
/**
|
|
247
|
-
* Concatenate Middleware arrays from every plugin that provides a
|
|
248
|
-
* `defineMiddlewareChain`. The returned array is prepended to the
|
|
249
|
-
* user-declared `ManduConfig.middleware` at server boot time (see
|
|
250
|
-
* `runtime/server.ts`).
|
|
251
|
-
*/
|
|
252
|
-
export async function runDefineMiddlewareChain(
|
|
253
|
-
ctx: PluginContext,
|
|
254
|
-
args: RunnerArgs,
|
|
255
|
-
): Promise<HookRunReport<Middleware[]>> {
|
|
256
|
-
const errors: HookError[] = [];
|
|
257
|
-
const collected: Middleware[] = [];
|
|
258
|
-
for (const { source, fn } of iterateHook("defineMiddlewareChain", args)) {
|
|
259
|
-
const out = await invokeSafely(
|
|
260
|
-
"defineMiddlewareChain",
|
|
261
|
-
source,
|
|
262
|
-
() => fn(ctx),
|
|
263
|
-
errors,
|
|
264
|
-
);
|
|
265
|
-
if (Array.isArray(out)) {
|
|
266
|
-
collected.push(...out);
|
|
267
|
-
}
|
|
268
|
-
}
|
|
269
|
-
return { result: collected, errors };
|
|
270
|
-
}
|
|
271
|
-
|
|
272
|
-
/**
|
|
273
|
-
* Pipe test-file source through every plugin's `defineTestTransform`.
|
|
274
|
-
* Each plugin sees the output of the previous plugin. A thrown error
|
|
275
|
-
* leaves the source unchanged for that step.
|
|
276
|
-
*/
|
|
277
|
-
export async function runDefineTestTransform(
|
|
278
|
-
ctx: TestTransformContext,
|
|
279
|
-
args: RunnerArgs,
|
|
280
|
-
): Promise<HookRunReport<string>> {
|
|
281
|
-
const errors: HookError[] = [];
|
|
282
|
-
let current = ctx.source;
|
|
283
|
-
for (const { source, fn } of iterateHook("defineTestTransform", args)) {
|
|
284
|
-
const out = await invokeSafely(
|
|
285
|
-
"defineTestTransform",
|
|
286
|
-
source,
|
|
287
|
-
() => fn({ testFile: ctx.testFile, source: current }),
|
|
288
|
-
errors,
|
|
289
|
-
);
|
|
290
|
-
if (typeof out === "string") {
|
|
291
|
-
current = out;
|
|
292
|
-
}
|
|
293
|
-
}
|
|
294
|
-
return { result: current, errors };
|
|
295
|
-
}
|
|
296
|
-
|
|
297
|
-
/**
|
|
298
|
-
* Driver-friendly wrapper for `runDefineMiddlewareChain` that returns
|
|
299
|
-
* the merged Middleware[] directly (errors are logged to stderr via
|
|
300
|
-
* the same path as the inner runner). Use when a sync caller like
|
|
301
|
-
* `startServer()` needs the array pre-resolved — wrap the call in
|
|
302
|
-
* `await` from your driver (the CLI), then pass the result in as a
|
|
303
|
-
* PREFIX to `options.middleware`.
|
|
304
|
-
*
|
|
305
|
-
* @example
|
|
306
|
-
* ```ts
|
|
307
|
-
* const pluginMiddleware = await resolvePluginMiddleware({
|
|
308
|
-
* plugins: config.plugins ?? [],
|
|
309
|
-
* configHooks: config.hooks,
|
|
310
|
-
* rootDir: process.cwd(),
|
|
311
|
-
* mode: isDev ? "development" : "production",
|
|
312
|
-
* });
|
|
313
|
-
* startServer(manifest, {
|
|
314
|
-
* ...serverOptions,
|
|
315
|
-
* middleware: [...pluginMiddleware, ...(config.middleware ?? [])],
|
|
316
|
-
* plugins: config.plugins,
|
|
317
|
-
* configHooks: config.hooks,
|
|
318
|
-
* });
|
|
319
|
-
* ```
|
|
320
|
-
*/
|
|
321
|
-
export async function resolvePluginMiddleware(input: {
|
|
322
|
-
plugins: readonly ManduPlugin[];
|
|
323
|
-
configHooks?: Partial<ManduHooks>;
|
|
324
|
-
rootDir: string;
|
|
325
|
-
mode: "development" | "production";
|
|
326
|
-
}): Promise<Middleware[]> {
|
|
327
|
-
const ctx: PluginContext = {
|
|
328
|
-
rootDir: input.rootDir,
|
|
329
|
-
mode: input.mode,
|
|
330
|
-
logger: {
|
|
331
|
-
debug: (m, d) => console.debug(`[plugin] ${m}`, d ?? ""),
|
|
332
|
-
info: (m, d) => console.info(`[plugin] ${m}`, d ?? ""),
|
|
333
|
-
warn: (m, d) => console.warn(`[plugin] ${m}`, d ?? ""),
|
|
334
|
-
error: (m, d) => console.error(`[plugin] ${m}`, d ?? ""),
|
|
335
|
-
},
|
|
336
|
-
};
|
|
337
|
-
const report = await runDefineMiddlewareChain(ctx, {
|
|
338
|
-
plugins: input.plugins,
|
|
339
|
-
configHooks: input.configHooks,
|
|
340
|
-
});
|
|
341
|
-
return report.result;
|
|
342
|
-
}
|
|
343
|
-
|
|
344
|
-
// ═══════════════════════════════════════════════════════════════════════
|
|
345
|
-
// Convenience: summarize errors for CLI output
|
|
346
|
-
// ═══════════════════════════════════════════════════════════════════════
|
|
347
|
-
|
|
348
|
-
/**
|
|
349
|
-
* Render an error rollup the CLI can print. Returns `null` when the
|
|
350
|
-
* report is clean — callers should treat `null` as "nothing to log".
|
|
351
|
-
*/
|
|
352
|
-
export function formatHookErrors(
|
|
353
|
-
report: HookRunReport<unknown>,
|
|
354
|
-
): string | null {
|
|
355
|
-
if (report.errors.length === 0) return null;
|
|
356
|
-
const lines = [`[plugin] ${report.errors.length} hook failure(s):`];
|
|
357
|
-
for (const e of report.errors) {
|
|
358
|
-
lines.push(` - ${e.hook} in ${e.source}: ${e.error.message}`);
|
|
359
|
-
}
|
|
360
|
-
return lines.join("\n");
|
|
361
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Canonical Plugin Hook Runner — Phase 18.τ.
|
|
3
|
+
*
|
|
4
|
+
* This module is the only place where plugin hook dispatch + merge
|
|
5
|
+
* semantics are implemented. Every integration point (bundler, prerender,
|
|
6
|
+
* router scanner, runtime server) imports from here so the rules are
|
|
7
|
+
* consistent and easy to audit.
|
|
8
|
+
*
|
|
9
|
+
* Dispatch order, common to every hook:
|
|
10
|
+
* 1. Config-level hooks (from `ManduConfig.hooks`) run FIRST.
|
|
11
|
+
* 2. Plugin hooks (from `ManduConfig.plugins[].hooks`) run in
|
|
12
|
+
* declaration order.
|
|
13
|
+
* 3. Each invocation is isolated in try/catch — one failure does not
|
|
14
|
+
* block subsequent hooks. Errors are collected in the returned
|
|
15
|
+
* {@link HookRunReport} so callers can surface a single rollup.
|
|
16
|
+
*
|
|
17
|
+
* Merge semantics (per hook type):
|
|
18
|
+
*
|
|
19
|
+
* | Hook | Return type | Merge rule |
|
|
20
|
+
* | ----------------------- | ---------------------- | ------------------- |
|
|
21
|
+
* | `onRouteRegistered` | void | — |
|
|
22
|
+
* | `onBundleComplete` | void | — |
|
|
23
|
+
* | `definePrerenderHook` | PrerenderOverride|void | Object spread |
|
|
24
|
+
* | `onManifestBuilt` | RoutesManifest|void | Pipe (last wins) |
|
|
25
|
+
* | `defineBundlerPlugin` | BunPlugin|BunPlugin[] | Concat |
|
|
26
|
+
* | `defineMiddlewareChain` | Middleware[] | Concat |
|
|
27
|
+
* | `defineTestTransform` | string | Pipe (each sees prev)|
|
|
28
|
+
*
|
|
29
|
+
* @see `docs/architect/plugin-api.md`
|
|
30
|
+
* @see `./hooks.ts` for the typed surface
|
|
31
|
+
*/
|
|
32
|
+
|
|
33
|
+
import type { BunPlugin } from "bun";
|
|
34
|
+
import type { Middleware } from "../middleware/define";
|
|
35
|
+
import type { BundleStats } from "../bundler/types";
|
|
36
|
+
import type { RouteSpec, RoutesManifest } from "../spec/schema";
|
|
37
|
+
import type {
|
|
38
|
+
ManduPlugin,
|
|
39
|
+
ManduHooks,
|
|
40
|
+
PluginContext,
|
|
41
|
+
PrerenderContext,
|
|
42
|
+
PrerenderOverride,
|
|
43
|
+
TestTransformContext,
|
|
44
|
+
} from "./hooks";
|
|
45
|
+
|
|
46
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
47
|
+
// Shared types
|
|
48
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Per-hook-invocation error record. Attached to the returned report so
|
|
52
|
+
* callers can log "plugin X failed on hook Y" without swallowing the
|
|
53
|
+
* stack trace.
|
|
54
|
+
*/
|
|
55
|
+
export interface HookError {
|
|
56
|
+
/** Hook name that failed. */
|
|
57
|
+
hook: keyof ManduHooks;
|
|
58
|
+
/** Plugin label — either `"config"` or `plugin.name`. */
|
|
59
|
+
source: string;
|
|
60
|
+
/** Captured error. */
|
|
61
|
+
error: Error;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
export interface HookRunReport<T = unknown> {
|
|
65
|
+
/** Merged result (shape varies by hook — see merge table above). */
|
|
66
|
+
result: T;
|
|
67
|
+
/** Errors swallowed per hook invocation. Empty when all plugins succeeded. */
|
|
68
|
+
errors: HookError[];
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* Args common to every runner call.
|
|
73
|
+
*/
|
|
74
|
+
export interface RunnerArgs {
|
|
75
|
+
plugins: readonly ManduPlugin[];
|
|
76
|
+
configHooks?: Partial<ManduHooks>;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
80
|
+
// Internal helpers
|
|
81
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Yield every `(source, fn)` pair for a given hook name, in the canonical
|
|
85
|
+
* dispatch order: config hook first, then plugin hooks by declaration.
|
|
86
|
+
*/
|
|
87
|
+
function* iterateHook<K extends keyof ManduHooks>(
|
|
88
|
+
hookName: K,
|
|
89
|
+
args: RunnerArgs,
|
|
90
|
+
): Generator<{ source: string; fn: NonNullable<ManduHooks[K]> }> {
|
|
91
|
+
const configFn = args.configHooks?.[hookName];
|
|
92
|
+
if (configFn) {
|
|
93
|
+
yield { source: "config", fn: configFn as NonNullable<ManduHooks[K]> };
|
|
94
|
+
}
|
|
95
|
+
for (const plugin of args.plugins) {
|
|
96
|
+
const pluginFn = plugin.hooks?.[hookName];
|
|
97
|
+
if (pluginFn) {
|
|
98
|
+
yield {
|
|
99
|
+
source: plugin.name,
|
|
100
|
+
fn: pluginFn as NonNullable<ManduHooks[K]>,
|
|
101
|
+
};
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Invoke a hook function and surface any thrown error as a HookError so
|
|
108
|
+
* the caller can carry on with the next plugin. Returns the function's
|
|
109
|
+
* return value (or `undefined` on error).
|
|
110
|
+
*/
|
|
111
|
+
async function invokeSafely<R>(
|
|
112
|
+
hookName: keyof ManduHooks,
|
|
113
|
+
source: string,
|
|
114
|
+
thunk: () => R | Promise<R>,
|
|
115
|
+
errors: HookError[],
|
|
116
|
+
): Promise<R | undefined> {
|
|
117
|
+
try {
|
|
118
|
+
return await thunk();
|
|
119
|
+
} catch (raw) {
|
|
120
|
+
const error = raw instanceof Error ? raw : new Error(String(raw));
|
|
121
|
+
errors.push({ hook: hookName, source, error });
|
|
122
|
+
// Surface to stderr too — matches the legacy `runHook` behaviour so
|
|
123
|
+
// users see the failure even if the caller ignores the report.
|
|
124
|
+
console.error(`[plugin] ${hookName} failed in ${source}: ${error.message}`);
|
|
125
|
+
return undefined;
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
130
|
+
// Observer runners (void return — "fire and forget")
|
|
131
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Fire `onRouteRegistered` for each scanned route. Errors in one plugin
|
|
135
|
+
* don't stop the scan, or even other plugins for the same route.
|
|
136
|
+
*/
|
|
137
|
+
export async function runOnRouteRegistered(
|
|
138
|
+
route: RouteSpec,
|
|
139
|
+
args: RunnerArgs,
|
|
140
|
+
): Promise<HookRunReport<void>> {
|
|
141
|
+
const errors: HookError[] = [];
|
|
142
|
+
for (const { source, fn } of iterateHook("onRouteRegistered", args)) {
|
|
143
|
+
await invokeSafely("onRouteRegistered", source, () => fn(route), errors);
|
|
144
|
+
}
|
|
145
|
+
return { result: undefined, errors };
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Fire `onBundleComplete` with the final BundleStats.
|
|
150
|
+
*/
|
|
151
|
+
export async function runOnBundleComplete(
|
|
152
|
+
stats: BundleStats,
|
|
153
|
+
args: RunnerArgs,
|
|
154
|
+
): Promise<HookRunReport<void>> {
|
|
155
|
+
const errors: HookError[] = [];
|
|
156
|
+
for (const { source, fn } of iterateHook("onBundleComplete", args)) {
|
|
157
|
+
await invokeSafely("onBundleComplete", source, () => fn(stats), errors);
|
|
158
|
+
}
|
|
159
|
+
return { result: undefined, errors };
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
163
|
+
// Transformer runners (return-value pipe / spread)
|
|
164
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
165
|
+
|
|
166
|
+
/**
|
|
167
|
+
* Merge strategy for `definePrerenderHook`:
|
|
168
|
+
*
|
|
169
|
+
* - Each plugin's non-undefined return spreads onto the accumulator so
|
|
170
|
+
* later plugins can override earlier fields (`{ skip: true }` wins
|
|
171
|
+
* unless a later plugin sets `{ skip: false }` explicitly).
|
|
172
|
+
* - An undefined / void return is treated as "no change".
|
|
173
|
+
*/
|
|
174
|
+
export async function runDefinePrerenderHook(
|
|
175
|
+
ctx: PrerenderContext,
|
|
176
|
+
args: RunnerArgs,
|
|
177
|
+
): Promise<HookRunReport<PrerenderOverride>> {
|
|
178
|
+
const errors: HookError[] = [];
|
|
179
|
+
let merged: PrerenderOverride = {};
|
|
180
|
+
for (const { source, fn } of iterateHook("definePrerenderHook", args)) {
|
|
181
|
+
const out = await invokeSafely(
|
|
182
|
+
"definePrerenderHook",
|
|
183
|
+
source,
|
|
184
|
+
() => fn(ctx),
|
|
185
|
+
errors,
|
|
186
|
+
);
|
|
187
|
+
if (out && typeof out === "object") {
|
|
188
|
+
merged = { ...merged, ...out };
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
return { result: merged, errors };
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
/**
|
|
195
|
+
* Pipe `onManifestBuilt` across plugins. Each plugin receives the
|
|
196
|
+
* manifest the previous plugin returned. A `void` / `undefined` return
|
|
197
|
+
* means "no change, pass through".
|
|
198
|
+
*/
|
|
199
|
+
export async function runOnManifestBuilt(
|
|
200
|
+
manifest: RoutesManifest,
|
|
201
|
+
args: RunnerArgs,
|
|
202
|
+
): Promise<HookRunReport<RoutesManifest>> {
|
|
203
|
+
const errors: HookError[] = [];
|
|
204
|
+
let current: RoutesManifest = manifest;
|
|
205
|
+
for (const { source, fn } of iterateHook("onManifestBuilt", args)) {
|
|
206
|
+
const out = await invokeSafely(
|
|
207
|
+
"onManifestBuilt",
|
|
208
|
+
source,
|
|
209
|
+
() => fn(current),
|
|
210
|
+
errors,
|
|
211
|
+
);
|
|
212
|
+
if (out && typeof out === "object") {
|
|
213
|
+
current = out;
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
return { result: current, errors };
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
/**
|
|
220
|
+
* Collect BunPlugin contributions across every plugin. Scalar returns are
|
|
221
|
+
* wrapped into a single-element array; array returns are concatenated.
|
|
222
|
+
* `undefined` is skipped.
|
|
223
|
+
*/
|
|
224
|
+
export async function runDefineBundlerPlugin(
|
|
225
|
+
args: RunnerArgs,
|
|
226
|
+
): Promise<HookRunReport<BunPlugin[]>> {
|
|
227
|
+
const errors: HookError[] = [];
|
|
228
|
+
const collected: BunPlugin[] = [];
|
|
229
|
+
for (const { source, fn } of iterateHook("defineBundlerPlugin", args)) {
|
|
230
|
+
const out = await invokeSafely(
|
|
231
|
+
"defineBundlerPlugin",
|
|
232
|
+
source,
|
|
233
|
+
() => fn(),
|
|
234
|
+
errors,
|
|
235
|
+
);
|
|
236
|
+
if (!out) continue;
|
|
237
|
+
if (Array.isArray(out)) {
|
|
238
|
+
collected.push(...out);
|
|
239
|
+
} else {
|
|
240
|
+
collected.push(out);
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
return { result: collected, errors };
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
/**
|
|
247
|
+
* Concatenate Middleware arrays from every plugin that provides a
|
|
248
|
+
* `defineMiddlewareChain`. The returned array is prepended to the
|
|
249
|
+
* user-declared `ManduConfig.middleware` at server boot time (see
|
|
250
|
+
* `runtime/server.ts`).
|
|
251
|
+
*/
|
|
252
|
+
export async function runDefineMiddlewareChain(
|
|
253
|
+
ctx: PluginContext,
|
|
254
|
+
args: RunnerArgs,
|
|
255
|
+
): Promise<HookRunReport<Middleware[]>> {
|
|
256
|
+
const errors: HookError[] = [];
|
|
257
|
+
const collected: Middleware[] = [];
|
|
258
|
+
for (const { source, fn } of iterateHook("defineMiddlewareChain", args)) {
|
|
259
|
+
const out = await invokeSafely(
|
|
260
|
+
"defineMiddlewareChain",
|
|
261
|
+
source,
|
|
262
|
+
() => fn(ctx),
|
|
263
|
+
errors,
|
|
264
|
+
);
|
|
265
|
+
if (Array.isArray(out)) {
|
|
266
|
+
collected.push(...out);
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
return { result: collected, errors };
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
/**
|
|
273
|
+
* Pipe test-file source through every plugin's `defineTestTransform`.
|
|
274
|
+
* Each plugin sees the output of the previous plugin. A thrown error
|
|
275
|
+
* leaves the source unchanged for that step.
|
|
276
|
+
*/
|
|
277
|
+
export async function runDefineTestTransform(
|
|
278
|
+
ctx: TestTransformContext,
|
|
279
|
+
args: RunnerArgs,
|
|
280
|
+
): Promise<HookRunReport<string>> {
|
|
281
|
+
const errors: HookError[] = [];
|
|
282
|
+
let current = ctx.source;
|
|
283
|
+
for (const { source, fn } of iterateHook("defineTestTransform", args)) {
|
|
284
|
+
const out = await invokeSafely(
|
|
285
|
+
"defineTestTransform",
|
|
286
|
+
source,
|
|
287
|
+
() => fn({ testFile: ctx.testFile, source: current }),
|
|
288
|
+
errors,
|
|
289
|
+
);
|
|
290
|
+
if (typeof out === "string") {
|
|
291
|
+
current = out;
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
return { result: current, errors };
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Driver-friendly wrapper for `runDefineMiddlewareChain` that returns
|
|
299
|
+
* the merged Middleware[] directly (errors are logged to stderr via
|
|
300
|
+
* the same path as the inner runner). Use when a sync caller like
|
|
301
|
+
* `startServer()` needs the array pre-resolved — wrap the call in
|
|
302
|
+
* `await` from your driver (the CLI), then pass the result in as a
|
|
303
|
+
* PREFIX to `options.middleware`.
|
|
304
|
+
*
|
|
305
|
+
* @example
|
|
306
|
+
* ```ts
|
|
307
|
+
* const pluginMiddleware = await resolvePluginMiddleware({
|
|
308
|
+
* plugins: config.plugins ?? [],
|
|
309
|
+
* configHooks: config.hooks,
|
|
310
|
+
* rootDir: process.cwd(),
|
|
311
|
+
* mode: isDev ? "development" : "production",
|
|
312
|
+
* });
|
|
313
|
+
* startServer(manifest, {
|
|
314
|
+
* ...serverOptions,
|
|
315
|
+
* middleware: [...pluginMiddleware, ...(config.middleware ?? [])],
|
|
316
|
+
* plugins: config.plugins,
|
|
317
|
+
* configHooks: config.hooks,
|
|
318
|
+
* });
|
|
319
|
+
* ```
|
|
320
|
+
*/
|
|
321
|
+
export async function resolvePluginMiddleware(input: {
|
|
322
|
+
plugins: readonly ManduPlugin[];
|
|
323
|
+
configHooks?: Partial<ManduHooks>;
|
|
324
|
+
rootDir: string;
|
|
325
|
+
mode: "development" | "production";
|
|
326
|
+
}): Promise<Middleware[]> {
|
|
327
|
+
const ctx: PluginContext = {
|
|
328
|
+
rootDir: input.rootDir,
|
|
329
|
+
mode: input.mode,
|
|
330
|
+
logger: {
|
|
331
|
+
debug: (m, d) => console.debug(`[plugin] ${m}`, d ?? ""),
|
|
332
|
+
info: (m, d) => console.info(`[plugin] ${m}`, d ?? ""),
|
|
333
|
+
warn: (m, d) => console.warn(`[plugin] ${m}`, d ?? ""),
|
|
334
|
+
error: (m, d) => console.error(`[plugin] ${m}`, d ?? ""),
|
|
335
|
+
},
|
|
336
|
+
};
|
|
337
|
+
const report = await runDefineMiddlewareChain(ctx, {
|
|
338
|
+
plugins: input.plugins,
|
|
339
|
+
configHooks: input.configHooks,
|
|
340
|
+
});
|
|
341
|
+
return report.result;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
345
|
+
// Convenience: summarize errors for CLI output
|
|
346
|
+
// ═══════════════════════════════════════════════════════════════════════
|
|
347
|
+
|
|
348
|
+
/**
|
|
349
|
+
* Render an error rollup the CLI can print. Returns `null` when the
|
|
350
|
+
* report is clean — callers should treat `null` as "nothing to log".
|
|
351
|
+
*/
|
|
352
|
+
export function formatHookErrors(
|
|
353
|
+
report: HookRunReport<unknown>,
|
|
354
|
+
): string | null {
|
|
355
|
+
if (report.errors.length === 0) return null;
|
|
356
|
+
const lines = [`[plugin] ${report.errors.length} hook failure(s):`];
|
|
357
|
+
for (const e of report.errors) {
|
|
358
|
+
lines.push(` - ${e.hook} in ${e.source}: ${e.error.message}`);
|
|
359
|
+
}
|
|
360
|
+
return lines.join("\n");
|
|
361
|
+
}
|