Uh oh!
There was an error while loading. Please reload this page.
fix(console): validate OBJECTSTACK_CLIENT_DIST before aliasing it - #6114
Conversation
The client-dist hook was a bare string alias: no existence check, no manifest read, no dependency check. Pointed at an out-of-tree client build — the situation the hook exists for, where a reachable node_modules is absent — `vite build` printed `8615 modules transformed`, wrote every chunk, and left the client's three own bare specifiers (@objectstack/core/logger, @objectstack/spec/api, @objectstack/spec/data) in assets/framework-*.js as calls to rolldown's `require` shim: a bundle no browser can load, from a build whose output never named the variable. The value is now resolved and validated before it is aliased. One upward walk handles all three legal spellings — a package directory, its dist/, or a built entry file inside it — and stops at the nearest ancestor manifest that IS @objectstack/client, so the entry-file spelling cannot silently resolve to "nothing to check". That package's own declared dependencies must then resolve from where it lives. The check is re-stated beside the client hook rather than extracted from the spec hook: the two are not symmetrical (18 derived aliases from an exports map vs. one alias over a wider accepted input), and a shared helper would impose the spec hook's assumptions on this one. Alias target, fs.allow values and inert-when-unset behaviour are unchanged; only the realpath'd package directory used for the dependency walk is new, and it is load-bearing — under pnpm the installed client is a symlink whose ancestors never include the store directory holding its dependencies, so without it a VALID override is rejected. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019b5UBNMtTzKbVtZZGvFuxe
yinlianghui-tw
commented
Aug 24, 2026
PM: ACCEPTRuled shape taken exactly: validation in a new sibling module, The directory-or-entry-file ambiguity is solved by one idea, not two branchesI flagged this as the substance of the card. Your answer — one upward walk stopping at the nearest ancestor manifest whose
|
✅ Console Performance Budget
The eager closure is every chunk the entry reaches through static imports — what the browser fetches and parses before the app renders. The entry chunk on its own is a small fraction of it. 📦 Bundle Size Report
Size Limits
|
Uh oh!
There was an error while loading. Please reload this page.
Fixes#6094
OBJECTSTACK_CLIENT_DISTwas a bare string alias: no existence check, no manifest read, no dependency check. Its siblingOBJECTSTACK_SPEC_DISTgot a fail-fast dependency check in PR #5995; this is the client half, taken as the cheaper first cut ruled on the card — the check lands beside the client hook, andscripts/vite-objectstack-spec-dist.tsis not refactored.The reproduction, measured on
a100f77d3Installed
@objectstack/client@17.2.0copied outside the workspace (manifest +dist/, no reachablenode_modules),OBJECTSTACK_CLIENT_DISTaimed at it,vite buildinapps/console:The bundle is written. The client's three own bare specifiers survive into
assets/framework-Lm271O55.jsas calls to rolldown'srequireshim:where
eis rolldown's runtime require shim — in a browser it throws Callingrequirefor "…" in an environment that doesn't expose therequirefunction. So the card's characterisation holds exactly: the build reads as success through transform, render and emit, and produces a bundle no browser can load.One refinement worth recording. The process still exits non-zero, for reasons that never name the override:
ineffective-dynamic-import-ledger'scloseBundle— "43 pinned ineffective dynamic import(s) did NOT fire", a list ofpackages/fieldswidgets (ineffective-dynamic-import-ledger's closeBundle counter-probe replaces the real error whenever an earlier console plugin fails the build #6093, dispatched separately, untouched here);Rolldown failed to resolve import "@objectstack/spec/data" from "…/client-pkg/dist/index.js".Neither names
OBJECTSTACK_CLIENT_DIST, and both arrive a whole build after the value that caused them was read. That is the #5391 failure mode, on the other hook.The fix
scripts/vite-objectstack-client-dist.ts—resolveClientDistInjection(raw), called fromapps/console/vite.config.ts. Set and valid → the same alias target andfs.allowvalues as before. Unset →null, everything inert. Set and wrong → throws at config load, withOBJECTSTACK_CLIENT_DISTin the message.The directory-or-entry-file ambiguity is the substance, since a dependency check is parameterised over a package directory and this hook may be handed a file. Resolution is one upward walk that stops at the nearest ancestor manifest whose
nameis@objectstack/client, so…/client,…/client/distand…/client/dist/index.mjsall resolve to…/client. The name match is load-bearing rather than decorative: without it the walk stops at whatever manifest sits above the value — a framework monorepo root, or this repo — and validates that package's dependencies, i.e. a check that always passes on the wrong subject.realpathSyncon the package directory is not cosmetic. Under pnpm the installed client is reached throughapps/console/node_modules/@objectstack/client, a symlink into the store, while its dependencies live beside the store copy. Walking up from the symlink path never passes that directory, so a valid, installed-package override is rejected without the realpath — measured, and pinned by a case that asserts both walks directly. The alias target keeps its old, un-realpath'd value: this card adds validation, it does not move the alias or the module ids that follow from it.Verification
Union of the gates below re-run on the final commit
3f59d1979.Refusal — the real, unmodified plugin list (
OBJECTSTACK_CLIENT_DIST= the out-of-tree copy):The refusal needs no un-masking: it lands at config load, before any plugin runs, so #6093 cannot hide it.
Non-vacuity, the other direction — a valid override must still build. Both spellings, against the installed client, with the
#6093plugin removed for the run so a green build is observable at all: directory…/node_modules/@objectstack/client→BUILD_EXIT=0; entry file…/@objectstack/client/dist/index.mjs→BUILD_EXIT=0.Freshness of the artifact under test. Vite compiles the config and the
scripts/modules it imports into a per-invocation file undernode_modules/.vite-temp/, namedvite.config.ts.timestamp-plus a millisecond stamp and a random suffix, and deletes it at the end — so a stale-artifact reading is a real risk. Each run was polled while its file existed: the four runs carry four distinct names (…-1787592975150-…,…-1787593597147-…,…-1787593681607-…,…-1787593759077-…), and for three of them the poller also read the compiled bundle and found the new module's own text inside it (points at a client build with no reachable) — stronger than differing names, since it proves this source compiled into the artifact that ran. The fourth (the entry-file green build) raced the delete and its marker read returnedENOENT; its evidence is the distinct filename plus the identical tree, verified by marker two runs earlier.Unit suite —
scripts/__tests__/vite-objectstack-client-dist.test.ts, 19 cases: inert-when-unset (incl. the real console config's baseline alias and absentserver.fs), all three valid spellings against the real installed client, the pnpm-symlink counter-probe,peerDependenciesdeliberately not demanded, and every refusal asserted for both shapes.Reverse verification, direction predicted before each run, one prediction wrong and kept:
resolvedeven for a file…/index.mjs/package.jsonsimply misses and the next iteration lands on…/dist. The branch is explicitness, not the mechanism. Recorded in the suite header rather than deleted.realpathSyncEach ablation restored under a
trap … EXIT INT TERM, with the mutation proven on disk by counting the removed and introduced text before reading any result.Gates (exit code captured before any pipe):
vitest run scripts/__tests__/vite-objectstack-client-dist.test.ts scripts/__tests__/vite-objectstack-spec-dist.test.ts→Test Files 2 passed (2) / Tests 52 passed (52)(the spec suite runs because it pins the console config's baseline surfaces) ·check-control-bytes→✅ OK (scanned 5059 tracked text file(s))·check-lint-coverage→✅ 46/46·check-type-check-coverage→✅ 45/46 … 0 errors outstanding·check-changeset-presence→✅ No source of a released package changed in this range, so no changeset is owed·tsc -p tsconfig.scripts.json→ 0 ·eslinton the two newscripts/files → 0 ·eslint apps/console/vite.config.ts→ 0 ·apps/consoletsc -b tsconfig.node.json --force→ 0.One declared narrowing.
pnpm --filter @object-ui/console type-checkrunstsc --noEmit && tsc -b tsconfig.node.json --force; the first half fails in this worktree withTS2307 Cannot find module '@object-ui/…'acrosssrc/*.tsx— unbuilt workspace dependencies, which CI resolves by building through turbo's pipeline before type-checking. It cannot be this diff: that program'sincludeis["src", "dev"], and none of the three changed files is in it. The program that does contain them —tsconfig.node.json, whoseincludeglobs../../scripts/vite-*.ts— was run in full and is green.No changeset: nothing under a released package's
src/changed, andcheck-changeset-presenceagrees.Generated by Claude Code