@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
|
@@ -1,253 +1,253 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* React Compiler bailout detector (#240 Phase 2).
|
|
3
|
-
*
|
|
4
|
-
* React Compiler silently skips components it can't safely memoize
|
|
5
|
-
* ("15% bailout"). Developers have no way to know which components
|
|
6
|
-
* are in the skipped set without this static pass.
|
|
7
|
-
*
|
|
8
|
-
* This module runs `eslint-plugin-react-compiler` in-process against a
|
|
9
|
-
* caller-supplied list of files (islands / `"use client"` pages /
|
|
10
|
-
* partials — the same files the bundler's `manduClientPlugins` gate
|
|
11
|
-
* picks up) and returns normalized diagnostics. `mandu check` wires
|
|
12
|
-
* this up behind `experimental.reactCompiler.enabled`.
|
|
13
|
-
*
|
|
14
|
-
* ## Why ESLint, not oxlint?
|
|
15
|
-
*
|
|
16
|
-
* The canonical rule lives at `react-compiler/react-compiler` in
|
|
17
|
-
* `eslint-plugin-react-compiler`. Oxlint has no port at the time of
|
|
18
|
-
* writing. When oxlint gains the rule we can swap this runner for a
|
|
19
|
-
* thin `oxlint --rule react-compiler/react-compiler` shell without
|
|
20
|
-
* changing callers — the `runReactCompilerLint` signature stays.
|
|
21
|
-
*
|
|
22
|
-
* ## Graceful degradation
|
|
23
|
-
*
|
|
24
|
-
* `eslint` and `eslint-plugin-react-compiler` are **optional** peer
|
|
25
|
-
* dependencies. If either is missing the runner returns `[]` and logs
|
|
26
|
-
* a single warning — the build is never blocked.
|
|
27
|
-
*/
|
|
28
|
-
|
|
29
|
-
import path from "node:path";
|
|
30
|
-
|
|
31
|
-
export interface ReactCompilerDiagnostic {
|
|
32
|
-
/** Absolute path to the offending file. */
|
|
33
|
-
file: string;
|
|
34
|
-
/** 1-indexed line. */
|
|
35
|
-
line: number;
|
|
36
|
-
/** 1-indexed column. */
|
|
37
|
-
column: number;
|
|
38
|
-
/** Human-readable message produced by the rule. */
|
|
39
|
-
message: string;
|
|
40
|
-
/** Always `"react-compiler/react-compiler"`. */
|
|
41
|
-
ruleId: string;
|
|
42
|
-
/** ESLint severity — "error" (`2`) or "warning" (`1`). */
|
|
43
|
-
severity: "warning" | "error";
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
export interface RunReactCompilerLintOptions {
|
|
47
|
-
/**
|
|
48
|
-
* Project root — used to resolve `eslint` / the plugin from the
|
|
49
|
-
* project's own `node_modules` before falling back to Mandu's.
|
|
50
|
-
*/
|
|
51
|
-
projectRoot: string;
|
|
52
|
-
/**
|
|
53
|
-
* Absolute file paths to lint. Callers should pass only files the
|
|
54
|
-
* bundler would run through the Compiler (islands / client pages /
|
|
55
|
-
* partials). Passing server files wastes CPU — the rule still runs
|
|
56
|
-
* but the diagnostics are irrelevant.
|
|
57
|
-
*/
|
|
58
|
-
targetFiles: readonly string[];
|
|
59
|
-
/**
|
|
60
|
-
* Severity level used for the rule. Default `"warning"`. `"error"`
|
|
61
|
-
* makes the diagnostic a hard failure in strict mode.
|
|
62
|
-
*/
|
|
63
|
-
severity?: "warning" | "error";
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
export interface RunReactCompilerLintResult {
|
|
67
|
-
/** All diagnostics found. Empty array on success or graceful skip. */
|
|
68
|
-
diagnostics: ReactCompilerDiagnostic[];
|
|
69
|
-
/** When `true` the runner could not load its peers and returned `[]`. */
|
|
70
|
-
skipped: boolean;
|
|
71
|
-
/** Human-readable explanation when `skipped` is `true`. */
|
|
72
|
-
skipReason?: string;
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
// Minimal structural types for ESLint's flat-config API. We avoid a
|
|
76
|
-
// hard dependency on `@types/eslint` because eslint is an optional
|
|
77
|
-
// peer dep; the real module is imported dynamically at runtime.
|
|
78
|
-
type ESLintMessage = {
|
|
79
|
-
ruleId: string | null;
|
|
80
|
-
severity: number;
|
|
81
|
-
message: string;
|
|
82
|
-
line?: number;
|
|
83
|
-
column?: number;
|
|
84
|
-
};
|
|
85
|
-
type ESLintLintResult = {
|
|
86
|
-
filePath: string;
|
|
87
|
-
messages: ESLintMessage[];
|
|
88
|
-
};
|
|
89
|
-
type ESLintCtor = new (opts: { overrideConfigFile?: boolean; overrideConfig?: unknown }) => {
|
|
90
|
-
lintFiles: (patterns: readonly string[]) => Promise<ESLintLintResult[]>;
|
|
91
|
-
};
|
|
92
|
-
|
|
93
|
-
async function resolvePeers(
|
|
94
|
-
_projectRoot: string,
|
|
95
|
-
): Promise<{ ESLint: ESLintCtor; plugin: unknown } | { error: string }> {
|
|
96
|
-
try {
|
|
97
|
-
const eslintMod = (await import(/* @vite-ignore */ "eslint" as string)) as {
|
|
98
|
-
ESLint?: ESLintCtor;
|
|
99
|
-
default?: { ESLint: ESLintCtor };
|
|
100
|
-
};
|
|
101
|
-
const ESLint = eslintMod.ESLint ?? eslintMod.default?.ESLint;
|
|
102
|
-
if (!ESLint) {
|
|
103
|
-
return { error: "`eslint` loaded but `ESLint` constructor not exported" };
|
|
104
|
-
}
|
|
105
|
-
const pluginMod = (await import(
|
|
106
|
-
/* @vite-ignore */ "eslint-plugin-react-compiler" as string
|
|
107
|
-
)) as { default?: unknown };
|
|
108
|
-
const plugin = pluginMod.default ?? pluginMod;
|
|
109
|
-
return { ESLint, plugin };
|
|
110
|
-
} catch (err) {
|
|
111
|
-
return {
|
|
112
|
-
error: err instanceof Error ? err.message : String(err),
|
|
113
|
-
};
|
|
114
|
-
}
|
|
115
|
-
}
|
|
116
|
-
|
|
117
|
-
function mapSeverity(raw: number): "warning" | "error" {
|
|
118
|
-
return raw >= 2 ? "error" : "warning";
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
/**
|
|
122
|
-
* Run the `react-compiler/react-compiler` rule against a list of
|
|
123
|
-
* files. Does **not** read any ESLint config from disk — we assemble
|
|
124
|
-
* an inline flat config so the result is deterministic regardless of
|
|
125
|
-
* the host project's ESLint setup.
|
|
126
|
-
*/
|
|
127
|
-
export async function runReactCompilerLint(
|
|
128
|
-
options: RunReactCompilerLintOptions,
|
|
129
|
-
): Promise<RunReactCompilerLintResult> {
|
|
130
|
-
if (options.targetFiles.length === 0) {
|
|
131
|
-
return { diagnostics: [], skipped: false };
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
const peers = await resolvePeers(options.projectRoot);
|
|
135
|
-
if ("error" in peers) {
|
|
136
|
-
console.warn(
|
|
137
|
-
"[Mandu React Compiler] Skipping bailout diagnostics — " +
|
|
138
|
-
"install `eslint` and `eslint-plugin-react-compiler` to enable. " +
|
|
139
|
-
`Reason: ${peers.error}`,
|
|
140
|
-
);
|
|
141
|
-
return {
|
|
142
|
-
diagnostics: [],
|
|
143
|
-
skipped: true,
|
|
144
|
-
skipReason: peers.error,
|
|
145
|
-
};
|
|
146
|
-
}
|
|
147
|
-
const { ESLint, plugin } = peers;
|
|
148
|
-
const severityNumber = options.severity === "error" ? 2 : 1;
|
|
149
|
-
|
|
150
|
-
// Flat config — no eslintrc resolution, no plugin auto-discovery.
|
|
151
|
-
// The override is evaluated for every target file.
|
|
152
|
-
const overrideConfig = [
|
|
153
|
-
{
|
|
154
|
-
files: ["**/*.{js,jsx,ts,tsx,mjs,cjs,mts,cts}"],
|
|
155
|
-
plugins: { "react-compiler": plugin },
|
|
156
|
-
rules: { "react-compiler/react-compiler": severityNumber },
|
|
157
|
-
},
|
|
158
|
-
];
|
|
159
|
-
|
|
160
|
-
let linter;
|
|
161
|
-
try {
|
|
162
|
-
linter = new ESLint({ overrideConfigFile: true, overrideConfig });
|
|
163
|
-
} catch (err) {
|
|
164
|
-
const reason = err instanceof Error ? err.message : String(err);
|
|
165
|
-
console.warn(
|
|
166
|
-
`[Mandu React Compiler] ESLint construction failed — skipping diagnostics. ${reason}`,
|
|
167
|
-
);
|
|
168
|
-
return { diagnostics: [], skipped: true, skipReason: reason };
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
const results = await linter.lintFiles(options.targetFiles.slice());
|
|
172
|
-
const diagnostics: ReactCompilerDiagnostic[] = [];
|
|
173
|
-
for (const result of results) {
|
|
174
|
-
for (const msg of result.messages) {
|
|
175
|
-
if (msg.ruleId !== "react-compiler/react-compiler") continue;
|
|
176
|
-
diagnostics.push({
|
|
177
|
-
file: result.filePath,
|
|
178
|
-
line: msg.line ?? 1,
|
|
179
|
-
column: msg.column ?? 1,
|
|
180
|
-
message: msg.message,
|
|
181
|
-
ruleId: "react-compiler/react-compiler",
|
|
182
|
-
severity: mapSeverity(msg.severity),
|
|
183
|
-
});
|
|
184
|
-
}
|
|
185
|
-
}
|
|
186
|
-
return { diagnostics, skipped: false };
|
|
187
|
-
}
|
|
188
|
-
|
|
189
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
190
|
-
// Report formatter — kept pure so the CLI can render or tests can snapshot
|
|
191
|
-
// ─────────────────────────────────────────────────────────────────────────
|
|
192
|
-
|
|
193
|
-
export interface FormatCompilerReportOptions {
|
|
194
|
-
/** Omit absolute-path prefixes starting with this root. */
|
|
195
|
-
projectRoot?: string;
|
|
196
|
-
/** Max diagnostics shown. Excess is summarised as "... N more". Default 25. */
|
|
197
|
-
limit?: number;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
|
-
/**
|
|
201
|
-
* Format diagnostics as a plain-text block suitable for the `mandu
|
|
202
|
-
* check` console output. Returns a single string so the caller can
|
|
203
|
-
* `log()` it in one shot.
|
|
204
|
-
*/
|
|
205
|
-
export function formatCompilerReport(
|
|
206
|
-
diagnostics: readonly ReactCompilerDiagnostic[],
|
|
207
|
-
options: FormatCompilerReportOptions = {},
|
|
208
|
-
): string {
|
|
209
|
-
if (diagnostics.length === 0) {
|
|
210
|
-
return "🧠 React Compiler — no bailouts detected";
|
|
211
|
-
}
|
|
212
|
-
const projectRoot = options.projectRoot;
|
|
213
|
-
const limit = options.limit ?? 25;
|
|
214
|
-
const byFile = new Map<string, ReactCompilerDiagnostic[]>();
|
|
215
|
-
for (const d of diagnostics) {
|
|
216
|
-
const list = byFile.get(d.file) ?? [];
|
|
217
|
-
list.push(d);
|
|
218
|
-
byFile.set(d.file, list);
|
|
219
|
-
}
|
|
220
|
-
|
|
221
|
-
const fileCount = byFile.size;
|
|
222
|
-
const lines: string[] = [];
|
|
223
|
-
lines.push(
|
|
224
|
-
`🧠 React Compiler — ${diagnostics.length} bailout(s) in ${fileCount} file(s)`,
|
|
225
|
-
);
|
|
226
|
-
lines.push("");
|
|
227
|
-
|
|
228
|
-
const shown: ReactCompilerDiagnostic[] = [];
|
|
229
|
-
for (const ds of byFile.values()) {
|
|
230
|
-
for (const d of ds) {
|
|
231
|
-
if (shown.length >= limit) break;
|
|
232
|
-
shown.push(d);
|
|
233
|
-
}
|
|
234
|
-
if (shown.length >= limit) break;
|
|
235
|
-
}
|
|
236
|
-
for (const d of shown) {
|
|
237
|
-
const relative = projectRoot ? path.relative(projectRoot, d.file) : d.file;
|
|
238
|
-
lines.push(` ${relative}:${d.line}:${d.column}`);
|
|
239
|
-
lines.push(` ${d.message}`);
|
|
240
|
-
}
|
|
241
|
-
if (diagnostics.length > shown.length) {
|
|
242
|
-
lines.push(` … and ${diagnostics.length - shown.length} more`);
|
|
243
|
-
}
|
|
244
|
-
lines.push("");
|
|
245
|
-
lines.push(
|
|
246
|
-
"→ These components will NOT be auto-memoized. Most bailouts come from " +
|
|
247
|
-
"conditional hook calls, ref escape, or mutation of shared values.",
|
|
248
|
-
);
|
|
249
|
-
lines.push(
|
|
250
|
-
"→ See https://react.dev/learn/react-compiler for common patterns.",
|
|
251
|
-
);
|
|
252
|
-
return lines.join("\n");
|
|
253
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* React Compiler bailout detector (#240 Phase 2).
|
|
3
|
+
*
|
|
4
|
+
* React Compiler silently skips components it can't safely memoize
|
|
5
|
+
* ("15% bailout"). Developers have no way to know which components
|
|
6
|
+
* are in the skipped set without this static pass.
|
|
7
|
+
*
|
|
8
|
+
* This module runs `eslint-plugin-react-compiler` in-process against a
|
|
9
|
+
* caller-supplied list of files (islands / `"use client"` pages /
|
|
10
|
+
* partials — the same files the bundler's `manduClientPlugins` gate
|
|
11
|
+
* picks up) and returns normalized diagnostics. `mandu check` wires
|
|
12
|
+
* this up behind `experimental.reactCompiler.enabled`.
|
|
13
|
+
*
|
|
14
|
+
* ## Why ESLint, not oxlint?
|
|
15
|
+
*
|
|
16
|
+
* The canonical rule lives at `react-compiler/react-compiler` in
|
|
17
|
+
* `eslint-plugin-react-compiler`. Oxlint has no port at the time of
|
|
18
|
+
* writing. When oxlint gains the rule we can swap this runner for a
|
|
19
|
+
* thin `oxlint --rule react-compiler/react-compiler` shell without
|
|
20
|
+
* changing callers — the `runReactCompilerLint` signature stays.
|
|
21
|
+
*
|
|
22
|
+
* ## Graceful degradation
|
|
23
|
+
*
|
|
24
|
+
* `eslint` and `eslint-plugin-react-compiler` are **optional** peer
|
|
25
|
+
* dependencies. If either is missing the runner returns `[]` and logs
|
|
26
|
+
* a single warning — the build is never blocked.
|
|
27
|
+
*/
|
|
28
|
+
|
|
29
|
+
import path from "node:path";
|
|
30
|
+
|
|
31
|
+
export interface ReactCompilerDiagnostic {
|
|
32
|
+
/** Absolute path to the offending file. */
|
|
33
|
+
file: string;
|
|
34
|
+
/** 1-indexed line. */
|
|
35
|
+
line: number;
|
|
36
|
+
/** 1-indexed column. */
|
|
37
|
+
column: number;
|
|
38
|
+
/** Human-readable message produced by the rule. */
|
|
39
|
+
message: string;
|
|
40
|
+
/** Always `"react-compiler/react-compiler"`. */
|
|
41
|
+
ruleId: string;
|
|
42
|
+
/** ESLint severity — "error" (`2`) or "warning" (`1`). */
|
|
43
|
+
severity: "warning" | "error";
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export interface RunReactCompilerLintOptions {
|
|
47
|
+
/**
|
|
48
|
+
* Project root — used to resolve `eslint` / the plugin from the
|
|
49
|
+
* project's own `node_modules` before falling back to Mandu's.
|
|
50
|
+
*/
|
|
51
|
+
projectRoot: string;
|
|
52
|
+
/**
|
|
53
|
+
* Absolute file paths to lint. Callers should pass only files the
|
|
54
|
+
* bundler would run through the Compiler (islands / client pages /
|
|
55
|
+
* partials). Passing server files wastes CPU — the rule still runs
|
|
56
|
+
* but the diagnostics are irrelevant.
|
|
57
|
+
*/
|
|
58
|
+
targetFiles: readonly string[];
|
|
59
|
+
/**
|
|
60
|
+
* Severity level used for the rule. Default `"warning"`. `"error"`
|
|
61
|
+
* makes the diagnostic a hard failure in strict mode.
|
|
62
|
+
*/
|
|
63
|
+
severity?: "warning" | "error";
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export interface RunReactCompilerLintResult {
|
|
67
|
+
/** All diagnostics found. Empty array on success or graceful skip. */
|
|
68
|
+
diagnostics: ReactCompilerDiagnostic[];
|
|
69
|
+
/** When `true` the runner could not load its peers and returned `[]`. */
|
|
70
|
+
skipped: boolean;
|
|
71
|
+
/** Human-readable explanation when `skipped` is `true`. */
|
|
72
|
+
skipReason?: string;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Minimal structural types for ESLint's flat-config API. We avoid a
|
|
76
|
+
// hard dependency on `@types/eslint` because eslint is an optional
|
|
77
|
+
// peer dep; the real module is imported dynamically at runtime.
|
|
78
|
+
type ESLintMessage = {
|
|
79
|
+
ruleId: string | null;
|
|
80
|
+
severity: number;
|
|
81
|
+
message: string;
|
|
82
|
+
line?: number;
|
|
83
|
+
column?: number;
|
|
84
|
+
};
|
|
85
|
+
type ESLintLintResult = {
|
|
86
|
+
filePath: string;
|
|
87
|
+
messages: ESLintMessage[];
|
|
88
|
+
};
|
|
89
|
+
type ESLintCtor = new (opts: { overrideConfigFile?: boolean; overrideConfig?: unknown }) => {
|
|
90
|
+
lintFiles: (patterns: readonly string[]) => Promise<ESLintLintResult[]>;
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
async function resolvePeers(
|
|
94
|
+
_projectRoot: string,
|
|
95
|
+
): Promise<{ ESLint: ESLintCtor; plugin: unknown } | { error: string }> {
|
|
96
|
+
try {
|
|
97
|
+
const eslintMod = (await import(/* @vite-ignore */ "eslint" as string)) as {
|
|
98
|
+
ESLint?: ESLintCtor;
|
|
99
|
+
default?: { ESLint: ESLintCtor };
|
|
100
|
+
};
|
|
101
|
+
const ESLint = eslintMod.ESLint ?? eslintMod.default?.ESLint;
|
|
102
|
+
if (!ESLint) {
|
|
103
|
+
return { error: "`eslint` loaded but `ESLint` constructor not exported" };
|
|
104
|
+
}
|
|
105
|
+
const pluginMod = (await import(
|
|
106
|
+
/* @vite-ignore */ "eslint-plugin-react-compiler" as string
|
|
107
|
+
)) as { default?: unknown };
|
|
108
|
+
const plugin = pluginMod.default ?? pluginMod;
|
|
109
|
+
return { ESLint, plugin };
|
|
110
|
+
} catch (err) {
|
|
111
|
+
return {
|
|
112
|
+
error: err instanceof Error ? err.message : String(err),
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function mapSeverity(raw: number): "warning" | "error" {
|
|
118
|
+
return raw >= 2 ? "error" : "warning";
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Run the `react-compiler/react-compiler` rule against a list of
|
|
123
|
+
* files. Does **not** read any ESLint config from disk — we assemble
|
|
124
|
+
* an inline flat config so the result is deterministic regardless of
|
|
125
|
+
* the host project's ESLint setup.
|
|
126
|
+
*/
|
|
127
|
+
export async function runReactCompilerLint(
|
|
128
|
+
options: RunReactCompilerLintOptions,
|
|
129
|
+
): Promise<RunReactCompilerLintResult> {
|
|
130
|
+
if (options.targetFiles.length === 0) {
|
|
131
|
+
return { diagnostics: [], skipped: false };
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
const peers = await resolvePeers(options.projectRoot);
|
|
135
|
+
if ("error" in peers) {
|
|
136
|
+
console.warn(
|
|
137
|
+
"[Mandu React Compiler] Skipping bailout diagnostics — " +
|
|
138
|
+
"install `eslint` and `eslint-plugin-react-compiler` to enable. " +
|
|
139
|
+
`Reason: ${peers.error}`,
|
|
140
|
+
);
|
|
141
|
+
return {
|
|
142
|
+
diagnostics: [],
|
|
143
|
+
skipped: true,
|
|
144
|
+
skipReason: peers.error,
|
|
145
|
+
};
|
|
146
|
+
}
|
|
147
|
+
const { ESLint, plugin } = peers;
|
|
148
|
+
const severityNumber = options.severity === "error" ? 2 : 1;
|
|
149
|
+
|
|
150
|
+
// Flat config — no eslintrc resolution, no plugin auto-discovery.
|
|
151
|
+
// The override is evaluated for every target file.
|
|
152
|
+
const overrideConfig = [
|
|
153
|
+
{
|
|
154
|
+
files: ["**/*.{js,jsx,ts,tsx,mjs,cjs,mts,cts}"],
|
|
155
|
+
plugins: { "react-compiler": plugin },
|
|
156
|
+
rules: { "react-compiler/react-compiler": severityNumber },
|
|
157
|
+
},
|
|
158
|
+
];
|
|
159
|
+
|
|
160
|
+
let linter;
|
|
161
|
+
try {
|
|
162
|
+
linter = new ESLint({ overrideConfigFile: true, overrideConfig });
|
|
163
|
+
} catch (err) {
|
|
164
|
+
const reason = err instanceof Error ? err.message : String(err);
|
|
165
|
+
console.warn(
|
|
166
|
+
`[Mandu React Compiler] ESLint construction failed — skipping diagnostics. ${reason}`,
|
|
167
|
+
);
|
|
168
|
+
return { diagnostics: [], skipped: true, skipReason: reason };
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
const results = await linter.lintFiles(options.targetFiles.slice());
|
|
172
|
+
const diagnostics: ReactCompilerDiagnostic[] = [];
|
|
173
|
+
for (const result of results) {
|
|
174
|
+
for (const msg of result.messages) {
|
|
175
|
+
if (msg.ruleId !== "react-compiler/react-compiler") continue;
|
|
176
|
+
diagnostics.push({
|
|
177
|
+
file: result.filePath,
|
|
178
|
+
line: msg.line ?? 1,
|
|
179
|
+
column: msg.column ?? 1,
|
|
180
|
+
message: msg.message,
|
|
181
|
+
ruleId: "react-compiler/react-compiler",
|
|
182
|
+
severity: mapSeverity(msg.severity),
|
|
183
|
+
});
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return { diagnostics, skipped: false };
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
190
|
+
// Report formatter — kept pure so the CLI can render or tests can snapshot
|
|
191
|
+
// ─────────────────────────────────────────────────────────────────────────
|
|
192
|
+
|
|
193
|
+
export interface FormatCompilerReportOptions {
|
|
194
|
+
/** Omit absolute-path prefixes starting with this root. */
|
|
195
|
+
projectRoot?: string;
|
|
196
|
+
/** Max diagnostics shown. Excess is summarised as "... N more". Default 25. */
|
|
197
|
+
limit?: number;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
/**
|
|
201
|
+
* Format diagnostics as a plain-text block suitable for the `mandu
|
|
202
|
+
* check` console output. Returns a single string so the caller can
|
|
203
|
+
* `log()` it in one shot.
|
|
204
|
+
*/
|
|
205
|
+
export function formatCompilerReport(
|
|
206
|
+
diagnostics: readonly ReactCompilerDiagnostic[],
|
|
207
|
+
options: FormatCompilerReportOptions = {},
|
|
208
|
+
): string {
|
|
209
|
+
if (diagnostics.length === 0) {
|
|
210
|
+
return "🧠 React Compiler — no bailouts detected";
|
|
211
|
+
}
|
|
212
|
+
const projectRoot = options.projectRoot;
|
|
213
|
+
const limit = options.limit ?? 25;
|
|
214
|
+
const byFile = new Map<string, ReactCompilerDiagnostic[]>();
|
|
215
|
+
for (const d of diagnostics) {
|
|
216
|
+
const list = byFile.get(d.file) ?? [];
|
|
217
|
+
list.push(d);
|
|
218
|
+
byFile.set(d.file, list);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
const fileCount = byFile.size;
|
|
222
|
+
const lines: string[] = [];
|
|
223
|
+
lines.push(
|
|
224
|
+
`🧠 React Compiler — ${diagnostics.length} bailout(s) in ${fileCount} file(s)`,
|
|
225
|
+
);
|
|
226
|
+
lines.push("");
|
|
227
|
+
|
|
228
|
+
const shown: ReactCompilerDiagnostic[] = [];
|
|
229
|
+
for (const ds of byFile.values()) {
|
|
230
|
+
for (const d of ds) {
|
|
231
|
+
if (shown.length >= limit) break;
|
|
232
|
+
shown.push(d);
|
|
233
|
+
}
|
|
234
|
+
if (shown.length >= limit) break;
|
|
235
|
+
}
|
|
236
|
+
for (const d of shown) {
|
|
237
|
+
const relative = projectRoot ? path.relative(projectRoot, d.file) : d.file;
|
|
238
|
+
lines.push(` ${relative}:${d.line}:${d.column}`);
|
|
239
|
+
lines.push(` ${d.message}`);
|
|
240
|
+
}
|
|
241
|
+
if (diagnostics.length > shown.length) {
|
|
242
|
+
lines.push(` … and ${diagnostics.length - shown.length} more`);
|
|
243
|
+
}
|
|
244
|
+
lines.push("");
|
|
245
|
+
lines.push(
|
|
246
|
+
"→ These components will NOT be auto-memoized. Most bailouts come from " +
|
|
247
|
+
"conditional hook calls, ref escape, or mutation of shared values.",
|
|
248
|
+
);
|
|
249
|
+
lines.push(
|
|
250
|
+
"→ See https://react.dev/learn/react-compiler for common patterns.",
|
|
251
|
+
);
|
|
252
|
+
return lines.join("\n");
|
|
253
|
+
}
|