From d247e1450e4c7afe3c7c6656afa460f0e4b25b0f Mon Sep 17 00:00:00 2001 From: Mykhailo Chalyi Date: Sun, 25 Oct 2026 09:17:03 +0000 Subject: [PATCH] fix(release): ship runtime-functions.js in the npm publish artifact `runtime-functions.ts` arrived in #2740 without being added to `tsconfig.json`'s `include`. tsc compiles a reachable import whether or not it is listed, so it kept emitting `runtime-functions.js` and every local build and `js.yml` run stayed green. Two things broke on that omission. `publish-js.yml` builds the package once and ships the result as the `js-stubs` artifact, and that list is derived by hand from `include`, so it never gained the file. And `scripts/tests/test_js_publish_files.py`, the guard added after 0.18.2 shipped without `langchain.js`, reads its notion of "tsc output" out of the same `include` -- so the guard was blind to exactly the file that was missing from it. v0.20.0 therefore published to crates.io and PyPI while all fifteen `Publish JS` test jobs failed on `ERR_MODULE_NOT_FOUND` for runtime-functions.js, which `wrapper.js` (the package `main`) imports at its first line. `release-js` is gated on those jobs, so npm was never reached and both npm packages remain on 0.19.0. Had the tests been bypassed, the published tarball would have omitted the file too: `package.json` `files` lists it and the publish job never runs tsc. Fixes the root omission in both tsconfigs, adds the two outputs to the artifact, and closes the guard's blind spot by checking `include` against the sources on disk instead of trusting it. `tsconfig.check.json` overrides `include` rather than extending it, so it needs the entry too. Before / After, reproducing the CI failure locally: $ mv runtime-functions.js /tmp/ && node __test__/fixtures/runtime-functions-shutdown.mjs Error [ERR_MODULE_NOT_FOUND]: Cannot find module '.../crates/bashkit-js/runtime-functions.js' imported from .../crates/bashkit-js/wrapper.js With the file present, resolution moves past it to the native binding that CI supplies via the `js-bindings-*` artifact. The new guard fails on the pre-fix tree: AssertionError: 'runtime-functions.ts' not found in {'scripted.ts', 'langchain.ts', 'internal.ts', 'anthropic.ts', 'wrapper.ts', 'ai.ts', 'openai.ts'} : runtime-functions.ts is a package source but is not in tsconfig.json include and restoring `include` alone then makes the two pre-existing checks fail independently on the artifact gap, confirming the chain. Verified: 181/181 script tests pass; `tsc --noEmit -p tsconfig.check.json` reports an identical error set before and after (11 environmental errors from the absent napi-generated `index.d.ts`, no new ones); prettier, check-okf, check-doc-links, check-workflow-parity, check-capability-parity and check-changelog pass; the artifact now covers every tsc output. This does not republish 0.20.0. A failed npm publish cannot be recovered by re-dispatching from `main`, since only a `refs/tags/vX.Y.Z` ref publishes as `latest`; npm needs a patch release once this has landed. Claude-Session: https://claude.ai/code/session_01RE65s6DTUE7LzZgc1o6Hzi --- .github/workflows/publish-js.yml | 2 ++ crates/bashkit-js/tsconfig.check.json | 1 + crates/bashkit-js/tsconfig.json | 1 + knowledge/log.md | 4 ++++ knowledge/operations/release-process.md | 11 +++++++++ scripts/tests/test_js_publish_files.py | 30 +++++++++++++++++++++++++ 6 files changed, 49 insertions(+) diff --git a/.github/workflows/publish-js.yml b/.github/workflows/publish-js.yml index 909ab7c97..fa83a171a 100644 --- a/.github/workflows/publish-js.yml +++ b/.github/workflows/publish-js.yml @@ -149,6 +149,8 @@ jobs: crates/bashkit-js/scripted.d.ts crates/bashkit-js/internal.js crates/bashkit-js/internal.d.ts + crates/bashkit-js/runtime-functions.js + crates/bashkit-js/runtime-functions.d.ts crates/bashkit-js/langchain.js crates/bashkit-js/langchain.d.ts crates/bashkit-js/anthropic.js diff --git a/crates/bashkit-js/tsconfig.check.json b/crates/bashkit-js/tsconfig.check.json index 193a4bced..cb531b323 100644 --- a/crates/bashkit-js/tsconfig.check.json +++ b/crates/bashkit-js/tsconfig.check.json @@ -10,6 +10,7 @@ "wrapper.ts", "scripted.ts", "internal.ts", + "runtime-functions.ts", "langchain.ts", "anthropic.ts", "openai.ts", diff --git a/crates/bashkit-js/tsconfig.json b/crates/bashkit-js/tsconfig.json index 969b28fda..8eca060fd 100644 --- a/crates/bashkit-js/tsconfig.json +++ b/crates/bashkit-js/tsconfig.json @@ -18,6 +18,7 @@ "wrapper.ts", "scripted.ts", "internal.ts", + "runtime-functions.ts", "langchain.ts", "anthropic.ts", "openai.ts", diff --git a/knowledge/log.md b/knowledge/log.md index 5db87c4c8..9fb78a480 100644 --- a/knowledge/log.md +++ b/knowledge/log.md @@ -1,5 +1,9 @@ # Bashkit Knowledge Update Log +## 2026-10-25 + +* **npm publish gap**: `tsconfig.json`'s `include` must name every `.ts` source in the native package, including one reached only through an import. tsc compiles a reachable import regardless, so omitting `runtime-functions.ts` broke no build but narrowed what `scripts/tests/test_js_publish_files.py` treats as a tsc output, disabling that guard for the one file it omitted. The `js-stubs` artifact then shipped without `runtime-functions.js` and all fifteen v0.20.0 `Publish JS` test jobs failed on `ERR_MODULE_NOT_FOUND`, leaving both npm packages on 0.19.0 while crates.io and PyPI published. `include` completeness is now guarded; see [Release Process](operations/release-process.md). + ## 2026-10-24 * **0.20.0 release preparation**: Synchronize workspace, native/browser package versions, internal release dependencies and standalone lockfiles; refresh self-hosted Python/TypeScript API references. The minor release documents the Rust analysis enum migration from `CommandContext` to `CommandPlacement`, live command I/O, runtime callbacks, and the security fixes since 0.19.0. Keep the opt-in SSH RSA Marvin and unmaintained atomic-polyfill caveats explicit. Exact multi-crate packaging validation and registry/platform verification follow the [Release Process](operations/release-process.md). diff --git a/knowledge/operations/release-process.md b/knowledge/operations/release-process.md index 39d788306..1c3313145 100644 --- a/knowledge/operations/release-process.md +++ b/knowledge/operations/release-process.md @@ -321,6 +321,17 @@ carries. That artifact must list every `tsc` output (`.js` + `.d.ts` for each without `langchain.js`/`ai.js`/`anthropic.js`/`openai.js` while `package.json` exported them. Guarded by `scripts/tests/test_js_publish_files.py`. +`tsconfig.json`'s `include` must name every `.ts` source in the package, even +one reached only through an import. tsc compiles a reachable import whether or +not it is listed, so an omission breaks no build: it narrows what the guard +above treats as a `tsc` output, disabling the guard for exactly the file it +left out. `runtime-functions.ts` landed that way in #2740, so v0.20.0 reached +the registry on crates.io and PyPI while all fifteen `Publish JS` test jobs +died on `ERR_MODULE_NOT_FOUND` for `runtime-functions.js`, leaving both npm +packages on 0.19.0. `include` completeness is now itself guarded by that same +test file -- treat `include`, the `js-stubs` list and `package.json` `files` as +one invariant, since each is a hand-maintained copy of the same set. + ### publish-wasm.yml Dispatched by `release.yml` from the verified release tag. Builds the diff --git a/scripts/tests/test_js_publish_files.py b/scripts/tests/test_js_publish_files.py index 8f0759917..c39f7a9ab 100644 --- a/scripts/tests/test_js_publish_files.py +++ b/scripts/tests/test_js_publish_files.py @@ -4,6 +4,18 @@ artifact, so `@everruns/bashkit@0.18.2` shipped without langchain.js, ai.js, anthropic.js and openai.js even though package.json exports them. The publish job never runs tsc itself; whatever the artifact omits is missing on npm. + +Second regression, v0.20.0: `runtime-functions.ts` arrived in #2740 without +being added to tsconfig's `include`. tsc still emitted it, because `wrapper.ts` +imports it and TypeScript compiles a reachable import either way, so every +local build and `js.yml` stayed green. But the omission also made the file +invisible to the two checks above, which read their notion of "tsc output" +straight out of `include` -- so the guard written for the 0.18.2 failure could +not see the next instance of it. All fifteen `Publish JS` test jobs then died +on `ERR_MODULE_NOT_FOUND` for runtime-functions.js and npm stalled at 0.19.0. + +`include` is therefore checked against the sources on disk, so no entry point +can be absent from it and silently narrow the two checks that depend on it. """ from __future__ import annotations @@ -25,6 +37,24 @@ def stub_paths() -> set[str]: class JsPublishFilesTests(unittest.TestCase): + def test_tsconfig_includes_every_ts_source(self) -> None: + """`include` is the root both checks below trust; it must be complete. + + A source missing here is not a build failure -- tsc follows imports -- + so nothing else reveals it. It just quietly shrinks `tsc_outputs`. + `tsconfig.check.json` overrides `include` rather than extending it, so + it needs the same entries to type-check each source in its own right. + """ + sources = {p.name for p in JS.glob("*.ts") if not p.name.endswith(".d.ts")} + for config in ("tsconfig.json", "tsconfig.check.json"): + included = set(json.loads((JS / config).read_text())["include"]) + for source in sorted(sources): + self.assertIn( + source, + included, + f"{source} is a package source but is not in {config} include", + ) + def test_every_ts_entry_output_is_uploaded(self) -> None: tsconfig = json.loads((JS / "tsconfig.json").read_text()) uploaded = stub_paths()