fix: drop the stale pnpm lockfile, and prove the bundle rebuilds in CI (#476)

* fix: drop the stale pnpm lockfile, and prove the bundle rebuilds in CI

#472 by @Tony-ooo, two staleness reports, both verified.

The pnpm-lock.yaml was real but abandoned: last touched around #124, it
predates seven of the current dependencies (it does not mention
dsh-settings at all), so `pnpm install --frozen-lockfile` fails exactly as
reported. Nothing consumes it — CI installs with npm on every job, the docs
name no pnpm path for the repo itself, and package-lock.json is current.
The market USES pnpm inside profiles; it is not developed with pnpm. A
lockfile that nobody updates is worse than none: it breaks the one command
that trusts it and misleads about what the project supports. Removed rather
than regenerated, because regenerating it recreates the maintenance trap
that produced this issue.

The bundle half is measured differently than reported. At the v1.39.0 tag
the committed client.js was indeed built from an older dependency set. At
current HEAD a fresh `npm run build:client` reproduces the committed bundle
byte-for-byte on the machine that committed it — verified before this
change. What was actually missing is enforcement: `npm run check` already
rebuilds the bundle in CI, and nothing failed when the rebuild differed
from what was committed. Now `git diff --exit-code client/` does, on both
platforms.

That guard is also an experiment, stated as one: if the build embeds
anything environment-dependent (the CSS module hash prefixes have drifted
between machines before), this PR's own CI will say so on Linux and
Windows, and that answer is worth having either way.

* fix: hash CSS module classes from the repo-relative path

The reproducibility guard in the previous commit did its job on its first
run: check (ubuntu) rebuilt the bundle and got `.eGUBIq_root` where the
committed artifact says `.SOz1_a_root`. That named the input — lightningcss
derives `[hash]` from the `filename` it is given, and the plugin handed it
the ABSOLUTE path, so the class prefix was a fingerprint of the checkout
directory. Two builds on one machine agreed; two machines never could.

The filename is now repo-relative with posix separators — the same string
on every machine including Windows — so the hash depends on what the file
is, not where the clone lives. The committed bundle is rebuilt under the
new scheme, and the guard that caught this stays in: from here, a client PR
whose artifact does not match its source fails check on both platforms.

* fix: pin LF checkouts so the sourcemap can rebuild identically on Windows

Second finding from the reproducibility guard: with the hash input fixed,
check (ubuntu) went green and check (windows) kept failing — but only on
client.js.map. The runner checks sources out under autocrlf, the sourcemap
embeds them in sourcesContent, and CRLF sources can never reproduce the
committed LF map. The bundle itself was already clean; the map is where
checkout-time conversion leaks into a build artifact.

`git add --renormalize .` under the new attributes changes nothing — the
repository is already all-LF — so this pins what Windows checks out rather
than rewriting anything.
This commit is contained in:
fkysly
2026-09-01 21:11:03 -07:00
committed by GitHub
parent a2a21c5147
commit 9bdfd06f89
5 changed files with 348 additions and 3258 deletions
+13
View File
@@ -0,0 +1,13 @@
# Source files check out with LF everywhere. Without this, a Windows
# checkout under autocrlf gets CRLF sources, the client sourcemap embeds
# them in sourcesContent, and the committed map can never match a Windows
# rebuild — the reproducibility guard measured exactly that on its second
# run (#472). The bundle itself was already line-ending-clean; the map is
# where checkout-time conversion leaks into a build artifact.
* text=auto eol=lf
# Binary-ish assets keep their bytes.
*.png -text
*.jpg -text
*.gif -text
*.ico -text
*.woff2 -text
+7
View File
@@ -47,6 +47,13 @@ jobs:
version: 10
- run: npm install
- run: npm run check
# The committed client bundle must be reproducible from the committed
# source (#472, and the review-noise half of the same problem: a
# 320-line artifact diff on every client PR is where a bad change can
# hide). npm run check just rebuilt it; if the rebuild differs from
# what is committed, either the source and artifact are out of sync or
# the build is environment-dependent — both are things to know loudly.
- run: git diff --exit-code client/
- run: npm test
- run: node scripts/preflight.mjs
- run: node scripts/validate-registry.mjs
+320 -320
View File
File diff suppressed because one or more lines are too long
-2936
View File
File diff suppressed because it is too large Load Diff
+8 -2
View File
@@ -12,7 +12,7 @@
* exact `window.__ModuleLoader__.load({ id: "dshmarket"` prefix.
*/
import { readFile } from 'node:fs/promises'
import { basename, dirname, resolve as resolvePath } from 'node:path'
import { basename, dirname, relative, resolve as resolvePath } from 'node:path'
import { defineConfig } from 'tsdown'
import { transform } from 'lightningcss'
@@ -71,8 +71,14 @@ export default defineConfig({
// The virtual id otherwise hides the physical stylesheet from Rolldown's watch graph.
this.addWatchFile(fileId)
const source = await readFile(fileId)
// The filename feeds lightningcss's `[hash]`. Handing it the absolute
// path made the class prefix a fingerprint of the checkout location:
// the same commit built `.SOz1_a_root` on one machine and
// `.eGUBIq_root` on the CI runner (#472's second half, measured by the
// reproducibility guard this repo now runs). Repo-relative with posix
// separators is the same input everywhere, including Windows.
const { code, exports: cssExports } = transform({
filename: fileId,
filename: relative(process.cwd(), fileId).split('\\').join('/'),
code: source,
cssModules: { pattern: '[hash]_[local]' },
minify: true,