Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Keeps the SHA-pinned actions in .github/workflows/* from rotting.
#
# Pinning by commit SHA is what stops an upstream action changing under us
# (release.yml runs with `contents: write`), but a pin no one refreshes is just
# a frozen, unpatched version. Dependabot opens a PR when a newer release
# exists, rewriting both the SHA and its trailing `# vX.Y.Z` comment.
#
# Deliberately github-actions only -- no `cargo` ecosystem entry. Rust
# dependency bumps are handled deliberately via the `/upgrade-packages`
# workflow described in CLAUDE.md, and a stream of cargo PRs here would bury
# the action-pin refreshes this file exists to surface.
version: 2
updates:
- package-ecosystem: github-actions
# "/" means .github/workflows/, not the repo root -- this is the path
# convention the github-actions ecosystem expects.
directory: "/"
schedule:
interval: weekly
commit-message:
prefix: ci
labels:
- dependencies
# One PR per action keeps an unrelated breakage from blocking the others.
open-pull-requests-limit: 5
48 changes: 32 additions & 16 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,17 @@
name: CI

# Actions are pinned to full commit SHAs (the trailing comment records the
# readable version); `.github/dependabot.yml` refreshes them by PR.
#
# dtolnay/rust-toolchain is the exception worth knowing about: its ref is not
# just a version, it *selects the toolchain* -- each branch (`stable`,
# `nightly`, `1.90.0`) ships an action.yml whose `toolchain` input defaults to
# that branch's own name. The SHA below is the tip of the `stable` branch, so
# it still installs stable Rust; repointing it at a commit from any other
# branch would silently change the Rust version with nothing else in the diff
# to show it. It publishes no release tags, so dependabot cannot bump it --
# refresh that one by hand.

on:
pull_request:
push:
Expand All @@ -18,12 +30,12 @@ jobs:
name: fmt + clippy
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
- uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4 # stable branch tip
with:
components: rustfmt, clippy
- uses: Swatinem/rust-cache@v2
- uses: extractions/setup-just@v3
- uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
- uses: extractions/setup-just@f8a3cce218d9f83db3a2ecd90e41ac3de6cdfd9b # v3.1.0
# `just lint` is the same command CLAUDE.md/README.md tell a developer
# to run locally, so the gate and the local check cannot drift.
- run: just lint
Expand All @@ -32,23 +44,27 @@ jobs:
name: workspace tests
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: Swatinem/rust-cache@v2
- uses: extractions/setup-just@v3
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
- uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4 # stable branch tip
- uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
- uses: extractions/setup-just@f8a3cce218d9f83db3a2ecd90e41ac3de6cdfd9b # v3.1.0
# `release.yml` builds with `--locked`; nothing else does. Without this a
# stale Cargo.lock passes every PR check and first fails at tag-push time.
# Covers all six committed locks (workspace + the excluded plugins).
- run: just check-lock
- run: just test

plugins:
name: example plugins
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
- uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4 # stable branch tip
with:
components: rustfmt, clippy
targets: wasm32-unknown-unknown
- uses: Swatinem/rust-cache@v2
- uses: extractions/setup-just@v3
- uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
- uses: extractions/setup-just@f8a3cce218d9f83db3a2ecd90e41ac3de6cdfd9b # v3.1.0
# plugins/* are excluded workspace members — invisible to the jobs above.
- run: just lint-plugins
- run: just test-plugins
Expand All @@ -57,12 +73,12 @@ jobs:
name: wasm end-to-end
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
- uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4 # stable branch tip
with:
targets: wasm32-unknown-unknown
- uses: Swatinem/rust-cache@v2
- uses: extractions/setup-just@v3
- uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
- uses: extractions/setup-just@f8a3cce218d9f83db3a2ecd90e41ac3de6cdfd9b # v3.1.0
# Builds weather.wasm, then runs both opt-in suites — the only coverage of
# the Extism host boundary. Both bind localhost mock servers; no network.
- run: just test-wasm
46 changes: 40 additions & 6 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,16 @@
name: Release

# Actions are pinned to full commit SHAs (the trailing comment records the
# readable version); `.github/dependabot.yml` refreshes them by PR. This
# workflow runs with `contents: write`, so it is the one where an upstream
# action changing under us actually matters.
#
# dtolnay/rust-toolchain's ref *selects the toolchain* -- each branch
# (`stable`, `nightly`, `1.90.0`) ships an action.yml defaulting `toolchain` to
# that branch's name -- so the SHA below is the tip of the `stable` branch.
# Repointing it at another branch's commit would silently change the Rust
# version. It has no release tags, so dependabot cannot bump it.

on:
push:
tags: ['v*']
Expand Down Expand Up @@ -38,13 +49,13 @@ jobs:
# from source — an aarch64 binary will not run there. Revisit if a
# supported Intel runner label reappears.
steps:
- uses: actions/checkout@v4
- uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0

- uses: dtolnay/rust-toolchain@stable
- uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4 # stable branch tip
with:
targets: ${{ matrix.target }}

- uses: Swatinem/rust-cache@v2
- uses: Swatinem/rust-cache@c19371144df3bb44fab255c43d04cbc2ab54d1c4 # v2.9.1
with:
key: ${{ matrix.target }}

Expand All @@ -56,6 +67,29 @@ jobs:
- name: Build
run: cargo build --release --locked --target ${{ matrix.target }} -p rustline

# Nothing else ties the pushed tag to the version compiled into the
# binary: tarballs are named from GITHUB_REF_NAME while the binary
# reports CARGO_PKG_VERSION, so a `v0.2.0` tag would happily ship a
# binary that identifies itself as 0.1.0. Ask the freshly built binary
# what it thinks it is rather than re-reading Cargo.toml -- that checks
# the artifact actually being shipped, and every target is native to its
# runner (the Package step below already runs it to emit completions).
#
# Only meaningful on a tag: a `workflow_dispatch` dry run is on a branch,
# whose name is not a version, so it is skipped rather than failed.
- name: Verify tag matches binary version
if: github.ref_type == 'tag'
shell: bash
run: |
set -euo pipefail
bin="target/${{ matrix.target }}/release/rustline"
binary_version="$("$bin" --version | awk '{print $2}')"
if [ "${GITHUB_REF_NAME}" != "v${binary_version}" ]; then
echo "::error::tag ${GITHUB_REF_NAME} does not match binary version ${binary_version} (expected tag v${binary_version}); bump [workspace.package] version in Cargo.toml or retag"
exit 1
fi
echo "tag ${GITHUB_REF_NAME} matches binary version ${binary_version}"

- name: Package
shell: bash
run: |
Expand Down Expand Up @@ -87,7 +121,7 @@ jobs:

tar -C dist -czf "dist/${stage}.tar.gz" "${stage}"

- uses: actions/upload-artifact@v4
- uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
with:
name: rustline-${{ matrix.target }}
path: dist/*.tar.gz
Expand All @@ -98,7 +132,7 @@ jobs:
needs: build
runs-on: ubuntu-latest
steps:
- uses: actions/download-artifact@v4
- uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093 # v4.3.0
with:
path: artifacts
merge-multiple: true
Expand All @@ -116,7 +150,7 @@ jobs:
# other step above (build, package, checksum) still runs and can be
# inspected via the uploaded artifacts, so the whole pipeline gets
# exercised without creating a real GitHub release.
- uses: softprops/action-gh-release@v2
- uses: softprops/action-gh-release@3bb12739c298aeb8a4eeaf626c5b8d85266b0e65 # v2.6.2
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/')
with:
files: |
Expand Down
45 changes: 40 additions & 5 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -2472,7 +2472,12 @@ branch on platform.
## Development

- **`just`** recipes: `just build`, `just test` (hermetic — no wasm toolchain
needed), `just lint`, `just preview` (colour preview via `cargo run --`, live
needed), `just lint`, `just check-lock` (verifies all **six** committed
`Cargo.lock` files — the workspace plus each of the five excluded
`plugins/*` — are up to date, via `cargo metadata --locked`, which resolves
without compiling and so runs in about a second; deliberately its own recipe
rather than `--locked` bolted onto `test`/`lint`, so a local `just test`
keeps auto-refreshing the lock as before), `just preview` (colour preview via `cargo run --`, live
tmux context when inside tmux, else samples — needs a Nerd/powerline font for
the glyphs), `just build-plugin NAME` (builds `plugins/<NAME>` for
`wasm32-unknown-unknown` and installs `<NAME>.wasm` into the plugin dir —
Expand Down Expand Up @@ -2517,16 +2522,37 @@ branch on platform.
wrapper around an existing `just` recipe so the gate and the documented
local command can't drift apart: `lint` → `just lint`, `test` → `just test`,
`plugins` → `just lint-plugins` + `just test-plugins`, and `wasm-e2e` →
`just test-wasm`. Toolchain via `dtolnay/rust-toolchain@stable` +
`Swatinem/rust-cache@v2`; `just` itself via `extractions/setup-just@v3`.
`just test-wasm`. The `test` job runs `just check-lock` ahead of `just test`:
`release.yml` builds with `--locked` and nothing else did, so before this a
stale `Cargo.lock` passed every PR check and first failed at tag-push time —
the most expensive moment to discover it. Toolchain via
`dtolnay/rust-toolchain` + `Swatinem/rust-cache`; `just` itself via
`extractions/setup-just`.
Deliberately no `rust-toolchain.toml` (would change every developer's local
toolchain resolution) and no workspace-level `RUSTFLAGS: -D warnings`
(redundant — `just lint`'s own clippy `-D warnings` already enforces the
zero-warning bar). A `v*` tag push instead triggers
`.github/workflows/release.yml` — see the Roadmap entry below for what it
builds/publishes and README's install section for verifying a downloaded
tarball against `SHA256SUMS`.
- Commit `Cargo.lock` alongside any dependency change.
- **Actions are pinned to full commit SHAs** in both workflows, each with a
trailing `# vX.Y.Z` comment, and `.github/dependabot.yml` (github-actions
ecosystem, weekly) opens PRs to refresh them — a pin nothing refreshes is
just a frozen unpatched version. Dependabot is deliberately **not** given a
`cargo` entry: Rust bumps go through the `/upgrade-packages` flow, and cargo
PRs would bury the action refreshes. One wrinkle worth knowing before
touching those pins: **`dtolnay/rust-toolchain`'s ref selects the
toolchain** — each branch (`stable`, `nightly`, `1.90.0`) ships an
`action.yml` whose `toolchain` input defaults to that branch's own name — so
its pin is the tip of the **`stable` branch**, and repointing it at a commit
from another branch would silently change the Rust version with nothing in
the diff to say so. It publishes no release tags, so dependabot cannot bump
that one; refresh it by hand. Note this pins the *action code*, not the Rust
version: `stable` still resolves to whatever stable is on the day a job runs,
so a new stable release can still redden CI with no repo change.
- Commit `Cargo.lock` alongside any dependency change — `just check-lock` (run
in CI's `test` job) fails the build if you forget, for the workspace lock and
all five plugin locks alike.
- Tests are TDD unit tests in each core module (incl. the powerline renderer and
the ANSI transcoder) plus `crates/rustline/tests/smoke.rs` integration tests.
`rustline-wasm` adds unit tests for allow-patterns, path sandboxing, and
Expand Down Expand Up @@ -2907,7 +2933,16 @@ branch on platform.
`x86_64-apple-darwin` leg — GitHub's `macos-13` label no longer picks up
jobs (a dry run sat queued indefinitely while the other legs finished),
and since `publish` declares `needs: build`, a never-scheduled leg would
hang the entire release rather than fail it.
hang the entire release rather than fail it. A **`Verify tag matches
binary version`** step sits between Build and Package on every leg:
tarballs are named from `GITHUB_REF_NAME` while the binary reports
`CARGO_PKG_VERSION`, so nothing otherwise stopped a `v0.2.0` tag shipping a
binary that identifies itself as `0.1.0`. It asks the freshly built binary
(`"$bin" --version`) rather than re-reading `Cargo.toml`, so it checks the
artifact actually being shipped — free, since Package already runs that
binary to emit completions. Gated on `github.ref_type == 'tag'`, because a
`workflow_dispatch` dry run is on a *branch* whose name is not a version
and would otherwise fail every time.
- Per-widget richer customization; naming the widget in the panic-guard `warn!`.
- Range-on-binding — today a `run`/`open_url` click binding only fires on a
widget that already emits a clickable range (i.e. has a non-empty
Expand Down
5 changes: 4 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -759,7 +759,10 @@ just preview

Other recipes: `just build`, `just test`, `just lint`, `just lint-plugins` and
`just test-plugins` (fmt/clippy/tests for the excluded example plugins under
`plugins/*`, which the root `cargo` commands never see).
`plugins/*`, which the root `cargo` commands never see), and `just check-lock`
(verifies every committed `Cargo.lock` — the workspace's plus each plugin's —
is up to date; CI runs it, so it is what catches a forgotten lock refresh
before a release build with `--locked` would).

## Benchmarking

Expand Down
18 changes: 18 additions & 0 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,24 @@ build:
test:
cargo test --workspace

# Verify every committed Cargo.lock is up to date (workspace + the five
# excluded example plugins, which each carry their own lock).
#
# `release.yml` builds with `--locked`, but `just test`/`just lint` do not --
# so without this gate a stale lock passes every PR check and only fails at
# tag-push time, the one moment it is most expensive to discover. Kept as its
# own recipe rather than adding `--locked` to `test`/`lint` so local runs keep
# their normal auto-refresh behaviour; `cargo metadata` resolves the graph
# without compiling, so this costs seconds.
check-lock:
#!/usr/bin/env bash
set -euo pipefail
cargo metadata --locked --format-version 1 --manifest-path Cargo.toml >/dev/null
for p in {{plugins}}; do
cargo metadata --locked --format-version 1 \
--manifest-path "plugins/$p/Cargo.toml" >/dev/null
done

# Build the weather plugin and run the end-to-end WASM host tests (opt-in)
test-wasm: build-weather
cargo test -p rustline-wasm --features wasm-e2e --test e2e
Expand Down
Loading