build: stop embedding source text in published source maps - #16492
Conversation
…es-content gate Set sourcesContent:false once (scripts/tsup-drop-sources-content.mjs, wired into every tsup.config.ts via tsup's esbuildOptions hook) so published .js.map files no longer embed the complete original source text. mappings are untouched -- stack-trace positions still resolve correctly. Add pnpm check:sourcemap-no-sources-content, a closure-wide gate over dist/**/*.map with a positive-control fixture, wired into ci.yml's Build Core job beside check:dual-build-cjs-loads. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Vbw3RPgdtqesx4azk9SbW8
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Vbw3RPgdtqesx4azk9SbW8
…e root tsc program The root tsup.config.ts imports the shared helper, and the root TypeScript program (scripts/** plus top-level configs) type-checks it -- an untyped .mjs import there is TS7016, which pushed the @objectstack/spec-monorepo DEBT ledger from 26 to 27. Follows the invoked-as.d.mts / workspace- enumerator.d.mts convention: a hand-written declaration, discovered by check-declaration-mirrors.mjs. Re-measured: 26, no drift. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Vbw3RPgdtqesx4azk9SbW8
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Vbw3RPgdtqesx4azk9SbW8
📓 Docs Drift Check1 anchor(s) derived from 19 changed package(s); no hand-written page names any of them. What this run could not see
Coarse fallback — 144 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin b9bd03ac7169a80aec6119e47febddf970b8b7b3 && git checkout b9bd03ac7169a80aec6119e47febddf970b8b7b3
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 4500bc5d7917324330f062424efebdadc62c6fb7 205163545494e7a6fb252a5f15f89898b0b86b6b && git checkout -B drift-repro 4500bc5d7917324330f062424efebdadc62c6fb7 && git merge --no-ff 205163545494e7a6fb252a5f15f89898b0b86b6b
node scripts/docs-audit/affected-docs.mjs --json 4500bc5d7917324330f062424efebdadc62c6fb7 |
|
Queue reading 2026-09-07T05:48Z (PM seat devx, session_01Vbw3RPgdtqesx4azk9SbW8) — still queued, regroup count 1 (was 0 at the 05:25Z probe), no
Worth stating plainly, since it looks like a failure and is not: a green group run that gets superseded is the queue re-forming around a change in the batch ahead, not a verdict on this PR. Nothing here is red, no shard has hit the wall (#16173), and the seat's one re-queue stays unspent. Next probe 06:05Z. Generated by Claude Code |
Fixes #16469
What
sourcesContentis nowfalseby construction for everytsup/esbuild build in this repo, and a new gate pins it.packages/cli(built withtsc, nottsup) needed no code change — see the "cli decision" section below.Where the setting lives
There is no monorepo-wide tsup config every package extends —
tsup.config.tsfiles are independent per-package files, most of them thin wrapperstsupreads via--config. Measured onorigin/main:tsup.config.ts(tsup --config ../../tsup.config.tsor../../../tsup.config.tsin theirbuildscript — no config file of their own).tsup.config.ts(multiple entries,external,shims, etc.) and are not eligible for the shared file.create-objectstack) never setssourcemap: trueat all — it ships no.mapfiles, so it is untouched (verified: no.mapin itsdist/before or after).So the "shared configuration level" the card asks for is real but split:
scripts/tsup-drop-sources-content.mjsis the one place the setting is decided (options.sourcesContent = false, wired through tsup'sesbuildOptionshook — the only seam tsup exposes into esbuild's realBuildOptions, since tsup's ownOptionstype has nosourcesContentkey). The root config picks it up once and every package that builds through it inherits it with zero config changes of their own. The 19 packages with bespoke configs (i.e. 20 minuscreate-objectstack) each get a one-lineesbuildOptions: dropSourcesContent(or, where anesbuildOptions-shapedshared/base object already exists, the call is folded into that one object). That is the smallest correct route — not a shared preset nobody asked for, and not 55 independently-reasoned edits.mappingsare untouched everywhere; verified a rebuilt@objectstack/specmap still resolves a real generated position to the correct original file/line/column (node:module'sSourceMap.findEntry).The
clidecision (the card's open question)The card's options were: ship
src/**infiles[](probably not), or "emit maps whosesourcesresolve" — picking whichever is consistent with the rest of the fleet.Measured:
cli's situation is not actually unique — it is what the whole fleet's maps now look like. Everytsup-built package's mapsourcesare../src/**-relative paths (../src/index.ts, etc.), andfiles: ["dist", "README.md", "CHANGELOG.md"](the same three entries oncliand on every plain package) does not shipsrc/. Before this PR that was invisible becausesourcesContentgave a debugger the file's text without ever needing to resolvesourceson disk.clinever had that mask (tsc'ssourceMapdoes not setinlineSourceshere), which is why its dead references were the ones the #15905 measurement actually saw — not because its maps are worse, but because they were the only ones already showing the fleet's real shape.So: left
cli's maps exactly astscemits them. Shippingsrc/**was rejected for the same reason it would be rejected everywhere else — it puts most of the bytes this card removes straight back (measured on@objectstack/spec:src/**/*.zod.tsis 63% of that package's unique mapsources, and it already ships infiles[]for a different, pre-existing reason — those particular entries already resolve; the fleet is not "all dead," it's mixed, andcliships no source directory at all so 100% of itssourcesare non-resolving). There is no lighter-weight way to make asourcesentry "resolve" for a package that intentionally ships onlydist/— the two real options are "ship the source" (rejected) or "don't" (whattscand everytsupconfig now do alike).cli's maps are the fleet's shape now, not an exception to it.Measured, before → after
Fresh builds, same commit, real bytes (
@objectstack/specand@objectstack/types— small):.mapbytes.map% of dist@objectstack/spec— before@objectstack/spec— after@objectstack/types— before@objectstack/types— afterThese are materially larger reductions than the card's own recollection ("spec: about half of 49 MB") — that number came from a different session's measurement and does not match a fresh build read on this commit; recorded here as the real numbers rather than reconciled against the card's.
The gate
pnpm check:sourcemap-no-sources-content(scripts/check-sourcemap-no-sources-content.mjs) sweeps every built, non-private workspace package'sdist/**/*.map(recursively —dist/browser/**included) and fails if any of them carries a non-emptysourcesContentarray.mappingsare not its business. Self-test is fixture-driven against real files on real disk (temp dir), including the positive control the acceptance list asks for: a planted map that genuinely embedssourcesContentand is asserted to go red, named by package and file. Wired intoci.yml'sBuild Corejob immediately aftercheck:dual-build-cjs-loads, for the same reason that gate lives there — it reads a realdist/and refuses (exit 3, PREREQUISITE NOT MET) rather than passing silently when there is none. Declares theROOT_DIR_WATCH_HINTS/ROOT_FILE_WATCH_HINTSdispatch-gates idiom so atsup.config.tsedit (anywhere) correctly derives this gate.Confirmed red-first: ran the gate against a fresh, unmodified build of
origin/mainbefore making any config change — 55 built packages flagged, matching the card's "55 of 57" figure closely (the small delta is which packages happened to be in the built closure for that run, not a disagreement).A transient, unrelated build race observed and ruled out
One full
pnpm buildrun in this branch's verification failed at@objectstack/client-react's DTS pass withTS2307: Cannot find module '@objectstack/client', even though@objectstack/client's own build had already completed and itsdist/index.d.tswas on disk and correctly symlinked. Neither@objectstack/clientnor@objectstack/client-react(nor their configs) are touched by this diff, andclient-reacthas notsup.config.tsof its own — it builds through the shared root config, whose only change here is theesbuildOptionshook (esbuild-only; it cannot affect the separate rollup/tsc-based DTS pass that failed). Rebuilding@objectstack/client-reactalone, immediately after, succeeded cleanly. Read as a pre-existing closure-build race (the classcheck:dts-closure's header already documents, #15042), not a regression from this change — recorded here rather than silently re-run away.Scope
Out of scope per the card and left untouched: the E3 text change itself (#15905, skills seat), and any classification of which comments are "internal."
Test plan
node scripts/check-sourcemap-no-sources-content.mjs --self-test— fixture batteries including the positive controlorigin/main: gate reports 55 built packages withsourcesContent(red, as expected)@objectstack/spec,@objectstack/cli's full dependency closure, and a fullpnpm build: gate greennode:module'sSourceMap.findEntryagainst a rebuilt map resolves a real generated position to the correct original file/line/column —mappingsunaffectednode scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack --ran <file>reconciled🤖 Generated with Claude Code
https://claude.ai/code/session_01Vbw3RPgdtqesx4azk9SbW8
Generated by Claude Code