Skip to content

build(publish): gate npm publish on the suite, and rebuild dist at pack time - #23

Merged
andrei-hasna merged 1 commit into
mainfrom
4efcbd8a
Aug 1, 2026
Merged

build(publish): gate npm publish on the suite, and rebuild dist at pack time#23
andrei-hasna merged 1 commit into
mainfrom
4efcbd8a

Conversation

@andrei-hasna

@andrei-hasna andrei-hasna commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

Closes todos 4efcbd8a. Two lines in package.json, no source change.

npm publish shipped whatever dist/ happened to be on the publishing machine. Nothing forced a rebuild and nothing ran the tests, so a stale, partial or locally-patched dist/ would reach the registry as-is and the tarball would not be derivable from any commit. On a package whose entire purpose is redacting credentials, the security claim then rests on the publisher checking by hand.

I paid that cost twice tonight publishing 0.1.5 — typecheck and the suite run manually, on the exact published tree rather than on my branch. The next publisher would have neither the gate nor a reason to suspect one was missing.

The change

hook command hazard it closes
prepublishOnly bun run typecheck && bun run test publishing a red tree
prepack bun run build shipping a dist/ that is not a clean rebuild from source

build already opens with rm -rf dist, so prepack makes the shipped artefact a function of the commit rather than of the machine. It also covers npm pack, so a tarball someone inspects is built the same way as one that ships.

Verified in both directions

A gate that cannot block is the thing this change exists to remove, not to add — so both states were exercised.

POSITIVEdist/ deleted on purpose, then npm publish --dry-run:

  • prepublishOnly ran (typecheck and suite output present in the log)
  • prepack rebuilt dist from source
  • pack contained all 24 files including dist/index.js
  • the rc=1 there is cannot publish over the previously published versions: 0.1.5 — the registry refusing a duplicate version, not the gate

NEGATIVE — scratch copy at unpublished version 99.99.99, so the gate is the only possible failure source, with one deliberately false assertion planted:

  • npm publish --dry-run exits 1 at prepublishOnly
  • the planted failure is what stops it
  • total files appears zero times — refused before a tarball is built

Gates on this tree: typecheck rc=0, bun test rc=0, build rc=0.

Sibling survey — the task asked for this rather than fixing one package in isolation

Probed 60 of the 165 first-party packages in the bunfig excludes inventory, reading each package's published scripts from the registry rather than a local checkout:

prepublishOnly present   36     <- positive control: the probe finds the hook where it exists
prepublishOnly ABSENT    16
not published             8
no build-at-pack hook    37     <- tai's exact hazard

The 36 are what make "absent" an observation rather than a broken read.

Scope, not completeness. The survey is alphabetically bounded and stopped at 60 — every one of the 16 falls between access and entities, so this is a sample and the true count is higher. Filed separately with the names; not fixed here, because sixteen packages' publish behaviour does not belong in a tai PR.

Refs: todos 4efcbd8a


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

…ck time

`npm publish` shipped whatever `dist/` happened to be on the publishing machine.
Nothing forced a rebuild and nothing ran the tests, so a stale, partial or
locally-patched `dist/` would go to the registry as-is and the tarball would not
be derivable from any commit. On a package whose entire purpose is redacting
credentials, that means the security claim rested on the publisher checking by
hand — which is diligence, not a property of the package.

I paid that cost twice tonight publishing 0.1.5: typecheck and the suite run
manually, on the exact published tree rather than on my branch, because there was
no gate. The next publisher would have neither the gate nor a reason to suspect
one was missing.

Two hooks, because the task names two distinct hazards:

  prepublishOnly  bun run typecheck && bun run test    refuses to publish a red tree
  prepack         bun run build                        guarantees dist is a clean
                                                       rebuild from source

`build` already opens with `rm -rf dist`, so `prepack` makes the shipped artefact
a function of the commit rather than of the machine. `prepack` also covers
`npm pack`, so a tarball someone inspects is built the same way as one that ships.

VERIFIED IN BOTH DIRECTIONS, because a gate that cannot block is the thing this
change exists to remove rather than to add:

  POSITIVE — dist DELETED on purpose, then `npm publish --dry-run`: prepublishOnly
  ran (typecheck and suite output present), prepack rebuilt dist from source, and
  the pack contained all 24 files including dist/index.js. The rc=1 there is
  `cannot publish over the previously published versions: 0.1.5`, which is the
  registry refusing a duplicate version, not the gate.

  NEGATIVE — a scratch copy at an unpublished version 99.99.99, so the gate is the
  only possible failure source, with one deliberately false assertion planted:
  `npm publish --dry-run` exits 1 at `prepublishOnly`, the planted failure is what
  stops it, and `total files` appears ZERO times — it is refused BEFORE a tarball
  is built.

Gates on this tree: typecheck rc=0, bun test rc=0, build rc=0.

SIBLING SURVEY, which the task asked for rather than fixing this package in
isolation. Probed 60 of the 165 first-party packages enumerated in the bunfig
excludes inventory, reading each package's PUBLISHED scripts from the registry
rather than a local checkout:

  prepublishOnly present   36
  prepublishOnly ABSENT    16
  not published             8
  no build-at-pack hook    37

The 36 are the positive control: the same probe finds the hook where it exists, so
"absent" is an observation and not a broken read. The survey is alphabetically
bounded and stopped at 60 — every one of the 16 falls between `access` and
`entities`, so that is a SAMPLE and the true count is higher. Filed separately
with the names rather than fixed here.

Refs: todos 4efcbd8a

Agent: aemilius
@andrei-hasna
andrei-hasna merged commit 06cc7de into main Aug 1, 2026
2 checks passed
@andrei-hasna
andrei-hasna deleted the 4efcbd8a branch August 1, 2026 06:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant