From 4500d32a839b8df852178297ec1de41095313422 Mon Sep 17 00:00:00 2001 From: Alisue Date: Sun, 11 Oct 2026 13:55:00 +0900 Subject: [PATCH 1/3] chore(*): run the quality gate with just and drop flake-utils The gate was the `verify` npm script, while CI also checked the JSR manifests and the build on its own, so passing it locally did not mean CI would pass. `just ci` runs what CI's check job runs, plus a nixfmt check, and `test` forwards its arguments intact. `verify` goes away rather than drift from the gate. The flake lists its systems with nixpkgs.lib.genAttrs instead of flake-utils and is formatted with nixfmt, which the dev shell now provides along with just. The repository ignores its own .envrc, .direnv, result and worktrees instead of relying on a global ignore, and .gitattributes keeps checkouts LF on every OS. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01N3xvJ1TaozjYSE6eoyo9mq --- .claude/rules/repository.md | 2 +- .envrc.sample | 1 + .gitattributes | 3 + .gitignore | 4 ++ README.md | 25 ++++---- flake.lock | 34 ----------- flake.nix | 112 ++++++++++++++++++++---------------- justfile | 53 +++++++++++++++++ package.json | 1 - 9 files changed, 139 insertions(+), 96 deletions(-) create mode 100644 .gitattributes create mode 100644 justfile diff --git a/.claude/rules/repository.md b/.claude/rules/repository.md index 34c5f97..b87c109 100644 --- a/.claude/rules/repository.md +++ b/.claude/rules/repository.md @@ -7,7 +7,7 @@ Project-specific rules for the Probitas Packages monorepo. BEFORE reporting task completion, run and ensure zero errors/warnings: ```bash -bun run verify +just ci ``` ## Commit Conventions diff --git a/.envrc.sample b/.envrc.sample index 3550a30..11af9c3 100644 --- a/.envrc.sample +++ b/.envrc.sample @@ -1 +1,2 @@ +# Copy to .envrc and run `direnv allow` to enter the Nix dev shell automatically. use flake diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..ca5e98b --- /dev/null +++ b/.gitattributes @@ -0,0 +1,3 @@ +# Check text out with LF on every OS, so a Windows checkout builds and tests +# the same bytes as the others. +* text=auto eol=lf diff --git a/.gitignore b/.gitignore index 508fdf6..2c4028d 100644 --- a/.gitignore +++ b/.gitignore @@ -6,3 +6,7 @@ node_modules/ dist/ dist-pack/ packages/*/LICENSE +/.envrc +.direnv/ +result +/.claude/worktrees/ diff --git a/README.md b/README.md index 603d741..d5744d9 100644 --- a/README.md +++ b/README.md @@ -66,7 +66,8 @@ framework package with unified API for writing scenarios. ### Prerequisites -- Bun 1.4+ (or use Nix: `nix develop`) +- Nix (`nix develop`, or copy `.envrc.sample` to `.envrc` for direnv), which + provides Bun 1.4+ and just - Docker Compose (for integration testing services) ### Setup @@ -76,11 +77,11 @@ framework package with unified API for writing scenarios. git clone https://github.com/probitas-test/probitas-packages.git cd probitas-packages -# Enter development environment (optional, using Nix) +# Enter the development environment nix develop # Install dependencies -bun install +just install # Start integration test services docker compose up -d @@ -89,17 +90,17 @@ docker compose up -d ### Tasks ```bash -# Run all checks and tests (recommended before committing) -bun run verify +# Run everything CI checks (recommended before committing) +just ci # Individual tasks -bun run fmt # Format code (dprint) -bun run lint # Lint code (Biome) -bun run check # Type check (tsc) -bun run test # Run tests +just fmt # Format code (dprint, nixfmt) +just lint # Lint code (Biome) +just typecheck # Type check (tsc) +just test # Run tests -# Coverage -bun run test --coverage +# Coverage (lcov report in .coverage/) +just coverage ``` ### Integration Services @@ -167,7 +168,7 @@ docs(*): update copyright headers `client-http`) - `feat:` → minor bump, `fix:`/`docs:`/`refactor:` → patch bump - `BREAKING:` → major bump (except for `/unstable` scopes, always patch) -- Run `bun run verify` before committing +- Run `just ci` before committing See [`.claude/rules/repository.md`](./.claude/rules/repository.md) for detailed contribution guidelines. diff --git a/flake.lock b/flake.lock index a7d579d..644277d 100644 --- a/flake.lock +++ b/flake.lock @@ -1,23 +1,5 @@ { "nodes": { - "flake-utils": { - "inputs": { - "systems": "systems" - }, - "locked": { - "lastModified": 1731533236, - "narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=", - "owner": "numtide", - "repo": "flake-utils", - "rev": "11707dc2f618dd54ca8739b309ec4fc024de578b", - "type": "github" - }, - "original": { - "owner": "numtide", - "repo": "flake-utils", - "type": "github" - } - }, "nixpkgs": { "locked": { "lastModified": 1767364772, @@ -36,24 +18,8 @@ }, "root": { "inputs": { - "flake-utils": "flake-utils", "nixpkgs": "nixpkgs" } - }, - "systems": { - "locked": { - "lastModified": 1681028828, - "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=", - "owner": "nix-systems", - "repo": "default", - "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e", - "type": "github" - }, - "original": { - "owner": "nix-systems", - "repo": "default", - "type": "github" - } } }, "root": "root", diff --git a/flake.nix b/flake.nix index 0757a4b..23072e7 100644 --- a/flake.nix +++ b/flake.nix @@ -3,58 +3,74 @@ inputs = { nixpkgs.url = "github:NixOS/nixpkgs/nixpkgs-unstable"; - flake-utils.url = "github:numtide/flake-utils"; }; - outputs = { self, nixpkgs, flake-utils }: - flake-utils.lib.eachDefaultSystem (system: - let - pkgs = nixpkgs.legacyPackages.${system}; + outputs = + { self, nixpkgs }: + let + systems = [ + "x86_64-linux" + "aarch64-linux" + "x86_64-darwin" + "aarch64-darwin" + ]; + forAllSystems = f: nixpkgs.lib.genAttrs systems (system: f system); + in + { + devShells = forAllSystems ( + system: + let + pkgs = nixpkgs.legacyPackages.${system}; - # nixpkgs lags behind Bun releases, and gRPC server streaming over - # node:http2 needs Bun >= 1.4. Reuse the nixpkgs derivation (patching, - # completions) with the upstream release binary. - bunVersion = "1.4.3"; - bunSources = { - x86_64-linux = { - asset = "bun-linux-x64.zip"; - hash = "sha256-PnMFKNiXdfA/h/BcBnh8TfQJnQo38/JS57gpyyOBbhg="; + # nixpkgs lags behind Bun releases, and gRPC server streaming over + # node:http2 needs Bun >= 1.4. Reuse the nixpkgs derivation (patching, + # completions) with the upstream release binary. + bunVersion = "1.4.3"; + bunSources = { + x86_64-linux = { + asset = "bun-linux-x64.zip"; + hash = "sha256-PnMFKNiXdfA/h/BcBnh8TfQJnQo38/JS57gpyyOBbhg="; + }; + aarch64-linux = { + asset = "bun-linux-aarch64.zip"; + hash = "sha256-76mBPaXtckI7+Ef5FujSxHwNd2rdlyNUAmp14Q2pqiE="; + }; + x86_64-darwin = { + asset = "bun-darwin-x64.zip"; + hash = "sha256-YmYOZDjrhyJ4h3rMp34RUtXQMTJN82jUqBtCghFInS0="; + }; + aarch64-darwin = { + asset = "bun-darwin-aarch64.zip"; + hash = "sha256-gK/UwGm0am+o8+xSC+vHUKeEPkqNW5WD0prUNsAKNAM="; + }; }; - aarch64-linux = { - asset = "bun-linux-aarch64.zip"; - hash = "sha256-76mBPaXtckI7+Ef5FujSxHwNd2rdlyNUAmp14Q2pqiE="; - }; - x86_64-darwin = { - asset = "bun-darwin-x64.zip"; - hash = "sha256-YmYOZDjrhyJ4h3rMp34RUtXQMTJN82jUqBtCghFInS0="; - }; - aarch64-darwin = { - asset = "bun-darwin-aarch64.zip"; - hash = "sha256-gK/UwGm0am+o8+xSC+vHUKeEPkqNW5WD0prUNsAKNAM="; - }; - }; - bun = pkgs.bun.overrideAttrs (_: { - version = bunVersion; - src = pkgs.fetchurl { - url = "https://github.com/oven-sh/bun/releases/download/bun-v${bunVersion}/${bunSources.${system}.asset}"; - inherit (bunSources.${system}) hash; - }; - }); - in - { - devShells.default = pkgs.mkShell { - packages = [ - bun - ]; + bun = pkgs.bun.overrideAttrs (_: { + version = bunVersion; + src = pkgs.fetchurl { + url = "https://github.com/oven-sh/bun/releases/download/bun-v${bunVersion}/${bunSources.${system}.asset}"; + inherit (bunSources.${system}) hash; + }; + }); + in + { + default = pkgs.mkShell { + packages = [ + bun + pkgs.just + pkgs.nixfmt + ]; + + # Prebuilt native addons (DuckDB's duckdb.node) link libstdc++, which + # Nix's dynamic loader does not find on its own. + LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath [ pkgs.stdenv.cc.cc.lib ]; - # Prebuilt native addons (DuckDB's duckdb.node) link libstdc++, which - # Nix's dynamic loader does not find on its own. - LD_LIBRARY_PATH = pkgs.lib.makeLibraryPath [ pkgs.stdenv.cc.cc.lib ]; + shellHook = '' + echo "Entering Probitas Packages development environment" + ''; + }; + } + ); - shellHook = '' - echo "Entering Probitas Packages development environment" - ''; - }; - } - ); + formatter = forAllSystems (system: nixpkgs.legacyPackages.${system}.nixfmt); + }; } diff --git a/justfile b/justfile new file mode 100644 index 0000000..817c455 --- /dev/null +++ b/justfile @@ -0,0 +1,53 @@ +# Run `nix develop` (or use direnv) first so every tool is on PATH. + +set shell := ["bash", "-euo", "pipefail", "-c"] + +# List the recipes. +default: + @just --list + +# Install the dependencies exactly as bun.lock records them. +install: + bun install --frozen-lockfile + +# Format every source. +fmt: + bun run fmt + nixfmt $(git ls-files '*.nix') + +# Check formatting without changing files. +fmt-check: + bun run fmt:check + nixfmt --check $(git ls-files '*.nix') + +# Lint with Biome. +lint: + bun run lint + +# Type check with tsc. +typecheck: + bun run check + +# Check that the JSR manifests (deno.json) match package.json. +jsr-check: + bun scripts/sync_jsr.ts --check + +# Regenerate the JSR manifests (deno.json) from package.json. +jsr-sync: + bun run jsr:sync + +# Run the tests. Integration tests skip unless `docker compose up -d` is running. +[positional-arguments] +test *args: + bun run test "$@" + +# Run the tests and write an lcov report to .coverage/. +coverage: + bun run test --coverage --coverage-reporter=lcov --coverage-dir=.coverage + +# Build the packages for npm. +build: + bun run build + +# Everything CI checks. +ci: install fmt-check lint typecheck jsr-check test build diff --git a/package.json b/package.json index 27daf5d..728b255 100644 --- a/package.json +++ b/package.json @@ -12,7 +12,6 @@ "lint": "biome lint .", "check": "tsc -p .", "test": "bun test --timeout 60000 ./internal ./packages ./scripts", - "verify": "dprint check && biome lint . && tsc -p . && bun test --timeout 60000 ./internal ./packages ./scripts", "build": "bun scripts/build.ts", "pack": "bun scripts/pack.ts", "jsr:sync": "bun scripts/sync_jsr.ts" From 1c819ba8c0e756d1224023526801d634f1e276a1 Mon Sep 17 00:00:00 2001 From: Alisue Date: Sun, 11 Oct 2026 13:55:00 +0900 Subject: [PATCH 2/3] docs(*): keep the agent guide in AGENTS.md A CLAUDE.md anywhere in the tree makes Claude Code ignore AGENTS.md, and other agents read only AGENTS.md, so the guide lived where only one tool looked. Its content moves to a root AGENTS.md with the layer table, the quality gate (`just ci`), the layout, and the commit convention in use. The ADR and spec procedures (docs/adr/README.md, docs/spec/README.md) and the rules that make them get read land now, so records written from here on follow them. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01N3xvJ1TaozjYSE6eoyo9mq --- .claude/CLAUDE.md | 29 ---------------- .claude/rules/adr.md | 18 ++++++++++ .claude/rules/spec.md | 20 +++++++++++ AGENTS.md | 81 +++++++++++++++++++++++++++++++++++++++++++ docs/adr/README.md | 80 ++++++++++++++++++++++++++++++++++++++++++ docs/spec/README.md | 59 +++++++++++++++++++++++++++++++ 6 files changed, 258 insertions(+), 29 deletions(-) delete mode 100644 .claude/CLAUDE.md create mode 100644 .claude/rules/adr.md create mode 100644 .claude/rules/spec.md create mode 100644 AGENTS.md create mode 100644 docs/adr/README.md create mode 100644 docs/spec/README.md diff --git a/.claude/CLAUDE.md b/.claude/CLAUDE.md deleted file mode 100644 index 1ed91f8..0000000 --- a/.claude/CLAUDE.md +++ /dev/null @@ -1,29 +0,0 @@ -# Probitas Packages - -Monorepo for Probitas scenario-based testing framework and client libraries. - -## Quick Reference - -- **Runtime**: Bun 1.4+ (pinned in `flake.nix`) -- **Registries**: npm (`@probitas-test/*`) and JSR (`@probitas/*`) -- **Framework Entry Point**: `@probitas-test/probitas` (user-facing API) -- **Client Packages**: `@probitas-test/client-*` (HTTP, gRPC, SQL, etc.) - -## Repository Structure - -This monorepo combines: - -- **Framework Packages**: Builder, Runner, Reporter, Core, Discover, Expect, - etc. -- **Client Libraries**: HTTP, gRPC, SQL (PostgreSQL, MySQL), Redis, MongoDB, - etc. - -Note: `@probitas-test/probitas` (Entrypoint, CLI) is maintained separately. - -## Commands - -```bash -bun run verify # Run format check, lint, type check and tests (USE THIS) -bun run test # Run tests only -bun run fmt # Format with dprint -``` diff --git a/.claude/rules/adr.md b/.claude/rules/adr.md new file mode 100644 index 0000000..ebdb7ba --- /dev/null +++ b/.claude/rules/adr.md @@ -0,0 +1,18 @@ +--- +paths: "docs/adr/**" +--- + +# Writing an ADR + +Before writing, fixing or reviewing a record in `docs/adr/`, read +`docs/adr/README.md` and follow "How to write one" step by step. It is the whole +rule; this file only makes sure it is read at the moment of writing. + +The three steps people skip: + +- Write the title first, as one sentence. It is the decision; everything else + restates it or follows from it. +- Ask of every line: would changing this take a new record that supersedes this + one? If an ordinary change would do, the line is specification — leave it out. +- When fixing a record, rewrite it from the title. Do not edit the previous + draft. diff --git a/.claude/rules/spec.md b/.claude/rules/spec.md new file mode 100644 index 0000000..268130d --- /dev/null +++ b/.claude/rules/spec.md @@ -0,0 +1,20 @@ +--- +paths: "docs/spec/**" +--- + +# Writing a spec + +Before writing, fixing or reviewing a file in `docs/spec/`, read +`docs/spec/README.md` and follow "How to write one". It is the whole rule; this +file only makes sure it is read at the moment of writing. + +The checks people skip: + +- Ask of every line: can a test pin this down? Then it belongs in a test. Would + a user call a change to it a change in behaviour? If not, it is not spec. +- Numbered steps, mapping tables, other programs' file locations, exact strings + and ordering rules are candidates to take out — unless they describe a format + this project defines for others to write. +- A review fix that does not change what a user sees does not touch the spec. +- Do not copy the level of detail of neighbouring specs; they may be too + detailed themselves. diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..b97d49e --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,81 @@ +# Probitas Packages + +Monorepo containing core framework packages and client libraries for the +Probitas scenario-based testing framework. + +- **Runtime**: Bun 1.4+ (pinned in `flake.nix`) +- **Registries**: npm (`@probitas-test/*`) and JSR (`@probitas/*`) +- **Framework packages**: builder, runner, reporter, core, discover, expect, and + the other non-client packages +- **Client packages**: `@probitas-test/client-*` (HTTP, gRPC, SQL, Redis, + MongoDB, and the other protocols) + +`@probitas-test/probitas` (the user-facing entry point and CLI) is maintained +separately in [probitas](https://github.com/probitas-test/probitas). + +## How it is built + +- **Test first.** Red → green → refactor. Write the failing test before the code + that makes it pass. +- **Each layer holds one thing.** Put a fact in the one layer that owns it, and + nowhere else. + + | Layer | Holds | + | ------------ | ------------------------------------------------------------------ | + | `docs/adr/` | one decision and why it was made | + | `docs/spec/` | the behaviour users and other components can rely on | + | Tests | the details that pin the behaviour down: rules, boundaries, tables | + | Code | how it is done | + | Commits | why a change was made | + | Comments | why it is not done another way | + + Before writing, fixing or reviewing a record, read `docs/adr/README.md`; + before writing, fixing or reviewing a spec, read `docs/spec/README.md`. Follow + their writing order. +- **Dependencies are current.** Before adding or bumping one, look up its latest + release in the registry. Do not copy a version from memory or from another + repository. Client packages may depend only on what + [`.claude/rules/dependencies/client-packages.md`](.claude/rules/dependencies/client-packages.md) + allows. + +The coding rules live in `.claude/rules/` (design, development, TypeScript, +logging, package structure); each applies to the paths named in its header. + +## Quality gate + +Run `just ci` before reporting a change as done. CI runs the same recipe. + +Integration tests skip when their service is not reachable. Start the services +with `docker compose up -d` (`compose.yaml`) to run them locally; CI runs them +in the `test` job against service containers. When adding an integration test, +add its service to both `compose.yaml` and `.github/workflows/test.yml`. + +When touching `package.json` exports or dependencies, run `just jsr-sync` so the +`deno.json` files stay in step (`just ci` checks them). + +## Layout + +| Path | What lives there | +| -------------------- | -------------------------------------------------------------- | +| `packages/` | the published packages, one directory per package | +| `internal/` | private workspace packages (the `bun:test` adapter) | +| `scripts/` | build, pack, JSR sync and release scripts | +| `docs/` | ADRs and specs | +| `.github/workflows/` | `test.yml` (quality gate and integration tests), `publish.yml` | + +## Language + +- README, docs and commit messages: English +- Code comments, log and error messages: English + +## Commits + +[Conventional Commits](https://www.conventionalcommits.org/) with a required +scope: the package name without its registry scope (`feat(runner): …`, +`fix(client-http): …`), several joined with `,` (`fix(runner,core): …`), and +`(*)` for repository-wide changes. Release versions are derived from the commit +types, so the type matters: `feat` is minor, `!` or `BREAKING CHANGE:` is major, +everything else is patch, and a `/unstable` scope is always patch. + +[`.claude/rules/repository.md`](.claude/rules/repository.md) has the full +convention and how releases are made. diff --git a/docs/adr/README.md b/docs/adr/README.md new file mode 100644 index 0000000..7c56b15 --- /dev/null +++ b/docs/adr/README.md @@ -0,0 +1,80 @@ +# Architecture Decision Records + +The significant decisions behind Probitas Packages, one decision per file. + +## Conventions + +- Files are named `YYYYMMDD-.md` after the day the decision was made, so + listing the directory in name order lists the decisions in time order. Numbers + and an index are avoided because parallel changes would collide on them. +- The slug is the title in kebab case, so a file name and its title always lead + to each other. +- Each record has a status and the sections Context, Decision, Alternatives + considered and Consequences. +- An accepted record is not rewritten. When the decision changes, a new record + supersedes it, and both say so in their status. +- Records refer to each other by file name. A record does not link to the + specification, which keeps changing; the decision is complete within the + record. + +## What belongs in a record + +A record holds one decision, not everything decided while doing the work. + +For every line, ask: **if this changed, would it take a new record that +supersedes this one?** + +- Yes — it is part of the decision. Keep it. +- No, an ordinary change to the code or the specification would do — it is + specification or implementation. Leave it out. + +Formats, file locations, encodings, UI details and the order of steps almost +always answer no. + +## How to write one + +Write in this order, and do not judge details line by line along the way: + +1. **Title** — the decision as one sentence. This sentence is the decision. +2. **Decision** — restate the title, with only what the replacement test above + keeps. +3. **Alternatives considered** — only options that would have replaced the + title's decision, each with why it was not taken. +4. **Consequences** — only what follows directly from the decision. +5. **Context** — only what a reader needs to see why the title was the answer. + +When a record under review needs fixing, rewrite it from the title without +looking at the previous draft. Editing the draft keeps the details the new +decision no longer needs. + +Before committing, apply the replacement test to every line once more. + +## Example + +This is the size to aim for. + +```markdown +# Keep the user's words in a dictionary of their own + +Status: Accepted + +## Context + +Words the user registers were appended to the bundled dictionary. Every update +replaces the bundled dictionary, so the user's words were lost. + +## Decision + +The user's words live in a separate dictionary that updates never touch. + +## Alternatives considered + +- Merge the user's words back into the bundled dictionary after each update. A + failed merge still loses them, and the two sources become impossible to tell + apart. + +## Consequences + +- Updates can replace the bundled dictionary wholesale. +- Lookups consult two dictionaries. +``` diff --git a/docs/spec/README.md b/docs/spec/README.md new file mode 100644 index 0000000..0fd37c7 --- /dev/null +++ b/docs/spec/README.md @@ -0,0 +1,59 @@ +# Specification + +What Probitas Packages promises to whoever uses it, one topic per file. + +## Who reads it + +Users of Probitas Packages, and authors of other programs that work with it. A +spec holds what they can rely on, not everything that is true of the code. + +## What belongs in a spec + +Each layer holds one thing: + +| Layer | Holds | +| ----- | ------------------------------------------------------------------ | +| ADR | one decision and why it was made | +| Spec | the behaviour users and other components can rely on | +| Tests | the details that pin the behaviour down: rules, boundaries, tables | +| Code | how it is done | + +Tests hold the what in detail, so a spec is narrower than the tests. What stays +in a spec is what tests express poorly: the flow a user goes through, a promise +such as "existing data is never rewritten", the scope of what is handled. + +For every line, ask: + +1. **Can a test pin this down (and should one)?** Then it goes in a test, not + here. +2. **Would a change to this be called a change in behaviour by a user?** If not, + it is not spec. + +These usually fail the questions; when one appears, take it out: + +- numbered steps (an algorithm) +- mapping tables +- where another program keeps its files +- exact strings: headers, file names +- ordering rules, screen layout + +### The exception: formats Probitas Packages defines + +A format that Probitas Packages defines and that others write — its own data +files, its configuration — is read by outside authors, so the spec describes it +in full. A format someone else defines (another program's files) is not Probitas +Packages's to promise; reading it is left to the code and the tests. + +## How to write one + +1. Write first, briefly, what Probitas Packages promises. Leave the details out. +2. Write the details as test names. Tests named as sentences make the list of + tests a readable specification. +3. When review leads to a fix, do not touch the spec unless the behaviour a user + sees changes. + +Before committing, ask both questions of every line once more. + +A spec is rewritten in place, in the same change as the behaviour it describes. +It never carries "to be updated" notes, and it does not say why — the reasons +live in `docs/adr/`. From 881a07985bc11f5d486e1fca68561eb2eb7e0a77 Mon Sep 17 00:00:00 2001 From: Alisue Date: Sun, 11 Oct 2026 13:55:00 +0900 Subject: [PATCH 3/3] ci(*): run just ci in the dev shell and harden the workflows CI ran its checks step by step, each wrapped in `nix develop -c`, so the list could drift from what developers ran. The check job now loads the dev shell with nicknovitski/nix-develop and runs `just ci`; the test job keeps the service containers and uses the shell the same way. Both job names stay, as the ruleset requires them. Workflows read the repository by default and widen permissions per job, checkouts no longer keep credentials, pull request runs cancel superseded ones while main and publish runs finish, values reach scripts through env, the plan upload survives a re-run (--clobber), and the actions are on their latest majors. Co-Authored-By: Claude Opus 5.5 Claude-Session: https://claude.ai/code/session_01N3xvJ1TaozjYSE6eoyo9mq --- .github/workflows/publish.yml | 42 ++++++++++++++++++++++------------- .github/workflows/test.yml | 42 +++++++++++++++++++++++------------ 2 files changed, 54 insertions(+), 30 deletions(-) diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index bb3a717..de9ffd7 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -22,8 +22,7 @@ on: workflow_dispatch: permissions: - contents: write # attach versions.json and edit the release notes - id-token: write # npm trusted publishing and JSR OIDC + contents: read concurrency: group: publish @@ -33,6 +32,9 @@ jobs: publish: runs-on: ubuntu-latest timeout-minutes: 20 + permissions: + contents: write # attach versions.json and edit the release notes + id-token: write # npm trusted publishing and JSR OIDC env: GH_TOKEN: ${{ github.token }} steps: @@ -43,13 +45,16 @@ jobs: echo "NOTES=$RUNNER_TEMP/notes.md" } >> "$GITHUB_ENV" - - uses: actions/checkout@v6 + - uses: actions/checkout@v7 with: fetch-depth: 0 # the plan compares against the previous release tag + persist-credentials: false - - uses: nixbuild/nix-quick-install-action@v34 - - run: nix develop -c bun install --frozen-lockfile - - run: nix develop -c bun run check + - uses: nixbuild/nix-quick-install-action@v35 + # Load the development shell (flake.nix) into the job. + - uses: nicknovitski/nix-develop@v1 + - run: just install + - run: just typecheck - name: Decide what to publish id: mode @@ -92,10 +97,10 @@ jobs: echo "Reusing the plan attached to $TAG" exit 0 fi - nix develop -c bun scripts/release_plan.ts --tag "$TAG" --previous "$PREVIOUS" \ + bun scripts/release_plan.ts --tag "$TAG" --previous "$PREVIOUS" \ --out "$PLAN" --notes "$NOTES" if [ "$EVENT" = "release" ]; then - gh release upload "$TAG" "$PLAN" + gh release upload "$TAG" "$PLAN" --clobber gh release view "$TAG" --json body --jq .body > "$RUNNER_TEMP/body.md" printf '\n\n' >> "$RUNNER_TEMP/body.md" cat "$NOTES" >> "$RUNNER_TEMP/body.md" @@ -104,17 +109,20 @@ jobs: - name: Apply versions if: steps.mode.outputs.dry-run != 'true' + env: + DIST_TAG: ${{ steps.mode.outputs.dist-tag }} + RUN_NUMBER: ${{ github.run_number }} run: | - if [ "${{ steps.mode.outputs.dist-tag }}" = "next" ]; then - nix develop -c bun scripts/apply_versions.ts --plan "$PLAN" --suffix "next.${{ github.run_number }}" + if [ "$DIST_TAG" = "next" ]; then + bun scripts/apply_versions.ts --plan "$PLAN" --suffix "next.$RUN_NUMBER" else - nix develop -c bun scripts/apply_versions.ts --plan "$PLAN" + bun scripts/apply_versions.ts --plan "$PLAN" fi - nix develop -c bun run build - nix develop -c bun run pack - nix develop -c bun run jsr:sync + bun run build + bun run pack + bun run jsr:sync - - uses: actions/setup-node@v5 + - uses: actions/setup-node@v7 if: steps.mode.outputs.dry-run != 'true' with: node-version: 24 @@ -127,4 +135,6 @@ jobs: - name: Publish if: steps.mode.outputs.dry-run != 'true' - run: nix develop -c bun scripts/publish.ts --plan "$PLAN" --dist-tag "${{ steps.mode.outputs.dist-tag }}" + env: + DIST_TAG: ${{ steps.mode.outputs.dist-tag }} + run: bun scripts/publish.ts --plan "$PLAN" --dist-tag "$DIST_TAG" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 14dd693..5c02bbb 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -7,18 +7,28 @@ on: pull_request: workflow_dispatch: +# A pull request only needs its latest commit checked. A run on main waits +# for the one before it instead of cancelling it, so each one finishes. +concurrency: + group: test-${{ github.ref }} + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + +permissions: + contents: read + jobs: check: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 - - uses: nixbuild/nix-quick-install-action@v34 - - run: nix develop -c bun install --frozen-lockfile - - run: nix develop -c bun run fmt:check - - run: nix develop -c bun run lint - - run: nix develop -c bun run check - - run: nix develop -c bun scripts/sync_jsr.ts --check - - run: nix develop -c bun run build + - uses: actions/checkout@v7 + with: + persist-credentials: false + - uses: nixbuild/nix-quick-install-action@v35 + # Load the development shell (flake.nix) into the job. + - uses: nicknovitski/nix-develop@v1 + # The same gate as local development (justfile). Integration tests skip + # here; the test job runs them against the services. + - run: just ci test: runs-on: ubuntu-latest @@ -104,19 +114,23 @@ jobs: # - GitHub Actions services don't support custom command options # - Transactions require replica set mode (--replSet flag) # - supercharge/mongodb-github-action handles replica set initialization - - uses: supercharge/mongodb-github-action@1.12.0 + - uses: supercharge/mongodb-github-action@1.12.1 with: mongodb-version: "7.0" mongodb-replica-set: rs0 - - uses: actions/checkout@v6 - - uses: nixbuild/nix-quick-install-action@v34 + - uses: actions/checkout@v7 + with: + persist-credentials: false + - uses: nixbuild/nix-quick-install-action@v35 + # Load the development shell (flake.nix) into the job. + - uses: nicknovitski/nix-develop@v1 - - run: nix develop -c bun install --frozen-lockfile + - run: just install - - run: nix develop -c bun run test --coverage --coverage-reporter=lcov --coverage-dir=.coverage + - run: just coverage timeout-minutes: 5 - - uses: codecov/codecov-action@v5 + - uses: codecov/codecov-action@v7 with: files: ./.coverage/lcov.info slug: probitas-test/probitas-packages