@mandujs/core 0.54.32 → 0.55.0-beta.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +654 -654
- package/package.json +106 -210
- package/scripts/postinstall-lock.ts +153 -153
- 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 +15 -15
- package/src/a11y/types.ts +125 -125
- package/src/agent/__tests__/apply.test.ts +224 -0
- package/src/agent/__tests__/context.test.ts +97 -98
- package/src/agent/apply.ts +1031 -0
- package/src/agent/context.ts +552 -552
- package/src/agent/index.ts +4 -3
- package/src/agent/plan.ts +161 -243
- package/src/agent/repair.ts +239 -162
- package/src/agent/sync.ts +197 -198
- package/src/agent/types.ts +242 -94
- package/src/agent/verify.ts +55 -55
- package/src/auth/__tests__/login.test.ts +1 -1
- package/src/auth/__tests__/password.test.ts +15 -5
- package/src/auth/__tests__/reset.test.ts +2 -2
- 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 +14 -2
- 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/brain/doctor/analyzer.ts +7 -7
- package/src/bundler/__snapshots__/build.test.ts.snap +5 -5
- package/src/bundler/__tests__/build-runner.ts +166 -166
- package/src/bundler/__tests__/client-boundary-transform.test.ts +524 -524
- package/src/bundler/__tests__/cold-start.test.ts +60 -60
- package/src/bundler/__tests__/css.test.ts +49 -20
- 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 +24 -24
- package/src/bundler/__tests__/generation.test.ts +447 -0
- package/src/bundler/__tests__/hdr.test.ts +24 -18
- package/src/bundler/__tests__/hmr-client.test.ts +62 -24
- package/src/bundler/__tests__/jsx-runtime-shim.test.ts +179 -140
- package/src/bundler/__tests__/manifest-schema.test.ts +305 -266
- package/src/bundler/__tests__/prod-smoke.test.ts +138 -138
- package/src/bundler/__tests__/reverse-import-graph.test.ts +42 -42
- 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/analyzer.ts +15 -15
- package/src/bundler/budget.ts +404 -404
- package/src/bundler/build.test.ts +898 -915
- package/src/bundler/build.ts +113 -24
- package/src/bundler/client-boundary-transform.ts +977 -977
- package/src/bundler/css.ts +65 -44
- package/src/bundler/dev.ts +105 -73
- package/src/bundler/fast-refresh-preamble.ts +47 -47
- package/src/bundler/generation.ts +602 -0
- package/src/bundler/index.ts +3 -3
- package/src/bundler/manifest-schema.ts +55 -40
- package/src/bundler/plugins/__tests__/block-generated-imports.test.ts +13 -13
- 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 +16 -15
- 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/prerender.ts +29 -9
- package/src/bundler/reverse-import-graph.ts +339 -339
- package/src/bundler/safe-build.test.ts +1 -1
- package/src/bundler/safe-build.ts +103 -103
- package/src/bundler/scenario-matrix.ts +229 -229
- package/src/bundler/types.ts +58 -56
- 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__/props-serialization.test.ts +37 -37
- 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 +2 -2
- package/src/client/index.ts +1 -1
- package/src/client/island.ts +79 -79
- package/src/client/prefetch-helper.ts +55 -55
- package/src/client/props-serialization.ts +233 -233
- package/src/client/runtime-entry.ts +598 -598
- package/src/client/runtime.ts +1 -1
- package/src/client/serialize.ts +50 -50
- 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-compat.ts +3 -0
- package/src/components/Image.tsx +162 -162
- package/src/config/validate.ts +1 -1
- package/src/config/watcher.ts +311 -311
- package/src/constants.ts +40 -40
- package/src/content/collection.ts +9 -9
- 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/generate-types.ts +1 -1
- package/src/content/index.ts +2 -2
- 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/types.ts +58 -58
- package/src/db/__tests__/db.test.ts +9 -9
- package/src/db/index.ts +2 -2
- 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 +82 -82
- 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__/smoke.test.ts +2 -2
- package/src/desktop/__tests__/webview-fallback.test.ts +12 -32
- package/src/desktop/__tests__/window.test.ts +20 -99
- 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/webview-fallback.ts +17 -4
- package/src/desktop/window.ts +17 -8
- 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/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 +16 -16
- package/src/devtools/client/components/panel/network-panel.tsx +291 -291
- package/src/devtools/client/components/panel/panel-container.tsx +1 -1
- 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/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/types.ts +35 -35
- package/src/devtools/worker/index.ts +25 -25
- package/src/devtools/worker/redaction-worker.ts +233 -233
- package/src/diagnose/__tests__/checks.test.ts +136 -136
- package/src/diagnose/checks.ts +185 -185
- package/src/diagnose/index.ts +17 -17
- package/src/diagnose/run.ts +10 -10
- 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/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 +5 -1
- package/src/filling/auth.ts +308 -308
- package/src/filling/body-parse.test.ts +60 -60
- package/src/filling/cookie-codec.ts +5 -3
- package/src/filling/deps.ts +265 -265
- package/src/filling/head-method.test.ts +154 -154
- 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/generate.ts +30 -30
- package/src/generator/index.ts +3 -3
- package/src/generator/templates.test.ts +48 -48
- package/src/generator/templates.ts +219 -219
- 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/check.ts +39 -24
- package/src/guard/config-guard.ts +13 -13
- 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/fs-routes-policy.ts +51 -51
- package/src/guard/graph.ts +1 -1
- package/src/guard/healing.ts +36 -36
- package/src/guard/index.ts +11 -11
- 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/__tests__/id.test.ts +3 -3
- package/src/id/index.ts +105 -105
- package/src/index.ts +9 -48
- package/src/internal/client-boundary.ts +266 -266
- package/src/internal/index.ts +2 -2
- package/src/kitchen/api/agent-devtools-api.ts +779 -779
- package/src/kitchen/api/bundle-inspector.ts +179 -0
- package/src/kitchen/api/errors-grouping.ts +126 -126
- package/src/kitchen/api/file-api.ts +11 -11
- package/src/kitchen/kitchen-handler.ts +36 -0
- package/src/kitchen/kitchen-ui.ts +456 -0
- 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/perf/user-marks.ts +1 -1
- 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 +7 -7
- package/src/resource/__tests__/schema.test.ts +14 -14
- package/src/resource/ddl/__tests__/diff.test.ts +639 -639
- package/src/resource/ddl/__tests__/emit.test.ts +165 -165
- package/src/resource/ddl/__tests__/snapshot.test.ts +499 -499
- package/src/resource/ddl/emit.ts +146 -146
- 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/generator-schema.ts +11 -11
- package/src/resource/generators/slot.ts +72 -72
- package/src/resource/schema.ts +21 -21
- package/src/router/client-entry.test.ts +227 -227
- package/src/router/client-entry.ts +218 -218
- package/src/router/fs-patterns.test.ts +96 -96
- package/src/router/fs-routes.test.ts +532 -532
- package/src/router/fs-routes.ts +53 -53
- package/src/router/fs-scanner.ts +216 -216
- package/src/router/fs-types.ts +19 -19
- package/src/router/route-source-analyzer.ts +521 -521
- 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__/devtools-adapter.test.ts +68 -68
- 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__/inline-client-hydration.test.ts +237 -237
- package/src/runtime/__tests__/not-found.test.ts +152 -152
- package/src/runtime/__tests__/observability-lifecycle.test.ts +103 -103
- package/src/runtime/__tests__/page-render-response.test.ts +517 -517
- package/src/runtime/__tests__/request-middleware.test.ts +70 -70
- package/src/runtime/__tests__/searchparams-page-props.test.ts +81 -81
- 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/devtools-adapter.ts +68 -68
- package/src/runtime/escape.ts +34 -34
- package/src/runtime/fast-refresh-runtime.ts +322 -322
- package/src/runtime/fast-refresh-types.ts +1 -1
- package/src/runtime/handler.ts +65 -65
- package/src/runtime/handlers.ts +30 -11
- 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/observability-lifecycle.ts +290 -290
- package/src/runtime/openapi-endpoint.ts +236 -236
- package/src/runtime/page-render-response.ts +287 -274
- package/src/runtime/ppr.ts +74 -74
- package/src/runtime/rate-limit.ts +1 -1
- package/src/runtime/redirect.ts +1 -1
- package/src/runtime/registry.ts +171 -171
- package/src/runtime/request-middleware.ts +31 -31
- package/src/runtime/router.test.ts +4 -4
- package/src/runtime/router.ts +10 -10
- package/src/runtime/server.ts +127 -211
- package/src/runtime/shims.ts +48 -48
- package/src/runtime/ssr.ts +43 -17
- package/src/runtime/static-files.ts +369 -289
- package/src/runtime/streaming-ssr.ts +219 -180
- package/src/runtime/trace.ts +144 -144
- package/src/scheduler/__tests__/scheduler.test.ts +12 -11
- package/src/scheduler/index.ts +11 -5
- 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/spec/schema.ts +35 -35
- 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/index.ts +9 -3
- package/src/testing/lcov.ts +192 -0
- 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/__tests__/watcher.test.ts +59 -59
- package/src/watcher/watcher.ts +61 -61
|
@@ -19,9 +19,9 @@
|
|
|
19
19
|
* packages/core/src/runtime/fast-refresh-runtime.ts
|
|
20
20
|
*/
|
|
21
21
|
|
|
22
|
-
import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
|
|
23
|
-
import { mkdtemp, mkdir, rm, writeFile, readFile } from "fs/promises";
|
|
24
|
-
import path from "path";
|
|
22
|
+
import { afterAll, beforeAll, beforeEach, describe, expect, test } from "bun:test";
|
|
23
|
+
import { mkdtemp, mkdir, rm, writeFile, readFile } from "fs/promises";
|
|
24
|
+
import path from "path";
|
|
25
25
|
|
|
26
26
|
import {
|
|
27
27
|
appendBoundary,
|
|
@@ -40,19 +40,19 @@ import {
|
|
|
40
40
|
_isRefreshScheduledForTests,
|
|
41
41
|
type ReactRefreshRuntime,
|
|
42
42
|
} from "../../runtime/fast-refresh-runtime";
|
|
43
|
-
import {
|
|
44
|
-
createManduHot,
|
|
45
|
-
dispatchReplacement,
|
|
46
|
-
_resetRegistryForTests,
|
|
47
|
-
} from "../../runtime/hmr-client";
|
|
48
|
-
import { generateFastRefreshPreamble } from "../dev";
|
|
49
|
-
|
|
50
|
-
const repoTempRoot = path.resolve(import.meta.dir, "../../../../..", ".tmp-test-artifacts");
|
|
51
|
-
|
|
52
|
-
async function mkRepoTempDir(prefix: string): Promise<string> {
|
|
53
|
-
await mkdir(repoTempRoot, { recursive: true });
|
|
54
|
-
return mkdtemp(path.join(repoTempRoot, prefix));
|
|
55
|
-
}
|
|
43
|
+
import {
|
|
44
|
+
createManduHot,
|
|
45
|
+
dispatchReplacement,
|
|
46
|
+
_resetRegistryForTests,
|
|
47
|
+
} from "../../runtime/hmr-client";
|
|
48
|
+
import { generateFastRefreshPreamble } from "../dev";
|
|
49
|
+
|
|
50
|
+
const repoTempRoot = path.resolve(import.meta.dir, "../../../../..", ".tmp-test-artifacts");
|
|
51
|
+
|
|
52
|
+
async function mkRepoTempDir(prefix: string): Promise<string> {
|
|
53
|
+
await mkdir(repoTempRoot, { recursive: true });
|
|
54
|
+
return mkdtemp(path.join(repoTempRoot, prefix));
|
|
55
|
+
}
|
|
56
56
|
|
|
57
57
|
// ═══════════════════════════════════════════════════════════════════
|
|
58
58
|
// Section A — plugin pure unit tests
|
|
@@ -270,10 +270,10 @@ describe("manduHMR — __MANDU_HMR__ global behavior", () => {
|
|
|
270
270
|
describe.skipIf(process.env.MANDU_SKIP_BUNDLER_TESTS === "1")(
|
|
271
271
|
"Bun.build({ reactFastRefresh: true }) + fastRefreshPlugin()",
|
|
272
272
|
() => {
|
|
273
|
-
let rootDir: string;
|
|
274
|
-
|
|
275
|
-
beforeAll(async () => {
|
|
276
|
-
rootDir = await mkRepoTempDir("fr-build-");
|
|
273
|
+
let rootDir: string;
|
|
274
|
+
|
|
275
|
+
beforeAll(async () => {
|
|
276
|
+
rootDir = await mkRepoTempDir("fr-build-");
|
|
277
277
|
// Three source files — one boundary, one plain, one .island.tsx —
|
|
278
278
|
// give us the minimal matrix for the plugin's include filter.
|
|
279
279
|
await writeFile(
|
|
@@ -519,10 +519,10 @@ describe("dispatchReplacement + __MANDU_HMR__ integration", () => {
|
|
|
519
519
|
describe.skipIf(process.env.MANDU_SKIP_BUNDLER_TESTS === "1")(
|
|
520
520
|
"buildVendorShims emits fast-refresh shims in dev mode",
|
|
521
521
|
() => {
|
|
522
|
-
let rootDir: string;
|
|
523
|
-
|
|
524
|
-
beforeAll(async () => {
|
|
525
|
-
rootDir = await mkRepoTempDir("fr-vendor-");
|
|
522
|
+
let rootDir: string;
|
|
523
|
+
|
|
524
|
+
beforeAll(async () => {
|
|
525
|
+
rootDir = await mkRepoTempDir("fr-vendor-");
|
|
526
526
|
await mkdir(path.join(rootDir, "app"), { recursive: true });
|
|
527
527
|
await writeFile(
|
|
528
528
|
path.join(rootDir, "package.json"),
|
|
@@ -0,0 +1,447 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
2
|
+
import os from "os";
|
|
3
|
+
import path from "path";
|
|
4
|
+
import fs from "fs/promises";
|
|
5
|
+
import type { RoutesManifest } from "../../spec/schema";
|
|
6
|
+
import { buildClientBundles } from "../build";
|
|
7
|
+
import {
|
|
8
|
+
__clearBuildGenerationCacheForTests,
|
|
9
|
+
__promoteStagedBuildGenerationForTests,
|
|
10
|
+
beginBuildGeneration,
|
|
11
|
+
failBuildGeneration,
|
|
12
|
+
publishBuildGeneration,
|
|
13
|
+
readActiveBuildGeneration,
|
|
14
|
+
resolveActiveBuildArtifacts,
|
|
15
|
+
runSerializedClientBuild,
|
|
16
|
+
scopeBundleManifestToGeneration,
|
|
17
|
+
} from "../generation";
|
|
18
|
+
import { serveStaticFile } from "../../runtime/static-files";
|
|
19
|
+
|
|
20
|
+
const EMPTY_ROUTES: RoutesManifest = {
|
|
21
|
+
version: 1,
|
|
22
|
+
routes: [],
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
describe("atomic client build generations", () => {
|
|
26
|
+
let rootDir = "";
|
|
27
|
+
|
|
28
|
+
beforeEach(async () => {
|
|
29
|
+
rootDir = await fs.mkdtemp(path.join(os.tmpdir(), "mandu-generation-"));
|
|
30
|
+
await fs.mkdir(path.join(rootDir, ".mandu", "client"), { recursive: true });
|
|
31
|
+
await fs.writeFile(path.join(rootDir, ".mandu", "client", "runtime.js"), "legacy-good");
|
|
32
|
+
await fs.writeFile(
|
|
33
|
+
path.join(rootDir, ".mandu", "manifest.json"),
|
|
34
|
+
JSON.stringify({
|
|
35
|
+
version: 1,
|
|
36
|
+
buildTime: "2026-08-13T00:00:00.000Z",
|
|
37
|
+
env: "development",
|
|
38
|
+
bundles: {},
|
|
39
|
+
shared: { runtime: "/.mandu/client/runtime.js", vendor: "" },
|
|
40
|
+
}),
|
|
41
|
+
);
|
|
42
|
+
__clearBuildGenerationCacheForTests();
|
|
43
|
+
});
|
|
44
|
+
|
|
45
|
+
afterEach(async () => {
|
|
46
|
+
__clearBuildGenerationCacheForTests();
|
|
47
|
+
await fs.rm(rootDir, { recursive: true, force: true });
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
test("publishes the complete generation and updates compatibility artifacts", async () => {
|
|
51
|
+
const generation = await beginBuildGeneration(rootDir);
|
|
52
|
+
await fs.writeFile(path.join(generation.clientDir, "runtime.js"), "generation-good");
|
|
53
|
+
await fs.writeFile(
|
|
54
|
+
generation.manifestPath,
|
|
55
|
+
JSON.stringify({
|
|
56
|
+
version: 1,
|
|
57
|
+
generationId: generation.id,
|
|
58
|
+
buildTime: "2026-08-13T00:00:01.000Z",
|
|
59
|
+
env: "development",
|
|
60
|
+
bundles: {},
|
|
61
|
+
shared: { runtime: "/.mandu/client/runtime.js", vendor: "" },
|
|
62
|
+
}),
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
const pointer = await publishBuildGeneration(generation);
|
|
66
|
+
const active = await resolveActiveBuildArtifacts(rootDir);
|
|
67
|
+
|
|
68
|
+
expect(pointer.generationId).toBe(generation.id);
|
|
69
|
+
expect(active.source).toBe("generation");
|
|
70
|
+
expect(active.generationId).toBe(generation.id);
|
|
71
|
+
expect(await fs.readFile(path.join(active.clientDir, "runtime.js"), "utf8")).toBe(
|
|
72
|
+
"generation-good",
|
|
73
|
+
);
|
|
74
|
+
expect(
|
|
75
|
+
await fs.readFile(path.join(rootDir, ".mandu", "client", "runtime.js"), "utf8"),
|
|
76
|
+
).toBe("generation-good");
|
|
77
|
+
|
|
78
|
+
const manifest = JSON.parse(
|
|
79
|
+
await fs.readFile(path.join(rootDir, ".mandu", "manifest.json"), "utf8"),
|
|
80
|
+
) as { generationId?: string };
|
|
81
|
+
expect(manifest.generationId).toBe(generation.id);
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test("falls back to a validated copy when Windows keeps build handles open", async () => {
|
|
85
|
+
const generation = await beginBuildGeneration(rootDir);
|
|
86
|
+
await fs.writeFile(path.join(generation.clientDir, "runtime.js"), "copied-generation");
|
|
87
|
+
await fs.writeFile(generation.manifestPath, JSON.stringify({ version: 1 }));
|
|
88
|
+
let renameAttempts = 0;
|
|
89
|
+
|
|
90
|
+
await __promoteStagedBuildGenerationForTests(generation, {
|
|
91
|
+
platform: "win32",
|
|
92
|
+
rename: async () => {
|
|
93
|
+
renameAttempts++;
|
|
94
|
+
throw Object.assign(new Error("build output is still open"), { code: "EPERM" });
|
|
95
|
+
},
|
|
96
|
+
sleep: async () => {},
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
expect(renameAttempts).toBe(5);
|
|
100
|
+
expect(await fs.readFile(path.join(generation.finalRoot, "client", "runtime.js"), "utf8")).toBe(
|
|
101
|
+
"copied-generation",
|
|
102
|
+
);
|
|
103
|
+
expect(await Bun.file(path.join(generation.finalRoot, "manifest.json")).exists()).toBe(true);
|
|
104
|
+
expect(await Bun.file(generation.stagingRoot).exists()).toBe(false);
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
test("removes a partial final generation when the Windows copy fallback fails", async () => {
|
|
108
|
+
const generation = await beginBuildGeneration(rootDir);
|
|
109
|
+
await fs.writeFile(path.join(generation.clientDir, "runtime.js"), "partial-generation");
|
|
110
|
+
await fs.writeFile(generation.manifestPath, JSON.stringify({ version: 1 }));
|
|
111
|
+
|
|
112
|
+
const promotion = __promoteStagedBuildGenerationForTests(generation, {
|
|
113
|
+
platform: "win32",
|
|
114
|
+
rename: async () => {
|
|
115
|
+
throw Object.assign(new Error("build output is still open"), { code: "EPERM" });
|
|
116
|
+
},
|
|
117
|
+
sleep: async () => {},
|
|
118
|
+
copy: async (_source, destination) => {
|
|
119
|
+
await fs.mkdir(destination, { recursive: true });
|
|
120
|
+
await fs.writeFile(path.join(destination, "partial.js"), "partial");
|
|
121
|
+
throw new Error("injected copy failure");
|
|
122
|
+
},
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
await expect(promotion).rejects.toThrow("injected copy failure");
|
|
126
|
+
expect(await Bun.file(generation.finalRoot).exists()).toBe(false);
|
|
127
|
+
expect(await fs.access(generation.stagingRoot).then(
|
|
128
|
+
() => true,
|
|
129
|
+
() => false,
|
|
130
|
+
)).toBe(true);
|
|
131
|
+
expect(
|
|
132
|
+
await fs.readFile(path.join(rootDir, ".mandu", "client", "runtime.js"), "utf8"),
|
|
133
|
+
).toBe("legacy-good");
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
test("failed generation keeps the active and compatibility artifacts unchanged", async () => {
|
|
137
|
+
const first = await buildClientBundles(EMPTY_ROUTES, rootDir, { mode: "production" });
|
|
138
|
+
expect(first.success).toBe(true);
|
|
139
|
+
const activeBefore = await readActiveBuildGeneration(rootDir);
|
|
140
|
+
expect(activeBefore?.generationId).toBe(first.manifest.generationId);
|
|
141
|
+
|
|
142
|
+
await fs.mkdir(path.join(rootDir, "app"), { recursive: true });
|
|
143
|
+
await fs.writeFile(
|
|
144
|
+
path.join(rootDir, "app", "page.tsx"),
|
|
145
|
+
"export default function Page() { return null; }\n",
|
|
146
|
+
);
|
|
147
|
+
const brokenManifest: RoutesManifest = {
|
|
148
|
+
version: 1,
|
|
149
|
+
routes: [
|
|
150
|
+
{
|
|
151
|
+
id: "broken",
|
|
152
|
+
kind: "page",
|
|
153
|
+
pattern: "/",
|
|
154
|
+
module: "app/page.tsx",
|
|
155
|
+
componentModule: "app/page.tsx",
|
|
156
|
+
clientModule: "app/page.tsx",
|
|
157
|
+
hydration: { strategy: "island", priority: "visible", preload: false },
|
|
158
|
+
},
|
|
159
|
+
],
|
|
160
|
+
};
|
|
161
|
+
|
|
162
|
+
const failed = await buildClientBundles(brokenManifest, rootDir, { mode: "production" });
|
|
163
|
+
const activeAfter = await readActiveBuildGeneration(rootDir);
|
|
164
|
+
const state = JSON.parse(
|
|
165
|
+
await fs.readFile(path.join(rootDir, ".mandu", "build-state.json"), "utf8"),
|
|
166
|
+
) as { activeGenerationId: string | null; lastAttempt: { status: string } };
|
|
167
|
+
|
|
168
|
+
expect(failed.success).toBe(false);
|
|
169
|
+
expect(failed.errors.join("\n")).toContain('missing "use client"');
|
|
170
|
+
expect(failed.manifest.generationId).toBe(activeBefore?.generationId);
|
|
171
|
+
expect(activeAfter?.generationId).toBe(activeBefore?.generationId);
|
|
172
|
+
expect(state.activeGenerationId).toBe(activeBefore?.generationId ?? null);
|
|
173
|
+
expect(state.lastAttempt.status).toBe("failed");
|
|
174
|
+
expect(
|
|
175
|
+
await fs.readFile(path.join(rootDir, ".mandu", "client", "runtime.js"), "utf8"),
|
|
176
|
+
).toBe("legacy-good");
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
test("static serving ignores a poisoned compatibility JS file", async () => {
|
|
180
|
+
const generation = await beginBuildGeneration(rootDir);
|
|
181
|
+
await fs.writeFile(path.join(generation.clientDir, "runtime.js"), "active-generation");
|
|
182
|
+
await fs.writeFile(path.join(generation.clientDir, "globals.css"), "generation-css");
|
|
183
|
+
await fs.writeFile(
|
|
184
|
+
generation.manifestPath,
|
|
185
|
+
JSON.stringify({
|
|
186
|
+
version: 1,
|
|
187
|
+
generationId: generation.id,
|
|
188
|
+
buildTime: "2026-08-13T00:00:01.000Z",
|
|
189
|
+
env: "development",
|
|
190
|
+
bundles: {},
|
|
191
|
+
shared: { runtime: "/.mandu/client/runtime.js", vendor: "" },
|
|
192
|
+
}),
|
|
193
|
+
);
|
|
194
|
+
await publishBuildGeneration(generation);
|
|
195
|
+
|
|
196
|
+
await fs.writeFile(path.join(rootDir, ".mandu", "client", "runtime.js"), "poisoned");
|
|
197
|
+
await fs.writeFile(path.join(rootDir, ".mandu", "client", "globals.css"), "watcher-css");
|
|
198
|
+
|
|
199
|
+
const js = await serveStaticFile(
|
|
200
|
+
"/.mandu/client/runtime.js",
|
|
201
|
+
{ rootDir, publicDir: "public", isDev: true },
|
|
202
|
+
);
|
|
203
|
+
const css = await serveStaticFile(
|
|
204
|
+
"/.mandu/client/globals.css",
|
|
205
|
+
{ rootDir, publicDir: "public", isDev: true },
|
|
206
|
+
);
|
|
207
|
+
|
|
208
|
+
expect(await js.response?.text()).toBe("active-generation");
|
|
209
|
+
expect(js.response?.headers.get("X-Mandu-Build-Generation")).toBe(generation.id);
|
|
210
|
+
expect(await css.response?.text()).toBe("watcher-css");
|
|
211
|
+
expect(css.response?.headers.get("X-Mandu-Build-Generation")).toBeNull();
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
test("unscoped legacy-only assets fall back without weakening scoped generation URLs", async () => {
|
|
215
|
+
const generation = await beginBuildGeneration(rootDir);
|
|
216
|
+
await fs.writeFile(path.join(generation.clientDir, "runtime.js"), "active-generation");
|
|
217
|
+
await fs.writeFile(
|
|
218
|
+
generation.manifestPath,
|
|
219
|
+
JSON.stringify({
|
|
220
|
+
version: 1,
|
|
221
|
+
generationId: generation.id,
|
|
222
|
+
buildTime: "2026-08-13T00:00:01.000Z",
|
|
223
|
+
env: "development",
|
|
224
|
+
bundles: {},
|
|
225
|
+
shared: { runtime: "/.mandu/client/runtime.js", vendor: "" },
|
|
226
|
+
}),
|
|
227
|
+
);
|
|
228
|
+
await publishBuildGeneration(generation);
|
|
229
|
+
await fs.writeFile(
|
|
230
|
+
path.join(rootDir, ".mandu", "client", "compat-only.js"),
|
|
231
|
+
"compatibility-module",
|
|
232
|
+
);
|
|
233
|
+
|
|
234
|
+
const unscoped = await serveStaticFile(
|
|
235
|
+
"/.mandu/client/compat-only.js",
|
|
236
|
+
{ rootDir, publicDir: "public", isDev: true },
|
|
237
|
+
);
|
|
238
|
+
const scoped = await serveStaticFile(
|
|
239
|
+
"/.mandu/client/compat-only.js",
|
|
240
|
+
{ rootDir, publicDir: "public", isDev: true },
|
|
241
|
+
new Request(
|
|
242
|
+
`http://mandu.test/.mandu/client/compat-only.js?g=${generation.id}`,
|
|
243
|
+
),
|
|
244
|
+
);
|
|
245
|
+
|
|
246
|
+
expect(await unscoped.response?.text()).toBe("compatibility-module");
|
|
247
|
+
expect(unscoped.response?.headers.get("X-Mandu-Build-Generation")).toBeNull();
|
|
248
|
+
expect(scoped.response?.status).toBe(404);
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
test("an older document can still load every asset from its own generation", async () => {
|
|
252
|
+
const first = await beginBuildGeneration(rootDir);
|
|
253
|
+
await fs.writeFile(path.join(first.clientDir, "runtime.js"), "generation-one");
|
|
254
|
+
await fs.writeFile(
|
|
255
|
+
first.manifestPath,
|
|
256
|
+
JSON.stringify({
|
|
257
|
+
version: 1,
|
|
258
|
+
generationId: first.id,
|
|
259
|
+
buildTime: "2026-08-13T00:00:01.000Z",
|
|
260
|
+
env: "development",
|
|
261
|
+
bundles: {},
|
|
262
|
+
shared: { runtime: "/.mandu/client/runtime.js", vendor: "" },
|
|
263
|
+
}),
|
|
264
|
+
);
|
|
265
|
+
await publishBuildGeneration(first);
|
|
266
|
+
|
|
267
|
+
const second = await beginBuildGeneration(rootDir);
|
|
268
|
+
await fs.writeFile(path.join(second.clientDir, "runtime.js"), "generation-two");
|
|
269
|
+
await fs.writeFile(
|
|
270
|
+
second.manifestPath,
|
|
271
|
+
JSON.stringify({
|
|
272
|
+
version: 1,
|
|
273
|
+
generationId: second.id,
|
|
274
|
+
buildTime: "2026-08-13T00:00:02.000Z",
|
|
275
|
+
env: "development",
|
|
276
|
+
bundles: {},
|
|
277
|
+
shared: { runtime: "/.mandu/client/runtime.js", vendor: "" },
|
|
278
|
+
}),
|
|
279
|
+
);
|
|
280
|
+
await publishBuildGeneration(second);
|
|
281
|
+
|
|
282
|
+
const oldAsset = await serveStaticFile(
|
|
283
|
+
"/.mandu/client/runtime.js",
|
|
284
|
+
{ rootDir, publicDir: "public", isDev: true },
|
|
285
|
+
new Request(`http://mandu.test/.mandu/client/runtime.js?g=${first.id}`),
|
|
286
|
+
);
|
|
287
|
+
const activeAsset = await serveStaticFile(
|
|
288
|
+
"/.mandu/client/runtime.js",
|
|
289
|
+
{ rootDir, publicDir: "public", isDev: true },
|
|
290
|
+
new Request("http://mandu.test/.mandu/client/runtime.js"),
|
|
291
|
+
);
|
|
292
|
+
const unknownAsset = await serveStaticFile(
|
|
293
|
+
"/.mandu/client/runtime.js",
|
|
294
|
+
{ rootDir, publicDir: "public", isDev: true },
|
|
295
|
+
new Request("http://mandu.test/.mandu/client/runtime.js?g=missing-generation"),
|
|
296
|
+
);
|
|
297
|
+
|
|
298
|
+
expect(await oldAsset.response?.text()).toBe("generation-one");
|
|
299
|
+
expect(oldAsset.response?.headers.get("X-Mandu-Build-Generation")).toBe(first.id);
|
|
300
|
+
expect(await activeAsset.response?.text()).toBe("generation-two");
|
|
301
|
+
expect(activeAsset.response?.headers.get("X-Mandu-Build-Generation")).toBe(second.id);
|
|
302
|
+
expect(unknownAsset.response?.status).toBe(404);
|
|
303
|
+
});
|
|
304
|
+
|
|
305
|
+
test("scopes all framework-owned manifest URLs without mutating the disk shape", () => {
|
|
306
|
+
const manifest = {
|
|
307
|
+
version: 1,
|
|
308
|
+
generationId: "generation-123",
|
|
309
|
+
buildTime: "2026-08-13T00:00:01.000Z",
|
|
310
|
+
env: "development" as const,
|
|
311
|
+
bundles: {
|
|
312
|
+
home: {
|
|
313
|
+
js: "/.mandu/client/home.js",
|
|
314
|
+
css: "/.mandu/client/home.css",
|
|
315
|
+
dependencies: [],
|
|
316
|
+
priority: "visible" as const,
|
|
317
|
+
},
|
|
318
|
+
},
|
|
319
|
+
islands: {
|
|
320
|
+
card: {
|
|
321
|
+
js: "/.mandu/client/card.js",
|
|
322
|
+
route: "home",
|
|
323
|
+
priority: "visible" as const,
|
|
324
|
+
},
|
|
325
|
+
},
|
|
326
|
+
shared: {
|
|
327
|
+
runtime: "/.mandu/client/runtime.js",
|
|
328
|
+
vendor: "/.mandu/client/vendor.js",
|
|
329
|
+
router: "/.mandu/client/router.js",
|
|
330
|
+
},
|
|
331
|
+
importMap: {
|
|
332
|
+
imports: {
|
|
333
|
+
react: "/.mandu/client/vendor.js",
|
|
334
|
+
external: "external-package",
|
|
335
|
+
},
|
|
336
|
+
},
|
|
337
|
+
};
|
|
338
|
+
|
|
339
|
+
const scoped = scopeBundleManifestToGeneration(manifest)!;
|
|
340
|
+
|
|
341
|
+
expect(scoped.bundles.home.js).toBe(
|
|
342
|
+
"/.mandu/client/home.js?g=generation-123",
|
|
343
|
+
);
|
|
344
|
+
expect(scoped.bundles.home.css).toBe(
|
|
345
|
+
"/.mandu/client/home.css?g=generation-123",
|
|
346
|
+
);
|
|
347
|
+
expect(scoped.islands?.card.js).toBe(
|
|
348
|
+
"/.mandu/client/card.js?g=generation-123",
|
|
349
|
+
);
|
|
350
|
+
expect(scoped.shared.runtime).toBe(
|
|
351
|
+
"/.mandu/client/runtime.js?g=generation-123",
|
|
352
|
+
);
|
|
353
|
+
expect(scoped.importMap?.imports.react).toBe(
|
|
354
|
+
"/.mandu/client/vendor.js?g=generation-123",
|
|
355
|
+
);
|
|
356
|
+
expect(scoped.importMap?.imports.external).toBe("external-package");
|
|
357
|
+
expect(manifest.bundles.home.js).toBe("/.mandu/client/home.js");
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
test("serializes client builds for the same project root", async () => {
|
|
361
|
+
let active = 0;
|
|
362
|
+
let maxActive = 0;
|
|
363
|
+
const order: number[] = [];
|
|
364
|
+
|
|
365
|
+
await Promise.all(
|
|
366
|
+
[1, 2, 3].map((id) =>
|
|
367
|
+
runSerializedClientBuild(rootDir, async () => {
|
|
368
|
+
active++;
|
|
369
|
+
maxActive = Math.max(maxActive, active);
|
|
370
|
+
order.push(id);
|
|
371
|
+
await Bun.sleep(5);
|
|
372
|
+
active--;
|
|
373
|
+
}),
|
|
374
|
+
),
|
|
375
|
+
);
|
|
376
|
+
|
|
377
|
+
expect(maxActive).toBe(1);
|
|
378
|
+
expect(order).toEqual([1, 2, 3]);
|
|
379
|
+
});
|
|
380
|
+
|
|
381
|
+
test("records an explicit failed attempt without publishing it", async () => {
|
|
382
|
+
const generation = await beginBuildGeneration(rootDir);
|
|
383
|
+
await fs.writeFile(path.join(generation.clientDir, "runtime.js"), "partial-output");
|
|
384
|
+
await failBuildGeneration(generation, ["synthetic failure"]);
|
|
385
|
+
|
|
386
|
+
expect(await readActiveBuildGeneration(rootDir)).toBeNull();
|
|
387
|
+
expect(await Bun.file(generation.stagingRoot).exists()).toBe(false);
|
|
388
|
+
expect(
|
|
389
|
+
await fs.readFile(path.join(rootDir, ".mandu", "client", "runtime.js"), "utf8"),
|
|
390
|
+
).toBe("legacy-good");
|
|
391
|
+
});
|
|
392
|
+
|
|
393
|
+
test("100 injected build failures expose zero mixed generations", async () => {
|
|
394
|
+
const baseline = await beginBuildGeneration(rootDir);
|
|
395
|
+
await fs.writeFile(path.join(baseline.clientDir, "runtime.js"), "baseline");
|
|
396
|
+
await fs.writeFile(path.join(baseline.clientDir, "home.js"), "baseline");
|
|
397
|
+
await fs.writeFile(
|
|
398
|
+
baseline.manifestPath,
|
|
399
|
+
JSON.stringify({
|
|
400
|
+
version: 1,
|
|
401
|
+
generationId: baseline.id,
|
|
402
|
+
buildTime: "2026-08-13T00:00:01.000Z",
|
|
403
|
+
env: "development",
|
|
404
|
+
bundles: {
|
|
405
|
+
home: {
|
|
406
|
+
js: "/.mandu/client/home.js",
|
|
407
|
+
dependencies: [],
|
|
408
|
+
priority: "visible",
|
|
409
|
+
},
|
|
410
|
+
},
|
|
411
|
+
shared: { runtime: "/.mandu/client/runtime.js", vendor: "" },
|
|
412
|
+
}),
|
|
413
|
+
);
|
|
414
|
+
await publishBuildGeneration(baseline);
|
|
415
|
+
|
|
416
|
+
for (let attempt = 0; attempt < 100; attempt++) {
|
|
417
|
+
const failed = await beginBuildGeneration(rootDir);
|
|
418
|
+
await fs.writeFile(
|
|
419
|
+
path.join(failed.clientDir, "runtime.js"),
|
|
420
|
+
`candidate-${attempt}`,
|
|
421
|
+
);
|
|
422
|
+
if (attempt % 2 === 0) {
|
|
423
|
+
await fs.rm(path.join(failed.clientDir, "home.js"), { force: true });
|
|
424
|
+
}
|
|
425
|
+
await failBuildGeneration(failed, [`injected failure ${attempt}`]);
|
|
426
|
+
|
|
427
|
+
const [runtime, home] = await Promise.all([
|
|
428
|
+
serveStaticFile(
|
|
429
|
+
"/.mandu/client/runtime.js",
|
|
430
|
+
{ rootDir, publicDir: "public", isDev: true },
|
|
431
|
+
),
|
|
432
|
+
serveStaticFile(
|
|
433
|
+
"/.mandu/client/home.js",
|
|
434
|
+
{ rootDir, publicDir: "public", isDev: true },
|
|
435
|
+
),
|
|
436
|
+
]);
|
|
437
|
+
expect(await runtime.response?.text()).toBe("baseline");
|
|
438
|
+
expect(await home.response?.text()).toBe("baseline");
|
|
439
|
+
expect(runtime.response?.headers.get("X-Mandu-Build-Generation")).toBe(
|
|
440
|
+
baseline.id,
|
|
441
|
+
);
|
|
442
|
+
expect(home.response?.headers.get("X-Mandu-Build-Generation")).toBe(
|
|
443
|
+
baseline.id,
|
|
444
|
+
);
|
|
445
|
+
}
|
|
446
|
+
}, 60_000);
|
|
447
|
+
});
|
|
@@ -40,6 +40,11 @@ import type { RoutesManifest } from "../../spec/schema";
|
|
|
40
40
|
import type { HDRPayload, ViteHMRPayload } from "../hmr-types";
|
|
41
41
|
import { PORTS } from "../../constants";
|
|
42
42
|
|
|
43
|
+
const describeCoreIsolated =
|
|
44
|
+
process.env.MANDU_SKIP_CORE_ISOLATED_TESTS === "1"
|
|
45
|
+
? describe.skip
|
|
46
|
+
: describe;
|
|
47
|
+
|
|
43
48
|
// -----------------------------------------------------------------------------
|
|
44
49
|
// Mirrored helpers — we reproduce the CLI helper logic here so the test
|
|
45
50
|
// file doesn't have to import from `@mandujs/cli` (circular package
|
|
@@ -53,7 +58,8 @@ function isSlotFile(filePath: string): boolean {
|
|
|
53
58
|
|
|
54
59
|
function normalizeCompareFactory(rootDir: string) {
|
|
55
60
|
return (p: string): string => {
|
|
56
|
-
const
|
|
61
|
+
const portablePath = p.replace(/[\\/]+/g, path.sep);
|
|
62
|
+
const resolved = path.resolve(rootDir, portablePath).replace(/\\/g, "/");
|
|
57
63
|
return process.platform === "win32" ? resolved.toLowerCase() : resolved;
|
|
58
64
|
};
|
|
59
65
|
}
|
|
@@ -114,7 +120,7 @@ function buildManifest(): RoutesManifest {
|
|
|
114
120
|
// Section A — Slot detection + routeId mapping
|
|
115
121
|
// -----------------------------------------------------------------------------
|
|
116
122
|
|
|
117
|
-
|
|
123
|
+
describeCoreIsolated("Phase 7.2 Agent B — slot detection", () => {
|
|
118
124
|
test("[1] isSlotFile accepts .slot.ts and .slot.tsx", () => {
|
|
119
125
|
expect(isSlotFile("app/page.slot.ts")).toBe(true);
|
|
120
126
|
expect(isSlotFile("app/page.slot.tsx")).toBe(true);
|
|
@@ -135,7 +141,7 @@ describe("Phase 7.2 Agent B — slot detection", () => {
|
|
|
135
141
|
});
|
|
136
142
|
});
|
|
137
143
|
|
|
138
|
-
|
|
144
|
+
describeCoreIsolated("Phase 7.2 Agent B — findRouteIdForSlot", () => {
|
|
139
145
|
test("[2] returns the owning routeId when the slot path matches exactly", () => {
|
|
140
146
|
const rootDir = path.join(tmpdir(), "mandu-hdr-fixture-1");
|
|
141
147
|
const manifest = buildManifest();
|
|
@@ -204,25 +210,25 @@ describe("Phase 7.2 Agent B — findRouteIdForSlot", () => {
|
|
|
204
210
|
// Section B — HMR server broadcast of slot-refetch
|
|
205
211
|
// -----------------------------------------------------------------------------
|
|
206
212
|
|
|
207
|
-
const HMR_TEST_PORT_STATE = "__MANDU_HMR_TEST_PORT_STATE__";
|
|
208
|
-
|
|
209
|
-
function pickPort(): number {
|
|
210
|
-
const stateGlobal = globalThis as typeof globalThis & {
|
|
211
|
-
__MANDU_HMR_TEST_PORT_STATE__?: { next: number };
|
|
212
|
-
};
|
|
213
|
-
stateGlobal.__MANDU_HMR_TEST_PORT_STATE__ ??= { next: 0 };
|
|
214
|
-
const index = stateGlobal.__MANDU_HMR_TEST_PORT_STATE__.next++;
|
|
215
|
-
return 41000 + (((process.pid % 3500) * 2 + index * 2) % 7000);
|
|
216
|
-
}
|
|
217
|
-
|
|
218
|
-
|
|
213
|
+
const HMR_TEST_PORT_STATE = "__MANDU_HMR_TEST_PORT_STATE__";
|
|
214
|
+
|
|
215
|
+
function pickPort(): number {
|
|
216
|
+
const stateGlobal = globalThis as typeof globalThis & {
|
|
217
|
+
__MANDU_HMR_TEST_PORT_STATE__?: { next: number };
|
|
218
|
+
};
|
|
219
|
+
stateGlobal.__MANDU_HMR_TEST_PORT_STATE__ ??= { next: 0 };
|
|
220
|
+
const index = stateGlobal.__MANDU_HMR_TEST_PORT_STATE__.next++;
|
|
221
|
+
return 41000 + (((process.pid % 3500) * 2 + index * 2) % 7000);
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
describeCoreIsolated("Phase 7.2 Agent B — slot-refetch broadcast", () => {
|
|
219
225
|
let server: HMRServer | null = null;
|
|
220
226
|
let hmrPort = 0;
|
|
221
227
|
|
|
222
228
|
beforeEach(() => {
|
|
223
229
|
const basePort = pickPort();
|
|
224
230
|
hmrPort = basePort + PORTS.HMR_OFFSET;
|
|
225
|
-
server = createHMRServer(basePort);
|
|
231
|
+
server = createHMRServer(basePort, { hostname: "127.0.0.1" });
|
|
226
232
|
});
|
|
227
233
|
|
|
228
234
|
afterEach(() => {
|
|
@@ -245,7 +251,7 @@ describe("Phase 7.2 Agent B — slot-refetch broadcast", () => {
|
|
|
245
251
|
});
|
|
246
252
|
|
|
247
253
|
test("[5] custom slot-refetch reaches the client with expected shape", async () => {
|
|
248
|
-
const ws = new WebSocket(`ws://
|
|
254
|
+
const ws = new WebSocket(`ws://127.0.0.1:${hmrPort}/`);
|
|
249
255
|
await new Promise<void>((resolve, reject) => {
|
|
250
256
|
ws.addEventListener("open", () => resolve(), { once: true });
|
|
251
257
|
ws.addEventListener("error", (e) => reject(e as unknown as Error), {
|
|
@@ -324,7 +330,7 @@ describe("Phase 7.2 Agent B — slot-refetch broadcast", () => {
|
|
|
324
330
|
// Section C — HDRPayload shape contract
|
|
325
331
|
// -----------------------------------------------------------------------------
|
|
326
332
|
|
|
327
|
-
|
|
333
|
+
describeCoreIsolated("Phase 7.2 Agent B — HDRPayload shape", () => {
|
|
328
334
|
test("[7] HDRPayload type includes routeId / slotPath / rebuildId / timestamp", () => {
|
|
329
335
|
// Compile-time assertion: if the HDRPayload type shape changes in
|
|
330
336
|
// hmr-types.ts without updating this test, TypeScript compilation
|