Repository navigation
Conversation
|
Updated 11:28 AM PT - Jul 14th, 2026
❌ @robobun, your commit ccfbd59 has 2 failures in
🧪 To try this PR locally: bunx bun-pr 33115That installs a local version of the PR into your bun-33115 --bun |
There was a problem hiding this comment.
All prior feedback is addressed and I found nothing new this round, but this is a 300-file rewrite of the install-test registry infrastructure (new test/npm-registry/ library, 33 test files migrated, hundreds of fixtures restructured) — the design and the migration's fidelity to the old assertions are worth a human pass.
Extended reasoning...
Overview
This PR replaces verdaccio and two hand-rolled mock registries with test/npm-registry/, a new in-process spec-compliant npm registry built on Bun.serve({routes}). It migrates 33 test files (bun-install*.test.ts, bun-add, bun-publish, bun-update, bunx, etc.) from VerdaccioRegistry/dummy.registry.ts/simple-dummy-registry.ts to the new NpmRegistry/TestRegistry API; deletes 254 prebuilt .tgz fixtures and all 210 hand-written packument JSONs in favor of source directories the registry packs deterministically; regenerates two large snapshot files; adds a new registry-resolver-matrix.test.ts; and touches .gitattributes, .gitignore, test/bunfig.toml, and test/bun.lock. No src/ (production) code is changed.
Security risks
None material. This is test infrastructure only. The new registry binds an ephemeral local port per test, the CLI debug entrypoint defaults to 127.0.0.1, and nothing reaches outside the process. The .gitignore change un-ignores node_modules only under the fixture tree (test data, not installed deps).
Level of scrutiny
High, for breadth rather than risk. While it touches no production code, it (a) introduces a ~1k-line library that will underpin the entire install test suite going forward, with real design choices (deterministic tarball writer, packument derivation, auth/2FA/publish semantics, request interception API), and (b) re-expresses ~320 existing test assertions against a registry that now serves real abbreviated packuments where verdaccio served full ones — the PR description itself notes this is the first time bun has been tested against the document shape npmjs.org actually sends. A human should sanity-check that the migration preserved test intent (e.g. the handful of assertion changes where extracted-package.json expectations changed because the old mock served a different tarball than the version requested).
Other factors
I've reviewed this across six prior rounds; every nit (scoped routing edge cases, fixture determinism on Windows, semver/normalization spec gaps, an accidentally-altered assertion byte) was promptly fixed with a test, and all twelve inline threads are resolved. The library has its own 51-test suite, the migrated files are reported green, and CI is building the head commit. The remaining reason to defer is purely scale and design-level sign-off, not any specific concern.
|
All six merge-bar items from the request-changes review are verified and resolved by @alii. Tip is
Earlier commits in the merge-bar stack: 62a14ee, ff4facb, 411e91b, 12f4ece, f4f5344 (the six items); 8a06cf1 (quick fixes); 971e09c / 69a304c / 348cb3b / 6eee7b1 / 4617a96 / 17ed114 (residuals and the matrix-assertion settling). PR body rewritten with the honest CI numbers. All review threads resolved. Verified on Two pre-existing items @alii flagged as worth a separate look: |
There was a problem hiding this comment.
Nothing further from me — every finding I raised has been fixed with a covering test. This is a large test-infrastructure overhaul (a new ~spec-compliant registry library, 33 migrated test files, the fixture format change, and a few intentional assertion-semantics changes like the whoami 401-vs-200 split), so it's worth a human pass on the library design and the migration's coverage parity before it lands.
Extended reasoning...
Overview
This PR replaces verdaccio and two hand-rolled mock npm registries with test/npm-registry/, a new in-process spec-compliant registry library built on Bun.serve({ routes }), and migrates every install test (33 files, ~320 tests) to it. It also reworks the fixture tree: 210 hand-written packument JSONs and 254 prebuilt .tgz files are deleted in favor of source directories the registry packs deterministically; a .gitattributes -text rule and a .gitignore carve-out keep those byte-exact. A new registry-resolver-matrix.test.ts adds a {linker} × {Cache-Control} matrix the old infrastructure structurally couldn't test. 300+ files changed, ~510 KB of diff.
Security risks
None for production — no src/ code is touched. The new library implements auth (bearer/basic, the OTP web flow, per-glob access rules), but only as a test fixture bound to localhost. The .gitignore un-ignore for registry/packages/**/node_modules is scoped to test fixtures and the rationale (bundled-dependency tarballs) is sound.
Level of scrutiny
Medium-high, despite being test-only. The new library is the substrate every bun install test now runs against, so its semantics (packument shape, abbreviated-vs-full negotiation, tarball URL shape, integrity, auth, OTP, the deterministic tar writer) directly determine what those tests are actually asserting. The migration also includes a handful of deliberate assertion changes — e.g. bun pm whoami with a bad token now asserts the registry.npmjs.org 401 shape instead of verdaccio's non-standard 200-empty, with the old behavior moved to a separate registry.intercept() test — that are improvements but change what the suite covers. Those, plus the library's public API design (define(), defineFallback(), intercept(), simulateFailures(), the fixture-directory format), are the kind of decisions a maintainer should sign off on.
Other factors
Across six review rounds I raised twelve findings (scoped-route gaps for the version-manifest/dist-tags/-rev/publish endpoints, a readdirSync-order-dependent fixture guard, a platform-dependent executable-bit input to the tarball, a HeadersInit object-spread, a SemVer build-metadata prerelease misclassification, an over-broad bin dot-prefix filter, a UTF-8/Latin-1 tar-header mismatch, a stripped C1 control in a migrated assertion, and a CLI --hostname default contradicting its own docstring). All twelve were latent test-infra nits, all are fixed with a test each, and all threads are resolved. The author's CI accounting shows no failures in test/cli/install/ or test/npm-registry/ on any platform; the remaining reds are unrelated infrastructure. The library has its own 51-test suite. I'm out of substantive findings, but the scope and the new-library design warrant a human look.
|
Thanks. For whoever does the human pass, the two areas the review above calls out are already written up, so you do not have to reverse-engineer them from the diff:
All twelve review findings are fixed, each with a covering test that fails without its fix, and every thread is resolved. |
…s NpmRegistry library
7a90d7d to
c62042d
Compare
…mlink in a directory fixture
…eclared SRI, guard non-@-scope tarball routes
…rite-stable - observe.ts: each interceptor receives request.clone(). The live Request was handed over uncloned, so an interceptor reading the body left the route handler with `Body already used`, which readJsonObject's bare catch laundered into a 400 "invalid JSON body". - registry.ts: #write now primes every version's implicit time alongside modified and created. A version without an explicit time derives it from its current semver index, so publishing a lower version shifted every later one's time entry. The prime locks them in at the values the client observed before the write. Both pinned by tests that fail on a674a31.
|
Thank you for the precise write-up of what Verified Pushed (1) (2) (3) I did not change the default hostname, for two reasons:
The refuted list is noted; thank you for closing those off. |
…registry.json executable - define.ts: splitSpecTree's files and mode maps were the two remaining path-keyed objects in the tar path not yet null-prototype. binModeMap and the merge site were already converted in a674a31. - README.md: the execute-bit bullet now names _registry.json's executable map alongside pkg.bin.
…stability tests Both asserted a value reads back unchanged after a write but discarded the write's response. A rejected PUT leaves the record unmutated, which also satisfies after === before. Assert 201 and include the new version in the expected document so the assertion cannot be satisfied by an unmutated record. The sibling tests in the same describe already did.
|
CI status at The two failed jobs are unrelated to this diff:
The two remaining jobs ( The two postgres mock-server tests that went red on Windows across builds 71731/71758/71781 ( This PR's own test surface ( |
|
@robobun conflicts |
|
Merged main at Verified against a fresh debug build: |
|
CI at The four failing tests are unrelated to this diff:
Ready for review. |
bun-install-retry.test.ts: #34103 added two describe.each tests using setHandler; ported to registry.intercept, matching the rest of the file.
|
Merged main at
|
|
CI at The failing tests are not in this diff: 0 unresolved review threads. |
…6822) ### What `test/cli/install/config-version.test.ts` took ~10s on alpine 3.23 x64 in CI (build 88024). Most of that time was forking a verdaccio child process and polling it for up to 30 seconds before any test could run, with two of the three tests gated behind `test.skipIf(!registryCanServe)`, which silently reports them as passed when the poll loop gives up. ### Change All three tests cover the `configVersion` field in `bun.lock` and the linker selection it gates; neither depends on where packages come from. Replace the registry dependency with a local `file:` dependency (same pattern as `lockfile-version-2.test.ts` in the same directory), drop the verdaccio process and the 30s readiness poll entirely, and run the three independent installs under `describe.concurrent`. Assertions are tightened along the way: - The two previously skippable tests now always run. - `bun install` stdout is asserted via `normalizeBunSnapshot` inline snapshots before the exit code. - The indirect `.not.toContain("panic:")` check inherited from `runBunInstall` is gone. No tests are deleted or skipped; the lockfile inline snapshots and the `node_modules` layout checks are preserved. ### Why this is correct The `configVersion` field and the default linker decision are computed from whether the project is a workspace root and whether a lockfile already existed, independent of the dependency protocol. Running the same three scenarios with a `file:` dependency exercises the same code path in `src/install/` that writes `configVersion` and picks the linker; the only thing that changes in the lockfile snapshot is the `packages` entry shape (`["dep@file:dep", {}]` instead of the registry URL/sha line), which is not what these tests cover. Registry resolution itself is covered extensively elsewhere in `test/cli/install/`. ### Timing `bun bd test test/cli/install/config-version.test.ts`, best of three: | | before | after | | --- | --- | --- | | debug | 3.48s | 2.24s | | release | 970ms | 155ms | On alpine the verdaccio fork is considerably slower than locally (the file reports ~10s there), so the CI-side improvement should be larger than the local release ratio suggests. #33115 replaces verdaccio across the whole install suite with an in-process registry and also touches this file; this change is independent of that (the file no longer uses any registry at all) and can land on its own. <!-- robobun:evidence:begin --> --- **[stamp-90s]** gate passed · iteration 0 · 1 files touched <details><summary>passes on PR (with fix)</summary> ```console Test-only change. Debug/ASAN (expected pass): $ bun bd test 'test/cli/install/config-version.test.ts' $ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test test/cli/install/config-version.test.ts bun test v1.4.0 (784a264) test/cli/install/config-version.test.ts: (pass) configVersion > new projects use current config version [228.75ms] (pass) configVersion > should add configVersion@v0 to an existing lockfile [189.45ms] (pass) configVersion > new monorepos use isolated linker [238.33ms] 3 pass 0 fail 6 snapshots, 21 expect() calls Ran 3 tests across 1 file. [2.42s] Exit: 0 ``` </details> <details><summary>diff hotspot</summary> ``` test/cli/install/config-version.test.ts | 221 ++++++++++++++++---------------- 1 file changed, 108 insertions(+), 113 deletions(-) ``` </details> **gate history** · 1 passed · 0 rejected · iteration 0 <details><summary>evidence per changed file</summary> ``` file reads edits tests test/cli/install/config-version.test.ts 1 1 0 ``` </details> **self-review** · no surviving concerns 28 concerns were raised and did not survive verification. <!-- robobun:evidence:end -->
…en-sh#36822) ### What `test/cli/install/config-version.test.ts` took ~10s on alpine 3.23 x64 in CI (build 88024). Most of that time was forking a verdaccio child process and polling it for up to 30 seconds before any test could run, with two of the three tests gated behind `test.skipIf(!registryCanServe)`, which silently reports them as passed when the poll loop gives up. ### Change All three tests cover the `configVersion` field in `bun.lock` and the linker selection it gates; neither depends on where packages come from. Replace the registry dependency with a local `file:` dependency (same pattern as `lockfile-version-2.test.ts` in the same directory), drop the verdaccio process and the 30s readiness poll entirely, and run the three independent installs under `describe.concurrent`. Assertions are tightened along the way: - The two previously skippable tests now always run. - `bun install` stdout is asserted via `normalizeBunSnapshot` inline snapshots before the exit code. - The indirect `.not.toContain("panic:")` check inherited from `runBunInstall` is gone. No tests are deleted or skipped; the lockfile inline snapshots and the `node_modules` layout checks are preserved. ### Why this is correct The `configVersion` field and the default linker decision are computed from whether the project is a workspace root and whether a lockfile already existed, independent of the dependency protocol. Running the same three scenarios with a `file:` dependency exercises the same code path in `src/install/` that writes `configVersion` and picks the linker; the only thing that changes in the lockfile snapshot is the `packages` entry shape (`["dep@file:dep", {}]` instead of the registry URL/sha line), which is not what these tests cover. Registry resolution itself is covered extensively elsewhere in `test/cli/install/`. ### Timing `bun bd test test/cli/install/config-version.test.ts`, best of three: | | before | after | | --- | --- | --- | | debug | 3.48s | 2.24s | | release | 970ms | 155ms | On alpine the verdaccio fork is considerably slower than locally (the file reports ~10s there), so the CI-side improvement should be larger than the local release ratio suggests. oven-sh#33115 replaces verdaccio across the whole install suite with an in-process registry and also touches this file; this change is independent of that (the file no longer uses any registry at all) and can land on its own. <!-- robobun:evidence:begin --> --- **[stamp-90s]** gate passed · iteration 0 · 1 files touched <details><summary>passes on PR (with fix)</summary> ```console Test-only change. Debug/ASAN (expected pass): $ bun bd test 'test/cli/install/config-version.test.ts' $ BUN_DEBUG_QUIET_LOGS=1 bun scripts/build.ts --profile=debug --quiet test test/cli/install/config-version.test.ts bun test v1.4.0 (784a264) test/cli/install/config-version.test.ts: (pass) configVersion > new projects use current config version [228.75ms] (pass) configVersion > should add configVersion@v0 to an existing lockfile [189.45ms] (pass) configVersion > new monorepos use isolated linker [238.33ms] 3 pass 0 fail 6 snapshots, 21 expect() calls Ran 3 tests across 1 file. [2.42s] Exit: 0 ``` </details> <details><summary>diff hotspot</summary> ``` test/cli/install/config-version.test.ts | 221 ++++++++++++++++---------------- 1 file changed, 108 insertions(+), 113 deletions(-) ``` </details> **gate history** · 1 passed · 0 rejected · iteration 0 <details><summary>evidence per changed file</summary> ``` file reads edits tests test/cli/install/config-version.test.ts 1 1 0 ``` </details> **self-review** · no surviving concerns 28 concerns were raised and did not survive verification. <!-- robobun:evidence:end -->
|
@alii @Jarred-Sumner Two open PRs now have the same goal: this one and #44055. Both replace verdaccio with a registry on
The Cost to bring this PR up to date: a trial merge of main (2090 commits since the last merge) gives 31 conflicted files. The fixtures that main added since July also need conversion to source directories. Two options:
Option 1 has less risk, because each step is small enough to review. Option 2 keeps the review work that is already done here. I did not start the merge, and I did not change #44055. Tell me which option you want. |
### Problem
- The install tests fork verdaccio 6 and assert its answers where npm
differs. `/-/whoami` with an unknown token: verdaccio `200 {}`, npm `401
{}`.
- All 31 files share one `htpasswd` and one storage directory. When
verdaccio cannot listen on its random port, `start()` never settles: 6
of 155 local file runs, and main build 124221.
### Fix
- `test/packages/registry` is an npm registry on `Bun.serve`, without
dependencies. Its state is in memory, per instance.
- The 31 files import `TestRegistry` from `"registry"`.
`VerdaccioRegistry` and the verdaccio dependencies are removed, and
three packages that only verdaccio installed leave the bytecode snapshot
of `libraries.js`.
- `yarn-berry.test.ts` came to main with #40057, after the rebase. It is
migrated too (7025402), so 32 files use `TestRegistry`. Each number
for 31 files is from before that.
- Verified: `test/packages/registry/` (183 tests) and the 31 files (2518
pass, 0 fail).
### Test timings
| 31 files | main, verdaccio | this PR |
|---|---|---|
| local, wall-clock | 158 s | 83 s |
| local, CPU | 214 s | 113 s |
| CI, six lanes | 985 to 1090 s | 549 s |
Same base commit. Details: Notes.
### Background
- #33115 has the same goal. A maintainer has to choose.
- Reads follow registry.npmjs.org: 49 of 63 compared requests agree.
Writes follow the npm docs.
- Considered a class in `harness.ts`: every test file would load it.
### Downsides
- The registry is faster, so packuments arrive in another order. One
test depended on that (54 of 100 local runs failed) and orders them now.
- The registry runs on the test thread: a `spawnSync` of bun against it
never returns.
<details><summary>Notes</summary>
#### What I ran
After the rebase. The head is 9e9e394 on main c6da4a4. Linux x64,
12 vCPU. bun is the canary build e555359, which is 8 commits behind
that main commit.
| what | result |
|---|---|
| the 31 migrated files, 5 rounds, head 2d236bf | 154 of 155 file
runs passed: 2518 pass and 3 skip in a round. One run had one failing
test, which the commit after it repairs |
| the 31 files, 5 more passes and 5 runs in a row of each file, head
9e9e394 | 310 file runs, 0 fail |
| the same 31 files on main with verdaccio, 5 rounds | 2516 pass, 0
fail, 3 skip. 6 of 155 file runs did not start verdaccio |
| `test/packages/registry/` | 183 pass |
| `bundler_bytecode_portable.test.ts` | 22 pass |
| `tsc --noEmit` in `test/` | 7 errors on main, the same 7 on this
branch, none in a file of this PR |
| CI, build https://buildkite.com/bun/bun/builds/124229, head 2d236bf
| 183 of 183 jobs passed |
- The two tests that only this PR has are the difference between 2516
and 2518: the `whoami` test and the test of the publish header.
- The runs are after a clean install of `test/node_modules` from the
lockfile of each tree.
- The canary build passes the bytecode test of main on the tree of main.
So its bytecode is the bytecode of main.
- I did not run Windows or macOS after the rebase. The Windows machine
was not available. CI ran both.
- Build 124229 has one test of this PR that passed on a retry: see the
flaky tests below.
After the merge of main 110efa0 (d947d38) and the migration of
`yarn-berry.test.ts` (7025402). Linux x64. bun is `bun-linux-x64` of
main build 124948, which is 110efa0:
| what | result |
|---|---|
| `yarn-berry.test.ts` on d947d38 | does not load: `Export named
'VerdaccioRegistry' not found` |
| `yarn-berry.test.ts` on 7025402 | 58 pass, 0 fail |
| the same file, 40 runs in a row, then 5 rounds of 8 runs at the same
time | 80 runs, 0 fail |
| the same file with a registry that has no packages | 21 of 58 fail, so
the tests do read the registry |
| `bundler_bytecode_portable.test.ts` | 22 pass |
| `test/packages/registry/` | 183 pass |
- None of the inputs of the bytecode snapshot changed between c6da4a4
and 110efa0, so its values stay.
- The other 31 files were not run again after the merge. CI runs them.
Before the rebase, on the head a4367e5 (then 3b5d5ee):
- CI build https://buildkite.com/bun/bun/builds/121299 passed 181 of 181
jobs.
- Windows x64, canary a4f1429: `test/packages/registry/` had 183 pass.
`bun-publish`, `config-precedence`, `pnpm-lock-v9` and
`bun-update-lockfile-sync` had 258 pass, 0 fail, 1 skip, three commits
earlier.
- A local debug build with ASAN: `test/packages/registry/`, `hoist`,
`npmrc`, `config-precedence`, `bun-publish` and `bun-lockb` had 319
pass, 0 fail, when the package had 165 tests.
- With the leftovers of verdaccio in the fixture directory, the 31 files
had 2499 pass and 1 fail. The failure was `npm manifest cache entries
are only reused for the package name they were saved for`, which is
flaky on main too.
#### The rebase
- The base moved from a4f1429 to c6da4a4, 88 commits. The merge
commit is gone. The 12 commits were replayed, so each has a new id.
- 7 files had a conflict. In 5 of them #44614 changed a line that this
PR deletes or renames. `test/package.json` is the file of main without
the two verdaccio lines.
- `test/bun.lock` is the lockfile of main after `bun install
--lockfile-only` without verdaccio. Its diff has the same 578 lines as
before the rebase.
- After the replay, 42 of the 59 files had the same bytes as before the
rebase. For the other 17, the patch of this PR had the same lines, apart
from lines of main that it deletes and the snapshot.
- The snapshot of `libraries.js` has new values, because main has a new
WebKit: see the bytecode snapshot below.
- Two commits are new. 2d236bf: main changed bun-types and
`test/tsconfig.json` (#44614). With them, four lines of the package had
a type error. `request.bytes()` and `request.clone()` have the types of
undici there, and `BodyInit` is `Bun.BodyInit`.
- 9e9e394: see "A test that depended on the order of two packuments".
#### This PR and #33115
#33115 replaces verdaccio too. I did not find it before I opened this
PR: my search for open pull requests stopped at 20 results. Both PRs
change the same test files, so only one can land as it is. The author of
#33115 compared them in
#33115 (comment).
| | #33115 | this PR |
|---|---|---|
| Files | 1083 | 59 |
| State | conflicts with main, four review passes | applies to main |
| Fixtures | converted to source directories | as they are on main |
| The two mock registries (`dummy.registry.ts`,
`simple-dummy-registry.ts`) | replaced | not touched |
A maintainer has to choose. If this PR lands, #33115 can be closed, or
reduced to the parts that only it has.
**Rulings of the review of #33115 that this PR follows**
- A publish is refused with 415 unless the Content-Type is
`application/json`, character for character. `bun-publish.test.ts`
asserts the header that bun sends. The two comments in
`src/runtime/cli/publish_command.rs` stay as they are.
- An error that a handler throws fails the test: `stop()` throws it.
- The bulk advisory endpoint reads a gzip body, and a test sends one.
- `createTestDir()` does not remove users or tokens.
- `hasInstallScript` is true for a script that is not empty.
- `time.modified` moves on each write.
- A field of an advisory that is passed as undefined keeps its default.
The test compares with literal values.
- Nothing here says that bun reads `Cache-Control`. The registry sends
the header because npm does.
**What only #33115 has**
- The replacement of `dummy.registry.ts` and `simple-dummy-registry.ts`.
18 test files import them.
- `registry.define()`: a package that a test declares in code, with a
tarball writer and its golden files.
- The conversion of the fixtures to source directories.
- `registry-resolver-matrix.test.ts`.
Each of these can go on top of this registry as a PR of its own. So can
the three servers in `bun-audit.test.ts`, `config-precedence.test.ts`
and `bun-install-lifecycle-scripts.test.ts` that forward to the registry
to see or to change a request: `intercept` and `recordRequests` do that
now.
#### How each behaviour was established
**Compared with registry.npmjs.org, GET and HEAD only**
I sent 63 requests to registry.npmjs.org and to this registry with
copies of `is-number` and `@types/is-number`, and compared the status,
nine headers and the body. 49 agree. The requests cover:
- The packument in both forms, `HEAD`, `Range`, `?write=true`, and a
scoped name as `@s%2fn`, `@s/n` and `%40s%2fn`.
- A version by number and by tag, and one that is not there: 404 with
the JSON string `"version not found: 9.9.9"`.
- A tarball: `GET`, `HEAD`, `Range` with 206 and 416, `If-None-Match`,
and the 404 for the scope in the file name.
- `dist-tags`, `collaborators`, `visibility`, `ping`, `whoami`, the
account endpoints, `/-/v1/done`.
- The same endpoints with a token that npm does not know.
Separate probes gave:
- The abbreviated version is an allow list without `libc`,
`description`, `scripts` and `time`. `hasInstallScript` is there only
when true. The census covered 5763 versions of 9 packages.
- `Accept`: the registry looks for the media type anywhere in the
header, in exact case. It does not weigh q.
- The entity tag is the hex md5 of the body. It is weak on an encoded
answer. `If-Modified-Since` has no effect.
- Encoding: brotli when accepted, else gzip. Bodies of 21 and 26 bytes
came back as they are, a body of 63 bytes came back encoded. The
registry here starts at 48 bytes.
- `Last-Modified` is `modified` rounded up to the second. I checked one
packument.
- `You must be logged in to publish packages.` is the text of npm for a
token that it does not know, on `collaborators`.
**The 14 requests that differ**
| request | npm | this registry |
|---|---|---|
| a version, and search (4 requests) | the headers change between a
first and a later request: `public`, `last-modified` and `accept-ranges`
come and go | always `cache-control: max-age=300` |
| a packument or tarball that is not there (3) | `cache-control: public,
max-age=300` for an unscoped name, nothing for a scoped one | no
`cache-control` |
| `collaborators` | names in another order | the order of `maintainers`
|
| `/-/npm/v1/keys` | the keys of npm | `{"keys":[]}`, it does not sign |
| `/-/nonexistent-endpoint`, `/-/v1/login/cli/<id>` | 405 with `allow` |
404 `ResourceNotFound` |
| `/<name>/-rev/<rev>` with GET | `allow: PUT` | `allow: PUT, DELETE` |
| `?write=true` with the abbreviated `Accept` | `content-type:
text/plain` | `application/json` |
| `/login/cli/<id>` | 404, the page is on the website | stands in for
that page |
**From the npm/registry docs and the npm CLI source, not sent to the
live registry**
- Login with `PUT /-/user/org.couchdb.user:<name>`: 201 and `{ok, id,
rev, token}`, or 401 and `{"ok":false}`.
- Tokens and profile under `/-/npm/v1/`.
- The publish document, `www-authenticate: OTP`, the web flow with
`authUrl` and `doneUrl`, 202 with `retry-after`.
- The one-time password message. bun compares against this exact text.
- The request sequences of deprecate, unpublish, owner and dist-tag.
- The bulk advisory request and answer.
**My wording, because I do not know the text of the registry**
- Each `400 Bad Request: ...` for a publish body that fails a check, the
415, and the 403 for a read-only token.
- `409 Document update conflict.` is the text of CouchDB.
- Publish answers `200 {"ok":true,"id","rev"}` and a dist-tag write
answers `{"ok":"dist-tags updated"}`. I recall both from npm output. I
did not verify them.
- A restricted package answers 404 to a client that cannot read it. I
recall this from npm. I could not probe it, because I know no private
package.
- `You cannot publish over the previously published versions`, `You do
not have permission to publish` and `cannot be republished until 24
hours have passed` are the texts of npm as I recall them. I did not
probe them.
- A publish with a token that the registry does not know answers 401
without `www-authenticate`. npm sends `www-authenticate: Basic, Bearer`
on `collaborators`. I do not know what it sends on a publish.
#### Where the registry is more permissive than npm
- Every user can publish into every scope. npm needs a user or an org
with the name of the scope.
- A restricted package needs no paid plan. npm answers 402.
- A package of the storage directory has no maintainers, so every user
can add a version to it.
Not implemented: signatures, provenance, OIDC, orgs and teams.
#### What the tests saw change
Two tests of the 31 files depended on verdaccio. `whoami > invalid
token` asserted its answer, `200 {}`. It asserts `401 Unauthorized` now,
and a new test keeps the `200 {}` case with a mock server. `ranged peer
dependency resolution is stable across installs from bun.lock` depended
on its speed: see the section on the order of two packuments. Every
other test passes unchanged.
`bun-publish.test.ts`:
- Each `rm` of a package directory is a `registry.packages.delete()` of
that name. The next section has the reason.
- `--ignore-scripts` published `publish-pkg-4` and removed
`publish-pkg-5`. It passed only because the test before it removed
`publish-pkg-4`. It removes the right package now.
- `republishing normally fails` matched `/403|409|already exists|already
present|cannot publish/`. It asserts the status and the message.
- A new test asserts that the publish request has `content-type:
application/json`.
`harness.ts` does not export `VerdaccioRegistry`. `REVIEW.md` says to
delete code in the PR that makes it dead. The eight files that had a
variable `verdaccio` keep that name, so their diff stays small.
A bug of the registry that the review found: `bun publish` of a version
with build metadata got a 400. bun sends the key `1.0.0` and a manifest
with `1.0.0+build.5`. The registry accepts that now, and `cli.test.ts`
publishes one with bun.
#### A checkout that ran the tests on verdaccio
Verdaccio wrote each package that a test published into
`test/cli/install/registry/packages`. That is the fixture directory too,
and ignore rules hide what verdaccio wrote. One run of the 31 files on
main left 18 packages and `.verdaccio-db.json` there.
The new registry reads that directory. So it served those packages, and
a publish of the same version got `403 You cannot publish over the
previously published versions`. With the leftovers of that run in place,
15 of the 47 tests of `bun-publish.test.ts` failed.
- `bun-publish.test.ts` calls `registry.packages.delete()` in the 26
places where main removes a package directory. With the same leftovers
in place the file passes.
- The ignore rules stay as they are on main, so the leftovers stay out
of `git status`.
- The builds of this PR did not show this: a checkout of CI has no
leftovers.
#### Layout
The package has the layout of `test/packages/s3-server`, which came to
main while this PR was open: the sources in `src/`, the tests in
`test/`, and the helpers of the tests in `test/helpers.ts`. `index.ts`
does not import the harness. `test-registry.ts` has the class for the
tests of bun, and the alias `"registry"` points to it.
#### One port, one registry
`Bun.serve` sets `SO_REUSEPORT` when it gets `development: false`
(`src/runtime/server/ServerConfig.rs:716`). The registry passed that
option, so a second registry on the same explicit port started with no
error. It passes `reusePort: false` now, and the second one fails with
`EADDRINUSE`. The new test fails without the option.
The tests listen on port 0. On Linux, 20000 listeners on port 0 with
`SO_REUSEPORT` got 20000 distinct ports, so I have no sign that a test
shared a port. I did not check that on macOS or Windows.
#### The bytecode snapshot
`bundler_bytecode_portable.test.ts` pins the bytecode of a bundle of 64
libraries from `test/node_modules`. The bundle holds each package that a
library can load, also one that it loads in a `try` block and does not
declare.
`test/` uses the isolated linker. A package that is not declared is
still found through `node_modules/.bun/node_modules`, which has one
version of each installed package. Two libraries of the bundle load a
package that way:
| library | loads | with verdaccio | without |
|---|---|---|---|
| `mongodb` (`lib/deps.js`) | `aws4` | `aws4@1.13.0`, installed only for
`@cypress/request`, a dependency of verdaccio | not found, so not in the
bundle |
| `follow-redirects` (`debug.js`) | `debug`, an optional peer |
`debug@4.3.4` with `ms@2.1.2`. Only `@verdaccio/config` and
`@verdaccio/local-storage-legacy` resolve to that version |
`debug@4.4.0`, which `superagent` and others already bring |
So 7 modules of 3 packages leave the bundle: `aws4@1.13.0`,
`debug@4.3.4` and `ms@2.1.2`. Nothing is added. To check this, the
lockfile of c6da4a4 and the lockfile of this PR were installed in two
directories, and `libraries.js` was built in each with the command of
the test. The sha256 of the two `.js` outputs are the old and the new
value of the snapshot. A metafile names the two importers above.
The bytecode is 32632 bytes smaller: 21179744, where main has 21212376.
Before the rebase the two numbers were 21174272 and 21206912, with the
WebKit of that time. The new values are the same on every platform,
which is the case where the comment at the top of that file says to
update the snapshot.
#### Test timings
"Before" is main at c6da4a4, where the 31 files fork verdaccio.
"After" is this PR on the same commit.
**Local**
- Linux x64 VM, 12 vCPU (Intel Xeon, 2.90 GHz), 16 GB. bun is the canary
build e555359 for both sides.
- Each file ran alone: `bun test --timeout=90000 <file>`. The two trees
took turns on each file. The order of the two changed with each file and
each round. 5 rounds, 310 file runs.
- Wall-clock is from the start of `bun test` to its exit. CPU is the
user and system time of all processes of the VM in that time, from the
cgroup. It has the verdaccio process, which the test does not wait for.
- The table has the median of the runs that exited with 0, and the
lowest and the highest in brackets. Seconds.
- The after side is the commit 2d236bf. The commit after it changes
one test of `isolated-install.test.ts` and adds no wait for time.
- The VM lost CPU time to other VMs in each round (steal time), so the
wall-clock has noise. The rounds agree: before is 1.7 to 2.1 times after
in each one.
| file | tests | before, wall | after, wall | ratio | before, CPU |
after, CPU |
|---|---|---|---|---|---|---|
| `bun-add-catalog.test.ts` | 149 | 4.81 (4.34 to 6.17) | 2.15 (1.88 to
3.47) | 2.2 | 6.94 | 3.16 |
| `bun-add-filter.test.ts` | 123 | 4.67 (3.81 to 7.01) | 1.91 (1.84 to
2.19) | 2.4 | 6.75 | 3.10 |
| `bun-audit.test.ts` | 182 | 6.79 (6.43 to 9.03) | 5.21 (4.62 to 5.39)
| 1.3 | 10.43 | 6.83 |
| `bun-dedupe.test.ts` | 82 | 4.31 (4.10 to 4.74) | 2.19 (1.99 to 2.37)
| 2.0 | 6.96 | 3.61 |
| `bun-install-lifecycle-scripts.test.ts` | 128 | 10.40 (8.70 to 14.14)
| 8.51 (7.50 to 9.92) | 1.2 | 21.07 | 16.04 |
| `bun-install-native-binlink.test.ts` | 16 | 3.93 (3.71 to 6.41) | 1.79
(1.40 to 5.45) | 2.2 | 5.40 | 3.04 |
| `bun-install-patch.test.ts` | 31 | 3.93 (3.77 to 4.66) | 2.52 (2.04 to
2.88) | 1.6 | 4.15 | 2.09 |
| `bun-install-registry.test.ts` | 282 | 18.79 (16.54 to 26.41) | 15.38
(10.74 to 16.10) | 1.2 | 18.89 | 12.45 |
| `bun-lock.test.ts` | 40 | 3.98 (3.40 to 4.51) | 1.33 (1.27 to 1.59) |
3.0 | 4.48 | 1.61 |
| `bun-lockb.test.ts` | 8 | 2.29 (1.90 to 2.39) | 0.53 (0.43 to 0.64) |
4.3 | 2.45 | 0.52 |
| `bun-patch.test.ts` | 37 | 4.16 (4.05 to 6.92) | 3.10 (2.44 to 3.78) |
1.3 | 5.02 | 3.18 |
| `bun-pm-licenses.test.ts` | 80 | 5.16 (3.53 to 5.78) | 2.19 (1.53 to
2.36) | 2.4 | 6.61 | 2.91 |
| `bun-prune.test.ts` | 121 | 5.40 (5.14 to 5.63) | 2.80 (2.62 to 3.46)
| 1.9 | 8.18 | 4.38 |
| `bun-publish.test.ts` | 47 | 5.40 (5.03 to 6.02) | 2.30 (2.00 to 3.30)
| 2.3 | 5.42 | 2.15 |
| `bun-update-lockfile-sync.test.ts` | 77 | 4.22 (3.39 to 4.68) | 1.78
(1.52 to 2.15) | 2.4 | 5.95 | 2.78 |
| `bun-update-transitive.test.ts` | 224 | 8.46 (7.73 to 10.16) | 5.06
(4.44 to 8.29) | 1.7 | 13.50 | 8.56 |
| `bun-update.test.ts` | 159 | 8.28 (7.66 to 8.69) | 4.59 (4.27 to 5.02)
| 1.8 | 11.96 | 6.73 |
| `bun-workspaces.test.ts` | 82 | 3.56 (3.22 to 3.75) | 1.59 (1.48 to
1.77) | 2.2 | 4.85 | 2.42 |
| `catalogs.test.ts` | 89 | 5.90 (4.93 to 6.02) | 2.04 (2.00 to 2.20) |
2.9 | 7.00 | 3.28 |
| `config-precedence.test.ts` | 51 | 3.65 (2.81 to 4.11) | 0.90 (0.74 to
0.97) | 4.1 | 4.46 | 1.12 |
| `frozen-lockfile-missing-workspace.test.ts` | 1 | 1.79 (1.64 to 2.12)
| 0.15 (0.14 to 0.18) | 11.9 | 2.02 | 0.18 |
| `frozen-lockfile-pruned.test.ts` | 101 | 4.76 (4.05 to 5.96) | 1.83
(1.71 to 1.95) | 2.6 | 7.05 | 3.23 |
| `hoist.test.ts` | 1 | 1.75 (1.71 to 1.91) | 0.17 (0.12 to 0.21) | 10.3
| 2.09 | 0.17 |
| `isolated-install.test.ts` | 85 | 6.24 (5.94 to 7.74) | 3.59 (3.15 to
3.95) | 1.7 | 7.67 | 4.32 |
| `isolated-relink.test.ts` | 6 | 2.29 (1.89 to 2.96) | 0.35 (0.30 to
0.47) | 6.5 | 2.72 | 0.48 |
| `migration/pnpm-lock-v9.test.ts` | 84 | 3.86 (3.64 to 4.85) | 1.54
(1.34 to 1.83) | 2.5 | 4.57 | 1.89 |
| `migration/pnpm-migration.test.ts` | 3 | 1.89 (1.72 to 2.16) | 0.18
(0.16 to 0.22) | 10.5 | 2.29 | 0.23 |
| `nested-overrides.test.ts` | 144 | 8.95 (8.20 to 10.21) | 5.92 (4.50
to 8.86) | 1.5 | 15.03 | 9.69 |
| `npmrc.test.ts` | 47 | 2.52 (2.32 to 2.68) | 0.58 (0.55 to 0.61) | 4.4
| 2.91 | 0.65 |
| `public-hoist-pattern.test.ts` | 14 | 2.53 (2.44 to 2.62) | 0.46 (0.44
to 0.48) | 5.5 | 3.01 | 0.60 |
| `update_interactive_formatting.test.ts` | 24 | 2.99 (2.79 to 3.28) |
0.70 (0.55 to 0.99) | 4.3 | 3.93 | 1.11 |
| **sum** | **2518** | **157.7** | **83.3** | **1.9** | **213.8** |
**112.5** |
Each round, the sum over the files where both sides exited with 0:
| round | files | before, wall | after, wall | before, CPU | after, CPU
|
|---|---|---|---|---|---|
| 1 | 29 | 146.4 | 71.2 | 184.7 | 95.8 |
| 2 | 30 | 140.9 | 68.0 | 188.4 | 98.6 |
| 3 | 31 | 165.4 | 84.1 | 217.1 | 115.2 |
| 4 | 29 | 158.3 | 80.0 | 202.1 | 106.5 |
| 5 | 29 | 150.3 | 88.1 | 209.3 | 114.6 |
Runs that are not in the tables:
- 6 of the 155 runs with verdaccio did not start it. The log has
`Verdaccio exited with code 2 and signal null`. Code 2 is the exit of
verdaccio when it cannot listen (`build/lib/bootstrap.js`). `start()`
waits for a message that does not come. Five runs ended at the hook
timeout of 90 s. `bun-install-registry.test.ts` waits at module scope,
where no timeout applies: I stopped it after 678 s.
- I started verdaccio on a port that was in use, to check: the same
line, and `start()` did not settle.
- 1 of the 155 runs of this PR had one failing test: see the next
section.
**CI**
Build 124221 is main at c6da4a4. Build 124229 is this PR on the same
commit. Builds 124209 and 124201 are main, one and four commits earlier,
with the same 31 files. Each number is the sum over the 31 files of the
duration that the runner logs for a file. I read the logs with the
parser of `scripts/update-test-durations.mjs`. Seconds.
| lane | main 124221 | main 124209 | main 124201 | this PR 124229 |
|---|---|---|---|---|
| debian 13 x64 | 222.6 | 131.4 | 126.7 | 51.5 |
| ubuntu 25.04 x64 | 126.1 | 126.0 | 126.4 | 39.1 |
| alpine 3.23 x64 | 203.8 | 172.8 | 156.9 | 119.8 |
| debian 13 aarch64 | 141.7 | 145.6 | 141.2 | 45.6 |
| Windows 2019 x64 | 149.6 | 166.2 | 171.6 | 109.9 |
| Windows 11 aarch64 | 245.8 | 243.4 | 264.1 | 182.8 |
| **sum of the six lanes above** | **1089.6** | **985.4** | **986.9** |
**548.7** |
| debian 13 x64 ASAN | no tests | no tests | no tests | 340.2 |
| macOS aarch64 | no tests | no tests | no tests | 53.1 |
| macOS x64 | no tests | no tests | no tests | 67.1 |
- The 222.6 s of build 124221 have one run of 90 s:
`isolated-install.test.ts` on shard 8. The log has `Verdaccio exited
with code 2 and signal null` and the hook timeout. The second attempt
passed.
- The main builds have no test jobs for ASAN and macOS.
- A build is one sample. Apart from the 90 s run, the three main builds
differ by up to 47 s on a lane (alpine).
- Not each file is faster in CI. On each lane, 1 to 6 of the 31 files
took longer than in each of the three main builds. The largest
differences are on alpine: `bun-install-registry.test.ts` took 37.3 s
against 27.9 to 29.2 s, and `bun-update.test.ts` took 7.2 s against 2.5
to 2.7 s. That is one sample, and I do not know the cause. Locally each
file is faster.
- The four test files of `test/packages/registry/` take 1.5 to 4.8 s on
a lane.
Import cost, CPU, the lowest of the runs. Measured before the rebase, on
a build of 367d939:
| | release | debug |
|---|---|---|
| `harness.ts` | 29.5 ms | 1378 ms |
| the registry | 21.5 ms | 1162 ms |
| `node:zlib` alone | 11.5 ms | 615 ms |
#### A test that depended on the order of two packuments
`isolated-install.test.ts`, `ranged peer dependency resolution is stable
across installs from bun.lock`. The root depends on
`normal-dep-and-dev-dep` (which has `no-deps@1.0.1`), on
`two-range-deps` (`no-deps@^1.0.0`) and on `peer-deps-fixed` (peer
`no-deps@^1.0.0`). The test needs 1.0.1 and 1.1.0 in the graph.
- bun resolves `^1.0.0` to 1.1.0 when the packument of `two-range-deps`
is there first. When the packument of `normal-dep-and-dev-dep` is there
first, 1.0.1 is in the graph and `^1.0.0` resolves to it.
- I delayed one packument by 40 ms, with 12 installs for each case. A
slow `two-range-deps` gave one version 12 times. A slow
`normal-dep-and-dev-dep` gave both versions 12 times. No delay gave both
7 times and one 5 times.
- On main with verdaccio the test passed 50 of 50 runs. With the new
registry it failed 54 of 100 runs alone, and 10 of 20 runs of the file.
It fails with the older bun build 367d939 too (15 of 40), so the
rebase did not bring it.
- CI did not show it, before and after the rebase. CI sets
`BUN_INSTALL_CACHE_DIR`, so the tests of a file share one manifest
cache. With that variable the file passed 10 of 10 local runs. Without
it each test has its own cache.
- My local runs before the rebase did not show it. They were on a host
with a load average of 400 to 900.
- The test has its own registry now. Its `intercept` answers for
`normal-dep-and-dev-dep` after bun asked for `no-deps`, which bun does
when it has the packument of `two-range-deps`. There is no wait for
time. 100 of 100 runs alone and 20 of 20 runs of the file pass.
- After that change: 5 passes over the 31 files, and 5 runs in a row of
each file. 310 file runs, 0 failures.
Other tests can depend on the order in the same way. `hoisting > peers >
it should hoist 1.0.1 when peer *` does: see the flaky tests below. The
cause is in `bun install`, not in the registry: one `package.json` gives
two different trees.
#### Other things I found
- Before the rebase, `test/bun.lock` on main was stale: `bun install
--lockfile-only` on an unchanged main dropped five nested `react`
entries, and I kept them by hand. The lockfile of main is current now.
The lockfile diff here is what bun writes: verdaccio and its
dependencies go, and `debug` hoists to 4.4.0.
- `manifest conditional requests > a changed etag returns 200 and the
new etag is cached` failed once on unchanged main and passed on the next
run. It uses its own mock server, not the registry. It is in
`test/flaky-tests.txt`.
- On my local debug build, `Bun.serve` adds `content-type:
application/octet-stream` to an answer that has no body: a 304, and a
401. Two release builds, one of 367d939 and one of 36cd151, do not.
I did not find the cause and I saw it on one local build only. The tests
here assert the exact headers on the `Response` of the registry, and on
the wire only the headers that the registry sets.
- `Bun.serve` sets `SO_REUSEPORT` when it gets `development: false`. The
type of `reusePort` says that the default is false.
- `bun install` gives another tree for the same `package.json` when the
packuments arrive in another order: see the section on the order of two
packuments.
- `bun run` and `bun test` use a whole core, and do not end, while a
top-level await waits for a promise that nothing settles. A file with
`await new Promise(() => {})` used 9.9 s of CPU in 10 s. node prints
`Warning: Detected unsettled top-level await` and exits with 13. The
loop is `wait_for_promise` in `src/jsc/event_loop.rs`. This is how
`bun-install-registry.test.ts` on main hangs when verdaccio does not
start.
#### Flaky tests in files that this PR edits
Two tests of migrated files passed on a retry in the earlier builds of
this PR. Main has both without this change. In build 121299, the last
one before the rebase, no file of this PR was in the flaky annotations.
Files outside this PR were.
- `bun-install-registry.test.ts`, `hoisting > peers > it should hoist
1.0.1 when peer *`, on Windows 11 aarch64, with `a-dep@1.0.9`. The same
test fails the same way on the same lane in the main builds
https://buildkite.com/bun/bun/builds/120426 and
https://buildkite.com/bun/bun/builds/120659. I could read 11 main
builds. After the rebase it passed on a retry on ubuntu 25.04 aarch64 in
build 124229, again with `a-dep@1.0.9`. The version that it gets depends
on the order of the packuments.
- `bun-lock.test.ts`, `peer no published version satisfies > declared by
a registry package`. It runs against its own mock server.
`test/flaky-tests.txt` lists it at 34 of 80 builds.
I cannot say that the rate of the first one is unchanged. On my machine
it failed 1 time in 150 runs with this registry, and 0 times in 29 runs
with verdaccio.
A third test failed in my runs, and not in the builds of this PR:
- `bun-install-registry.test.ts`, `npm manifest cache entries are only
reused for the package name they were saved for`: `failed to open
manifest file ... ENOENT`. It failed in 1 of 8 runs of the file. Alone,
it failed in 2 of 40 runs with this registry, and in 5 of 40 runs on
main with verdaccio. `bun install` removes a manifest file that is not
valid and saves the new one in a task of the thread pool
(`Serializer::save_async` in `src/install/npm.rs`). I found nothing that
waits for that task before the exit. The test reads the file after the
exit. `test/flaky-tests.txt` has `failed to open ...` for this file.
#### Reviews of the diff
My review of the diff raised 9 concerns. 8 were addressed in full: the
header of a publish, an error in a handler, the export of the old name,
`pack()`, the claims about npm, #33115, the flaky tests, and the time of
the tests in `cli.test.ts`. One is addressed in part: the review named
six follow-ups, and these Notes list the three that I can describe.
The reviews on GitHub raised 12 more. All are addressed:
- The URL of a registry names the host that it listens on. It was
`localhost` for each host.
- A checkout with the leftovers of verdaccio failed the publish tests:
see the section above.
- The ignore rules for the leftovers stay.
- `intercept` gets a copy of the request, so it can read the body of a
request that the registry answers.
- The web login test fails with the output of bun when bun exits before
it asks for the session. Before, it waited for the timeout.
- Each test of the package disposes its registry when an assertion
fails.
- `VerdaccioRegistry` is removed. My review asked to keep the export for
open PRs. The review on GitHub asked to remove it, and `REVIEW.md` says
the same.
- A request that the registry refused left a part of its change in the
package: a `PUT` to `/<name>/-rev/<rev>` with a tag that is not valid,
and a deprecation with a message that is not a string. The checks come
before the first change now.
- In a search, a qualifier that matched took away the miss of a word
before it. A package is found when each word matches.
- A storage that is not a directory gave a registry that answered 404
for every package. The constructor throws now, and `cli.ts` prints the
message. A read that failed does not stay as the state of the package.
- `cli.ts` ended with a stack trace for a `--user` that the registry
refuses, for an option that it does not know, and for a port that is in
use. One handler prints the message and the usage for each refusal now.
- `cli.ts` took an empty `--port` as port 0, and it took `0x50` and
`1e3`. The value must be digits now.
One more finding got no change: a scope directory that another process
replaces with a file while the registry reads it. Nothing writes into
the storage while a registry runs, and a check before the read has the
same window. The reviewer withdrew the finding.
#### Related open PRs
These change `VerdaccioRegistry` in `test/harness.ts`. This PR removes
that class, so git reports a conflict, and the problem each one
addresses is not there with the new registry:
- #36352: `stop()` does not end the process. The new `stop()` closes the
server.
- #38891: the random port and a verdaccio that exits early. The new
registry listens on port 0 in the process of the test.
- #40219: `createTestDir()` removes the users. The new registry keeps
them.
- #40766: run verdaccio under node in CI.
These edit the line of `hoist.test.ts` that imports `VerdaccioRegistry`.
Git reports a conflict on that line. The new line imports `TestRegistry`
from `"registry"`:
- #43878
- #38976
I looked at the 500 open PRs that changed last. 72 of them change a file
under `test/cli` or `test/regression`, the harness, or a test with
`install`, `publish` or `registry` in its path. I read the diff of each.
No PR other than the ones above adds or changes a line with
`VerdaccioRegistry`.
</details>
<!-- robobun:evidence:begin -->
---
**no test proof** · iteration 0 · platform-specific test(s) that do not
run on this machine, deferring to CI, which covers all platforms:
test/cli/update_interactive_formatting.test.ts,
test/cli/install/migration/migrate.test.ts,
test/cli/install/isolated-relink.test.ts,
test/cli/install/isolated-install.test.ts,
test/cli/install/frozen-lockfile-pruned.test.ts,
test/cli/install/bun-workspaces.test.ts,
test/cli/install/bun-publish.test.ts,
test/cli/install/bun-prune.test.ts, test/cli/install/bun-patch.test.ts,
test/cli/install/bun-lockb.test.ts, test/cli/install/bun-lock.test.ts,
test/cli/install/bun-install-registry.test.ts,
test/cli/install/bun-install-patch.test.ts,
test/cli/install/bun-install-native-binlink.test.ts,
test/cli/install/bun-install-lifecycle-scripts.test.ts,
test/cli/install/bun-add-filter.test.ts,
test/bundler/bundler_bytecode_portable.test.ts
<!-- robobun:evidence:end -->
---------
Co-authored-by: Dylan Conway <dylan.conway567@gmail.com>
|
Closing as superseded. #44055 landed the Four parts exist only in this PR. All four are still missing on main (checked at 5866b5f13d). Each one can be a PR of its own on top of
The code for these parts stays available from this PR (head ccfbd59). |
Replaces verdaccio and the two hand-rolled mock registries the install tests run against with
test/npm-registry/, an in-process npm registry built onBun.serve({ routes }).The case for this is structural, not a stopwatch win. Verdaccio is a real registry forked as a child process: a boot per test file, a shared mutable on-disk package store that forces every test in a file to run serially and every publish test to
rm -rfafter itself, and enough startup flakiness thatconfig-version.test.tsspent up to 30 s polling whether it had come up and silently skipped two of its three tests when it hadn't. An in-process registry starts in a socket bind, never touches disk, and every test can have its own, so tests in a file can run concurrently and nothing has state to clean up. Measured on CI (debian 13 x64 release,expected-durations.jsonvs build 70555), the 11 ex-verdaccio files I captured run in 8.9 s instead of 19.2 s; across the wholetest/cli/install/directory the sum goes from 147.8 s to 141.4 s because the long-pole files were never verdaccio-bound.The library
test/npm-registry/README.mdis the tour. The short version: describe a package'spackage.jsonin code and the registry packs the tarball, hashes it, derives the packument, and answers real content negotiation.What it implements, because the tests needed it:
Acceptnegotiation, including the derivedhasInstallScript.npm publishapplies (bincanonicalization, the implicitnode-gyp rebuildinstall script for abinding.gyp)..tgzwriter whose tar payload is byte-for-bytenpm pack's (node-tarportable: trueheader encoding, npm-packlist's entry sort, gzip OS byte normalized to 255).tar.test.tsassertsgunzip(buildTarball(src))equalsgunzip(npm pack src)against five committed golden tarballs (plain, bin at 0755,binding.gyp, scoped, and a 117-byte path that crosses ustar'sname/prefixsplit), and pins a known-answer sha512 so a zlib or compression-default change trips there instead of as an opaque integrity diff elsewhere.@scope/name/-/name-1.0.0.tgz), and scoped packuments at both/@scope%2fnameand/@scope/name.ETag/If-None-Match/ 304, plusVary: Accept.Cache-Controlis sent when configured, but bun does not read that header; its warm-manifest gate is an on-disk cache age.PUT /-/user/org.couchdb.user:<n>, bearer and basicAuthorization,/-/whoami, per-package-glob access rules.Content-Type: application/json(the verdaccio gate the two comments inpublish_command.rscite), attachment decoding, integrity verification of the uploaded bytes, a 403 on republish, deprecate. A failed publish is transactional.www-authenticate: OTP, the exact error string clients match,authUrl/doneUrl) and thedoneUrlpoll that hands a one-time password back. This replaces a third hand-rolled mock that lived insidebun-publish.test.ts.POST /-/npm/v1/security/advisories/bulkforbun audit, with requestContent-Encoding: gzipdecoded (whichbun auditalways sends).registry.paths,registry.requests[n].headers) and interception (registry.simulateFailures({ timesPerUrl: 4, status: 500 })), replacing theurls: string[]out-parameter and theexpect()calls the old mocks made inside their server handlers.The class is decoupled from the test harness (it imports only Bun and node builtins);
test/harness.tsexportsTestRegistry, a thin subclass that preloads the shared fixture tree and owns the bun-specific conveniences (writing abunfig.toml, the per-test project directory).The fixtures
test/cli/install/registry/packages/held 210 packages as verdaccio storage: a hand-written packument JSON each (some hundreds of lines of_attachments,_rev, and integrity strings) plus 287 prebuilt.tgzfiles. Adding one meant runningtarandsha512sumyourself;create-native-binlink-packages.tswas 155 lines of that boilerplate for two trivial packages.bin/gyp normalization above) plus four packages whosedist-tagsare not derivable, which get a three-line_registry.json. (That diff compared packument fields, includingdist.integritywhich at that point still came from hashing the original npm bytes; it is not a claim thatbuildTarballreproduces the deleted tarballs. Thenpm packgolden vectors are what establish that separately.).tgzfiles are deleted, exploded into plain source directories (pkg/1.0.0/package.json) the registry packs on demand. The 33 that remain are the ones whose exact bytes are pinned by an artifact that cannot be regenerated (checked-inpnpm-lock.yaml/package-lock.json/ yarn migration fixtures, an integrity inlined in a test, a committed snapshot) plus one whose deliberately non-standard internal layout is the thing its test exercises. Each.tgzI kept has a reason..tgzor a packument. A.gitattributesrule marks the tree-textso no platform's EOL conversion can change a packed tarball's bytes, and a single trailing!/test/cli/install/registry/packages/**in.gitignorekeeps the whole tree out of the global ignore rules.Validation
test/npm-registry/*.test.ts): the golden vectors, the pinned sha512, and the registry's HTTP contract.VerdaccioRegistryare migrated and green against a debug build: ~320 tests, zero failures attributable to the registry. (The handful of local reds are environmental and reproducible with no registry configured at all: tests that clone a real GitHub repo through this container's TLS-intercepting proxy, and acpuTime < 750msassertion for which a barebun installhere measures 961 ms.)Three things it surfaced:
application/vnd.npm.install-v1+json; registry.npmjs.org honors that (noscripts, a derivedhasInstallScript, a strict field subset) but verdaccio ignores theAcceptheader and serves the full document. Every install test now runs against the document shape the real registry sends. All of them pass, which is reassuring rather than alarming, but it was an accident before.bun pm whoamiwith an invalid token had only ever been tested against verdaccio's non-standard answer (a 200 with an empty body). registry.npmjs.org sends a 401. The registry now 401s, the test asserts bun's 401 output, and a second test reproduces the 200-with-no-username shape withregistry.intercept(), so both of bun's code paths for it are now covered deliberately.bun publishtests republished the samename@versionwithin one file and only passed because anrm -rfinto verdaccio's on-disk store unpublished it in between. A registry correctly rejects the second publish. Those three now callregistry.remove(), which is what thatrmreally was; the other eighteenrms againstregistry.packagesPathwere dead.The other half:
dummy.registry.tsAll 17 files that used
dummy.registry.tsorsimple-dummy-registry.tsare migrated too, and both modules are deleted, along with 56 of the 63 flattest/cli/install/*.tgzfiles they served (the seven that remain are each read by a test of bun's tarball-file dependency protocol,"pkg": "./x.tgz", which has nothing to do with a registry). Each of those tests now gets a fresh in-process registry in itsbeforeEachand declares its packages in code, so the files that used to share one mutable module-level handler can run their tests concurrently.Two details from that migration worth a reviewer's attention:
registry.simulateFailures({ timesPerUrl })fails exactly N, so the argument changed by one and the twelve recorded requests stayed twelve.<root>/<name>-<v>.tgz; a spec-compliant registry serves<root>/<name>/-/<name>-<v>.tgz. Every URL assertion changed shape, which is also the entire diff of the two regenerated security-scanner snapshot files (1,856 lines thatsort | uniq -creduces to four substitutions).The matrix
test/cli/install/registry-resolver-matrix.test.tsruns five resolver scenarios that have historically broken in exactly one configuration (a transitive peer chain, an optional peer reached from two aliases of one package, a workspace catalog, platform-filtered optional dependencies, and a pair ofnpm:aliases that point at each other) across{hoisted, isolated} × {cold, warm-on-disk}. The second axis variesBUN_INSTALL_CACHE_DIR(same dir for both passes vs a fresh one for pass 2), which is bun's actual warm-manifest gate; the test asserts the axis is real (a warm pass 2 issues strictly fewer packument requests than a cold one). Every cell of a scenario must produce a byte-identicalbun.lock, so resolution can never silently depend on the linker or on whether a manifest came from the network or the warm cache.Rebase onto 332f744
The branch is squashed and rebased onto
mainat 332f744. Tentest/cli/install/files had changed on both sides; here is what each resolution did:bun-install-lifecycle-scripts.test.tsand rewrotebun-workspaces.test.tsaround a semaphore-gatedsetupTest(). Resolution: takemain's version of both files whole, then re-apply only this PR'sverdacciotoregistry/VerdaccioRegistrytoTestRegistrysubstitution. Jarred's reordering and concurrency machinery are kept exactly.mainwritten against the infrastructure this PR deletes; they are migrated in place with no change in what they assert:bun-install-retry.test.ts: the three redirect-retry tests from http: fix use-after-free of the redirect URL on a retried request #33242.setHandler(h)becomesregistry.intercept(h); the handlers, the recorded-URL assertions, and thebar-0.0.2.tgzbytes are unchanged.bun-install.test.ts: the four manifest-tarball-URL-validation tests from Hardening round 11: input validation, bounds checks, lifetimes #33072.setContextHandler(ctx, h)becomesctx.registry.intercept(h). (install: link a transitive file: dependency of a local file: package #33159's two transitive-file:tests do not use a registry and auto-merged.)bun-lockb.test.tsandisolated-install.test.ts: the new binary-lockfile-flag test from Hardening round 11: input validation, bounds checks, lifetimes #33072 and the eight peer-stability/global-store/nested-alias tests from install: fix stale global-store links on disable and peer edge drift across bun.lock loads #32182, Hardening round 11: input validation, bounds checks, lifetimes #33072 and install: don't re-apply a patch per peer variant in isolated installs #33646. All already usedregistry.createTestDir(), whichTestRegistryimplements, so they auto-merged and pass unmodified.bun-install-tarball-integrity.test.ts: install: stop leaking pretty-markup tags into user-facing messages #33245's extra<r>assertions and Hardening round 11: input validation, bounds checks, lifetimes #33072's three multi-hashdist.integritytests auto-merged (the newdescribebrings its ownBun.serveand does not touch the registry); the resolution is a one-linegzipSyncimport my side had removed.bun-install-security-provider.test.ts: tookmain's in-memory large-payload path from make some tests faster #33622 (no more temp dir of 3000.tgzfiles) on top of this PR's per-test registry.bun-install-registry.test.ts(100 to 10 iterations),bun-lock.test.ts(await using file),.gitattributes,.gitignore,test/tsconfig.json: auto-merged, both sides kept.simple-dummy-registry.ts: deletion kept;NpmRegistryalready has[Symbol.asyncDispose].All nine migrated tests and the two reorganized files pass against a debug build after the rebase. The only local failures are the usual environmental ones the PR body already names (tests that clone a real GitHub/Bitbucket/GitLab repo, and
node -p/node-gyptests that emptyPATHand rely on the host'snode), which are unchanged by this PR and pass in CI.Related
no test proof · iteration 39 · Platform-specific test(s) that do not run on this machine. Deferring to CI, which covers all platforms: test/cli/install/bun-add.test.ts test/cli/install/bun-install-lifecycle-scripts.test.ts test/cli/install/bun-install-native-binlink.test.ts test/cli/install/bun-install-registry.test.ts test/cli/install/bun-install.test.ts test/cli/install/bun-link.test.ts test/cli/install/bun-lock.test.ts te…