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()