Skip to content

Tempdir out of the project; TS parse cache; fast local test default - #34

Open
brainkim wants to merge 7 commits into
mainfrom
fix-tempdir-out-of-project
Open

Tempdir out of the project; TS parse cache; fast local test default#34
brainkim wants to merge 7 commits into
mainfrom
fix-tempdir-out-of-project

Conversation

@brainkim

Copy link
Copy Markdown
Member

Three responses to "running tests is way too expensive" + ".libuild-test is annoying":

No more .libuild-test in the project

Bundles live in a per-run mkdtemp under OS tmp. Fixes two real defects beyond the annoyance: crashed runs no longer litter the project, and two simultaneous libuild test runs in one project no longer share the directory and delete each other's bundles. Externalized-package resolution is preserved by symlink-mirroring the cwd's node_modules ancestry into the temp dir (one nested level per ancestor with a node_modules, outermost first) — so hoisted workspace members resolve root-hoisted deps exactly as before. Verified e2e: plain consumers on bun+node, hoisted-workspace member, and post-run project cleanliness. --debug keeps and prints the dir.

TS parse cache (the suite's furnace)

Every build() created a fresh TS program, re-parsing lib.d.ts and all node_modules declarations — 136 times per suite run. Immutable inputs are now parsed once per process via a caching CompilerHost; fixture files are never cached. This speeds every consumer's builds, not just our suite. The three dts-heaviest test files (40 tests incl. declaration-content assertions) pass unchanged in 23.5s.

Local test economics

npm test = raw bun test (fast dev loop). test:all = the self-hosted dual-platform gate, run by CI on every release. Dogfooding stays a hard gate; laptops stop paying for it.

Verification note

Deliberately NOT fully suite-verified locally (the point of the change is to stop doing that); targeted files green, e2e consumers green, and CI's test:all is the gate before this can ship in a release.

🤖 Generated with Claude Code

https://claude.ai/code/session_01QAc3iDuBb3zN41HrepErUw

brainkim and others added 7 commits August 18, 2026 01:28
…ject

Three cost/annoyance fixes:

- dts: TypeScript lib files and node_modules types are parsed ONCE per
  process instead of once per createProgram. Every build paid the full
  lib.d.ts parse; libuild's own suite runs 136 builds, making this the
  suite's dominant compute. Fixture files are never cached; SourceFiles
  are immutable and all programs use identical options, so sharing is
  safe. The three dts-heaviest test files pass unchanged.

- test bundles live in a per-run mkdtemp under the OS temp dir, not
  <cwd>/.libuild-test: no gitignore noise, no litter after crashed
  runs, and two simultaneous runs in one project no longer share a
  directory and delete each other's bundles (real latent collision).
  Externalized-package resolution is preserved by mirroring the cwd's
  node_modules ancestry into the temp dir with symlinks (junction on
  Windows), one nested level per ancestor - verified e2e on plain
  consumers (bun+node) and a hoisted workspace member with no local
  node_modules. --debug keeps and prints the dir.

- npm test is fast raw `bun test` again; the self-hosted dual-platform
  gate is test:all, run by CI (release.yml). Local machines stop
  paying for what CI exists to do.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
bun defaults to 5s per test, so a legitimately slow test died inside
the shard long before the per-file budget mattered - "--timeout 60000"
silently didn't govern the failures users actually saw (fold's corpus
tests, 6-10s each, failed on bun for this alone). The file budget now
passes through as bun's per-test timeout too; one number serves both,
since no single test can legitimately exceed its file's budget. node
has no default per-test cap and its --test-timeout flag postdates our
supported floor, so node stays governed by the file budget alone.

Also adds the README section the runner never had: usage, flags with
the real timeout semantics, and runtime notes (bun's no-nested-test()
limitation and the ESLint RuleTester pattern that trips it, per fold's
migration report; the ESM-only policy).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Both steps of the CI-staging bootstrap - npm trust AND npm stage
publish - require the package to already exist, so the first publish
is the unique moment the setup becomes possible, and the stage-only
grant (--allow-stage-publish) is the part nobody discovers unaided.
After a publish of a previously-unpublished name, libuild now prints
the exact next command, with owner/repo parsed from the manifest's
repository field (verified against npm trust's documented syntax).

Suggested by the fold session after its stage preflight - which it
triggered by accident via a shell-quoting slip - turned out to be the
clearest statement anywhere of the package-must-exist constraint.

The existence check is non-blocking (offline skips the hint), and the
hint also prints on --dry-run of an unpublished name, matching the
existing dry-run "Published!" looseness.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The prepublishOnly-script guard accreted three mechanisms around one
idea: an executable shell one-liner in the manifest, substring
detection of it ("exit 1"), and installation coupled to --save's
manifest rewrite. Meanwhile a consumer hand-rolling the same guard had
a quoting slip that command-substituted and EXECUTED a release command
mid-edit - a safety feature complex enough to fire the gun it guards.

`private: true` is the entire mechanism now: npm itself refuses to
publish private packages. One word, nothing executable, no detection
heuristics needed (the substring check stays only to honor existing
script guards). The missing-guard warning names the one-word fix.

The dist manifest is unaffected - it never carried the guard, which is
why real publishes work.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
'Use libuild publish instead' was wrong guidance for anyone whose
release path is stage - which is why fold forked the message, and
forks drift. Brian's wording covers both paths in fewer words.
Applied to our own root manifest too.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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