diff --git a/.github/workflows/dirsql-rust-ci.yml b/.github/workflows/dirsql-rust-ci.yml index 9d389994e..34167919b 100644 --- a/.github/workflows/dirsql-rust-ci.yml +++ b/.github/workflows/dirsql-rust-ci.yml @@ -1,7 +1,8 @@ name: dirsql Rust CI -# The binding crates fan in (packages/ts/napi, packages/python/src): they are -# cargo workspace members, so the workspace-wide unit/lint jobs compile them. +# The binding crates fan in (packages/ts/napi with its manifest at +# packages/ts/Cargo.toml, and packages/python/src): they are cargo workspace +# members, so the workspace-wide unit/lint jobs compile them. # The root testing-conventions.toml carries this lane's config and the # [rust.coverage] floors the coverage job reads. on: @@ -10,6 +11,7 @@ on: paths: - 'packages/rust/**' - 'packages/ts/napi/**' + - 'packages/ts/Cargo.toml' - 'packages/python/src/**' - 'Cargo.toml' - 'Cargo.lock' @@ -22,6 +24,7 @@ on: - '!packages/rust/migrations.d/**' - '!packages/rust/README.md' - 'packages/ts/napi/**' + - 'packages/ts/Cargo.toml' - 'packages/python/src/**' - 'Cargo.toml' - 'Cargo.lock' diff --git a/.github/workflows/dirsql-typescript-ci.yml b/.github/workflows/dirsql-typescript-ci.yml index a91086a06..d30c9248c 100644 --- a/.github/workflows/dirsql-typescript-ci.yml +++ b/.github/workflows/dirsql-typescript-ci.yml @@ -113,7 +113,7 @@ jobs: build: # Builds the napi addon once and shares it with unit/binding/distcheck # below instead of each job/matrix-cell rebuilding it independently. - # The addon is N-API (napi6, see packages/ts/napi/Cargo.toml), which is + # The addon is N-API (napi6, see packages/ts/Cargo.toml), which is # ABI-stable across Node majors, so one build serves the whole Node # 20/22/24 matrix. name: Build native module diff --git a/Cargo.toml b/Cargo.toml index 928e1feef..85be71918 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,5 +1,5 @@ [workspace] -members = ["packages/python", "packages/rust", "packages/ts/napi"] +members = ["packages/python", "packages/rust", "packages/ts"] # Test-only loadable-extension fixture, built on demand by # packages/rust/tests/extensions.rs. Excluded so it isn't built/linted by # `cargo build --workspace` / `cargo clippy --workspace`. diff --git a/agents/reference/e2e-attestation.md b/agents/reference/e2e-attestation.md index cc7582007..9908fbf05 100644 --- a/agents/reference/e2e-attestation.md +++ b/agents/reference/e2e-attestation.md @@ -27,7 +27,7 @@ Receipts are append-only in practice -- a merged branch's receipt stays in the d **Everything compiled into the binding is in scope -- CI-enforced via `[e2e].extra_scope`.** The shared Rust core (`packages/rust/src`) is compiled into both bindings but lives in neither binding's subtree, and `e2e verify --scope` requires the scope to be a **descendant of the caller's `path`** -- so a binding caller cannot point its own `--scope` at the core. That case is solved by the `e2e verify --extra-scope` / `--exclude` flags (upstream #333): an `[e2e]` block declares the extra roots, `detect.py` reads it, and the reusable `e2e-verify` job passes it through. Pure config; the former bespoke `e2e_core_freshness.py` gate is not needed. Two roots per binding: - **The core, `packages/rust/src` -- puts BOTH bindings in scope** (#337). **After any shared-core change, attest each binding** (`just e2e-attest-python`, `just e2e-attest-ts`) -- CI enforces this, it is no longer a by-hand-only promise. **`cli`-only** core source (`packages/rust/src/cli/**`, `packages/rust/src/bin/**`) is feature-gated out of the binding *libraries*, but since #721 each launcher calls the core's `run_cli` **in-process through its binding** -- a CLI change alters what those suites exercise, so it demands a receipt from both bindings like any other core change (no `exclude` carve-out). -- **The binding crate itself -- puts ONLY its own package in scope** (#933): `packages/python/src` (the PyO3 glue) for python, `packages/ts/napi` (the napi-rs crate, manifest and `build.rs` included) for typescript. Same #721 argument one layer out: `run_cli` in each binding does argv framing, GIL detach / exit-code handling, and runs in every e2e case, so a binding-only diff changes what the suites exercise with no core change at all. A napi change has no bearing on the python suite and never demands a python receipt, and vice versa. +- **The binding crate itself -- puts ONLY its own package in scope** (#933): `packages/python/src` (the PyO3 glue) for python, `packages/ts/napi` plus `packages/ts/Cargo.toml` (the napi-rs crate, `build.rs` included; the manifest sits at the package root so the release stamps its version) for typescript. Same #721 argument one layer out: `run_cli` in each binding does argv framing, GIL detach / exit-code handling, and runs in every e2e case, so a binding-only diff changes what the suites exercise with no core change at all. A napi change has no bearing on the python suite and never demands a python receipt, and vice versa. **That per-package split is why `packages/python` and `packages/ts` each carry their own `testing-conventions.toml`** (passed explicitly as the lane's `config:`, the pattern `internals/checks` established in #550). `[e2e]` is a single global table applied to every caller reading a given config, so "core + *my* binding" is not expressible in one shared file: declaring both bindings at the repo root would make a napi change demand a python receipt (and force `packages/ts/napi/**` into the python lane's path triggers to enforce it at all). The root `[e2e]` block stays as the default for a caller with no config of its own. @@ -41,7 +41,7 @@ Agents must run the full e2e suite locally before any `git push` that includes a **"Substantial" means any change touching:** - `packages/rust/**` (Rust core) -- `packages/ts/napi/**` (napi-rs binding crate) +- `packages/ts/napi/**` and `packages/ts/Cargo.toml` (napi-rs binding crate) - `packages/python/src/**` (excluding files matching `*_test.py`) - `packages/ts/src/**` (excluding files matching `*.test.ts` / `*.spec.ts`) - Any shared SDK runtime code reachable from the above diff --git a/internals/checks/tests/integration/release_globs_test.py b/internals/checks/tests/integration/release_globs_test.py index 61f5d9f85..ca4786ad2 100644 --- a/internals/checks/tests/integration/release_globs_test.py +++ b/internals/checks/tests/integration/release_globs_test.py @@ -13,6 +13,9 @@ from __future__ import annotations +import tomllib +from pathlib import Path + import pytest from checks.release_globs.carve_out import carve_out @@ -81,3 +84,14 @@ def it_rejects_a_leading_bang_negation_in_the_publish_globs(tmp_path, capsys, ne globs = [*carve_out("packages/rust"), negation] assert run(*write(tmp_path, globs)) == 1 assert "leading-`!` negation" in capsys.readouterr().out + + +def describe_the_real_release_config(): + def it_keeps_the_napi_manifest_at_the_npm_package_path(): + # The release stamps the planned version into a napi crate only when its + # Cargo.toml sits at the npm package's `path`; elsewhere the CLI reports + # the committed literal. + repo = Path(__file__).resolve().parents[4] + config = tomllib.loads((repo / "putitoutthere.toml").read_text()) + (npm,) = [package for package in config["package"] if package["kind"] == "npm"] + assert (repo / npm["path"] / "Cargo.toml").is_file() diff --git a/packages/ts/napi/Cargo.toml b/packages/ts/Cargo.toml similarity index 86% rename from packages/ts/napi/Cargo.toml rename to packages/ts/Cargo.toml index 7047e651d..b53bee232 100644 --- a/packages/ts/napi/Cargo.toml +++ b/packages/ts/Cargo.toml @@ -3,8 +3,12 @@ name = "dirsql-napi" version.workspace = true edition.workspace = true publish = false +# Colocated with package.json because the release only stamps the planned +# version into a napi crate whose manifest sits at the npm package path. +build = "napi/build.rs" [lib] +path = "napi/src/lib.rs" crate-type = ["cdylib"] [dependencies] diff --git a/packages/ts/changelog.d/2026-10-05-npm-version.md b/packages/ts/changelog.d/2026-10-05-npm-version.md new file mode 100644 index 000000000..6e1e89449 --- /dev/null +++ b/packages/ts/changelog.d/2026-10-05-npm-version.md @@ -0,0 +1,2 @@ +**Fixed** `npx dirsql --version` reports the npm package's version. It used to +print `0.2.7`, the unreleased crate version, on every release. diff --git a/packages/ts/e2e-attestations/claude-1297-npm-version.json b/packages/ts/e2e-attestations/claude-1297-npm-version.json new file mode 100644 index 000000000..bd815ff69 --- /dev/null +++ b/packages/ts/e2e-attestations/claude-1297-npm-version.json @@ -0,0 +1,7 @@ +{ + "command": "pnpm test:e2e", + "ran_at": 1791237028, + "exit_code": 0, + "commit": "ce521cdd6a0d9088f75a689159ec3f37ff2c864a", + "branch": "claude/1297-npm-version" +} diff --git a/packages/ts/migrations.d/2026-10-05-npm-version.md b/packages/ts/migrations.d/2026-10-05-npm-version.md new file mode 100644 index 000000000..ddb37a897 --- /dev/null +++ b/packages/ts/migrations.d/2026-10-05-npm-version.md @@ -0,0 +1,22 @@ +### npm: `dirsql --version` reports the published version + +#### Summary + +`npx dirsql --version` now prints the installed package's version instead of +`0.2.7`. Nothing breaks. + +#### Required changes + +_None._ + +#### Deprecations removed + +_None._ + +#### Behavior changes without code changes + +_None._ + +#### Verification + +_None._ diff --git a/packages/ts/package.json b/packages/ts/package.json index 95788a0c1..5d23f9fa5 100644 --- a/packages/ts/package.json +++ b/packages/ts/package.json @@ -44,7 +44,7 @@ }, "wireit": { "napi:build": { - "command": "napi build --release --manifest-path napi/Cargo.toml --output-dir .", + "command": "napi build --release --manifest-path Cargo.toml --output-dir .", "output": ["dirsql.node", "index.d.ts"] }, "tsc": { @@ -66,7 +66,7 @@ "src/platforms.json", "napi/src/**/*.rs", "napi/build.rs", - "napi/Cargo.toml", + "Cargo.toml", "../rust/src/**/*.rs", "../rust/Cargo.toml" ], @@ -76,11 +76,11 @@ "dependencies": ["napi:build", "tsc", "stage:platform"] }, "build:debug": { - "command": "napi build --manifest-path napi/Cargo.toml --output-dir . && tsc -p tsconfig.build.json", + "command": "napi build --manifest-path Cargo.toml --output-dir . && tsc -p tsconfig.build.json", "files": [ "napi/src/**/*.rs", "napi/build.rs", - "napi/Cargo.toml", + "Cargo.toml", "../rust/src/**/*.rs", "src/**/*.ts", "src/**/*.json", diff --git a/packages/ts/testing-conventions.toml b/packages/ts/testing-conventions.toml index f952e4388..3ed868a12 100644 --- a/packages/ts/testing-conventions.toml +++ b/packages/ts/testing-conventions.toml @@ -4,11 +4,11 @@ # start staling this package. # # The napi crate is in scope because the npm launcher runs the CLI in-process -# through it (#721), so every e2e case crosses it -- the whole crate dir, since -# Cargo.toml and build.rs decide what lands in the addon. packages/python/src is -# not: it cannot reach this suite. +# through it (#721), so every e2e case crosses it -- the whole crate dir plus its +# manifest at the package root, since Cargo.toml and build.rs decide what lands +# in the addon. packages/python/src is not: it cannot reach this suite. [e2e] -extra_scope = ["packages/rust/src", "packages/ts/napi"] +extra_scope = ["packages/rust/src", "packages/ts/napi", "packages/ts/Cargo.toml"] # The live TypeScript floors; the root file carries no `[typescript]` table. [typescript.coverage] diff --git a/packages/ts/tools/stage-platform.ts b/packages/ts/tools/stage-platform.ts index aed9275b7..2eec6fb31 100644 --- a/packages/ts/tools/stage-platform.ts +++ b/packages/ts/tools/stage-platform.ts @@ -138,7 +138,7 @@ function stageOne(args: StageOneArgs): StageResult["staged"][number] { // instead of `dirsql.node`. Without it the cross-build's output // collides with the host's napi:build file at the package root. // `--manifest-path`/`--output-dir` point napi at the colocated - // `dirsql-napi` crate (packages/ts/napi) while still dropping the + // `dirsql-napi` crate (packages/ts/Cargo.toml) while still dropping the // artifact into this package (packages/ts) root. const cross = spawn( "npx", @@ -148,7 +148,7 @@ function stageOne(args: StageOneArgs): StageResult["staged"][number] { "--release", "--platform", "--manifest-path", - "napi/Cargo.toml", + "Cargo.toml", "--output-dir", ".", "--target",