@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
|
@@ -1,455 +1,455 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Phase 7.2.S2 — Tier 2 vendor shim disk cache tests (Agent A).
|
|
3
|
-
*
|
|
4
|
-
* Covers `readVendorCache` / `writeVendorCache` / `restoreVendorCache` —
|
|
5
|
-
* the ~25 KB cache layer that closes ~80-120 ms of cold start on warm
|
|
6
|
-
* restarts by reusing `_vendor-react.js` / `_vendor-react-dom.js` /
|
|
7
|
-
* `_vendor-react-refresh.js` / `_fast-refresh-runtime.js` instead of
|
|
8
|
-
* re-running Bun.build.
|
|
9
|
-
*
|
|
10
|
-
* Test strategy:
|
|
11
|
-
* - Each case operates on its own tmpdir so cache state is hermetic.
|
|
12
|
-
* - We write synthetic shim files (tiny JS strings) and an explicit
|
|
13
|
-
* manifest — we do NOT invoke the real `buildVendorShims` because
|
|
14
|
-
* that would lock tests to a Bun.build round-trip per case and bloat
|
|
15
|
-
* runtime to ~15-20 s. Integration with the real shim build is
|
|
16
|
-
* covered by `buildVendorShims emits fast-refresh shims in dev mode`
|
|
17
|
-
* in `fast-refresh.test.ts`.
|
|
18
|
-
* - Hash / size / version checks live in the read layer — we drive
|
|
19
|
-
* each branch by mutating the manifest on disk between calls.
|
|
20
|
-
*
|
|
21
|
-
* References:
|
|
22
|
-
* docs/bun/phase-7-2-team-plan.md §2.2, §3 Agent A (S2)
|
|
23
|
-
* packages/core/src/bundler/vendor-cache.ts
|
|
24
|
-
*/
|
|
25
|
-
|
|
26
|
-
import { describe, it, expect, beforeEach, afterEach } from "bun:test";
|
|
27
|
-
import { mkdtempSync, rmSync, writeFileSync, mkdirSync } from "fs";
|
|
28
|
-
import { tmpdir } from "os";
|
|
29
|
-
import path from "path";
|
|
30
|
-
import { createHash } from "node:crypto";
|
|
31
|
-
import {
|
|
32
|
-
readVendorCache,
|
|
33
|
-
writeVendorCache,
|
|
34
|
-
restoreVendorCache,
|
|
35
|
-
type VendorCacheKeyInput,
|
|
36
|
-
type VendorCacheWriteEntry,
|
|
37
|
-
} from "../vendor-cache";
|
|
38
|
-
import {
|
|
39
|
-
VENDOR_CACHE_DIR,
|
|
40
|
-
VENDOR_CACHE_FILENAME,
|
|
41
|
-
type VendorCacheManifest,
|
|
42
|
-
} from "../vendor-cache-types";
|
|
43
|
-
|
|
44
|
-
// ============================================
|
|
45
|
-
// Helpers
|
|
46
|
-
// ============================================
|
|
47
|
-
|
|
48
|
-
const BASELINE_KEYS: VendorCacheKeyInput = {
|
|
49
|
-
bunVersion: "1.3.12",
|
|
50
|
-
reactVersion: "19.2.0",
|
|
51
|
-
reactDomVersion: "19.2.0",
|
|
52
|
-
reactRefreshVersion: "0.18.0",
|
|
53
|
-
manduCoreVersion: "0.22.0",
|
|
54
|
-
};
|
|
55
|
-
|
|
56
|
-
function makeShim(label: string): string {
|
|
57
|
-
return `/* shim: ${label} */ export default { label: "${label}" };\n`;
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
function sha256(s: string): string {
|
|
61
|
-
return createHash("sha256").update(s).digest("hex");
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
function writeShimFile(root: string, relPath: string, contents: string): string {
|
|
65
|
-
const abs = path.join(root, relPath);
|
|
66
|
-
mkdirSync(path.dirname(abs), { recursive: true });
|
|
67
|
-
writeFileSync(abs, contents);
|
|
68
|
-
return abs;
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
function writeManifest(rootDir: string, manifest: VendorCacheManifest): string {
|
|
72
|
-
const cacheDir = path.join(rootDir, VENDOR_CACHE_DIR);
|
|
73
|
-
mkdirSync(cacheDir, { recursive: true });
|
|
74
|
-
const p = path.join(cacheDir, VENDOR_CACHE_FILENAME);
|
|
75
|
-
writeFileSync(p, JSON.stringify(manifest, null, 2));
|
|
76
|
-
return p;
|
|
77
|
-
}
|
|
78
|
-
|
|
79
|
-
/**
|
|
80
|
-
* Produce a complete manifest + write every referenced shim file. Returns
|
|
81
|
-
* the manifest (useful for mutating one field between calls).
|
|
82
|
-
*/
|
|
83
|
-
function seedCache(rootDir: string): VendorCacheManifest {
|
|
84
|
-
const cacheDir = path.join(rootDir, VENDOR_CACHE_DIR);
|
|
85
|
-
mkdirSync(cacheDir, { recursive: true });
|
|
86
|
-
|
|
87
|
-
const entries: VendorCacheManifest["entries"] = {};
|
|
88
|
-
const names: Array<[string, string]> = [
|
|
89
|
-
["react", "_react.js"],
|
|
90
|
-
["react-dom", "_react-dom.js"],
|
|
91
|
-
["react-dom-client", "_react-dom-client.js"],
|
|
92
|
-
["jsx-runtime", "_jsx-runtime.js"],
|
|
93
|
-
["jsx-dev-runtime", "_jsx-dev-runtime.js"],
|
|
94
|
-
["react-refresh-runtime", "_vendor-react-refresh.js"],
|
|
95
|
-
["fast-refresh-glue", "_fast-refresh-runtime.js"],
|
|
96
|
-
];
|
|
97
|
-
|
|
98
|
-
for (const [logicalId, fileName] of names) {
|
|
99
|
-
const contents = makeShim(logicalId);
|
|
100
|
-
writeFileSync(path.join(cacheDir, fileName), contents);
|
|
101
|
-
entries[logicalId] = {
|
|
102
|
-
path: fileName,
|
|
103
|
-
size: Buffer.byteLength(contents, "utf-8"),
|
|
104
|
-
hash: sha256(contents),
|
|
105
|
-
};
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
const manifest: VendorCacheManifest = {
|
|
109
|
-
version: 1,
|
|
110
|
-
bunVersion: BASELINE_KEYS.bunVersion,
|
|
111
|
-
reactVersion: BASELINE_KEYS.reactVersion,
|
|
112
|
-
reactDomVersion: BASELINE_KEYS.reactDomVersion,
|
|
113
|
-
reactRefreshVersion: BASELINE_KEYS.reactRefreshVersion,
|
|
114
|
-
manduCoreVersion: BASELINE_KEYS.manduCoreVersion,
|
|
115
|
-
entries,
|
|
116
|
-
generatedAt: new Date().toISOString(),
|
|
117
|
-
};
|
|
118
|
-
writeManifest(rootDir, manifest);
|
|
119
|
-
return manifest;
|
|
120
|
-
}
|
|
121
|
-
|
|
122
|
-
// ============================================
|
|
123
|
-
// Tests
|
|
124
|
-
// ============================================
|
|
125
|
-
|
|
126
|
-
describe("vendor-cache — read path", () => {
|
|
127
|
-
let rootDir: string;
|
|
128
|
-
|
|
129
|
-
beforeEach(() => {
|
|
130
|
-
rootDir = mkdtempSync(path.join(tmpdir(), "mandu-vendor-cache-"));
|
|
131
|
-
});
|
|
132
|
-
|
|
133
|
-
afterEach(() => {
|
|
134
|
-
try {
|
|
135
|
-
rmSync(rootDir, { recursive: true, force: true });
|
|
136
|
-
} catch {
|
|
137
|
-
// Windows lock tolerance
|
|
138
|
-
}
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
it("no manifest → miss with reason 'no-manifest'", async () => {
|
|
142
|
-
const result = await readVendorCache(rootDir, BASELINE_KEYS);
|
|
143
|
-
expect(result.kind).toBe("miss");
|
|
144
|
-
if (result.kind === "miss") {
|
|
145
|
-
expect(result.reason).toBe("no-manifest");
|
|
146
|
-
}
|
|
147
|
-
});
|
|
148
|
-
|
|
149
|
-
it("corrupt manifest JSON → miss with reason 'no-manifest'", async () => {
|
|
150
|
-
const cacheDir = path.join(rootDir, VENDOR_CACHE_DIR);
|
|
151
|
-
mkdirSync(cacheDir, { recursive: true });
|
|
152
|
-
writeFileSync(path.join(cacheDir, VENDOR_CACHE_FILENAME), "{not json");
|
|
153
|
-
|
|
154
|
-
const result = await readVendorCache(rootDir, BASELINE_KEYS);
|
|
155
|
-
expect(result.kind).toBe("miss");
|
|
156
|
-
if (result.kind === "miss") {
|
|
157
|
-
expect(result.reason).toBe("no-manifest");
|
|
158
|
-
}
|
|
159
|
-
});
|
|
160
|
-
|
|
161
|
-
it("happy path: full seeded cache → hit", async () => {
|
|
162
|
-
seedCache(rootDir);
|
|
163
|
-
const result = await readVendorCache(rootDir, BASELINE_KEYS);
|
|
164
|
-
expect(result.kind).toBe("hit");
|
|
165
|
-
if (result.kind === "hit") {
|
|
166
|
-
expect(result.manifest.bunVersion).toBe(BASELINE_KEYS.bunVersion);
|
|
167
|
-
expect(Object.keys(result.manifest.entries).length).toBe(7);
|
|
168
|
-
}
|
|
169
|
-
});
|
|
170
|
-
|
|
171
|
-
it("Bun version changed → miss 'version-mismatch' + mismatchedField=bunVersion", async () => {
|
|
172
|
-
seedCache(rootDir);
|
|
173
|
-
const result = await readVendorCache(rootDir, {
|
|
174
|
-
...BASELINE_KEYS,
|
|
175
|
-
bunVersion: "1.3.9",
|
|
176
|
-
});
|
|
177
|
-
expect(result.kind).toBe("miss");
|
|
178
|
-
if (result.kind === "miss") {
|
|
179
|
-
expect(result.reason).toBe("version-mismatch");
|
|
180
|
-
expect(result.mismatchedField).toBe("bunVersion");
|
|
181
|
-
}
|
|
182
|
-
});
|
|
183
|
-
|
|
184
|
-
it("React version changed → miss 'version-mismatch'", async () => {
|
|
185
|
-
seedCache(rootDir);
|
|
186
|
-
const result = await readVendorCache(rootDir, {
|
|
187
|
-
...BASELINE_KEYS,
|
|
188
|
-
reactVersion: "18.3.0",
|
|
189
|
-
});
|
|
190
|
-
expect(result.kind).toBe("miss");
|
|
191
|
-
if (result.kind === "miss") {
|
|
192
|
-
expect(result.reason).toBe("version-mismatch");
|
|
193
|
-
expect(result.mismatchedField).toBe("reactVersion");
|
|
194
|
-
}
|
|
195
|
-
});
|
|
196
|
-
|
|
197
|
-
it("@mandujs/core version changed → miss 'version-mismatch'", async () => {
|
|
198
|
-
seedCache(rootDir);
|
|
199
|
-
const result = await readVendorCache(rootDir, {
|
|
200
|
-
...BASELINE_KEYS,
|
|
201
|
-
manduCoreVersion: "0.99.0",
|
|
202
|
-
});
|
|
203
|
-
expect(result.kind).toBe("miss");
|
|
204
|
-
if (result.kind === "miss") {
|
|
205
|
-
expect(result.reason).toBe("version-mismatch");
|
|
206
|
-
expect(result.mismatchedField).toBe("manduCoreVersion");
|
|
207
|
-
}
|
|
208
|
-
});
|
|
209
|
-
|
|
210
|
-
it("hash mismatch on a cache file → miss 'hash-mismatch'", async () => {
|
|
211
|
-
const manifest = seedCache(rootDir);
|
|
212
|
-
// Overwrite `_react.js` with content that has the SAME size but
|
|
213
|
-
// different bytes — this forces the hash branch (not the size
|
|
214
|
-
// branch).
|
|
215
|
-
const original = makeShim("react");
|
|
216
|
-
const tampered = "/* shim: xxxxx */ export default { label: \"xxxxx\" };\n";
|
|
217
|
-
expect(Buffer.byteLength(tampered, "utf-8")).toBe(
|
|
218
|
-
Buffer.byteLength(original, "utf-8"),
|
|
219
|
-
);
|
|
220
|
-
|
|
221
|
-
writeFileSync(
|
|
222
|
-
path.join(rootDir, VENDOR_CACHE_DIR, "_react.js"),
|
|
223
|
-
tampered,
|
|
224
|
-
);
|
|
225
|
-
|
|
226
|
-
const result = await readVendorCache(rootDir, BASELINE_KEYS);
|
|
227
|
-
expect(result.kind).toBe("miss");
|
|
228
|
-
if (result.kind === "miss") {
|
|
229
|
-
expect(result.reason).toBe("hash-mismatch");
|
|
230
|
-
}
|
|
231
|
-
|
|
232
|
-
// Sanity — manifest entry still points at the intended file name.
|
|
233
|
-
expect(manifest.entries["react"]!.path).toBe("_react.js");
|
|
234
|
-
});
|
|
235
|
-
|
|
236
|
-
it("size mismatch on a cache file → miss 'size-mismatch' (cheap check)", async () => {
|
|
237
|
-
seedCache(rootDir);
|
|
238
|
-
writeFileSync(
|
|
239
|
-
path.join(rootDir, VENDOR_CACHE_DIR, "_react.js"),
|
|
240
|
-
makeShim("react") + "// padding\n",
|
|
241
|
-
);
|
|
242
|
-
|
|
243
|
-
const result = await readVendorCache(rootDir, BASELINE_KEYS);
|
|
244
|
-
expect(result.kind).toBe("miss");
|
|
245
|
-
if (result.kind === "miss") {
|
|
246
|
-
expect(result.reason).toBe("size-mismatch");
|
|
247
|
-
}
|
|
248
|
-
});
|
|
249
|
-
|
|
250
|
-
it("manifest references missing file → miss 'missing-entry'", async () => {
|
|
251
|
-
seedCache(rootDir);
|
|
252
|
-
rmSync(path.join(rootDir, VENDOR_CACHE_DIR, "_vendor-react-refresh.js"));
|
|
253
|
-
|
|
254
|
-
const result = await readVendorCache(rootDir, BASELINE_KEYS);
|
|
255
|
-
expect(result.kind).toBe("miss");
|
|
256
|
-
if (result.kind === "miss") {
|
|
257
|
-
expect(result.reason).toBe("missing-entry");
|
|
258
|
-
}
|
|
259
|
-
});
|
|
260
|
-
|
|
261
|
-
it("format version != 1 → miss 'format-version'", async () => {
|
|
262
|
-
const manifest = seedCache(rootDir);
|
|
263
|
-
// Write a manifest whose `version` field is unknown (forward-compat hedge).
|
|
264
|
-
const badManifest = { ...manifest, version: 99 } as unknown as VendorCacheManifest;
|
|
265
|
-
writeManifest(rootDir, badManifest);
|
|
266
|
-
|
|
267
|
-
const result = await readVendorCache(rootDir, BASELINE_KEYS);
|
|
268
|
-
expect(result.kind).toBe("miss");
|
|
269
|
-
if (result.kind === "miss") {
|
|
270
|
-
expect(result.reason).toBe("format-version");
|
|
271
|
-
}
|
|
272
|
-
});
|
|
273
|
-
});
|
|
274
|
-
|
|
275
|
-
describe("vendor-cache — write path", () => {
|
|
276
|
-
let rootDir: string;
|
|
277
|
-
|
|
278
|
-
beforeEach(() => {
|
|
279
|
-
rootDir = mkdtempSync(path.join(tmpdir(), "mandu-vendor-cache-w-"));
|
|
280
|
-
});
|
|
281
|
-
|
|
282
|
-
afterEach(() => {
|
|
283
|
-
try {
|
|
284
|
-
rmSync(rootDir, { recursive: true, force: true });
|
|
285
|
-
} catch {
|
|
286
|
-
// Windows lock tolerance
|
|
287
|
-
}
|
|
288
|
-
});
|
|
289
|
-
|
|
290
|
-
it("writes manifest + copies every shim file", async () => {
|
|
291
|
-
// Seed .mandu/client/ with fresh built shim files (simulate a
|
|
292
|
-
// successful `buildVendorShims` run).
|
|
293
|
-
const outDir = path.join(rootDir, ".mandu", "client");
|
|
294
|
-
mkdirSync(outDir, { recursive: true });
|
|
295
|
-
const reactAbs = writeShimFile(rootDir, ".mandu/client/_react.js", makeShim("react"));
|
|
296
|
-
const refreshAbs = writeShimFile(
|
|
297
|
-
rootDir,
|
|
298
|
-
".mandu/client/_vendor-react-refresh.js",
|
|
299
|
-
makeShim("refresh"),
|
|
300
|
-
);
|
|
301
|
-
|
|
302
|
-
const entries: VendorCacheWriteEntry[] = [
|
|
303
|
-
{ logicalId: "react", absPath: reactAbs },
|
|
304
|
-
{ logicalId: "react-refresh-runtime", absPath: refreshAbs },
|
|
305
|
-
];
|
|
306
|
-
|
|
307
|
-
const ok = await writeVendorCache(rootDir, BASELINE_KEYS, entries);
|
|
308
|
-
expect(ok).toBe(true);
|
|
309
|
-
|
|
310
|
-
// Manifest must exist at the right location.
|
|
311
|
-
const manifestPath = path.join(
|
|
312
|
-
rootDir,
|
|
313
|
-
VENDOR_CACHE_DIR,
|
|
314
|
-
VENDOR_CACHE_FILENAME,
|
|
315
|
-
);
|
|
316
|
-
const { readFileSync, existsSync } = await import("node:fs");
|
|
317
|
-
expect(existsSync(manifestPath)).toBe(true);
|
|
318
|
-
|
|
319
|
-
const parsed = JSON.parse(readFileSync(manifestPath, "utf-8")) as VendorCacheManifest;
|
|
320
|
-
expect(parsed.version).toBe(1);
|
|
321
|
-
expect(parsed.bunVersion).toBe(BASELINE_KEYS.bunVersion);
|
|
322
|
-
expect(parsed.entries.react).toBeDefined();
|
|
323
|
-
expect(parsed.entries["react-refresh-runtime"]).toBeDefined();
|
|
324
|
-
|
|
325
|
-
// Round-trip: re-read should hit.
|
|
326
|
-
const result = await readVendorCache(rootDir, BASELINE_KEYS);
|
|
327
|
-
expect(result.kind).toBe("hit");
|
|
328
|
-
});
|
|
329
|
-
|
|
330
|
-
it("skips entries whose source file is missing; still writes if at least one survived", async () => {
|
|
331
|
-
const outDir = path.join(rootDir, ".mandu", "client");
|
|
332
|
-
mkdirSync(outDir, { recursive: true });
|
|
333
|
-
const reactAbs = writeShimFile(rootDir, ".mandu/client/_react.js", makeShim("react"));
|
|
334
|
-
|
|
335
|
-
const entries: VendorCacheWriteEntry[] = [
|
|
336
|
-
{ logicalId: "react", absPath: reactAbs },
|
|
337
|
-
// The second entry points at a non-existent file — writeVendorCache
|
|
338
|
-
// should skip it and still write the manifest for `react`.
|
|
339
|
-
{
|
|
340
|
-
logicalId: "react-refresh-runtime",
|
|
341
|
-
absPath: path.join(rootDir, ".mandu/client/MISSING.js"),
|
|
342
|
-
},
|
|
343
|
-
];
|
|
344
|
-
|
|
345
|
-
const ok = await writeVendorCache(rootDir, BASELINE_KEYS, entries);
|
|
346
|
-
expect(ok).toBe(true);
|
|
347
|
-
|
|
348
|
-
const result = await readVendorCache(rootDir, BASELINE_KEYS);
|
|
349
|
-
expect(result.kind).toBe("hit");
|
|
350
|
-
if (result.kind === "hit") {
|
|
351
|
-
expect(Object.keys(result.manifest.entries)).toContain("react");
|
|
352
|
-
expect(Object.keys(result.manifest.entries)).not.toContain(
|
|
353
|
-
"react-refresh-runtime",
|
|
354
|
-
);
|
|
355
|
-
}
|
|
356
|
-
});
|
|
357
|
-
|
|
358
|
-
it("empty entry list → returns false (does not write a stub manifest)", async () => {
|
|
359
|
-
const ok = await writeVendorCache(rootDir, BASELINE_KEYS, []);
|
|
360
|
-
expect(ok).toBe(false);
|
|
361
|
-
|
|
362
|
-
// No manifest should exist.
|
|
363
|
-
const { existsSync } = await import("node:fs");
|
|
364
|
-
const manifestPath = path.join(
|
|
365
|
-
rootDir,
|
|
366
|
-
VENDOR_CACHE_DIR,
|
|
367
|
-
VENDOR_CACHE_FILENAME,
|
|
368
|
-
);
|
|
369
|
-
expect(existsSync(manifestPath)).toBe(false);
|
|
370
|
-
});
|
|
371
|
-
});
|
|
372
|
-
|
|
373
|
-
describe("vendor-cache — restore path", () => {
|
|
374
|
-
let rootDir: string;
|
|
375
|
-
|
|
376
|
-
beforeEach(() => {
|
|
377
|
-
rootDir = mkdtempSync(path.join(tmpdir(), "mandu-vendor-cache-r-"));
|
|
378
|
-
});
|
|
379
|
-
|
|
380
|
-
afterEach(() => {
|
|
381
|
-
try {
|
|
382
|
-
rmSync(rootDir, { recursive: true, force: true });
|
|
383
|
-
} catch {
|
|
384
|
-
// Windows lock tolerance
|
|
385
|
-
}
|
|
386
|
-
});
|
|
387
|
-
|
|
388
|
-
it("restores every cached shim into outDir and returns the path map", async () => {
|
|
389
|
-
const manifest = seedCache(rootDir);
|
|
390
|
-
const outDir = path.join(rootDir, ".mandu", "client");
|
|
391
|
-
|
|
392
|
-
const map = await restoreVendorCache(rootDir, manifest, outDir);
|
|
393
|
-
expect(map).not.toBeNull();
|
|
394
|
-
expect(map!.size).toBe(7);
|
|
395
|
-
|
|
396
|
-
for (const logicalId of Object.keys(manifest.entries)) {
|
|
397
|
-
const dst = map!.get(logicalId);
|
|
398
|
-
expect(dst).toBeDefined();
|
|
399
|
-
const { existsSync } = await import("node:fs");
|
|
400
|
-
expect(existsSync(dst!)).toBe(true);
|
|
401
|
-
}
|
|
402
|
-
});
|
|
403
|
-
|
|
404
|
-
it("returns null when a cached file is missing mid-restore", async () => {
|
|
405
|
-
const manifest = seedCache(rootDir);
|
|
406
|
-
// Remove one of the source files the manifest still references.
|
|
407
|
-
rmSync(path.join(rootDir, VENDOR_CACHE_DIR, "_jsx-runtime.js"));
|
|
408
|
-
|
|
409
|
-
const outDir = path.join(rootDir, ".mandu", "client");
|
|
410
|
-
const map = await restoreVendorCache(rootDir, manifest, outDir);
|
|
411
|
-
expect(map).toBeNull();
|
|
412
|
-
});
|
|
413
|
-
});
|
|
414
|
-
|
|
415
|
-
describe("vendor-cache — key resolution regression", () => {
|
|
416
|
-
let rootDir: string;
|
|
417
|
-
|
|
418
|
-
beforeEach(() => {
|
|
419
|
-
rootDir = mkdtempSync(path.join(tmpdir(), "mandu-vendor-cache-k-"));
|
|
420
|
-
});
|
|
421
|
-
|
|
422
|
-
afterEach(() => {
|
|
423
|
-
try {
|
|
424
|
-
rmSync(rootDir, { recursive: true, force: true });
|
|
425
|
-
} catch {
|
|
426
|
-
// Windows lock tolerance
|
|
427
|
-
}
|
|
428
|
-
});
|
|
429
|
-
|
|
430
|
-
it("resolveVendorCacheKeys: returns well-formed keys without throwing", async () => {
|
|
431
|
-
// The resolver walks up from `rootDir` looking for
|
|
432
|
-
// `node_modules/<pkg>/package.json`. Depending on where the
|
|
433
|
-
// tmpdir lives (same drive / filesystem as the monorepo or not)
|
|
434
|
-
// it may either (a) fall back to "unknown" (no node_modules on
|
|
435
|
-
// the way up) or (b) find a hoisted package in a parent. Either
|
|
436
|
-
// outcome is correct — the contract is "no throw, string
|
|
437
|
-
// result". We assert exactly that and nothing more brittle.
|
|
438
|
-
const { resolveVendorCacheKeys } = await import("../vendor-cache");
|
|
439
|
-
const keys = await resolveVendorCacheKeys(rootDir);
|
|
440
|
-
|
|
441
|
-
expect(typeof keys.bunVersion).toBe("string");
|
|
442
|
-
expect(keys.bunVersion.length).toBeGreaterThan(0);
|
|
443
|
-
|
|
444
|
-
expect(typeof keys.reactVersion).toBe("string");
|
|
445
|
-
expect(typeof keys.reactDomVersion).toBe("string");
|
|
446
|
-
expect(typeof keys.reactRefreshVersion).toBe("string");
|
|
447
|
-
|
|
448
|
-
// @mandujs/core: workspace dev resolves via monorepo walk, published
|
|
449
|
-
// install resolves via node_modules. Either way it should be
|
|
450
|
-
// non-empty — this project lives in the monorepo so we expect a
|
|
451
|
-
// semver-ish version from packages/core/package.json.
|
|
452
|
-
expect(keys.manduCoreVersion.length).toBeGreaterThan(0);
|
|
453
|
-
expect(keys.manduCoreVersion).not.toBe("unknown");
|
|
454
|
-
});
|
|
455
|
-
});
|
|
1
|
+
/**
|
|
2
|
+
* Phase 7.2.S2 — Tier 2 vendor shim disk cache tests (Agent A).
|
|
3
|
+
*
|
|
4
|
+
* Covers `readVendorCache` / `writeVendorCache` / `restoreVendorCache` —
|
|
5
|
+
* the ~25 KB cache layer that closes ~80-120 ms of cold start on warm
|
|
6
|
+
* restarts by reusing `_vendor-react.js` / `_vendor-react-dom.js` /
|
|
7
|
+
* `_vendor-react-refresh.js` / `_fast-refresh-runtime.js` instead of
|
|
8
|
+
* re-running Bun.build.
|
|
9
|
+
*
|
|
10
|
+
* Test strategy:
|
|
11
|
+
* - Each case operates on its own tmpdir so cache state is hermetic.
|
|
12
|
+
* - We write synthetic shim files (tiny JS strings) and an explicit
|
|
13
|
+
* manifest — we do NOT invoke the real `buildVendorShims` because
|
|
14
|
+
* that would lock tests to a Bun.build round-trip per case and bloat
|
|
15
|
+
* runtime to ~15-20 s. Integration with the real shim build is
|
|
16
|
+
* covered by `buildVendorShims emits fast-refresh shims in dev mode`
|
|
17
|
+
* in `fast-refresh.test.ts`.
|
|
18
|
+
* - Hash / size / version checks live in the read layer — we drive
|
|
19
|
+
* each branch by mutating the manifest on disk between calls.
|
|
20
|
+
*
|
|
21
|
+
* References:
|
|
22
|
+
* docs/bun/phase-7-2-team-plan.md §2.2, §3 Agent A (S2)
|
|
23
|
+
* packages/core/src/bundler/vendor-cache.ts
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import { describe, it, expect, beforeEach, afterEach } from "bun:test";
|
|
27
|
+
import { mkdtempSync, rmSync, writeFileSync, mkdirSync } from "fs";
|
|
28
|
+
import { tmpdir } from "os";
|
|
29
|
+
import path from "path";
|
|
30
|
+
import { createHash } from "node:crypto";
|
|
31
|
+
import {
|
|
32
|
+
readVendorCache,
|
|
33
|
+
writeVendorCache,
|
|
34
|
+
restoreVendorCache,
|
|
35
|
+
type VendorCacheKeyInput,
|
|
36
|
+
type VendorCacheWriteEntry,
|
|
37
|
+
} from "../vendor-cache";
|
|
38
|
+
import {
|
|
39
|
+
VENDOR_CACHE_DIR,
|
|
40
|
+
VENDOR_CACHE_FILENAME,
|
|
41
|
+
type VendorCacheManifest,
|
|
42
|
+
} from "../vendor-cache-types";
|
|
43
|
+
|
|
44
|
+
// ============================================
|
|
45
|
+
// Helpers
|
|
46
|
+
// ============================================
|
|
47
|
+
|
|
48
|
+
const BASELINE_KEYS: VendorCacheKeyInput = {
|
|
49
|
+
bunVersion: "1.3.12",
|
|
50
|
+
reactVersion: "19.2.0",
|
|
51
|
+
reactDomVersion: "19.2.0",
|
|
52
|
+
reactRefreshVersion: "0.18.0",
|
|
53
|
+
manduCoreVersion: "0.22.0",
|
|
54
|
+
};
|
|
55
|
+
|
|
56
|
+
function makeShim(label: string): string {
|
|
57
|
+
return `/* shim: ${label} */ export default { label: "${label}" };\n`;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
function sha256(s: string): string {
|
|
61
|
+
return createHash("sha256").update(s).digest("hex");
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function writeShimFile(root: string, relPath: string, contents: string): string {
|
|
65
|
+
const abs = path.join(root, relPath);
|
|
66
|
+
mkdirSync(path.dirname(abs), { recursive: true });
|
|
67
|
+
writeFileSync(abs, contents);
|
|
68
|
+
return abs;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function writeManifest(rootDir: string, manifest: VendorCacheManifest): string {
|
|
72
|
+
const cacheDir = path.join(rootDir, VENDOR_CACHE_DIR);
|
|
73
|
+
mkdirSync(cacheDir, { recursive: true });
|
|
74
|
+
const p = path.join(cacheDir, VENDOR_CACHE_FILENAME);
|
|
75
|
+
writeFileSync(p, JSON.stringify(manifest, null, 2));
|
|
76
|
+
return p;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Produce a complete manifest + write every referenced shim file. Returns
|
|
81
|
+
* the manifest (useful for mutating one field between calls).
|
|
82
|
+
*/
|
|
83
|
+
function seedCache(rootDir: string): VendorCacheManifest {
|
|
84
|
+
const cacheDir = path.join(rootDir, VENDOR_CACHE_DIR);
|
|
85
|
+
mkdirSync(cacheDir, { recursive: true });
|
|
86
|
+
|
|
87
|
+
const entries: VendorCacheManifest["entries"] = {};
|
|
88
|
+
const names: Array<[string, string]> = [
|
|
89
|
+
["react", "_react.js"],
|
|
90
|
+
["react-dom", "_react-dom.js"],
|
|
91
|
+
["react-dom-client", "_react-dom-client.js"],
|
|
92
|
+
["jsx-runtime", "_jsx-runtime.js"],
|
|
93
|
+
["jsx-dev-runtime", "_jsx-dev-runtime.js"],
|
|
94
|
+
["react-refresh-runtime", "_vendor-react-refresh.js"],
|
|
95
|
+
["fast-refresh-glue", "_fast-refresh-runtime.js"],
|
|
96
|
+
];
|
|
97
|
+
|
|
98
|
+
for (const [logicalId, fileName] of names) {
|
|
99
|
+
const contents = makeShim(logicalId);
|
|
100
|
+
writeFileSync(path.join(cacheDir, fileName), contents);
|
|
101
|
+
entries[logicalId] = {
|
|
102
|
+
path: fileName,
|
|
103
|
+
size: Buffer.byteLength(contents, "utf-8"),
|
|
104
|
+
hash: sha256(contents),
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
const manifest: VendorCacheManifest = {
|
|
109
|
+
version: 1,
|
|
110
|
+
bunVersion: BASELINE_KEYS.bunVersion,
|
|
111
|
+
reactVersion: BASELINE_KEYS.reactVersion,
|
|
112
|
+
reactDomVersion: BASELINE_KEYS.reactDomVersion,
|
|
113
|
+
reactRefreshVersion: BASELINE_KEYS.reactRefreshVersion,
|
|
114
|
+
manduCoreVersion: BASELINE_KEYS.manduCoreVersion,
|
|
115
|
+
entries,
|
|
116
|
+
generatedAt: new Date().toISOString(),
|
|
117
|
+
};
|
|
118
|
+
writeManifest(rootDir, manifest);
|
|
119
|
+
return manifest;
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
// ============================================
|
|
123
|
+
// Tests
|
|
124
|
+
// ============================================
|
|
125
|
+
|
|
126
|
+
describe("vendor-cache — read path", () => {
|
|
127
|
+
let rootDir: string;
|
|
128
|
+
|
|
129
|
+
beforeEach(() => {
|
|
130
|
+
rootDir = mkdtempSync(path.join(tmpdir(), "mandu-vendor-cache-"));
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
afterEach(() => {
|
|
134
|
+
try {
|
|
135
|
+
rmSync(rootDir, { recursive: true, force: true });
|
|
136
|
+
} catch {
|
|
137
|
+
// Windows lock tolerance
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
it("no manifest → miss with reason 'no-manifest'", async () => {
|
|
142
|
+
const result = await readVendorCache(rootDir, BASELINE_KEYS);
|
|
143
|
+
expect(result.kind).toBe("miss");
|
|
144
|
+
if (result.kind === "miss") {
|
|
145
|
+
expect(result.reason).toBe("no-manifest");
|
|
146
|
+
}
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it("corrupt manifest JSON → miss with reason 'no-manifest'", async () => {
|
|
150
|
+
const cacheDir = path.join(rootDir, VENDOR_CACHE_DIR);
|
|
151
|
+
mkdirSync(cacheDir, { recursive: true });
|
|
152
|
+
writeFileSync(path.join(cacheDir, VENDOR_CACHE_FILENAME), "{not json");
|
|
153
|
+
|
|
154
|
+
const result = await readVendorCache(rootDir, BASELINE_KEYS);
|
|
155
|
+
expect(result.kind).toBe("miss");
|
|
156
|
+
if (result.kind === "miss") {
|
|
157
|
+
expect(result.reason).toBe("no-manifest");
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
it("happy path: full seeded cache → hit", async () => {
|
|
162
|
+
seedCache(rootDir);
|
|
163
|
+
const result = await readVendorCache(rootDir, BASELINE_KEYS);
|
|
164
|
+
expect(result.kind).toBe("hit");
|
|
165
|
+
if (result.kind === "hit") {
|
|
166
|
+
expect(result.manifest.bunVersion).toBe(BASELINE_KEYS.bunVersion);
|
|
167
|
+
expect(Object.keys(result.manifest.entries).length).toBe(7);
|
|
168
|
+
}
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it("Bun version changed → miss 'version-mismatch' + mismatchedField=bunVersion", async () => {
|
|
172
|
+
seedCache(rootDir);
|
|
173
|
+
const result = await readVendorCache(rootDir, {
|
|
174
|
+
...BASELINE_KEYS,
|
|
175
|
+
bunVersion: "1.3.9",
|
|
176
|
+
});
|
|
177
|
+
expect(result.kind).toBe("miss");
|
|
178
|
+
if (result.kind === "miss") {
|
|
179
|
+
expect(result.reason).toBe("version-mismatch");
|
|
180
|
+
expect(result.mismatchedField).toBe("bunVersion");
|
|
181
|
+
}
|
|
182
|
+
});
|
|
183
|
+
|
|
184
|
+
it("React version changed → miss 'version-mismatch'", async () => {
|
|
185
|
+
seedCache(rootDir);
|
|
186
|
+
const result = await readVendorCache(rootDir, {
|
|
187
|
+
...BASELINE_KEYS,
|
|
188
|
+
reactVersion: "18.3.0",
|
|
189
|
+
});
|
|
190
|
+
expect(result.kind).toBe("miss");
|
|
191
|
+
if (result.kind === "miss") {
|
|
192
|
+
expect(result.reason).toBe("version-mismatch");
|
|
193
|
+
expect(result.mismatchedField).toBe("reactVersion");
|
|
194
|
+
}
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
it("@mandujs/core version changed → miss 'version-mismatch'", async () => {
|
|
198
|
+
seedCache(rootDir);
|
|
199
|
+
const result = await readVendorCache(rootDir, {
|
|
200
|
+
...BASELINE_KEYS,
|
|
201
|
+
manduCoreVersion: "0.99.0",
|
|
202
|
+
});
|
|
203
|
+
expect(result.kind).toBe("miss");
|
|
204
|
+
if (result.kind === "miss") {
|
|
205
|
+
expect(result.reason).toBe("version-mismatch");
|
|
206
|
+
expect(result.mismatchedField).toBe("manduCoreVersion");
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
it("hash mismatch on a cache file → miss 'hash-mismatch'", async () => {
|
|
211
|
+
const manifest = seedCache(rootDir);
|
|
212
|
+
// Overwrite `_react.js` with content that has the SAME size but
|
|
213
|
+
// different bytes — this forces the hash branch (not the size
|
|
214
|
+
// branch).
|
|
215
|
+
const original = makeShim("react");
|
|
216
|
+
const tampered = "/* shim: xxxxx */ export default { label: \"xxxxx\" };\n";
|
|
217
|
+
expect(Buffer.byteLength(tampered, "utf-8")).toBe(
|
|
218
|
+
Buffer.byteLength(original, "utf-8"),
|
|
219
|
+
);
|
|
220
|
+
|
|
221
|
+
writeFileSync(
|
|
222
|
+
path.join(rootDir, VENDOR_CACHE_DIR, "_react.js"),
|
|
223
|
+
tampered,
|
|
224
|
+
);
|
|
225
|
+
|
|
226
|
+
const result = await readVendorCache(rootDir, BASELINE_KEYS);
|
|
227
|
+
expect(result.kind).toBe("miss");
|
|
228
|
+
if (result.kind === "miss") {
|
|
229
|
+
expect(result.reason).toBe("hash-mismatch");
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
// Sanity — manifest entry still points at the intended file name.
|
|
233
|
+
expect(manifest.entries["react"]!.path).toBe("_react.js");
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
it("size mismatch on a cache file → miss 'size-mismatch' (cheap check)", async () => {
|
|
237
|
+
seedCache(rootDir);
|
|
238
|
+
writeFileSync(
|
|
239
|
+
path.join(rootDir, VENDOR_CACHE_DIR, "_react.js"),
|
|
240
|
+
makeShim("react") + "// padding\n",
|
|
241
|
+
);
|
|
242
|
+
|
|
243
|
+
const result = await readVendorCache(rootDir, BASELINE_KEYS);
|
|
244
|
+
expect(result.kind).toBe("miss");
|
|
245
|
+
if (result.kind === "miss") {
|
|
246
|
+
expect(result.reason).toBe("size-mismatch");
|
|
247
|
+
}
|
|
248
|
+
});
|
|
249
|
+
|
|
250
|
+
it("manifest references missing file → miss 'missing-entry'", async () => {
|
|
251
|
+
seedCache(rootDir);
|
|
252
|
+
rmSync(path.join(rootDir, VENDOR_CACHE_DIR, "_vendor-react-refresh.js"));
|
|
253
|
+
|
|
254
|
+
const result = await readVendorCache(rootDir, BASELINE_KEYS);
|
|
255
|
+
expect(result.kind).toBe("miss");
|
|
256
|
+
if (result.kind === "miss") {
|
|
257
|
+
expect(result.reason).toBe("missing-entry");
|
|
258
|
+
}
|
|
259
|
+
});
|
|
260
|
+
|
|
261
|
+
it("format version != 1 → miss 'format-version'", async () => {
|
|
262
|
+
const manifest = seedCache(rootDir);
|
|
263
|
+
// Write a manifest whose `version` field is unknown (forward-compat hedge).
|
|
264
|
+
const badManifest = { ...manifest, version: 99 } as unknown as VendorCacheManifest;
|
|
265
|
+
writeManifest(rootDir, badManifest);
|
|
266
|
+
|
|
267
|
+
const result = await readVendorCache(rootDir, BASELINE_KEYS);
|
|
268
|
+
expect(result.kind).toBe("miss");
|
|
269
|
+
if (result.kind === "miss") {
|
|
270
|
+
expect(result.reason).toBe("format-version");
|
|
271
|
+
}
|
|
272
|
+
});
|
|
273
|
+
});
|
|
274
|
+
|
|
275
|
+
describe("vendor-cache — write path", () => {
|
|
276
|
+
let rootDir: string;
|
|
277
|
+
|
|
278
|
+
beforeEach(() => {
|
|
279
|
+
rootDir = mkdtempSync(path.join(tmpdir(), "mandu-vendor-cache-w-"));
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
afterEach(() => {
|
|
283
|
+
try {
|
|
284
|
+
rmSync(rootDir, { recursive: true, force: true });
|
|
285
|
+
} catch {
|
|
286
|
+
// Windows lock tolerance
|
|
287
|
+
}
|
|
288
|
+
});
|
|
289
|
+
|
|
290
|
+
it("writes manifest + copies every shim file", async () => {
|
|
291
|
+
// Seed .mandu/client/ with fresh built shim files (simulate a
|
|
292
|
+
// successful `buildVendorShims` run).
|
|
293
|
+
const outDir = path.join(rootDir, ".mandu", "client");
|
|
294
|
+
mkdirSync(outDir, { recursive: true });
|
|
295
|
+
const reactAbs = writeShimFile(rootDir, ".mandu/client/_react.js", makeShim("react"));
|
|
296
|
+
const refreshAbs = writeShimFile(
|
|
297
|
+
rootDir,
|
|
298
|
+
".mandu/client/_vendor-react-refresh.js",
|
|
299
|
+
makeShim("refresh"),
|
|
300
|
+
);
|
|
301
|
+
|
|
302
|
+
const entries: VendorCacheWriteEntry[] = [
|
|
303
|
+
{ logicalId: "react", absPath: reactAbs },
|
|
304
|
+
{ logicalId: "react-refresh-runtime", absPath: refreshAbs },
|
|
305
|
+
];
|
|
306
|
+
|
|
307
|
+
const ok = await writeVendorCache(rootDir, BASELINE_KEYS, entries);
|
|
308
|
+
expect(ok).toBe(true);
|
|
309
|
+
|
|
310
|
+
// Manifest must exist at the right location.
|
|
311
|
+
const manifestPath = path.join(
|
|
312
|
+
rootDir,
|
|
313
|
+
VENDOR_CACHE_DIR,
|
|
314
|
+
VENDOR_CACHE_FILENAME,
|
|
315
|
+
);
|
|
316
|
+
const { readFileSync, existsSync } = await import("node:fs");
|
|
317
|
+
expect(existsSync(manifestPath)).toBe(true);
|
|
318
|
+
|
|
319
|
+
const parsed = JSON.parse(readFileSync(manifestPath, "utf-8")) as VendorCacheManifest;
|
|
320
|
+
expect(parsed.version).toBe(1);
|
|
321
|
+
expect(parsed.bunVersion).toBe(BASELINE_KEYS.bunVersion);
|
|
322
|
+
expect(parsed.entries.react).toBeDefined();
|
|
323
|
+
expect(parsed.entries["react-refresh-runtime"]).toBeDefined();
|
|
324
|
+
|
|
325
|
+
// Round-trip: re-read should hit.
|
|
326
|
+
const result = await readVendorCache(rootDir, BASELINE_KEYS);
|
|
327
|
+
expect(result.kind).toBe("hit");
|
|
328
|
+
});
|
|
329
|
+
|
|
330
|
+
it("skips entries whose source file is missing; still writes if at least one survived", async () => {
|
|
331
|
+
const outDir = path.join(rootDir, ".mandu", "client");
|
|
332
|
+
mkdirSync(outDir, { recursive: true });
|
|
333
|
+
const reactAbs = writeShimFile(rootDir, ".mandu/client/_react.js", makeShim("react"));
|
|
334
|
+
|
|
335
|
+
const entries: VendorCacheWriteEntry[] = [
|
|
336
|
+
{ logicalId: "react", absPath: reactAbs },
|
|
337
|
+
// The second entry points at a non-existent file — writeVendorCache
|
|
338
|
+
// should skip it and still write the manifest for `react`.
|
|
339
|
+
{
|
|
340
|
+
logicalId: "react-refresh-runtime",
|
|
341
|
+
absPath: path.join(rootDir, ".mandu/client/MISSING.js"),
|
|
342
|
+
},
|
|
343
|
+
];
|
|
344
|
+
|
|
345
|
+
const ok = await writeVendorCache(rootDir, BASELINE_KEYS, entries);
|
|
346
|
+
expect(ok).toBe(true);
|
|
347
|
+
|
|
348
|
+
const result = await readVendorCache(rootDir, BASELINE_KEYS);
|
|
349
|
+
expect(result.kind).toBe("hit");
|
|
350
|
+
if (result.kind === "hit") {
|
|
351
|
+
expect(Object.keys(result.manifest.entries)).toContain("react");
|
|
352
|
+
expect(Object.keys(result.manifest.entries)).not.toContain(
|
|
353
|
+
"react-refresh-runtime",
|
|
354
|
+
);
|
|
355
|
+
}
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
it("empty entry list → returns false (does not write a stub manifest)", async () => {
|
|
359
|
+
const ok = await writeVendorCache(rootDir, BASELINE_KEYS, []);
|
|
360
|
+
expect(ok).toBe(false);
|
|
361
|
+
|
|
362
|
+
// No manifest should exist.
|
|
363
|
+
const { existsSync } = await import("node:fs");
|
|
364
|
+
const manifestPath = path.join(
|
|
365
|
+
rootDir,
|
|
366
|
+
VENDOR_CACHE_DIR,
|
|
367
|
+
VENDOR_CACHE_FILENAME,
|
|
368
|
+
);
|
|
369
|
+
expect(existsSync(manifestPath)).toBe(false);
|
|
370
|
+
});
|
|
371
|
+
});
|
|
372
|
+
|
|
373
|
+
describe("vendor-cache — restore path", () => {
|
|
374
|
+
let rootDir: string;
|
|
375
|
+
|
|
376
|
+
beforeEach(() => {
|
|
377
|
+
rootDir = mkdtempSync(path.join(tmpdir(), "mandu-vendor-cache-r-"));
|
|
378
|
+
});
|
|
379
|
+
|
|
380
|
+
afterEach(() => {
|
|
381
|
+
try {
|
|
382
|
+
rmSync(rootDir, { recursive: true, force: true });
|
|
383
|
+
} catch {
|
|
384
|
+
// Windows lock tolerance
|
|
385
|
+
}
|
|
386
|
+
});
|
|
387
|
+
|
|
388
|
+
it("restores every cached shim into outDir and returns the path map", async () => {
|
|
389
|
+
const manifest = seedCache(rootDir);
|
|
390
|
+
const outDir = path.join(rootDir, ".mandu", "client");
|
|
391
|
+
|
|
392
|
+
const map = await restoreVendorCache(rootDir, manifest, outDir);
|
|
393
|
+
expect(map).not.toBeNull();
|
|
394
|
+
expect(map!.size).toBe(7);
|
|
395
|
+
|
|
396
|
+
for (const logicalId of Object.keys(manifest.entries)) {
|
|
397
|
+
const dst = map!.get(logicalId);
|
|
398
|
+
expect(dst).toBeDefined();
|
|
399
|
+
const { existsSync } = await import("node:fs");
|
|
400
|
+
expect(existsSync(dst!)).toBe(true);
|
|
401
|
+
}
|
|
402
|
+
});
|
|
403
|
+
|
|
404
|
+
it("returns null when a cached file is missing mid-restore", async () => {
|
|
405
|
+
const manifest = seedCache(rootDir);
|
|
406
|
+
// Remove one of the source files the manifest still references.
|
|
407
|
+
rmSync(path.join(rootDir, VENDOR_CACHE_DIR, "_jsx-runtime.js"));
|
|
408
|
+
|
|
409
|
+
const outDir = path.join(rootDir, ".mandu", "client");
|
|
410
|
+
const map = await restoreVendorCache(rootDir, manifest, outDir);
|
|
411
|
+
expect(map).toBeNull();
|
|
412
|
+
});
|
|
413
|
+
});
|
|
414
|
+
|
|
415
|
+
describe("vendor-cache — key resolution regression", () => {
|
|
416
|
+
let rootDir: string;
|
|
417
|
+
|
|
418
|
+
beforeEach(() => {
|
|
419
|
+
rootDir = mkdtempSync(path.join(tmpdir(), "mandu-vendor-cache-k-"));
|
|
420
|
+
});
|
|
421
|
+
|
|
422
|
+
afterEach(() => {
|
|
423
|
+
try {
|
|
424
|
+
rmSync(rootDir, { recursive: true, force: true });
|
|
425
|
+
} catch {
|
|
426
|
+
// Windows lock tolerance
|
|
427
|
+
}
|
|
428
|
+
});
|
|
429
|
+
|
|
430
|
+
it("resolveVendorCacheKeys: returns well-formed keys without throwing", async () => {
|
|
431
|
+
// The resolver walks up from `rootDir` looking for
|
|
432
|
+
// `node_modules/<pkg>/package.json`. Depending on where the
|
|
433
|
+
// tmpdir lives (same drive / filesystem as the monorepo or not)
|
|
434
|
+
// it may either (a) fall back to "unknown" (no node_modules on
|
|
435
|
+
// the way up) or (b) find a hoisted package in a parent. Either
|
|
436
|
+
// outcome is correct — the contract is "no throw, string
|
|
437
|
+
// result". We assert exactly that and nothing more brittle.
|
|
438
|
+
const { resolveVendorCacheKeys } = await import("../vendor-cache");
|
|
439
|
+
const keys = await resolveVendorCacheKeys(rootDir);
|
|
440
|
+
|
|
441
|
+
expect(typeof keys.bunVersion).toBe("string");
|
|
442
|
+
expect(keys.bunVersion.length).toBeGreaterThan(0);
|
|
443
|
+
|
|
444
|
+
expect(typeof keys.reactVersion).toBe("string");
|
|
445
|
+
expect(typeof keys.reactDomVersion).toBe("string");
|
|
446
|
+
expect(typeof keys.reactRefreshVersion).toBe("string");
|
|
447
|
+
|
|
448
|
+
// @mandujs/core: workspace dev resolves via monorepo walk, published
|
|
449
|
+
// install resolves via node_modules. Either way it should be
|
|
450
|
+
// non-empty — this project lives in the monorepo so we expect a
|
|
451
|
+
// semver-ish version from packages/core/package.json.
|
|
452
|
+
expect(keys.manduCoreVersion.length).toBeGreaterThan(0);
|
|
453
|
+
expect(keys.manduCoreVersion).not.toBe("unknown");
|
|
454
|
+
});
|
|
455
|
+
});
|