…build output
`@object-ui/console` publishes `main`/`types`/`exports` from `./plugin.js` and
`./plugin.d.ts` at the PACKAGE ROOT. Both are emitted by `build:plugin`
(`tsc -p tsconfig.plugin.json`) and gitignored, and the shared `build` task's
`outputs` (`dist/**`, `.next/**`, `build/**`, `**/*.tsbuildinfo`) match neither
— so turbo never STORED them. A cache hit restored `dist/` and left the
package's declared entry absent, and the tell was a `no-build-output` red
naming a package the author never touched.
Measured on bce5971, one fresh `--cache-dir`, cold build then wipe then replay:
before cache hit, replaying logs a218494ab23eb6f3 ... 248ms >>> FULL TURBO
apps/console/dist/ restored (12 entries), plugin.js ABSENT
check-node-esm-load --no-build -> exit 1
no-build-output - ./plugin.js does not exist after the build
after cache hit, replaying logs 68390122c3c328b0 ... 178ms >>> FULL TURBO
apps/console/dist/ restored (12 entries), plugin.js PRESENT
check-node-esm-load --no-build -> exit 0
The fix is a package configuration, so the shared task's cache key and storage
surface are untouched for every other package. It is `apps/console/turbo.json`
rather than a `turbo` key in the manifest: measured on turbo 2.10.9, a `turbo`
key in `package.json` is accepted silently and changes nothing
(`--dry=json` outputs stay the four root globs), while it does perturb the
task hash — declaring nothing at the price of a cache invalidation.
The config restates the root globs alongside its two additions because a
package configuration OVERRIDES the key it declares rather than appending to
it (measured: a config with only the two files resolves to
`outputs ["plugin.d.ts","plugin.js"]`, `excludedOutputs null`).
The audit the card asked for — every workspace `main`/`types`/`exports`/`bin`
target that does not resolve under `./dist/` — found 13 such targets across 4
packages, and `@object-ui/console` is the only one that is a build output.
`scripts/__tests__/turbo-build-outputs-cover-published-entries.test.ts` pins
the rule for packages that do not exist yet: every published entry target is
either tracked source or a declared build output, and no package configuration
narrows the root `outputs`.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01MM7kaS4dPpYHV5BsMyu4tQ
Fixes #7855
@object-ui/consolepublishesmain/types/exportsfrom./plugin.jsand./plugin.d.tsat the package root. Both are emitted bybuild:plugin(tsc -p tsconfig.plugin.json) and gitignored, and the sharedbuildtask'soutputs(dist/**,.next/**,build/**,**/*.tsbuildinfo, minusnode_modules) match neither — so turbo never stored them. A cache hit restoreddist/and left the package's declared entry absent.Two files:
apps/console/turbo.json(new) and one new pin underscripts/__tests__/. No package source, no manifest field, no change to the shared task.A2 — the card's measurement, reproduced and then closed
One worktree, one fresh
--cache-dirunder the scratchpad (turbo announcesRemote caching disabled, using shared worktree cache, so a sibling agent's artifacts could otherwise satisfy a leg). Cold build, wipedist/+plugin.js+plugin.d.ts+*.tsbuildinfo, replay.Before (tip
bce5971, no fix):node scripts/check-node-esm-load.mjs --no-buildon that exact cache-hit tree, exit 1:After (this branch, same fresh cache dir, cold rebuild then wipe then replay):
node scripts/check-node-esm-load.mjs --no-build, exit 0:dist/is still restored on the hit — the package configuration adds, it does not trade one for the other.Two narrowings, stated rather than hidden: the gate was run as
--no-buildon the tree the replay produced, so the reading is about this worktree's cache dir rather than the shared one the plainpnpm check:node-esm-loadwould build into; and reaching37 of 37needed@object-ui/cliand@object-ui/plugin-ai(outside the console's dependency closure) built into the same fresh cache first — before that they were refused on provenance, not graded, in both legs alike.Deviation from the dispatch's spelling — measured, not preferred
The ruling named a package-level
turbokey insideapps/console/package.json. That spelling does nothing on turbo 2.10.9. Measured withturbo run build --filter=@object-ui/console --dry=json, both shapes of the manifest key:outputs["**/*.tsbuildinfo",".next/**","build/**","dist/**"]a218494ab23eb6f3package.json→turbo.tasks.build.outputs4098312cd6437d3apackage.json→turbo.build.outputs5c19da105b7841bfapps/console/turbo.json,extends["//"][…,"plugin.d.ts","plugin.js"]68390122c3c328b0turbo accepts the manifest key silently — exit 0, no warning — and declares nothing, while still perturbing the hash (the manifest is an input), i.e. it buys a cache invalidation in exchange for nothing. So the fix takes the same route the ruling chose (package-level, one package's blast radius) in the spelling that turbo 2.x actually reads. The forbidden alternative — widening the shared task in the root
turbo.json— was not used and was not needed.Why the config restates the root globs
A package configuration overrides the key it declares; it does not append. turbo 2.10.9's own bundled
schema.json(node_modules/turbo/schema.json), onextends:Measured, a config declaring only the two additions:
dist/**gone. Keys the config does not declare are inherited intact —dependsOnstill resolves to the same 34 upstream builds,inputsandenvunchanged. Andextendsis mandatory; without it turbo refuses the file loudly (x No "extends" key found), which is the one failure mode here that cannot be silent.The audit (#7855's second deliverable)
Re-derived over all 46 workspace manifests (
packages/*,apps/*,examples/*,docs), resolvingmain,module,types,typings,browser, everyexportsleaf andbin. 13 targets across 4 packages do not resolve under./dist/:./dist/@object-ui/console(apps/console)main,types, and 3exportsconditions, all./plugin.js/./plugin.d.ts@object-ui/app-shellexports["./styles.css"]=./src/styles.css@object-ui/example-schema-catalog(private)exports["./schemas/*"]=./src/schemas/*@object-ui/test-support(private, nobuildscript)./src/The two candidates the dispatch flagged for checking both resolve under
./dist/and are covered:packages/types' subpath exports are./dist/base.js,./dist/layout.js, … andpackages/i18n's./locales/*is./dist/locales/*.js. No package with adist/entry lacks abuildscript. So@object-ui/consoleis the only instance of this defect shape, and nothing else was fixed or filed.The pin
scripts/__tests__/turbo-build-outputs-cover-published-entries.test.ts, beside the existing turbo-config guards and reusinghelpers/turbo-inputs.tsunchanged (workspacePackageDirs,globToRegExp,rel,repoRoot). One rule over every published entry of every workspace package: it is either tracked source, or a build output the task declares — stated so it holds for packages that do not exist yet.outputs— the trap that arrives with the fix, since trimming the config to "just my two files" silently stops cachingdist/while everything still builds and passes.@object-ui/consolepublishesplugin.jsfrom the package root, which turbo's buildoutputsdo not cover — a cache hit restores an incomplete package #7855 shape (a gitignored./plugin.jspublished from a package root) and requires the real predicate — not a paraphrase — to name it, then requires the same entry underdist/to fall silent, so the control cannot pass by flagging everything.One measured subtlety is baked in: Node's
exportsstar matches across/, unlike a turbo glob's*. Translating it with the turbo-glob translation flagged@object-ui/example-schema-catalog— whose 431 tracked schemas live two directories below the star — as if it published an undeclared build output. The index search uses Node's semantics; the first draft of this guard was red for exactly that reason.Ablation (fix committed first, then mutated, proven on disk, restored to the byte):
apps/console/turbo.jsondeleted (git status=D)× @object-ui/console— "publishes ./plugin.js, ./plugin.d.ts, … git does not track them""dist/**"occurrences 1 then 0)× no package configuration narrows the root outputs—drops ["dist/**",".next/**","build/**","**/*.tsbuildinfo","!**/node_modules/**"]Both legs restored with
git checkout HEAD -- apps/console/turbo.json; restored blobae6d38c…equals the HEAD blob,git diff HEADempty, tree clean. Each leg ran under atrap … EXIT INT TERMrestore.Gates (exit codes captured by redirect, never through a pipe)
Test Files 1 passed (1) / Tests 49 passed (49)scripts/__tests__/(108 files — every reader ofturbo.jsonand ofapps/console/package.json)Test Files 108 passed (108) / Tests 3294 passed (3294)turbo-build-inputs.test.ts(untouched, as required)Tests 46 passed (46)pnpm type-check:scripts--listFilesconfirms the new pin is in the program (1 hit) — not assumednode scripts/check-changeset-presence.mjs2 file(s) changed, 0 of them published source …, 0 of them a manifest whose published contract moved— no changeset owed, and the reading was taken after staging (untracked files are invisible to it)node scripts/check-changeset-no-major.mjsNo changeset declares a major bump.pnpm check:control-bytesscanned 6438 tracked text file(s)(6436 before staging)node scripts/check-governed-queue-guard.mjs --test(both paths)NOT GOVERNED — 2 path(s) checked against 5 governed surface(s); none matched.apps/consoletype-checktsc --noEmit && tsc -b tsconfig.node.json --forceapps/consolelint209 problems (0 errors, 209 warnings)— all pre-existinggrep -naPfor\x00-\x08\x0b\x0c\x0e-\x1f\x7fOrdinary changes for build-cache purposes only;
apps/console/turbo.jsonis not in the package'sfileslist, so nothing new is published.Live E2E (informational)is red on every branch today for an upstream reason (#7990 / objectstack#16186) and is not this diff's.🤖 Generated with Claude Code
https://claude.ai/code/session_01MM7kaS4dPpYHV5BsMyu4tQ
Generated by Claude Code