diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e66e4b3..0dbb28e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -4,10 +4,11 @@ permissions: contents: read on: + workflow_dispatch: pull_request: push: branches: - - main + - dev jobs: smoke: diff --git a/.gitignore b/.gitignore index 7a8c3d7..c193671 100644 --- a/.gitignore +++ b/.gitignore @@ -16,3 +16,4 @@ Cargo.lock # Repo-local agent context (internal pilot details) AGENTS.md +.claude/logs/ diff --git a/CHANGELOG.md b/CHANGELOG.md index a3ff56f..0215754 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,21 @@ # Changelog -## 0.1.0 — Unreleased +## 0.1.1 — 2026-05-08 + +Bug fixes for two consumer-surfaced defects in 0.1.0. + +- Pin the cargo package name on every inferred target. Previously the cargo + executor fell back to the Nx project name, so when another plugin (e.g. + `@nx/js` for napi-rs bindings) renamed the project from the cargo crate + name to a scoped JS package name, cargo received `-p @scope/name` and + rejected the invocation with `unexpected prerelease field`. +- Filter executor options against a per-subcommand allowlist before + forwarding to cargo. Nx merges unrelated CLI args (e.g. `--run`, + `--coverage` from `nx run-many -t test` in mixed JS+Rust workspaces) into + the test executor's options; without filtering, those landed on cargo's + argv as `cargo test --run --coverage [object Object]`. + +## 0.1.0 — 2026-05-08 Initial release. diff --git a/README.md b/README.md index abcfcd5..801c143 100644 --- a/README.md +++ b/README.md @@ -4,21 +4,22 @@ and parses `cargo metadata` into the Nx project graph so `nx affected` works across your Rust crates. -Spiritual successor to [`@monodon/rust`](https://github.com/Cammisuli/monodon) — -same shape, explicitly Apache-2.0 licensed, targeting Nx 22. +Inspired by the public API shape of +[`@monodon/rust`](https://github.com/Cammisuli/monodon), with an Apache-2.0 +codebase targeting Nx 22. ## Install ```sh -pnpm add -D nxrust -# or: npm i -D nxrust / yarn add -D nxrust +pnpm add -D @eddacraft/nxrust +# or: npm i -D @eddacraft/nxrust / yarn add -D @eddacraft/nxrust ``` Register in `nx.json`: ```json { - "plugins": ["nxrust"] + "plugins": ["@eddacraft/nxrust"] } ``` @@ -26,13 +27,13 @@ Register in `nx.json`: | Executor | Wraps | Cache | | ----------------------- | ---------------------- | ----- | -| `nxrust:build` | `cargo build` | yes | -| `nxrust:check` | `cargo check` | yes | -| `nxrust:clippy` / `lint`| `cargo clippy` | yes | -| `nxrust:fmt` | `cargo fmt` | yes | -| `nxrust:run` | `cargo run` | no | -| `nxrust:test` | `cargo test` | yes | -| `nxrust:release-publish`| `cargo publish` | no (use via `nx release publish`) | +| `@eddacraft/nxrust:build` | `cargo build` | yes | +| `@eddacraft/nxrust:check` | `cargo check` | yes | +| `@eddacraft/nxrust:clippy` / `lint`| `cargo clippy` | yes | +| `@eddacraft/nxrust:fmt` | `cargo fmt` | yes | +| `@eddacraft/nxrust:run` | `cargo run` | no | +| `@eddacraft/nxrust:test` | `cargo test` | yes | +| `@eddacraft/nxrust:release-publish`| `cargo publish` | no (use via `nx release publish`) | All executors accept a shared option set: @@ -53,15 +54,15 @@ Individual executors add specialised flags — see each schema. ```sh # Library crate -nx g nxrust:crate my-crate +nx g @eddacraft/nxrust:crate my-crate # Binary crate -nx g nxrust:crate my-cli --bin +nx g @eddacraft/nxrust:crate my-cli --bin # or alias: -nx g nxrust:binary my-cli +nx g @eddacraft/nxrust:binary my-cli # Library alias -nx g nxrust:library my-lib +nx g @eddacraft/nxrust:library my-lib ``` Generated crates are added to the root `Cargo.toml` `[workspace.members]` @@ -87,8 +88,13 @@ This is what makes `nx affected -t test` correct across your Rust crates. ## License -Apache-2.0 © EddaCraft. See [LICENSE](./LICENSE). +Apache-2.0 © eddacraft. See [LICENSE](./LICENSE). -This project does not contain any code copied from `@monodon/rust` — it -references its public API shape only. `cargo metadata` is the official -Rust tooling contract. +This project does not contain code copied from `@monodon/rust`; it references +the public API shape only. `cargo metadata` is the official Rust tooling +contract. + +## Acknowledgements + +Thanks to the author of `@monodon/rust`; that project established the shape of +Rust support many Nx users expect. diff --git a/e2e/fixture/nx.json b/e2e/fixture/nx.json index 0a43e36..6d091ec 100644 --- a/e2e/fixture/nx.json +++ b/e2e/fixture/nx.json @@ -1,3 +1,6 @@ { - "plugins": ["nxrust"] -} + "plugins": [ + "@eddacraft/nxrust" + ], + "analytics": true +} \ No newline at end of file diff --git a/generators.json b/generators.json index 34b3e9d..378db02 100644 --- a/generators.json +++ b/generators.json @@ -1,6 +1,6 @@ { "$schema": "http://json-schema.org/schema", - "name": "nxrust", + "name": "@eddacraft/nxrust", "version": "0.1.0", "generators": { "init": { diff --git a/package.json b/package.json index 58ef7be..eec33e3 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { - "name": "nxrust", - "version": "0.1.0", + "name": "@eddacraft/nxrust", + "version": "0.1.1", "description": "Nx plugin that wraps Cargo — executors, generators, and project-graph integration for Rust workspaces inside Nx monorepos.", "keywords": [ "nx", @@ -10,13 +10,13 @@ "monorepo", "workspace" ], - "homepage": "https://github.com/EddaCraft/nxrust", + "homepage": "https://github.com/eddacraft/nxrust", "repository": { "type": "git", - "url": "https://github.com/EddaCraft/nxrust.git" + "url": "git+https://github.com/eddacraft/nxrust.git" }, "license": "Apache-2.0", - "author": "EddaCraft", + "author": "eddacraft", "main": "dist/index.js", "types": "dist/index.d.ts", "files": [ @@ -36,6 +36,8 @@ "build": "tsc -p tsconfig.json && node ./tools/copy-assets.mjs", "clean": "rm -rf dist", "e2e": "node ./tools/e2e-smoke.mjs", + "prepublishOnly": "node ./tools/assert-release-branch.mjs && pnpm build && pnpm test && pnpm e2e", + "release:dry-run": "pnpm build && pnpm pack --dry-run", "typecheck": "tsc -p tsconfig.json --noEmit", "test": "vitest run", "test:watch": "vitest" diff --git a/plans/index.aps.md b/plans/index.aps.md index 6a45b8d..5c4fdd9 100644 --- a/plans/index.aps.md +++ b/plans/index.aps.md @@ -6,7 +6,7 @@ | Field | Value | |-------|-------| | Status | In Progress | -| Owner | @joshuaboys | +| Owner | eddacraft | | Created | 2026-04-21 | | Licence | Apache-2.0 | @@ -39,14 +39,14 @@ TypeScript + Rust monorepos. - [x] Project-graph plugin (`createNodesV2` + `createDependencies`) emits Rust crate nodes + cross-crate edges from `cargo metadata` - [x] Unit test suite green via Vitest -- [x] Council + adversarial review findings addressed or documented -- [ ] End-to-end pilot against a real Rust crate in a real Nx 22 +- [x] Review findings addressed or documented +- [ ] End-to-end validation against a real Rust crate in a real Nx 22 workspace passes and caches -- [ ] Rollout to every crate in the pilot workspace completes green on +- [ ] Rollout to every crate in the validation workspace completes green on `nx run-many` -- [ ] CI smoke test job in nxrust + dependent-repo smoke in the pilot +- [ ] CI smoke test job in nxrust + dependent-repo smoke in the validation workspace -- [ ] First `nxrust@0.1.0` published to npm (final scope TBD) +- [ ] First `@eddacraft/nxrust@0.1.0` published to npm ## Constraints @@ -60,7 +60,7 @@ TypeScript + Rust monorepos. not failure. - UK English in plan and README text; user-facing CLI output stays locale-neutral. -- Zero regressions on the pilot consumer: every +- Zero regressions on the validation consumer: every `cargo check/test/clippy/fmt` invocation that passed before must still pass after the plugin wraps it. @@ -68,7 +68,7 @@ TypeScript + Rust monorepos. | Module | Purpose | Status | Dependencies | |--------|---------|--------|--------------| -| [01-v0.1-shakedown](./modules/01-v0.1-shakedown.aps.md) | Prove the plugin end-to-end on a pilot consumer, ship first npm release | In Progress | — | +| [01-v0.1-shakedown](./modules/01-v0.1-shakedown.aps.md) | Prove the plugin end-to-end on a consumer workspace, ship first npm release | In Progress | — | Deferred (not yet active modules): @@ -87,15 +87,16 @@ Deferred (not yet active modules): | Risk | Impact | Mitigation | |------|--------|------------| -| `target/` caching yields stale artefacts under remote cache | high | Narrow `outputs` — cache test/clippy reports and binaries, not the whole `target/` tree; verify second-run cache hits on the pilot crate before rollout | +| `target/` caching yields stale artefacts under remote cache | high | Narrow `outputs` — cache test/clippy reports and binaries, not the whole `target/` tree; verify second-run cache hits before rollout | | Nx 22 project-graph plugin API drifts on minor upgrades | medium | Small public surface (`createNodesV2` + `createDependencies` only); CI smoke test pins the contract | -| `cargo metadata` performance on large workspaces | medium | Mtime-keyed `Cargo.lock` cache already in `graph.ts`; re-evaluate if the pilot hits a slowdown | -| Pilot switchover breaks the consumer's pnpm graph mid-flight | medium | Switch only after pilot + rollout + CI smoke are all green; keep a revert commit ready | +| `cargo metadata` performance on large workspaces | medium | Mtime-keyed `Cargo.lock` cache already in `graph.ts`; re-evaluate if validation hits a slowdown | +| Consumer switchover breaks the consumer's pnpm graph mid-flight | medium | Switch only after validation + rollout + CI smoke are all green; keep a revert commit ready | ## Open Questions -- [ ] Final npm scope for v0.1 publish — scoped vs unscoped. Default: - scoped, re-evaluate at v0.2. +- [x] Final npm scope for v0.1 publish — use scoped + `@eddacraft/nxrust`. The package is published under the eddacraft + npm organisation. - [ ] Keep the `release-publish` executor in v0.1, or defer until a crate actually publishes to crates.io? Implemented and tested — keep it, but document as "unvalidated against a real crates.io @@ -103,7 +104,7 @@ Deferred (not yet active modules): - [ ] Should the project-graph plugin emit external nodes for workspace dev-dependencies, or only runtime deps? Current behaviour skips `kind === 'dev'`; revisit if `nx affected -t test` misses edges - in the pilot. + in validation. ## Decisions @@ -121,3 +122,9 @@ Deferred (not yet active modules): - **D-004:** Build target — CommonJS to `./dist`. Nx devkit plugins are consumed by Nx's Node runtime; ESM offers no win here. *Accepted.* +- **D-005:** Distribution channel — publish `@eddacraft/nxrust` to npm, + not crates.io. + Nx plugins are JavaScript packages discovered through npm metadata + (`executors.json`, `generators.json`, and JS entrypoints). Cargo/crates.io + remains relevant for Rust consumers, not for this Nx plugin. + *Accepted 2026-05-07.* diff --git a/plans/modules/01-v0.1-shakedown.aps.md b/plans/modules/01-v0.1-shakedown.aps.md index e1ec2a3..fedf45e 100644 --- a/plans/modules/01-v0.1-shakedown.aps.md +++ b/plans/modules/01-v0.1-shakedown.aps.md @@ -10,41 +10,40 @@ first npm release. The nxrust plugin is feature-complete for v0.1: seven executors, five generators, a working `createNodesV2` + `createDependencies` graph -plugin, a unit-test suite, and two rounds of council + adversarial -review addressed. What it has **not** done yet: +plugin, a unit-test suite, and review findings addressed. What it has +**not** done yet: 1. Been driven end-to-end against a real Rust crate in a real Nx 22 workspace outside this repo 2. Shipped to npm -3. Replaced any in-place workspace-link copies in pilot consumers +3. Replaced any in-place workspace-link copies in validation consumers -Until those three land, the plugin is theoretical. This module closes -that gap. +This module closes the gap between implementation and a supported public +release. **Why this matters:** -1. **Downstream consumers depend on this.** Workspaces piloting nxrust +1. **Downstream consumers depend on this.** Workspaces validating nxrust via a pnpm workspace link cannot bump Nx independently of the plugin until a real release is on npm. -2. **Monodon is not coming back.** Shipping nxrust under Apache-2.0 - gives the wider Nx-plus-Rust community a maintained alternative. -3. **Review findings need real-world validation.** The adversarial - reviewer flagged `target/` caching and `cargo metadata` performance - as "unverified until pilot". The pilot verifies them. +2. **Nx 22 needs a maintained Rust plugin.** Shipping nxrust under + Apache-2.0 gives mixed TypeScript + Rust workspaces a current option. +3. **Review findings need real-world validation.** `target/` caching and + `cargo metadata` performance need validation in a real workspace. ## In Scope -- Smoke-test the plugin end-to-end in a pilot consumer workspace via - pnpm workspace protocol; run one pilot crate through the full +- Smoke-test the plugin end-to-end in a consumer workspace via + pnpm workspace protocol; run one representative crate through the full executor set; assert cache hit on second run -- Roll the pilot shape out to the remaining crates in the pilot +- Roll the validation shape out to the remaining crates in the consumer workspace - CI smoke test in this repo (nxrust): build + run all unit tests + run - the pilot executor against a fixture crate held inside `e2e/` -- Add a dependent-repo smoke job to the pilot workspace's CI that runs + the executor smoke test against a fixture crate held inside `e2e/` +- Add a dependent-repo smoke job to the consumer workspace's CI that runs `pnpm exec nx run :check` against the linked plugin -- First npm publish of `nxrust@0.1.0` (final scope TBD) -- Pilot consumer switches from `workspace:*` link to the published +- First npm publish of `@eddacraft/nxrust@0.1.0` +- Consumer switches from `workspace:*` link to the published version ## Out of Scope @@ -64,26 +63,26 @@ that gap. ### Depends On - `@nx/devkit ^22.6.5` (already in `package.json`) -- A real Nx 22 consumer workspace to pilot against +- A real Nx 22 consumer workspace to validate against - npm publish credentials for the chosen scope — needed only at R4 ### Exposes (at the end of this module) -- `nxrust@0.1.0` on npm -- A dependent-repo smoke job in the pilot workspace's CI that fails +- `@eddacraft/nxrust@0.1.0` on npm +- A dependent-repo smoke job in the consumer workspace's CI that fails loud if a nxrust release breaks the consumer -- The pilot workspace running on the plugin-inferred project graph +- The consumer workspace running on the plugin-inferred project graph (no per-crate `project.json` files required) ## Constraints -- **Zero regressions on the pilot consumer.** Every +- **Zero regressions on the consumer.** Every `cargo check/test/clippy/fmt` call that passes today must still pass - after the plugin wraps it. If the pilot shows a regression, the - pilot reverts — it does not press on. -- **No code changes to nxrust during pilot** unless the pilot exposes a + after the plugin wraps it. If validation shows a regression, the + consumer change reverts — it does not press on. +- **No code changes to nxrust during validation** unless validation exposes a real defect. Cosmetic edits wait for v0.2. -- **Pilot consumer switchover happens in one PR**, reversible with a +- **Consumer switchover happens in one PR**, reversible with a single git revert. Do not interleave the switchover with unrelated consumer-side work. - Published package ships `LICENSE`, `README.md`, `CHANGELOG.md`, @@ -97,7 +96,7 @@ that gap. - [x] Plugin builds to `./dist` with `pnpm build` - [x] Entrypoint smoke: `createNodesV2` + `createDependencies` loadable; all 7 executors and 5 generators `require.resolve` cleanly -- [x] Council + adversarial findings addressed or documented +- [x] Review findings addressed or documented - [x] `LICENSE` (Apache-2.0), `README.md`, `CHANGELOG.md` at repo root - [x] This APS plan written - [ ] Pilot consumer workspace reachable with a clean working tree @@ -106,16 +105,16 @@ that gap. ## Work Items -### NXRUST-R1: Pilot on smallest crate [Complete: 2026-04-21] +### NXRUST-R1: Validate on smallest crate [Complete: 2026-04-21] - **Intent:** Drive the full plugin end-to-end against the smallest real crate before touching any of the others -- **Expected Outcome:** the pilot workspace's `nx.json` registers +- **Expected Outcome:** the consumer workspace's `nx.json` registers `nxrust` as a plugin; `pnpm exec nx run :` matches `cargo -p ` for check/test/clippy/fmt-check; second local run reports cache hit; `nx graph --focus=` renders the correct edges -- **Scope:** pilot workspace — `nx.json` (register plugin), root +- **Scope:** consumer workspace — `nx.json` (register plugin), root `package.json` + `pnpm-lock.yaml` (add nxrust dev-dep) - **Validation:** All four checks above pass - **Confidence:** medium @@ -123,7 +122,7 @@ that gap. run rebuilds instead of cache-hitting, narrow the `outputs` declaration on the affected target rather than abandon the cache. - **Non-scope:** Other crates in the workspace -- **Resolution:** Linked into the pilot workspace via +- **Resolution:** Linked into the consumer workspace via `pnpm add -Dw nxrust@file:../../nxrust`; registered `{"plugin": "nxrust", "options": {}}` in its `nx.json`. `pnpm exec nx show projects` listed every Rust crate alongside the TS projects. @@ -132,19 +131,19 @@ that gap. defects surfaced. Two reconciliations captured: (a) Cargo package names map directly to Nx project keys; (b) the plugin auto-infers projects from `Cargo.toml` — no manual `project.json` files needed. - *Pilot-specific timings, branch, and crate names live in repo-local - `AGENTS.md`.* + Workspace-specific timings, branch names, and crate names are intentionally + omitted from the public plan. ### NXRUST-R2: Verify full-workspace run-many [Complete: 2026-05-05] -- **Intent:** Confirm every Rust crate in the pilot workspace +- **Intent:** Confirm every Rust crate in the consumer workspace participates correctly in the inferred project graph, then exercise `nx run-many` and `nx affected` across the workspace - **Expected Outcome:** `pnpm exec nx show projects` lists every Rust project; `pnpm exec nx run-many -t check` and `:test` complete green; a targeted `nx affected -t check --files=/src/lib.rs` runs that crate and its dependents only -- **Scope:** pilot workspace — validation only; no per-crate +- **Scope:** consumer workspace — validation only; no per-crate `project.json` files needed - **Dependencies:** NXRUST-R1 - **Validation:** Both `run-many` commands exit 0; the `affected` probe @@ -156,10 +155,8 @@ that gap. `pnpm exec nx show projects` listed the inferred Rust projects. Full `pnpm exec nx run-many -t check` completed green for all 16 Rust check targets. Full `pnpm exec nx run-many -t test` completed green - across the workspace. Targeted probe `pnpm exec nx affected -t check - --files=crates/anvil-policy/src/lib.rs` ran only - `eddacraft-anvil-policy` and dependent `eddacraft-anvil`, excluding - unrelated crates. + across the workspace. A targeted `nx affected -t check` probe ran only + the changed crate and its dependent crate, excluding unrelated crates. ### NXRUST-R3: CI smoke test in both repos [In Review] @@ -169,59 +166,94 @@ that gap. - **In nxrust:** A GitHub Actions workflow runs `pnpm install`, `pnpm build`, `pnpm test`, and a fixture-driven end-to-end that runs `nx run :check` against a tiny crate held in `e2e/` - - **In the pilot consumer:** the consumer's Rust CI workflow gains a + - **In the validation consumer:** the consumer's Rust CI workflow gains a job that runs `pnpm exec nx run :check` against the linked plugin - **Scope:** `nxrust/.github/workflows/**`, `nxrust/e2e/**` (new fixture - crate + project.json), pilot consumer's Rust CI workflow + crate + project.json), consumer Rust CI workflow - **Dependencies:** NXRUST-R2 -- **Validation:** Both workflows green on a canary PR; a deliberate - schema-break in nxrust's `executors.json` fails both +- **Validation:** Both workflows green on a canary PR; package metadata and + executor resolution failures are caught by CI - **Confidence:** high - **Resolution:** Added nxrust CI with `pnpm install --frozen-lockfile`, `pnpm build`, `pnpm test`, and `pnpm e2e`. The e2e smoke packs the - publishable `nxrust` tarball, installs it into a checked-in fixture + publishable `@eddacraft/nxrust` tarball, installs it into a checked-in fixture workspace via a frozen fixture lockfile, clears fixture Nx state, and - runs `nx run smoke:check --skip-nx-cache`. Added the pilot consumer - Rust workflow smoke job that runs `pnpm exec nx run - eddacraft-anvil-policy:check --skip-nx-cache --outputStyle=static` - against the linked plugin. Local validation passed for `pnpm build`, - `pnpm test`, `pnpm e2e`, and the pilot smoke command. Final completion + runs `nx run smoke:check --skip-nx-cache`. Added the consumer Rust + workflow smoke job that runs an Nx-backed Rust check target against the + linked plugin. Local validation passed for `pnpm build`, + `pnpm test`, `pnpm e2e`, and the consumer smoke command. Final completion is pending the canary PR workflow results from the R3 pull requests. -### NXRUST-R4: First npm publish of `nxrust@0.1.0` [Draft] +### NXRUST-R4: First npm publish of `@eddacraft/nxrust@0.1.0` [Complete: 2026-05-08] - **Intent:** Ship v0.1.0 to npm so consumers can install it without pnpm workspace protocol or git refs -- **Expected Outcome:** `nxrust@0.1.0` (final scope TBD) published; +- **Expected Outcome:** `@eddacraft/nxrust@0.1.0` published; `npm view versions` shows `0.1.0`; package tarball contains `dist/**`, `executors.json`, `generators.json`, `LICENSE`, `THIRD-PARTY-NOTICES.md` (if any monodon code was borrowed during R1–R3), `README.md`, `CHANGELOG.md` and nothing else -- **Scope:** nxrust — possibly a release workflow, `CHANGELOG.md` entry +- **Scope:** nxrust — package publish scripts, CI default-branch trigger, + `CHANGELOG.md` entry - **Dependencies:** NXRUST-R3 -- **Validation:** Fresh `pnpm dlx create-nx-workspace` + `pnpm add -D - nxrust`, then `pnpm exec nx g nxrust:init` exits 0 +- **Validation:** `pnpm release:dry-run` passes; `npm whoami` confirms a + logged-in publisher; fresh `pnpm dlx create-nx-workspace` + `pnpm add -D + @eddacraft/nxrust`, then `pnpm exec nx g @eddacraft/nxrust:init` exits 0 - **Confidence:** medium - **Risks:** Scope ownership — confirm scope is registered and publishable from the CI runner or a local account before attempting - **Non-scope:** Semantic-release automation — v0.1 is a manual `pnpm publish`; automation is a v0.2 concern -- **Readiness note:** `npm view nxrust version name --json` returned - 404 on 2026-05-05, so the unscoped package name is not currently - published. `pnpm pack --dry-run` produced a tarball containing - `dist/**`, `executors.json`, `generators.json`, `LICENSE`, - `README.md`, and `CHANGELOG.md`, with no unexpected source files. - -### NXRUST-R5: Switch pilot consumer onto published package [Draft] - -- **Intent:** Remove the pilot consumer's in-place workspace link and +- **Resolution:** `@eddacraft/nxrust@0.1.0` published to npm on + 2026-05-08; `npm view @eddacraft/nxrust dist-tags` reports + `latest: 0.1.0`. Defects discovered post-publish are tracked in + NXRUST-R4.1. + +### NXRUST-R4.1: Patch release `@eddacraft/nxrust@0.1.1` [In Progress] + +- **Intent:** Ship a patch release fixing two defects surfaced in + 0.1.0 by the downstream Anvil consumer. 0.1.0 cannot be republished + (npm forbids overwriting a published version), so the fixes ship as + 0.1.1. +- **Expected Outcome:** `@eddacraft/nxrust@0.1.1` published with + `dist-tags.latest = 0.1.1`. Anvil's previously failing CI jobs + (Check/Format and Test/Unit Tests) re-run green against + `@eddacraft/nxrust@^0.1.1`. +- **Scope:** nxrust — `package.json` version bump, CHANGELOG entry, + `inferProjectConfig` (pin cargo package on every target), + `buildCargoArgs` (per-subcommand option allowlist), specs for both. + PR #6 against `main`. +- **Dependencies:** NXRUST-R4 +- **Validation:** `pnpm test` 47/47 green (including new specs); + `pnpm typecheck` clean; `pnpm build` clean; CI smoke green on PR #6; + `pnpm publish` from `main` after merge; Anvil's failing CI jobs + re-run green against the patched version. +- **Confidence:** high +- **Risks:** Anvil's CI may surface a third defect. If so, treat the + same way — patch nxrust, ship 0.1.2, before unblocking R5. +- **Defects fixed:** + - `cargo -p` got the Nx project name. When `@nx/js` claimed a + sibling `package.json`'s name (napi-rs bindings), the cargo + executor fed `-p @scope/name` to cargo and got back + `unexpected prerelease field`. Fixed by pinning + `{ package: pkg.name }` on every inferred target. + - `buildCargoArgs` emitted every option key as a cargo flag. + `nx run-many -t test --run --coverage` in a mixed JS+Rust + workspace leaked vitest's `--run` / `--coverage` into + `cargo test --run --coverage [object Object]`. Fixed by adding a + per-subcommand allowlist that's unioned with the base cargo keys; + everything outside that union is dropped. + +### NXRUST-R5: Switch consumer onto published package [Draft] + +- **Intent:** Remove the consumer's in-place workspace link and consume the published package instead, so the consumer can bump Nx independently -- **Expected Outcome:** Pilot's root `package.json` pulls - `nxrust@^0.1.0` from npm; the in-place link directory is deleted; +- **Expected Outcome:** Consumer root `package.json` pulls + `@eddacraft/nxrust@^0.1.0` from npm; the in-place link directory is deleted; `pnpm install && pnpm exec nx run-many -t test,lint,build` passes -- **Scope:** pilot consumer — root `package.json`, `pnpm-lock.yaml`, +- **Scope:** consumer workspace — root `package.json`, `pnpm-lock.yaml`, the in-place plugin directory (delete), `pnpm-workspace.yaml` if needed - **Dependencies:** NXRUST-R4 @@ -238,14 +270,14 @@ that gap. | Risk | Impact | Likelihood | Mitigation | |------|--------|------------|------------| -| Pilot exposes a defect that forces nxrust code changes mid-module | medium | medium | That's the point of the pilot — patch nxrust, re-link, keep going. Do not let "no code changes during pilot" become "suppress real defects". | +| Consumer validation exposes a defect that forces nxrust code changes mid-module | medium | medium | Patch nxrust, re-link, and rerun validation rather than suppressing real defects. | | npm scope not registered | medium | low | Verify before R4; fall back to a different scope or unscoped if unavailable | -| Pilot switchover PR pulls in unrelated changes | low | medium | Keep R5 to one PR, scoped strictly to package.json + lockfile + in-place plugin deletion | +| Consumer switchover PR pulls in unrelated changes | low | medium | Keep R5 to one PR, scoped strictly to package.json + lockfile + in-place plugin deletion | | `target/` caching defeats the affected story on rollout | high | medium | Narrow `outputs` per target; cache reports not binaries where binaries embed absolute paths | ## Decisions -- **D-M1:** Pilot first on the smallest crate (no internal deps) +- **D-M1:** Validate first on the smallest crate (no internal deps) rather than a crate that touches everything. *Accepted.* - **D-M2:** Publish and consume via npm, not via git ref. Git refs work but hide drift. *Accepted.* diff --git a/src/executors/build/executor.ts b/src/executors/build/executor.ts index ebf0453..515ee21 100644 --- a/src/executors/build/executor.ts +++ b/src/executors/build/executor.ts @@ -12,10 +12,19 @@ export interface BuildExecutorSchema extends BaseCargoOptions { 'all-targets'?: boolean; } +const BUILD_KEYS = new Set([ + 'lib', + 'bin', + 'bins', + 'example', + 'examples', + 'all-targets', +]); + export default async function buildExecutor( options: BuildExecutorSchema, context: ExecutorContext, ): Promise<{ success: boolean }> { - const args = buildCargoArgs('build', options, context); + const args = buildCargoArgs('build', options, context, BUILD_KEYS); return cargoCommand(...args); } diff --git a/src/executors/build/schema.json b/src/executors/build/schema.json index 455bd8b..8da7967 100644 --- a/src/executors/build/schema.json +++ b/src/executors/build/schema.json @@ -2,7 +2,7 @@ "$schema": "http://json-schema.org/schema", "version": 2, "outputCapture": "direct-nodejs", - "title": "nxrust:build", + "title": "@eddacraft/nxrust:build", "description": "Build a Rust crate with `cargo build`.", "type": "object", "properties": { diff --git a/src/executors/check/executor.ts b/src/executors/check/executor.ts index 946eff7..7bbbbcf 100644 --- a/src/executors/check/executor.ts +++ b/src/executors/check/executor.ts @@ -8,10 +8,12 @@ export interface CheckExecutorSchema extends BaseCargoOptions { tests?: boolean; } +const CHECK_KEYS = new Set(['all-targets', 'tests']); + export default async function checkExecutor( options: CheckExecutorSchema, context: ExecutorContext, ): Promise<{ success: boolean }> { - const args = buildCargoArgs('check', options, context); + const args = buildCargoArgs('check', options, context, CHECK_KEYS); return cargoCommand(...args); } diff --git a/src/executors/check/schema.json b/src/executors/check/schema.json index a22003d..cdd8de1 100644 --- a/src/executors/check/schema.json +++ b/src/executors/check/schema.json @@ -2,7 +2,7 @@ "$schema": "http://json-schema.org/schema", "version": 2, "outputCapture": "direct-nodejs", - "title": "nxrust:check", + "title": "@eddacraft/nxrust:check", "description": "Type-check a Rust crate with `cargo check`.", "type": "object", "properties": { diff --git a/src/executors/clippy/executor.ts b/src/executors/clippy/executor.ts index 6a75d96..d201d43 100644 --- a/src/executors/clippy/executor.ts +++ b/src/executors/clippy/executor.ts @@ -8,10 +8,12 @@ export interface ClippyExecutorSchema extends BaseCargoOptions { fix?: boolean; } +const CLIPPY_KEYS = new Set(['all-targets', 'fix']); + export default async function clippyExecutor( options: ClippyExecutorSchema, context: ExecutorContext, ): Promise<{ success: boolean }> { - const args = buildCargoArgs('clippy', options, context); + const args = buildCargoArgs('clippy', options, context, CLIPPY_KEYS); return cargoCommand(...args); } diff --git a/src/executors/clippy/schema.json b/src/executors/clippy/schema.json index 4f8133d..8ab2c83 100644 --- a/src/executors/clippy/schema.json +++ b/src/executors/clippy/schema.json @@ -2,7 +2,7 @@ "$schema": "http://json-schema.org/schema", "version": 2, "outputCapture": "direct-nodejs", - "title": "nxrust:clippy", + "title": "@eddacraft/nxrust:clippy", "description": "Lint a Rust crate with `cargo clippy`.", "type": "object", "properties": { diff --git a/src/executors/fmt/schema.json b/src/executors/fmt/schema.json index 2479b8d..22f1bb6 100644 --- a/src/executors/fmt/schema.json +++ b/src/executors/fmt/schema.json @@ -2,7 +2,7 @@ "$schema": "http://json-schema.org/schema", "version": 2, "outputCapture": "direct-nodejs", - "title": "nxrust:fmt", + "title": "@eddacraft/nxrust:fmt", "description": "Format a Rust crate with `cargo fmt`.", "type": "object", "properties": { diff --git a/src/executors/release-publish/schema.json b/src/executors/release-publish/schema.json index 64fe83e..127cd23 100644 --- a/src/executors/release-publish/schema.json +++ b/src/executors/release-publish/schema.json @@ -2,7 +2,7 @@ "$schema": "http://json-schema.org/schema", "version": 2, "outputCapture": "direct-nodejs", - "title": "nxrust:release-publish", + "title": "@eddacraft/nxrust:release-publish", "description": "Publish a Rust crate. Invoked by `nx release publish`.", "type": "object", "properties": { diff --git a/src/executors/run/executor.ts b/src/executors/run/executor.ts index 2d5c6c6..1a9eac5 100644 --- a/src/executors/run/executor.ts +++ b/src/executors/run/executor.ts @@ -8,10 +8,12 @@ export interface RunExecutorSchema extends BaseCargoOptions { example?: string; } +const RUN_KEYS = new Set(['bin', 'example']); + export default async function runExecutor( options: RunExecutorSchema, context: ExecutorContext, ): Promise<{ success: boolean }> { - const args = buildCargoArgs('run', options, context); + const args = buildCargoArgs('run', options, context, RUN_KEYS); return cargoCommand(...args); } diff --git a/src/executors/run/schema.json b/src/executors/run/schema.json index 9ba7e70..09ad381 100644 --- a/src/executors/run/schema.json +++ b/src/executors/run/schema.json @@ -2,7 +2,7 @@ "$schema": "http://json-schema.org/schema", "version": 2, "outputCapture": "direct-nodejs", - "title": "nxrust:run", + "title": "@eddacraft/nxrust:run", "description": "Run a Rust binary with `cargo run`.", "type": "object", "properties": { diff --git a/src/executors/test/executor.ts b/src/executors/test/executor.ts index f83fd94..8781864 100644 --- a/src/executors/test/executor.ts +++ b/src/executors/test/executor.ts @@ -15,10 +15,22 @@ export interface TestExecutorSchema extends BaseCargoOptions { 'no-fail-fast'?: boolean; } +const TEST_KEYS = new Set([ + 'doc', + 'lib', + 'bin', + 'bins', + 'test', + 'tests', + 'all-targets', + 'no-run', + 'no-fail-fast', +]); + export default async function testExecutor( options: TestExecutorSchema, context: ExecutorContext, ): Promise<{ success: boolean }> { - const args = buildCargoArgs('test', options, context); + const args = buildCargoArgs('test', options, context, TEST_KEYS); return cargoCommand(...args); } diff --git a/src/executors/test/schema.json b/src/executors/test/schema.json index 7934cd2..1bdaf85 100644 --- a/src/executors/test/schema.json +++ b/src/executors/test/schema.json @@ -2,7 +2,7 @@ "$schema": "http://json-schema.org/schema", "version": 2, "outputCapture": "direct-nodejs", - "title": "nxrust:test", + "title": "@eddacraft/nxrust:test", "description": "Test a Rust crate with `cargo test`.", "type": "object", "properties": { diff --git a/src/generators/binary/generator.ts b/src/generators/binary/generator.ts index 2b21932..35659f6 100644 --- a/src/generators/binary/generator.ts +++ b/src/generators/binary/generator.ts @@ -5,7 +5,7 @@ import type { CrateGeneratorSchema } from '../crate/schema'; export type BinaryGeneratorSchema = Omit; /** - * Alias for `nxrust:crate --bin`. Kept as a distinct generator so it shows up + * Alias for `@eddacraft/nxrust:crate --bin`. Kept as a distinct generator so it shows up * in `nx list` with its own description and `x-type: application` metadata. */ export default async function binaryGenerator( diff --git a/src/generators/binary/schema.json b/src/generators/binary/schema.json index f91b6a7..9cc1bd1 100644 --- a/src/generators/binary/schema.json +++ b/src/generators/binary/schema.json @@ -1,7 +1,7 @@ { "$schema": "http://json-schema.org/schema", "$id": "NxRustBinary", - "title": "nxrust:binary", + "title": "@eddacraft/nxrust:binary", "description": "Scaffold a new Cargo binary crate.", "type": "object", "properties": { diff --git a/src/generators/crate/schema.json b/src/generators/crate/schema.json index 4fd2363..dfb6f61 100644 --- a/src/generators/crate/schema.json +++ b/src/generators/crate/schema.json @@ -1,7 +1,7 @@ { "$schema": "http://json-schema.org/schema", "$id": "NxRustCrate", - "title": "nxrust:crate", + "title": "@eddacraft/nxrust:crate", "description": "Scaffold a new Cargo workspace-member crate.", "type": "object", "properties": { diff --git a/src/generators/init/schema.json b/src/generators/init/schema.json index 707aae4..a41e8c1 100644 --- a/src/generators/init/schema.json +++ b/src/generators/init/schema.json @@ -1,7 +1,7 @@ { "$schema": "http://json-schema.org/schema", "$id": "NxRustInit", - "title": "nxrust:init", + "title": "@eddacraft/nxrust:init", "description": "Initialise a Cargo workspace inside an Nx workspace.", "type": "object", "properties": { diff --git a/src/generators/library/generator.ts b/src/generators/library/generator.ts index 5b7752a..776cae4 100644 --- a/src/generators/library/generator.ts +++ b/src/generators/library/generator.ts @@ -4,7 +4,7 @@ import type { CrateGeneratorSchema } from '../crate/schema'; export type LibraryGeneratorSchema = Omit; -/** Alias for `nxrust:crate` (library is the default). */ +/** Alias for `@eddacraft/nxrust:crate` (library is the default). */ export default async function libraryGenerator( tree: Tree, options: LibraryGeneratorSchema, diff --git a/src/generators/library/schema.json b/src/generators/library/schema.json index 619296a..1282ed4 100644 --- a/src/generators/library/schema.json +++ b/src/generators/library/schema.json @@ -1,7 +1,7 @@ { "$schema": "http://json-schema.org/schema", "$id": "NxRustLibrary", - "title": "nxrust:library", + "title": "@eddacraft/nxrust:library", "description": "Scaffold a new Cargo library crate.", "type": "object", "properties": { diff --git a/src/generators/release-version/schema.json b/src/generators/release-version/schema.json index ccd49a3..f48b3ec 100644 --- a/src/generators/release-version/schema.json +++ b/src/generators/release-version/schema.json @@ -1,7 +1,7 @@ { "$schema": "http://json-schema.org/schema", "$id": "NxRustReleaseVersion", - "title": "nxrust:release-version", + "title": "@eddacraft/nxrust:release-version", "description": "Bump a Cargo crate's version in Cargo.toml. Invoked via `nx release version`.", "type": "object", "properties": { diff --git a/src/graph.spec.ts b/src/graph.spec.ts index ddb92f6..8135a42 100644 --- a/src/graph.spec.ts +++ b/src/graph.spec.ts @@ -273,6 +273,61 @@ describe('createDependencies', () => { }); }); +describe('inferred project targets', () => { + beforeEach(async () => { + const { __resetGraphCacheForTests } = await load(); + __resetGraphCacheForTests(); + await setStatMtimes({ + '/ws/Cargo.lock': 1, + '/ws/Cargo.toml': 1, + '/ws/crates/foo/Cargo.toml': 1, + }); + }); + + afterEach(() => { + vi.restoreAllMocks(); + }); + + it('pins the cargo package name on every cargo-backed target', async () => { + // Without an explicit `package` option, the executor falls back to the Nx + // project name. When `@nx/js` claims the project name from a sibling + // package.json (e.g. napi-rs bindings) the Nx name becomes `@scope/foo`, + // which cargo rejects when handed to `-p`. Pinning here keeps the cargo + // package decoupled from whatever Nx ends up calling the project. + const publishablePkg = pkg({ + name: 'foo', + targets: [ + { kind: ['bin'], crate_types: ['bin'], name: 'foo', src_path: '' }, + ], + }); + await setMetadata(md([publishablePkg])); + + const { createNodesV2 } = await load(); + const fn = createNodesV2[1] as ( + paths: readonly string[], + opts: unknown, + ctx: { workspaceRoot: string; nxJsonConfiguration: object }, + ) => Promise< + Array<[string, { projects: Record }> }]> + >; + + const result = await fn(['crates/foo/Cargo.toml'], {}, { + workspaceRoot: ws, + nxJsonConfiguration: {}, + }); + + const [, payload] = result[0]; + const project = payload.projects['crates/foo']; + const expected = ['build', 'check', 'clippy', 'fmt', 'fmt-check', 'test', 'run', 'nx-release-publish']; + for (const target of expected) { + expect( + project.targets[target]?.options?.package, + `target ${target} should pin the cargo package name`, + ).toBe('foo'); + } + }); +}); + describe('graph cache invalidation', () => { beforeEach(async () => { const { __resetGraphCacheForTests } = await load(); diff --git a/src/graph.ts b/src/graph.ts index b56fe8f..edb722a 100644 --- a/src/graph.ts +++ b/src/graph.ts @@ -246,26 +246,33 @@ function inferProjectConfig( const hasBin = pkg.targets.some((t) => t.kind.includes('bin')); const isPrivate = pkg.publish?.length === 0; + // Pin the cargo package name on every target. When another Nx plugin (e.g. + // `@nx/js` for napi-rs bindings) claims the project name from package.json, + // Nx renames the inferred project to the JS package name and the cargo + // executor would otherwise feed that scoped/prerelease string to + // `cargo -p`, which cargo rejects. + const pkgOpts = { package: pkg.name }; + const targets: ProjectConfiguration['targets'] = { - build: buildTargetConfig(), - check: checkTargetConfig(), - clippy: clippyTargetConfig(), + build: buildTargetConfig(pkgOpts), + check: checkTargetConfig(pkgOpts), + clippy: clippyTargetConfig(pkgOpts), // `fmt` rewrites files (uncached); `fmt-check` is the lint mode that // caches safely because its output is just an exit status. - fmt: fmtTargetConfig(), - 'fmt-check': fmtCheckTargetConfig(), - test: testTargetConfig(), + fmt: fmtTargetConfig(pkgOpts), + 'fmt-check': fmtCheckTargetConfig(pkgOpts), + test: testTargetConfig(pkgOpts), }; if (hasBin) { - targets.run = runTargetConfig(); + targets.run = runTargetConfig(pkgOpts); } if (!isPrivate) { targets['nx-release-publish'] = { dependsOn: ['^nx-release-publish'], - executor: 'nxrust:release-publish', - options: {}, + executor: '@eddacraft/nxrust:release-publish', + options: { ...pkgOpts }, }; } diff --git a/src/index.ts b/src/index.ts index 132a36b..022024c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,6 @@ /** - * Entry point for the `nxrust` Nx plugin. Re-exports the project-graph - * integration so `nx.json`'s `plugins: ["nxrust"]` wiring sees the + * Entry point for the `@eddacraft/nxrust` Nx plugin. Re-exports the project-graph + * integration so `nx.json`'s `plugins: ["@eddacraft/nxrust"]` wiring sees the * `createNodesV2` + `createDependencies` pair. */ export { createNodesV2, createDependencies } from './graph'; diff --git a/src/utils/build-command.spec.ts b/src/utils/build-command.spec.ts index af37af1..69ea994 100644 --- a/src/utils/build-command.spec.ts +++ b/src/utils/build-command.spec.ts @@ -39,12 +39,13 @@ describe('buildCargoArgs', () => { }); it('repeats non-features array flags', () => { - // `bin` isn't in BaseCargoOptions but the runtime iterator walks every - // own property, so we cast through unknown to exercise the general path. + // `bin` isn't in BaseCargoOptions; the build executor opts it in via the + // allowedKeys set. With that opt-in, repeated values must each get a flag. const args = buildCargoArgs( 'build', { bin: ['a', 'b'] } as unknown as Parameters[1], ctx, + new Set(['bin']), ); expect(args.filter((a) => a === '--bin')).toHaveLength(2); }); @@ -92,4 +93,37 @@ describe('buildCargoArgs', () => { expect(args).not.toContain('--target'); expect(args).not.toContain('--manifest'); }); + + it('drops keys outside the base + caller allowlist', () => { + // Simulates Nx merging vitest-style CLI args (`--run`, `--coverage`) into + // the test executor's options when `nx run-many -t test --run --coverage` + // is invoked in a mixed JS+Rust workspace. Without filtering, those keys + // landed on cargo's argv (e.g. `cargo test --run --coverage [object Object]`). + const args = buildCargoArgs( + 'test', + { + run: true, + coverage: [true, { thresholds: 80 }, { reporter: 'json' }], + release: true, + } as unknown as Parameters[1], + ctx, + new Set(['doc', 'lib']), + ); + expect(args).not.toContain('--run'); + expect(args).not.toContain('--coverage'); + expect(args).toContain('--release'); + }); + + it('keeps subcommand-specific keys passed via the allowlist', () => { + const args = buildCargoArgs( + 'test', + { 'no-fail-fast': true, doc: true } as unknown as Parameters< + typeof buildCargoArgs + >[1], + ctx, + new Set(['no-fail-fast', 'doc']), + ); + expect(args).toContain('--no-fail-fast'); + expect(args).toContain('--doc'); + }); }); diff --git a/src/utils/build-command.ts b/src/utils/build-command.ts index 42e1d0a..a7596de 100644 --- a/src/utils/build-command.ts +++ b/src/utils/build-command.ts @@ -3,6 +3,26 @@ import type { BaseCargoOptions } from '../models/base-options'; const SKIP_KEYS = new Set(['toolchain', 'args', 'package']); +/** + * Keys from `BaseCargoOptions` that every cargo-wrapping executor accepts. + * Always allowed alongside any subcommand-specific allowlist a caller passes. + */ +export const BASE_CARGO_KEYS: ReadonlySet = new Set([ + 'toolchain', + 'target', + 'profile', + 'release', + 'target-dir', + 'features', + 'all-features', + 'no-default-features', + 'locked', + 'frozen', + 'offline', + 'package', + 'args', +]); + /** * Convert `allTargets` → `all-targets`. Executor schemas use camelCase keys * because that's what Nx's schema validator prefers, but cargo expects @@ -25,12 +45,19 @@ function toKebabFlag(key: string): string { * repeats — plus passthrough `args` split between `cargo ` and the binary * under `--`. * + * `allowedKeys` is a per-subcommand allowlist that's unioned with + * `BASE_CARGO_KEYS`. Anything outside that union is dropped silently — Nx + * merges CLI args (e.g. vitest `--run`/`--coverage` from `nx run-many -t test`) + * into the executor's options, and forwarding them unfiltered produced cargo + * invocations like `cargo test --run --coverage [object Object]`. + * * Kept as a pure function so it's unit-testable without touching cargo. */ export function buildCargoArgs( subcommand: string, options: T, context: Pick, + allowedKeys: ReadonlySet = new Set(), ): string[] { // The iterator below uses Object.entries, which at runtime sees every own // enumerable property regardless of declared type. @@ -49,6 +76,7 @@ export function buildCargoArgs( for (const [rawKey, rawValue] of Object.entries(opts)) { if (SKIP_KEYS.has(rawKey)) continue; + if (!BASE_CARGO_KEYS.has(rawKey) && !allowedKeys.has(rawKey)) continue; if (rawValue === undefined || rawValue === null) continue; if (rawKey === 'release' && hasProfile) continue; diff --git a/src/utils/normalize-options.ts b/src/utils/normalize-options.ts index 3041fe2..71dfbf0 100644 --- a/src/utils/normalize-options.ts +++ b/src/utils/normalize-options.ts @@ -25,7 +25,7 @@ export type NormalizedGeneratorOptions = * actually wants. * * Layout: crates live at `/`, defaulting to `crates/`. - * This matches the anvil-001 layout and is the commonest Cargo convention. + * This matches the common Cargo workspace convention. */ // Cargo accepts `[a-zA-Z0-9_-]` package names starting with a letter. Enforce // the same rule at generator time so we don't write manifests cargo rejects. diff --git a/src/utils/target-configs.ts b/src/utils/target-configs.ts index 3fde88f..8be98d5 100644 --- a/src/utils/target-configs.ts +++ b/src/utils/target-configs.ts @@ -19,7 +19,7 @@ export function buildTargetConfig( options: AnyOpts = {}, ): TargetConfiguration { return { - executor: 'nxrust:build', + executor: '@eddacraft/nxrust:build', cache: true, outputs: BINARY_OUTPUTS, options, @@ -33,7 +33,7 @@ export function checkTargetConfig( options: AnyOpts = {}, ): TargetConfiguration { return { - executor: 'nxrust:check', + executor: '@eddacraft/nxrust:check', cache: true, outputs: [], options, @@ -44,7 +44,7 @@ export function clippyTargetConfig( options: AnyOpts = {}, ): TargetConfiguration { return { - executor: 'nxrust:clippy', + executor: '@eddacraft/nxrust:clippy', cache: true, outputs: [], options, @@ -59,7 +59,7 @@ export function fmtTargetConfig( options: AnyOpts = {}, ): TargetConfiguration { return { - executor: 'nxrust:fmt', + executor: '@eddacraft/nxrust:fmt', options, }; } @@ -72,7 +72,7 @@ export function fmtCheckTargetConfig( options: AnyOpts = {}, ): TargetConfiguration { return { - executor: 'nxrust:fmt', + executor: '@eddacraft/nxrust:fmt', cache: true, outputs: [], options: { check: true, ...options }, @@ -83,7 +83,7 @@ export function testTargetConfig( options: AnyOpts = {}, ): TargetConfiguration { return { - executor: 'nxrust:test', + executor: '@eddacraft/nxrust:test', cache: true, outputs: BINARY_OUTPUTS, options, @@ -97,7 +97,7 @@ export function runTargetConfig( options: AnyOpts = {}, ): TargetConfiguration { return { - executor: 'nxrust:run', + executor: '@eddacraft/nxrust:run', options, configurations: { production: { release: true }, diff --git a/tools/assert-release-branch.mjs b/tools/assert-release-branch.mjs new file mode 100644 index 0000000..809bf95 --- /dev/null +++ b/tools/assert-release-branch.mjs @@ -0,0 +1,25 @@ +import { execFileSync } from 'node:child_process'; + +const expected = 'main'; + +function currentBranch() { + if (process.env.GITHUB_REF_NAME) return process.env.GITHUB_REF_NAME; + try { + return execFileSync('git', ['branch', '--show-current'], { + encoding: 'utf8', + stdio: ['ignore', 'pipe', 'ignore'], + }).trim(); + } catch { + return ''; + } +} + +const actual = currentBranch(); + +if (actual !== expected) { + console.error( + `Refusing to publish @eddacraft/nxrust from "${actual || 'unknown'}". ` + + `Release publishes must run from "${expected}".`, + ); + process.exit(1); +} diff --git a/tools/e2e-smoke.mjs b/tools/e2e-smoke.mjs index bd76e62..994805f 100644 --- a/tools/e2e-smoke.mjs +++ b/tools/e2e-smoke.mjs @@ -6,7 +6,12 @@ const root = process.cwd(); const packDir = join(root, '.e2e-pack'); const fixtureDir = join(root, 'e2e', 'fixture'); const fixturePackage = join(fixtureDir, 'package.json'); -const tarball = '../../.e2e-pack/nxrust-0.1.0.tgz'; + +// Derive the tarball name from package.json so version bumps don't break the +// e2e smoke. `pnpm pack` writes `--.tgz`. +const pkg = JSON.parse(readFileSync(join(root, 'package.json'), 'utf8')); +const tarballName = `${pkg.name.replace('@', '').replace('/', '-')}-${pkg.version}.tgz`; +const tarball = `../../.e2e-pack/${tarballName}`; function run(command, args, options = {}) { const result = spawnSync(command, args, { @@ -21,7 +26,7 @@ function run(command, args, options = {}) { rmSync(packDir, { recursive: true, force: true }); rmSync(join(fixtureDir, '.nx'), { recursive: true, force: true }); -rmSync(join(fixtureDir, 'node_modules', 'nxrust'), { +rmSync(join(fixtureDir, 'node_modules', '@eddacraft', 'nxrust'), { recursive: true, force: true, });