Skip to content

publish: runner-run crate to crates.io#8

Open
kjanat wants to merge 47 commits intomasterfrom
claude/setup-crates-publishing-eYir9
Open

publish: runner-run crate to crates.io#8
kjanat wants to merge 47 commits intomasterfrom
claude/setup-crates-publishing-eYir9

Conversation

@kjanat
Copy link
Copy Markdown
Owner

@kjanat kjanat commented May 5, 2026

Summary

This PR adds support for publishing the runner crate to crates.io and renames the package to runner-run to avoid conflicts with existing crates.

Key Changes

  • Renamed package from runner to runner-run in Cargo.toml to comply with crates.io naming requirements
  • Added library target configuration in Cargo.toml to expose the crate as a library while maintaining the binary
  • Added crates-release workflow (.github/workflows/crates-release.yml) that:
    • Publishes to crates.io after successful tag-triggered releases
    • Supports manual dispatch with optional dry-run verification
    • Validates that git tags match Cargo.toml version before publishing
    • Uses trusted trigger metadata (never build-time scripts) for release tag sourcing
    • Mirrors the security and structure of the existing npm-release workflow

Implementation Details

  • The workflow is triggered either by a successful release.yml workflow run on version tags (starting with 'v') or via manual workflow_dispatch
  • Version verification ensures the git tag matches the version in Cargo.toml before any publish attempt
  • Dry-run is always executed for verification; actual publish to crates.io only occurs when not in dry-run mode
  • Uses sparse-checkout pattern with trusted scripts from the default branch for security
  • Requires CARGO_REGISTRY_TOKEN secret for actual crate publication

https://claude.ai/code/session_014R4NQ7rUaJVZ2s36Kxe4WT

Comment thread .github/workflows/crates-release.yml Fixed
Comment thread .github/workflows/crates-release.yml Fixed
Comment thread .github/workflows/crates-release.yml Fixed
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 5, 2026

Review Change Stack

📝 Walkthrough
  • Rename package in Cargo.toml from runnerrunner-run to avoid crates.io name conflicts while preserving the library crate name runner ([lib] name = "runner") so existing runner::... imports remain valid.
  • Bump package version 0.6.0 → 0.7.0 in Cargo.toml.
  • Add an explicit library target ([lib] path = "src/lib.rs", name = "runner") alongside the existing binary so the crate can be consumed as a library while retaining the CLI binary (keep default-run = "runner" and existing [[bin]] entries).
  • Add GitHub Actions workflow .github/workflows/crates-release.yml to publish the crate to crates.io:
    • Triggered by published GitHub releases (version tags, e.g. v*) and by manual workflow_dispatch with tag and optional dry-run.
    • Derives RELEASE_TAG from trusted trigger metadata (manual inputs.tag or github.event.release.tag_name) and pins checkout to refs/tags/<tag> to avoid ambiguous tag/branch resolution.
    • Uses sparse-checkout and trusted trigger metadata to avoid executing build-time scripts from untrusted checkouts and mirrors the security/structure of the existing npm-release workflow.
    • Verifies the release tag (stripping a leading v) matches the runner-run version in Cargo.toml using cargo metadata + jq.
    • Always runs cargo publish --locked --dry-run for verification; performs the real cargo publish --locked only when not in dry-run mode and when CARGO_REGISTRY_TOKEN is provided.
    • Adds an early job-level HAS_CARGO_REGISTRY_TOKEN check to fail fast with a clear error when the registry token is missing.
  • Update CHANGELOG.md with an Unreleased post-release checklist and a 0.7.0 release entry documenting the crates.io publishing workflow, package rename, and related build/release changes.
  • Add supporting project and tooling updates required for the rename/publish (config, scripts, site files, CI refinements) while preserving public API stability for consumers of the runner library.

Walkthrough

Package renamed from runnerrunner-run and [lib] added with name = "runner". Adds cargo-alias discovery/expansion (new tool module, tests), integrates aliases into detection and run/list, introduces a crates.io crates-release workflow that verifies tag vs Cargo.toml and performs a locked dry-run before conditional publish, and ships a new static site plus build/dev tooling and CI/editor config updates.

Changes

Crate Publication and Release Automation

Layer / File(s) Summary
Data Shape / Package Manifest
Cargo.toml
name changed to runner-run; version bumped to 0.7.0; new [lib] name = "runner" path = "src/lib.rs"; toml dependency added; Clippy global level tightened to deny.
Cargo aliases core
src/tool/cargo_aliases.rs
New module walks .cargo/config{,.toml} chain, parses [alias], tokenizes values, merges precedence, recursively expands aliases with cycle/depth guard, exposes extract_tasks, find_configs, find_anchor, and run_cmd; unit tests added.
Types & Module Exposure
src/types.rs, src/tool/mod.rs
Adds TaskSource::CargoAliases, updates label/from_label/display_order, and exports pub(crate) mod cargo_aliases.
Detection & CLI wiring
src/detect.rs, src/cmd/{list,run}.rs
Detection appends CargoAliases tasks when Cargo present; list and run resolve anchors and delegate command construction to cargo_aliases.
Completion & Binaries
src/complete/grouped.zsh, bin/*
Zsh grouped completion initializes per-group var; bin/run and bin/runner now invoke cargo run --release with CARGO_TERM_QUIET=true and manifest path forwarding.
Site / Static Assets & Tooling
site/*
Adds static site: site/src/index.html, 404.html, styles, site/build.ts, site/dev.ts, site/package.json, tsconfig, biome.json, wrangler.jsonc, site/.dprint.json, public _headers/robots, and README documenting build/deploy; build exports meta and DistFile types.
Workflows / CI
.github/workflows/*
Adds crates-release.yml for crates.io publishing with tag/version verification and scoped token publish; refactors npm-release.yml, release-dryrun.yml, and release.yml to use top-level env, case(...), github.token, matrix/env improvements, and retention tweaks.
Build/Test Automation
justfile, npm/scripts/build-packages.ts
justfile tasks rewritten to use cargo metadata; readCargoManifest() now uses cargo metadata selecting workspace default member; build-packages/test-release logic updated.
Editor / Formatter / Ignore configs
.cargo/config.toml, .dprint.json, .rustfmt.toml, .gitignore, .zed/settings.json
Adds Cargo aliases and [term].hyperlinks = false; updates dprint excludes/plugins and adds site/.dprint.json; populates rustfmt options; expands .gitignore; maps SVG to *.ico in Zed settings; README badges updated.
Changelog / README
CHANGELOG.md, README.md
CHANGELOG adds Unreleased checklist and 0.7.0 release notes documenting the publish workflow, package rename, site, and other changes; README adds npm and license badges.

"Aye! The crate's renamed, the aliases combed, the site be built, and CI be set to check the tag afore it pushes to the sea. Ship it proper, or walk the plank!"

🚥 Pre-merge checks | ✅ 7 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Semver Version Bump Validation ⚠️ Warning Package renamed from 'runner' to 'runner-run' (breaking change) but version bumped only from 0.6.1 to 0.7.0 (MINOR). Renaming a published crate requires MAJOR version bump per SemVer. Bump version to 1.0.0 instead of 0.7.0 to reflect the breaking package name change that affects all downstream users of the crate on crates.io.
✅ Passed checks (7 passed)
Check name Status Explanation
Title check ✅ Passed The title 'publish: runner-run crate to crates.io' clearly describes the main objective of the PR: publishing the runner-run crate to crates.io. It follows conventional commit guidelines with a descriptive verb prefix and concisely conveys the key change.
Description check ✅ Passed The description comprehensively explains the PR's purpose, key changes, and implementation details, directly relating to the changeset which adds crates.io publishing support and renames the package to runner-run.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Changelog Update ✅ Passed CHANGELOG.md properly updated with version 0.7.0. Section header matches required format. Multiple source files modified with changes documented under Added/Changed subsections.
Agents.Md Documentation Updated ✅ Passed No AGENTS.md file exists in the repository. The custom check only applies if such a file exists and relevant code changes occur. Condition unmet—check is not applicable.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
⚔️ Resolve merge conflicts
  • Resolve merge conflict in branch claude/setup-crates-publishing-eYir9
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch claude/setup-crates-publishing-eYir9

Comment @coderabbitai help to get the list of available commands and usage tips.

@kjanat kjanat changed the title Publish runner crate to crates.io feat: publish runner-run crate to crates.io May 5, 2026
@kjanat kjanat changed the title feat: publish runner-run crate to crates.io publish: runner-run crate to crates.io May 5, 2026
@kjanat kjanat self-assigned this May 5, 2026
coderabbitai[bot]

This comment was marked as resolved.

@gitguardian
Copy link
Copy Markdown

gitguardian Bot commented May 5, 2026

️✅ There are no secrets present in this pull request anymore.

If these secrets were true positive and are still valid, we highly recommend you to revoke them.
While these secrets were previously flagged, we no longer have a reference to the
specific commits where they were detected. Once a secret has been leaked into a git
repository, you should consider it compromised, even if it was deleted immediately.
Find here more information about risks.


🦉 GitGuardian detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request.

@cloudflare-workers-and-pages
Copy link
Copy Markdown

cloudflare-workers-and-pages Bot commented May 5, 2026

Deploying with  Cloudflare Workers  Cloudflare Workers

The latest updates on your project. Learn more about integrating Git with Workers.

Status Name Latest Commit Preview URL Updated (UTC)
✅ Deployment successful!
View logs
runner 01dac4f Commit Preview URL

Branch Preview URL
May 09 2026, 01:31 AM

@kjanat kjanat added the enhancement New feature or request label May 5, 2026
@kjanat kjanat marked this pull request as ready for review May 8, 2026 02:34
coderabbitai[bot]

This comment was marked as low quality.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
Cargo.toml (1)

2-3: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Arrr — bump this to a MAJOR release for the crate rename.

Line 2 changes the published crate identity (runnerrunner-run), which is a breaking change for consumers; Line 3 at 0.7.0 under-signals that break.

Suggested patch
-version      = "0.7.0"
+version      = "1.0.0"

As per coding guidelines, “If the PR introduces breaking changes (removal or renaming of public APIs, changes to function signatures, deleted exported symbols, or incompatible config changes), MAJOR must increment.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@Cargo.toml` around lines 2 - 3, The crate rename in Cargo.toml changes the
published package identity (name = "runner-run") which is a breaking change, so
update the version string from "0.7.0" to a new MAJOR release (e.g., "1.0.0" or
appropriate next major) to signal the breaking change; modify the version field
in Cargo.toml to the new MAJOR version and ensure any related CI/release
metadata that reads the version is updated to match.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@CHANGELOG.md`:
- Around line 13-14: Update the `[Unreleased]` compare-link in the CHANGELOG
footer so it no longer points to `v0.6.0...HEAD`; replace the old tag portion
with the new release tag (e.g., `vX.Y.Z...HEAD`) so the `[Unreleased]` link
correctly compares the new tag to HEAD. Locate the `[Unreleased]` link in the
footer (the compare URL currently containing `v0.6.0...HEAD`) and update it to
`vX.Y.Z...HEAD`, ensuring the bracketed `[Unreleased]` text remains unchanged.

---

Duplicate comments:
In `@Cargo.toml`:
- Around line 2-3: The crate rename in Cargo.toml changes the published package
identity (name = "runner-run") which is a breaking change, so update the version
string from "0.7.0" to a new MAJOR release (e.g., "1.0.0" or appropriate next
major) to signal the breaking change; modify the version field in Cargo.toml to
the new MAJOR version and ensure any related CI/release metadata that reads the
version is updated to match.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: efcc71f0-539e-4ddb-a3e2-ff1c0099a799

📥 Commits

Reviewing files that changed from the base of the PR and between 72ebf53 and 8a785e0.

⛔ Files ignored due to path filters (1)
  • Cargo.lock is excluded by !**/*.lock
📒 Files selected for processing (2)
  • CHANGELOG.md
  • Cargo.toml
📜 Review details
⏰ Context from checks skipped due to timeout of 18000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
  • GitHub Check: verify
  • GitHub Check: Workers Builds: runner
  • GitHub Check: Analyze (actions)
  • GitHub Check: Analyze (javascript-typescript)
  • GitHub Check: Analyze (rust)
🧰 Additional context used
📓 Path-based instructions (2)
@(package.json|pyproject.toml|setup.py|Cargo.toml|go.mod|pom.xml|build.gradle|VERSION)

📄 CodeRabbit inference engine (Custom checks)

@(package.json|pyproject.toml|setup.py|Cargo.toml|go.mod|pom.xml|build.gradle|VERSION): If any source code files (excluding tests, docs, CI, markdown, or comments-only changes) are modified, then a version field MUST be updated in one of the following files if present in the repo: package.json, pyproject.toml, setup.py, Cargo.toml, go.mod, pom.xml, build.gradle, or a VERSION file.
The new version MUST follow SemVer (MAJOR.MINOR.PATCH). If the PR introduces breaking changes (removal or renaming of public APIs, changes to function signatures, deleted exported symbols, or incompatible config changes), MAJOR must increment. If it adds backward-compatible functionality, MINOR must increment. If it only fixes bugs without changing public APIs, PATCH must increment.

Files:

  • Cargo.toml
**/CHANGELOG.md

📄 CodeRabbit inference engine (Custom checks)

**/CHANGELOG.md: If any source code files (excluding tests, docs, CI, markdown, or comments-only changes) are modified, CHANGELOG.md MUST also be modified in the same PR.
If a version bump is detected, CHANGELOG.md MUST contain a new section header matching the exact new version number in the format: '## [X.Y.Z] - YYYY-MM-DD'.
If NO version bump is detected, the changes in the PR MUST be added under the existing '## [Unreleased]' section in CHANGELOG.md. The entry MUST describe the changes (e.g., Added, Changed, Fixed, Removed).

Files:

  • CHANGELOG.md
🧠 Learnings (1)
📚 Learning: 2026-03-26T16:14:15.754Z
Learnt from: kjanat
Repo: kjanat/runner PR: 1
File: src/tool/go_task.rs:64-81
Timestamp: 2026-03-26T16:14:15.754Z
Learning: If code uses let-chains (e.g., `if let Some(x) = foo && ... && let Some(y) = bar`), ensure the crate’s `Cargo.toml` sets `package.edition = "2024"`. Rust 2021/earlier should not be used with let-chains; require 2024 specifically for compilation.

Applied to files:

  • Cargo.toml
🔇 Additional comments (1)
Cargo.toml (1)

28-30: Good call: [lib] name = "runner" preserves import compatibility.

Nice move — this keeps existing runner::... paths stable while publishing under runner-run.

Comment thread CHANGELOG.md
kjanat added 18 commits May 9, 2026 03:26
Single static page deployed via Cloudflare Workers Static Assets.
Paper-zine + terminal hybrid: cream/ink/moss/tomato palette, monospace
throughout, one fake-terminal inset that mirrors actual `runner` and
`runner list` output (field widths and group labels match
src/cmd/{info,list}.rs).

Click-to-copy install commands (npm/cargo/curl), responsive grid of
supported package managers / task runners / sources, MIT footer.

No build step, no deps. `wrangler.jsonc` declares the Static Assets
binding and the `runner.kjanat.com` custom-domain route;
`workers_dev: false` keeps the *.workers.dev URL hidden. `_headers`
ships strict CSP, HSTS, and edge cache. README at repo root gains a
single Website: link.
site/wrangler.jsonc points $schema at ./node_modules/wrangler/
config-schema.json — make that real. Add a private package.json
declaring wrangler ^4.87.0 as devDependency, plus dev/deploy/tail
scripts. bun install populates node_modules; .gitignore keeps
node_modules and .wrangler out of git.
Format with tabs and trailing commas via dprint (typescript + svg
plugins added at the workspace root). Add site/biome.json: lint on,
formatter off (dprint owns format). Drop the local wrangler devDep
in favour of `bunx wrangler` via npm scripts; bun.lock removed.
README updated to use `bun --cwd=site dev|deploy`.
- prefers-color-scheme: dark — warm-graphite background, cream ink,
  brighter moss + tomato. Terminal block stays dark in both modes.
  color-scheme: light dark hints form controls + scrollbars.
- Wordmark cursor: 11s loop — solid ~10s, quick double-blink, repeat.
  Stops being wallpaper noise; reads as a tiny heartbeat instead.
- Terminal cursor: 5 iterations then settles solid, like a real
  prompt awaiting input.
- Cargo install command corrected: cargo install --git=https://
  github.com/kjanat/runner/ runner (not the unpublished crate name).
- install grid is now single-column at every breakpoint. Previously
  the primary npm row spanned full-width while cargo + curl were
  forced into half-cells — the shortest command got the most space,
  the longer ones overflowed. Stacking flips that.
- "cargo · from source" label clarifies it builds from the git tip,
  not a published crate. Visible cargo command is now full (no
  ellipsis) since the box has the room.
- Demo-terminal title row "runner 0.6.0" is now real anchors,
  matching the CLI's OSC8 hyperlinks: runner → repo, 0.6.0 →
  release tag. Added background: transparent on .term .link[:hover]
  so the global a:hover moss-swap doesn't bleed into the terminal.
dprint expanded the `idle-double-blink` keyframes block and the
cargo install button onto multiple lines — bringing both inline
with the rest of the file's already-expanded style. No behaviour
change.

Trim "— task execution backends not wired up" from the nx/mise
caveat. "detection-only" already says it; the longer form leaked
implementation roadmap into a user-facing line.
Real fixes the linter caught:
- "use strict" pragma at top of inline script
- Drop unused `const r = document.createRange()` (leftover from a
  fallback path that was never reached)
- Destructure `const { cmd } = btn.dataset`
- Annotate the empty execCommand catch (best-effort fallback)
- Extract setTimeout's 1400ms into TOAST_MS

Disabled rules that fight the design (all nursery/style):
- noExcessiveLinesPerFile — single-file is the entire premise
- noHexColors — six-colour palette doesn't need oklch ceremony
- useBaseline — `::selection` is universal in target browsers
- noMagicNumbers — animation timings are local literals

Add `lint` script alias so `bun lint` works without typing `biome`.

Verified: html-validate exit 0, biome lint exit 0.
New section between the demo and the matrix that surfaces the
binary's killer feature: dynamic, per-project tab completion.

- Two copy-buttons: `eval "$(runner completions)"` (auto-detects
  $SHELL — covers bash/zsh/fish in one line) and the powershell
  pipeline equivalent.
- Fake terminal showing `runner <TAB>` resolving to grouped
  candidates — package.json scripts, justfile recipes, justfile
  aliases, then built-in commands. Mirrors the real `_describe`
  group headers (`-- source --`).
- One liner about path-typed flags delegating to the shell's own
  file completer (`--dir <TAB>` → `~/`, globs, cdpath).

Single eval registers BOTH `runner` and `run` thanks to the
sibling-binary detection in cmd/completions.rs.
17,429 → 14,572 bytes (-2,857, -16.4%). Now fits in the initial
congestion window (10 × 1460 = 14,600) so a fresh visitor's first
flight delivers the whole page — single round-trip, even uncompressed.
Brotli compressed: 3,619 bytes.

Cuts:
- Drop inline SVG favicon (-344) → empty data: URI
- Drop OG url + type metas (-180); shorten description copy (-100)
- Drop view-source easter comments + verbose section comments (-280)
- Trim font stack: drop "IBM Plex Mono" + "DejaVu Sans Mono" (-50)
- Drop text-rendering, font-feature-settings, -webkit-text-size-adjust,
  -webkit-appearance — modern browsers don't need any of them (-200)
- Drop .meta-tight / .meta-loose helper classes (consolidate to .meta)
- Drop .term box-shadow (decorative, invisible against dark bg)
- Drop text-decoration-thickness + from-font keywords on a (-90)
- Inline single-use --term-bg/--term-link/--term-green vars (-130)
- Shorten --term-dim/--term-bold to 3-digit hex (-15)
- Refactor footer: drop .sep span structure for inline text (-200)
- Drop the textarea fallback in the click-to-copy script. The site
  is https-only, modern clipboard API works in all secure contexts;
  catch fails silently (-450).

Both validators clean: html-validate exit 0, biome lint exit 0.
- site/public/favicon.svg: serve the icon as a 241-byte file rather
  than inlining a data: URI. HTML now references it as
  `<link rel="icon" href="favicon.svg">` (31 bytes vs ~500 inline).
- index.html: 14,505 bytes — 95 under IW10 (14,600). Trim font stack
  to ui-monospace/Menlo/Consolas/monospace so dprint keeps it on
  one line; trim meta description; drop og:description.
- .dprint.json: bump markup printWidth to 160 so dprint stops
  splitting one-line meta tags into multi-line forms.
- .zed/settings.json: associate .ico as SVG syntax for the editor.

Both validators clean.
Rename favicon.svg → favicon.ico (same SVG bytes) and override the
Content-Type via `_headers` to `image/svg+xml`. Browsers auto-request
`/favicon.ico` regardless of the `<link>` tag; Cloudflare's
extension-based MIME detection would otherwise serve it as
`image/x-icon`, which the browser refuses to parse as SVG —
especially with `X-Content-Type-Options: nosniff` set globally.
Bumped `Cache-Control` on the icon to 1d/30d since favicons rarely
change. Updated `<link rel="icon">` href to match.
`287659d` dropped the local `wrangler` devDep in favour of
`bunx wrangler`, which left the `$schema` reference at
`./node_modules/wrangler/config-schema.json` — a path that only
resolves after a hypothetical install that no longer happens. Editors
(Zed, VSCode) couldn't pick up schema completions/validation on a
fresh clone.

Switch to `https://www.unpkg.com/wrangler/config-schema.json` so the
schema resolves without any local install. Tracks latest, same as
`bunx wrangler` does at runtime.
Below 40rem (~640px) the install grid and terminal blocks were
overflowing the viewport — `.copy .cmd` had `white-space: nowrap`,
so the longer `cargo install --git=...` command pushed the box wider
than the screen and forced horizontal scroll on the whole page.

Tighten `main` padding (3rem → 2.5rem 1.1rem), shrink `.copy` and
`.term` type, and let command text wrap with `overflow-wrap: anywhere`
so URLs break cleanly. Pull `hr.rule` margin in too — the section
gaps felt cavernous once the rest scaled down.

No desktop change; rules are scoped to `(max-width: 40rem)`.
Add `public/404.html` and flip `not_found_handling` from
`single-page-application` to `404-page` so wrangler serves the
file on unmatched paths instead of rewriting to `/`. The page
mirrors the main site's palette + mono stack and shows a fake
`runner <requested-path>` error line so the 404 reads like the
CLI itself rather than a generic browser page.

Also flip `workers_dev` to `true` so the *.workers.dev preview
URL stays reachable alongside the custom domain.
- mobile: copy buttons no longer blow out viewport. min-width: 0 on
  .copy lets the grid item shrink below min-content; word-break and
  overflow-wrap on .cmd let long URLs wrap. html/body get
  overflow-x: hidden as a safety net.
- copy: only mark "copied" when clipboard.writeText resolves; on
  failure, surface "Copy failed" via a polite live region so screen
  readers announce the result.
- README: 404.html now listed in the layout tree.
The .toast spans say "copied" visually, but we already announce
copy status via the polite live region. Marking them aria-hidden
keeps screen readers from reading the toast text alongside the
button name and the announcement.
.matrix ul forced two columns at every viewport. On phones that's a
cramped newspaper split — package.json / turbo.json wrap awkwardly.
Override to columns: 1 inside the existing 40rem media query.
kjanat added 28 commits May 9, 2026 03:26
Single-column 12-item list ate half the screen. Switch the matrix
lists to flex-wrap with bullet separators on mobile, so they read
as a compact inline group (npm · yarn · pnpm · …) and reflow
naturally to the available width.
Replaces the bare "Website:" line with a small block linking the
landing page (with a pointer to its source under site/) and the
npm package. Adds two shields.io badges at the top: npm version
of runner-run and MIT license.
## Summary

- Static landing page deployed to Cloudflare Workers Assets, served at
`runner.kjanat.com`.
- Single `index.html` (~3.6 KB brotli) + `404.html` (~1 KB brotli);
whole first response fits inside TCP IW10 with headroom.
- Self-contained styles, light/dark via `prefers-color-scheme`, mobile
breakpoint at 40 rem.
- Wrangler config: custom domain route, `workers_dev: true`,
`not_found_handling: "404-page"`, observability on.
- Auxiliary: `_headers`, `robots.txt`, SVG favicon served as
`favicon.ico`, biome + dprint config.
Replace the raw `public/` flow with `src/` → `dist/` driven by
`build.ts`: Bun bundles `index.html` / `404.html`, strips the
empty JS chunks Bun emits for script-less HTML, and substitutes
`{{version}}`, `{{repo}}`, `{{authorName}}`, etc. from
`Cargo.toml` so the site and the crate share one source of
truth for metadata.

Add `dev.ts`: serves `dist/`, watches `src/`, `public/`, and
`Cargo.toml`, debounces rebuilds at 80ms, and pings a tiny
WebSocket-reload snippet injected into served HTML.

Wrangler: asset dir → `./dist`, worker renamed `runner-site`
→ `runner`, `preview_urls: true`. Top-level `.gitignore`
follows the layout change (drop `npm/dist/`, `npm/downloads/`;
add `dist/`, `downloads/`, `.wrangler/`, `.dev.vars*`) and
dprint now ignores `**/dist`. Site `biome.json` gains
experimental HTML parsing for the templated files.
Consolidate the GitHub composite action, runner.kjanat.com landing
page, templated `site/build.ts` flow, README badges, npm publish
script refactor, `package-release-asset.sh` checksum filename fix,
`build-packages.ts` `Target.build` union expansion, and openbsd
residue removal under Added / Changed / Fixed.

Side housekeeping: reformat the inline `reloadSnippet` in
`site/dev.ts` from a one-liner to a readable IIFE, and add a
`repository` field (with `directory: site`) to `site/package.json`
so npm/registry tooling resolves the workspace correctly.
Replace the em-dash continuation with a `div.meta` wrapping two
`<p>` blocks so the path-flag note and the `~/`/glob/`cdpath`
example read as separate beats instead of one long sentence.
Move the Cloudflare Web Analytics beacon out of `src/index.html`
and into `build.ts`, injected only when `build({ analytics: true })`
is passed. The CLI entry sets it; `bun run dev` and library
imports leave the snippet out so local previews don't phone home.

`deploy` now chains `bun run build` before `wrangler deploy` so
the published `dist/` always has a freshly injected beacon, and
`_headers` adds `https://static.cloudflareinsights.com` to the CSP
`script-src` so the script actually loads.

Side tweaks: bump `--term-dim` `#888` → `#b3b3b3` for legible
secondary text, brighten `.term .green` to `#9bea9b`, and fold
`visibility` into the copy `.toast` transition so the hidden
state is also hidden from assistive tech, not just visually faded.
`.term .green` and `.term .link` baked in `#9bea9b` / `#b9d4b3`
that drifted from the rest of the term palette and ignored the
light/dark token swap. Reuse `--term-bold` / `--term-fg` so both
themes pick the right shade, and bump `.green` to weight 700 to
match the surrounding bold spans.
- `Cargo.toml`: bump `[lints.clippy.cargo]` warn → deny. Add
  `.cargo/config.toml` aliases `l`/`lint`/`t`/`bin-run`/`bin-runner`
  so the stricter clippy gate is one keystroke locally.
- `npm/scripts/build-packages.ts`: swap deprecated `cargo
  read-manifest` for `cargo metadata --no-deps`, picking the
  workspace's `workspace_default_members[0]` by id (falls back to
  the first package for non-virtual single-member workspaces).
  Bump `maxBuffer` to 64 MiB so large metadata payloads don't trip
  Node's 1 MiB default.
- `site/build.ts`: parallelize empty-chunk `rm` and HTML token
  substitution via `Promise.all` instead of serial awaits.
- `site/biome.json`: convert `correctness`/`suspicious` to
  `{ recommended: true }` objects so domain rules layer cleanly,
  and replace `organizeImports` with `useSortedProperties` /
  `noDuplicateClasses` / `useSortedInterfaceMembers`.
- `site/src/styles/*`: factor design tokens (`--radius-*`,
  `--ring-*`, `--dur-*`, `--ls-caps`, `--text-micro`/`--text-xs`,
  `--term-bg`/`--term-green`/`--term-red`), add `a:focus-visible`
  ring, `.meta a:hover` invert, `.term ::selection` themed
  selection, and reflow properties to match the new biome sort.
- `site/package.json`: add `fmt` → `bunx dprint fmt`.
- Skip beacon injection outside CI / GitHub Actions so local
  builds don't emit telemetry; warn instead of throw on missing
  `</body>`.
- Allow overriding the Cloudflare beacon token via
  `CF_BEACON_TOKEN`; literal kept as fallback.
- Build full GitHub Pages URL under GH Actions instead of a
  repo-relative path.
- Add `dir` option (`"relative" | "full"`) to `build()` so the
  size summary can print absolute paths; toggle via `FULL` env.
- Emit external sourcemaps when `SENTRY_DSN` is set.
- `bd` script runs `bun --silent` to keep summary output clean.
`publicPath` had a precedence bug: `env["PUBLIC_PATH"] || isCI ? X
: Y` parses as `(... || ...) ? X : Y`, so a literal `PUBLIC_PATH`
value never reached `Bun.build` — it acted as a boolean toggle
that always picked the GH-pages or `runner.kjanat.com` branch.
The hardcoded prod URL also leaked into Cloudflare Workers
preview deploys and tripped CSP `'self'` (page on `*.workers.dev`,
assets on `runner.kjanat.com`).

Replace with `env["PUBLIC_PATH"] || githubPagesUrl() || "/"`:
explicit override wins, GH Pages still gets its `/<repo>/` prefix,
everything else stays same-origin so preview URLs and local
builds load assets without a CSP fight.

`public/_headers`: drop the `/favicon.ico` override block — the
icon ships as an SVG via `<link rel="icon">`, so nothing routes
through `/favicon.ico` to need a content-type override.
Flatten the alias list into composable array-form commands so the
common loops are one keystroke and longer forms layer on top:
`bb` builds both bins, `bbr` is `bb --release`, `cl` is the bare
clippy invocation, `l` adds the deny flags on top of `cl`, `r`
is `run`, `rr` is `run --release`, and `rbin-run`/`rbin-runner`
re-use `bin-run`/`bin-runner` with `--release` instead of
duplicating the flag list. Adds `i`, `meta`, and `t` gains
`--all-targets`.

`justfile` now defers to those aliases (`cargo i`, `cargo
bin-run`, `cargo bin-runner`) so each entry point has one
source of truth for which bin/profile/flags it compiles.

Side: swap the `#:schema` URL from a local absolute path to the
published gist bundle so the file resolves on machines without
the schema repo cloned, and set `term.hyperlinks = false` since
some terminals mangle the OSC-8 escapes cargo emits.
Add `.rustfmt.toml` pinning the project's `cargo fmt` baseline:
`max_width=100`, 4-space indent, 2024 edition/style, plus a
commented appendix of the unstable knobs to revisit once they
stabilise. Reproducible formatting across machines, no more
diff churn from per-user defaults.

`bin/run` / `bin/runner`: drop the `BIN` indirection, swap
`--quiet` for `CARGO_TERM_QUIET=true` (covers cargo's
"Compiling/Finished" chatter, not just stdout), and build with
`--release` so the PATH shims spawn an optimised binary instead
of recompiling a debug build on every invocation. `bin/runner`
relies on `default-run = "runner"` to drop `--bin` entirely.

`justfile`, follow-up to cc8fd33:

- Inline the private `shim` recipe into `default` so the env
  passthrough is one hop instead of two.
- Flip `run` / `runner` default profile `release` → `dev` for
  faster local rebuilds; `PROFILE=release` still overrides.
- Swap `cargo read-manifest` → `cargo metadata --format-version
  1` for the npm name/scope lookups, matching what
  `build-packages.ts` already does and surviving the eventual
  removal of `read-manifest`.
- `test-release`: parse `npm/targets.json` with `jq` instead of
  a `node -p require(...)` heredoc, use the new `cargo bbr`
  alias instead of the inline `cargo build --bin runner --bin
  run --release`, and collapse the dual-command loop into one
  `for bin in runner run` pass.
- Fix `dowloads-dir` → `downloads-dir` typo.
Replace `${{ secrets.GITHUB_TOKEN }}` with `${{ github.token }}`
for `taiki-e/create-gh-release-action`,
`taiki-e/upload-rust-binary-action`, and the `cargo-build-std`
packaging step. Same token in practice, but `github.token` is
the documented context handle and doesn't require the secret to
be wired through explicitly — keeps fork PR runs and composite
calls working without extra plumbing.

`release-dryrun.yml` build-std step: drop the inline `cargo
+nightly build --release --locked --bin runner --bin run --target
"${{ matrix.target }}"` and move `--target` into
`CARGO_BUILD_TARGET` env. With `default-run = "runner"` and the
two-bin layout already covered by the alias chain, the bare
`cargo +nightly build --release --locked` matches what the rest
of the matrix runs and stays in sync if the bin list grows.
Collapse the now-single-line `run:` off its `|`, and pull
`matrix.target` into a step-level `target` env so the verify
loop reads `target/${target}/release/${bin}` instead of inlining
the `${{ }}` expansion mid-path.

`release-dryrun.yml` matrix: alphabetise the `workflow_dispatch`
target choice list (was loosely grouped by OS, drifted out of
order); compact the single-key `outputs:` / `with:` / `env:` /
`matrix:` blocks to flow style; rewrite the `INPUT_TARGET` jq
filter as one `jq --arg t` call with `$t == "all" or .rust ==
$t` instead of a bash `if`/`else` switching between two filter
strings.

Both workflows: replace `matrix.build-tool == 'a' ||
matrix.build-tool == 'b'` chains with `contains(fromJSON('["a",
"b"]'), matrix.build-tool)` so the allowlist is one literal
instead of an N-way `||` that grows with each new build tool.

`release.yml`: bump `npm-dist` artifact retention 7 → 14 days so
the npm publish window has more slack before artifacts expire.

`.cargo/config.toml`: append a commented `[target.<triple>]` /
`[target.<cfg>]` / `[target.<triple>.<links>]` reference block
as scaffolding for future per-target `rustflags` / `runner` /
build-script overrides. Pure documentation — no behaviour change.
Replace `${{ X && A || B }}` ternaries with `case(X, A, B)` across
`npm-release.yml`, `release-dryrun.yml`, and `release.yml` so the
selector is one expression instead of a short-circuit chain that
silently picks the wrong branch when `A` is falsy. Touches both
`run-name` strings and the `build-tool` cargo-cross-toolchain
remap.

`release.yml`: drop the redundant `matrix.experimental == true` —
`continue-on-error` already coerces, the `== true` was noise.

`npm-release.yml` triggers: add `release: { types: [published] }`
so a published GH Release fires the publish path directly, and
constrain the existing `workflow_run` to `branches: [v*]` so only
tag-driven release runs trigger npm publish (was firing on every
release.yml completion). Hoist `RELEASE_TAG` / `RUN_ID` /
`EVENT_NAME` / `INPUT_DIST_TAG` / `INPUT_DRY_RUN` / `REGISTRY` from
the job's `env:` to workflow-level `env:` so the same trusted
metadata is available to every job that grows here later, and
flow-compact the single-key `environment:` block. Switch
`setup-node` to read `node-version` and `registry-url` from
`env.NODE_VERSION` / `env.REGISTRY` instead of the inline literal,
keeping the registry pin in one place.

Multiline `description:` strings on `run-id` and `dist-tag` swap
`>-` for `|-` so the explanatory line breaks survive into the
dispatch UI instead of being folded into one paragraph.
Surface `[alias]` entries from the merged `.cargo/config.toml`
chain (cwd→root→`$CARGO_HOME`) plus cargo's built-ins as
`runner` tasks. Recursive alias references expand fully so
`runner list` shows the resolved command (`l → clippy
--all-targets --all-features -- -D warnings`), and built-ins
(`b/c/d/t/r/rm`) always surface even without a user config.
User redefinitions of built-ins are silently ignored to match
cargo's own resolution rule.

`runner run <alias>` shells out to `cargo <alias> <args...>`
so cargo's runtime resolution stays authoritative — no
re-implementing precedence in `runner`.

`Cargo.toml`: add `toml` (parse + serde, no default features)
to read alias tables. `src/types.rs` adds the `CargoAliases`
variant with label `"cargo"` (synthetic — no single file
backs it). `src/cmd/{list,run}.rs` pick the deepest
applicable config for OSC8 / nearest-source ranking, falling
back to `Cargo.toml`'s directory when only built-ins apply.

Side:

- `.cargo/config.toml`: `bin-run`/`bin-runner` get `--quiet`
  (less cargo chatter through the shims), add `comp` →
  `bin-runner -- completions`.
- `justfile`: rename `list` → `ls`.
- `CHANGELOG.md`: log this feature and catch up unreleased
  site/build entries (publicPath fix, CI-only beacon,
  `build-packages` metadata switch, `dir` toggle, sourcemap
  gating).
Renames the package on crates.io to runner-run (the bare `runner` name is
already taken). Pin [lib] name = "runner" so existing src/{main,bin}.rs
references to the library crate keep working unchanged.

Adds .github/workflows/crates-release.yml mirroring npm-release.yml: runs
on successful tag-triggered release.yml or manual dispatch, verifies the
tag matches Cargo.toml version, dry-runs publish, then uploads with
CARGO_REGISTRY_TOKEN under the crates-io environment.
Move `app.ts` → `app/copy.ts` and `icon.svg` → `assets/icon.svg`
so the flat `src/` root has room to grow without another rename
pass: copy-button code sits in a named module beside future UI
scripts, and static media lives under one prefix that maps onto
cache headers / CSP cleanly.

Update the two `<link rel="icon">` and the index `<script src>`
to the new paths so `dist/` regenerates without 404s.

Side: add `bd` → `bun run build` since it's the most-typed
local command.
`runner list` and `runner run` both needed "the file that
represents the cargo-aliases source for this root" — deepest
applicable `.cargo/config{,.toml}`, falling back to
`Cargo.toml` for built-ins-only projects. Both grew the same
seven-line `find_configs(root).into_iter().next().or_else(…)`
block, primed to drift apart.

Extract `tool::cargo_aliases::find_anchor` and call it from
both. `cmd/list.rs` keeps the OSC8 link target; `cmd/run.rs`
keeps the depth-based ranking — same behaviour, one source of
truth.
CodeQL flagged the previous workflow_run + checkout-of-tag pattern as
"untrusted code in a privileged context" (security/code-scanning #7-9):
workflow_run runs with secrets, and `cargo publish` executes build.rs
from the tag-controlled checkout, which would let a malicious tag
exfiltrate CARGO_REGISTRY_TOKEN.

Switch the trigger to `release: types: [published]`. release.yml still
creates the release as a draft — a maintainer must review and click
publish, which becomes the human gate. The `crates-io` environment's
required reviewers are the second gate. workflow_dispatch is preserved
for manual republishes.
`build()` now returns the emitted file list with bytes so callers
(and the CLI) can report compressed sizes without a second disk
read. The CLI prints a sorted `raw / gzip / br` table at quality
9/11 to mirror what a CDN actually serves, surfacing budget
regressions next to the build.

HTML post-processing reads from `Bun.build` outputs in memory
instead of re-reading `dist/`, and `copyTree` returns the bytes
it copied so `public/` files land in the same `DistFile[]` the
summary walks.

Derive `publicPath` per env: `PUBLIC_PATH` wins, GitHub Actions
falls back to `/<repo>` for Pages previews, other CI to the
prod URL, local to `./`. Lets PR previews and local builds
emit working asset URLs without a config flag.

`biome.json`: turn `complexity.useLiteralKeys` off so the env
bracket access (`env["PUBLIC_PATH"]`) needed under
`noPropertyAccessFromIndexSignature` doesn't trip the linter.
`local __runner_g` (no assignment) at the start of Pass 2 redeclared
a variable already localized by Pass 1. zsh treats `typeset NAME`
without an assignment as the *display* form, so every grouped TAB
completion printed `__runner_g=<last Pass 1 tag>` to the terminal.

Normally invisible — `zle redisplay` redraws the input line and
covers it — but oh-my-zsh's `COMPLETION_WAITING_DOTS` widget
prints its indicator before invoking the completion. Our spurious
typeset print sandwiched next to the dots, and `redisplay` only
repaints the input portion: both the dots and `__runner_g=Options`
survived alongside the menu, e.g. `runner waiting...
__runner_g=Options ` lingering on the prompt line for every TAB.

Switch to `local __runner_g=` (explicit empty assignment): the
declaration stays, but `typeset` with a value skips the display
path. Both `runner` and `run` binaries inherit the fix since the
template is shared.
Two CodeRabbit findings on the crates-release workflow:

- actions/checkout resolves bare refs to a branch first, then a tag, so
  a branch sharing a tag's name would silently checkout the wrong commit
  on workflow_dispatch input. Pin the ref to refs/tags/<tag> explicitly.

- cargo publish would die with an opaque auth error if CARGO_REGISTRY_TOKEN
  isn't wired up. Compute HAS_CARGO_REGISTRY_TOKEN at job-level (so the
  secret value never lands in a step env) and fail fast with a clear
  message before the publish step runs.
The package rename to runner-run isn't strictly breaking — runner-run
has never been published to crates.io and the bare `runner` name on
the registry belongs to someone else, so there's no prior consumer to
break. But a lot of work has accumulated under [Unreleased] since
v0.6.0 (composite action, cargo-aliases task source, landing page,
templated site build, npm publish hardening, BSD packaging fixes,
and now crates.io publishing), so cut a 0.7.0 section dated today
and let this PR's tag carry all of it.
@kjanat kjanat force-pushed the claude/setup-crates-publishing-eYir9 branch from 8a785e0 to 01dac4f Compare May 9, 2026 01:30
Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 16

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
.dprint.json (1)

25-30: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Exclude downloads/ here too.

Arrr, downloads/ is now treated as generated repo state in .gitignore, but dprint will still crawl it. That’s needless formatter churn waiting to happen once fetched assets land there.

🧹 Proposed fix
 	"excludes": [
 		"**/*.lock",
 		"**/node_modules",
 		"**/samples",
 		"**/target",
-		"**/dist"
+		"**/dist",
+		"**/downloads"
 	],
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.dprint.json around lines 25 - 30, The excludes array in .dprint.json
currently omits the downloads directory, so add an entry for downloads (e.g.,
"downloads" or "**/downloads") to the "excludes" array so dprint won't crawl or
format files under downloads; update the "excludes" value where the "excludes"
array is defined to include that new pattern.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/npm-release.yml:
- Line 50: The workflow currently wraps inputs.dry-run with fromJSON in the
INPUT_DRY_RUN env (INPUT_DRY_RUN: ${{ fromJSON(inputs.dry-run) }}), causing a
type mismatch because inputs.dry-run is already a boolean; remove the fromJSON
wrapper and assign the boolean directly (use inputs.dry-run as-is for
INPUT_DRY_RUN), or if you need null-safety for non-dispatch runs add a
conditional/default around inputs.dry-run rather than calling fromJSON.

In @.github/workflows/release-dryrun.yml:
- Around line 107-115: The taiki-e/upload-rust-binary-action step ("build
(taiki-e dry-run)" using taiki-e/upload-rust-binary-action@v1) is missing an
explicit ref for manual workflow_dispatch runs and the archive name uses an
undefined literal $tag; fix by adding a ref input to the action (e.g., set ref
to a tag input like refs/tags/${{ github.event.inputs.tag }} or to
github.ref_name when appropriate) and either define a workflow_dispatch input
named tag or replace the archive value to use a valid expression (e.g., a
derived/defined tag variable or github.ref_name) so archive no longer contains
the literal $tag.

In `@bin/runner`:
- Around line 20-23: The wrapper script bin/runner invokes cargo without the
--locked flag, allowing local runs to update Cargo.lock and diverge from
CI/release builds; update the cargo invocation in the exec line (the command
starting with exec env CARGO_TERM_QUIET=true \ cargo run --release
--manifest-path="${MANIFEST}" -- "$@") to include the --locked flag so cargo run
is invoked with --locked (e.g., cargo run --release
--manifest-path="${MANIFEST}" --locked -- "$@"), ensuring local runs use the
locked dependency graph.

In `@justfile`:
- Around line 47-49: The pkg lookup using jq can return an empty string which
later causes build-packages and the symlink step to fail silently; after the jq
assignment to pkg (the variable pkg in the justfile), immediately check if pkg
is empty and, if so, print a clear error message including the host-triple and
exit non‑zero to fail fast (so build-packages doesn't get only=""). Ensure you
reference the same pkg variable and the host-triple placeholder in the message.
- Around line 6-7: The jq selectors in justfile currently look up a package by
the old crate name ("runner") and return empty after the rename; change
npm-pkg-name and npm-pkg-scope to first resolve the workspace default member
(same approach as build-packages.ts) and then select the package whose id
matches that default member, extracting .metadata.npm.name and
.metadata.npm.subpkgscope; specifically, run cargo metadata and use jq to get
the workspace default member id, then select the package with that id (matching
.id) and return its metadata.npm.name / metadata.npm.subpkgscope instead of
select(.name == "runner").

In `@site/biome.json`:
- Around line 10-13: The Biome config's files.includes currently patterns
everything (files.includes) which causes generated output to be crawled; update
the includes list (the "files" -> "includes" entry) to explicitly exclude the
build output (e.g., add a negative glob for dist such as "!!dist/**") so that
the generated dist/ directory is ignored by Biome and not linted or scanned.

In `@site/build.ts`:
- Around line 107-111: The regex built for emptyScript interpolates raw chunk
names from emptyChunks which may contain regex metacharacters; update the
construction so you escape each chunk name before joining (e.g., map emptyChunks
to an escapeRegExp function that backslashes special chars) and then use the
escaped alternation in the RegExp constructor (keep the rest of the pattern and
flags the same) so the RegExp only matches exact chunk filenames; ensure the
escape helper is applied where emptyChunks is spread into the alternation used
for emptyScript.

In `@site/dev.ts`:
- Around line 89-103: The timer callback currently bails out when rebuilding is
true, dropping that file-change event; modify this by introducing a
queued/needsRebuild boolean (or reuse the existing pending variable) so that if
rebuilding is true you mark needsRebuild = true and return, and then after the
try/finally in the setTimeout callback (or at the end of the build
success/failure path inside the finally where rebuilding is set false) check
needsRebuild and if true clear it and schedule another setTimeout to run build
again (or call the same callback) so no file-change events are lost; update
references in the module to use the new queued flag and ensure sockets and
build() usage remain unchanged.

In `@site/public/_headers`:
- Line 6: The CSP in site/public/_headers currently allows the Cloudflare
Insights script via script-src but lacks a connect-src entry, so add a
connect-src directive that permits connections to https://cloudflareinsights.com
(and any subdomains used) so the Web Analytics beacon can send RUM data; update
the Content-Security-Policy line (the header string) to include connect-src
'self' https://cloudflareinsights.com and ensure this aligns with the script
injection in site/build.ts that adds the Cloudflare Web Analytics script.

In `@site/README.md`:
- Around line 5-7: The README is out of date: update its description to reflect
the actual pipeline by replacing the "no build/deps" claim with notes that the
site is built into ./dist using the build tooling defined in site/package.json
(mention the build script and any toolchain referenced), correct the Worker name
from runner-site to runner to match site/wrangler.jsonc, and update the
layout/source section to document the current public -> build -> dist flow
(including that ./dist is the deployed asset directory rather than
public/index.html). Ensure you reference site/package.json (build script),
site/wrangler.jsonc (name: "runner"), and the ./dist output directory so readers
can find the canonical sources.

In `@site/src/404.html`:
- Around line 19-21: The 404 currently prints the literal "<requested-path>" and
"searched: /" inside the <pre class="term"> output; update the template so these
reflect the real path by using client-side JS to replace the <span
class="dim">&lt;requested-path&gt;</span> and the trailing "/" with
window.location.pathname (e.g., select the <pre class="term"> or the specific
<span class="dim"> nodes and set their textContent to window.location.pathname),
or alternatively remove those echoed path nodes entirely if you prefer not to
display the requested path.

In `@site/src/index.html`:
- Around line 40-43: The cargo install command shown in the copy button still
references the old crate name "runner"; update the command string in the button
with data-cmd and the visible span (span class="cmd") to the new package name
"runner-run" so both the data-cmd attribute value and the displayed command text
change from "cargo install --git={{repo}}/ runner" / "runner" to "cargo install
--git={{repo}}/ runner-run" / "runner-run" (locate the button element with
class="copy" and the nested span.cmd to make the edits).

In `@site/src/styles/index.css`:
- Around line 5-15: The .visually-hidden utility is using the deprecated clip
property (clip: rect(...)), which Stylelint flags; replace that deprecated rule
with the modern equivalent by removing clip: rect(0, 0, 0, 0) and adding a
clip-path based solution (e.g., clip-path: inset(50%)) so the .visually-hidden
rule uses clip-path instead of clip while keeping the existing positioning,
size, overflow, and white-space semantics.
- Around line 295-299: The CSS rule for the selector ".copy .cmd" contains the
deprecated declaration "word-break: break-word"; remove that property (or reset
it to the default) since "overflow-wrap: anywhere" already provides the intended
wrapping behavior—update the ".copy .cmd" block to drop "word-break: break-word"
so Stylelint stops flagging it.

In `@src/tool/cargo_aliases.rs`:
- Around line 47-50: display_command currently joins tokens with spaces and
loses quoting for tokens that contain whitespace (mismatching what tokenize()
produces); update ExtractedAlias::display_command to quote/escape any token that
contains whitespace or special characters before joining so the rendered string
reflects the real argv shape (e.g., wrap space-containing tokens in quotes and
escape internal quotes/backslashes), using the same tokenization assumptions as
tokenize() to ensure symmetry between tokenize() and display_command().
- Around line 157-163: The loop that iterates aliases from
read_alias_table(path) currently silently skips failures from tokenize(&value),
causing invalid aliases to disappear; instead, when tokenize returns None or an
error, return/bubble an error with context including the alias name and the
config path. Replace the `continue` in the for loop (and the analogous site
around lines 198-204) so that failures from tokenize(&value) produce an
anyhow::Error (or use bail!/Context) that includes the alias `name` and
`path.display()` before returning, rather than inserting nothing into `merged`.

---

Outside diff comments:
In @.dprint.json:
- Around line 25-30: The excludes array in .dprint.json currently omits the
downloads directory, so add an entry for downloads (e.g., "downloads" or
"**/downloads") to the "excludes" array so dprint won't crawl or format files
under downloads; update the "excludes" value where the "excludes" array is
defined to include that new pattern.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 0522fb8d-9b35-40f5-8277-a8a6c22f86d8

📥 Commits

Reviewing files that changed from the base of the PR and between 8a785e0 and 01dac4f.

⛔ Files ignored due to path filters (3)
  • Cargo.lock is excluded by !**/*.lock
  • site/bun.lock is excluded by !**/*.lock
  • site/src/assets/icon.svg is excluded by !**/*.svg
📒 Files selected for processing (39)
  • .cargo/config.toml
  • .dprint.json
  • .github/workflows/crates-release.yml
  • .github/workflows/npm-release.yml
  • .github/workflows/release-dryrun.yml
  • .github/workflows/release.yml
  • .gitignore
  • .rustfmt.toml
  • .zed/settings.json
  • CHANGELOG.md
  • Cargo.toml
  • README.md
  • bin/run
  • bin/runner
  • justfile
  • npm/scripts/build-packages.ts
  • site/.dprint.json
  • site/README.md
  • site/biome.json
  • site/build.ts
  • site/dev.ts
  • site/package.json
  • site/public/_headers
  • site/public/robots.txt
  • site/src/404.html
  • site/src/app/copy.ts
  • site/src/index.html
  • site/src/styles/404.css
  • site/src/styles/base.css
  • site/src/styles/index.css
  • site/tsconfig.json
  • site/wrangler.jsonc
  • src/cmd/list.rs
  • src/cmd/run.rs
  • src/complete/grouped.zsh
  • src/detect.rs
  • src/tool/cargo_aliases.rs
  • src/tool/mod.rs
  • src/types.rs
📜 Review details
⏰ Context from checks skipped due to timeout of 18000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
  • GitHub Check: Workers Builds: runner
  • GitHub Check: Analyze (rust)
  • GitHub Check: Analyze (actions)
  • GitHub Check: Analyze (javascript-typescript)
🧰 Additional context used
📓 Path-based instructions (3)
.github/**/*.{yml,yaml}

⚙️ CodeRabbit configuration file

Do not ever warn about stylistic yamllint shit. Do warn about security related shit. Insecure shell handling of user supplied / defined strings: think of branch names, inputs, pr content, anything needs to be string interpolation and permission safe.

Files:

  • .github/workflows/release.yml
  • .github/workflows/release-dryrun.yml
  • .github/workflows/npm-release.yml
  • .github/workflows/crates-release.yml
@(package.json|pyproject.toml|setup.py|Cargo.toml|go.mod|pom.xml|build.gradle|VERSION)

📄 CodeRabbit inference engine (Custom checks)

@(package.json|pyproject.toml|setup.py|Cargo.toml|go.mod|pom.xml|build.gradle|VERSION): If any source code files (excluding tests, docs, CI, markdown, or comments-only changes) are modified, then a version field MUST be updated in one of the following files if present in the repo: package.json, pyproject.toml, setup.py, Cargo.toml, go.mod, pom.xml, build.gradle, or a VERSION file.
The new version MUST follow SemVer (MAJOR.MINOR.PATCH). If the PR introduces breaking changes (removal or renaming of public APIs, changes to function signatures, deleted exported symbols, or incompatible config changes), MAJOR must increment. If it adds backward-compatible functionality, MINOR must increment. If it only fixes bugs without changing public APIs, PATCH must increment.

Files:

  • Cargo.toml
**/CHANGELOG.md

📄 CodeRabbit inference engine (Custom checks)

**/CHANGELOG.md: If any source code files (excluding tests, docs, CI, markdown, or comments-only changes) are modified, CHANGELOG.md MUST also be modified in the same PR.
If a version bump is detected, CHANGELOG.md MUST contain a new section header matching the exact new version number in the format: '## [X.Y.Z] - YYYY-MM-DD'.
If NO version bump is detected, the changes in the PR MUST be added under the existing '## [Unreleased]' section in CHANGELOG.md. The entry MUST describe the changes (e.g., Added, Changed, Fixed, Removed).

Files:

  • CHANGELOG.md
🧠 Learnings (8)
📚 Learning: 2026-03-26T20:05:44.851Z
Learnt from: kjanat
Repo: kjanat/runner PR: 1
File: src/cmd/mod.rs:67-75
Timestamp: 2026-03-26T20:05:44.851Z
Learning: In Rust, `std::process::Command` does not provide getters for stdio configuration, so whether `Stdio::inherit()` (or other `Stdio::*` settings) was applied cannot be asserted via pure unit tests without spawning a process and inspecting OS-level fds. When reviewing Rust code, do not flag “missing unit test coverage” for stdio configuration on `std::process::Command` as a code issue—treat the explicit `Command::stdin/stdout/stderr` setter calls in the source as the meaningful guarantee.

Applied to files:

  • src/tool/mod.rs
  • src/detect.rs
  • src/cmd/run.rs
  • src/cmd/list.rs
  • src/types.rs
  • src/tool/cargo_aliases.rs
📚 Learning: 2026-04-21T15:16:40.277Z
Learnt from: kjanat
Repo: kjanat/runner PR: 4
File: src/tool/just.rs:132-133
Timestamp: 2026-04-21T15:16:40.277Z
Learning: In Rust, if a method like `ExtractedTask::name() -> &str` returns a borrowed `&str` tied to `&self`, then using `sort_unstable_by_key(|t| t.name())` should be avoided because `by_key` requires the key to not borrow from the element being sorted (it will fail to compile due to the key’s lifetime). Do not recommend `sort_unstable_by_key` as a simplification in this situation. If you need an allocation-free and idiomatic comparison, use `sort_unstable_by(|a, b| a.name().cmp(b.name()))` instead. Using `.to_owned()` inside `by_key` is an alternative but allocates a `String` per element.

Applied to files:

  • src/tool/mod.rs
  • src/detect.rs
  • src/cmd/run.rs
  • src/cmd/list.rs
  • src/types.rs
  • src/tool/cargo_aliases.rs
📚 Learning: 2026-05-04T23:28:17.947Z
Learnt from: kjanat
Repo: kjanat/runner PR: 5
File: src/lib.rs:222-223
Timestamp: 2026-05-04T23:28:17.947Z
Learning: When reviewing Rust `rustdoc` comments (`/// ...`), treat a trailing backslash (`\`) at the end of a comment line as valid CommonMark syntax for a hard line break (rendered as `<br>`). Do not flag such trailing backslashes as papercuts or recommend removing them unless there is clear evidence they are unintended (e.g., they are not in the `rustdoc` comment context or the surrounding formatting contradicts the intended CommonMark hard-break usage).

Applied to files:

  • src/tool/mod.rs
  • src/detect.rs
  • src/cmd/run.rs
  • src/cmd/list.rs
  • src/types.rs
  • src/tool/cargo_aliases.rs
📚 Learning: 2026-03-26T20:08:23.153Z
Learnt from: kjanat
Repo: kjanat/runner PR: 1
File: src/detect.rs:22-22
Timestamp: 2026-03-26T20:08:23.153Z
Learning: In `src/detect.rs` within the `kjanat/runner` codebase, `detect()` is intentionally infallible and should return `ProjectContext` directly. Parsing/I/O failures from task extractors (which return `anyhow::Result<Vec<String>>`) must be recorded by `push_extracted_tasks` into `ProjectContext.warnings` as `DetectionWarning` entries and then emitted to stderr via `cmd::print_warnings`. When reviewing, do not flag `detect()` for “swallowing errors” as long as failures are routed into the warnings-list design (i.e., they’re preserved in `ProjectContext.warnings`, not dropped silently).

Applied to files:

  • .rustfmt.toml
  • .dprint.json
  • .gitignore
📚 Learning: 2026-05-04T22:42:28.879Z
Learnt from: kjanat
Repo: kjanat/runner PR: 5
File: CHANGELOG.md:8-27
Timestamp: 2026-05-04T22:42:28.879Z
Learning: In `CHANGELOG.md`, do not treat a `## [Unreleased]` section as an error/mismatch during development PRs even if `Cargo.toml` includes an in-progress version bump. The versioned header format `## [X.Y.Z] - YYYY-MM-DD` should only be added at release/tag time; this repo’s `CHANGELOG.md` includes a “Post-release checklist” confirming that behavior. Only flag changelog/version mismatches if they contradict the intended workflow described in `CHANGELOG.md`.

Applied to files:

  • .rustfmt.toml
  • .dprint.json
  • .gitignore
📚 Learning: 2026-05-02T17:50:01.271Z
Learnt from: kjanat
Repo: kjanat/runner PR: 5
File: .gitignore:4-4
Timestamp: 2026-05-02T17:50:01.271Z
Learning: In this repository, `package-lock.json` is intentionally added to `.gitignore` and should not be committed. In future reviews, do not flag the absence/presence of `package-lock.json` as an issue as long as `.gitignore` continues to exclude it intentionally.

Applied to files:

  • .gitignore
📚 Learning: 2026-05-04T15:23:38.296Z
Learnt from: kjanat
Repo: kjanat/runner PR: 5
File: .github/workflows/release.yml:73-73
Timestamp: 2026-05-04T15:23:38.296Z
Learning: For GitHub Actions workflows in this repository (kjanat/runner), maintain the maintainer’s preference for “floating” action tags (e.g., `v6`, `v7`) instead of pinned full commit SHAs. During code review, do not flag action usages in `.github/workflows/**/*.yml` for not being pinned to full commit SHAs or suggest switching to SHA pinning.

Applied to files:

  • .github/workflows/release.yml
  • .github/workflows/release-dryrun.yml
  • .github/workflows/npm-release.yml
  • .github/workflows/crates-release.yml
📚 Learning: 2026-03-26T16:14:15.754Z
Learnt from: kjanat
Repo: kjanat/runner PR: 1
File: src/tool/go_task.rs:64-81
Timestamp: 2026-03-26T16:14:15.754Z
Learning: If code uses let-chains (e.g., `if let Some(x) = foo && ... && let Some(y) = bar`), ensure the crate’s `Cargo.toml` sets `package.edition = "2024"`. Rust 2021/earlier should not be used with let-chains; require 2024 specifically for compilation.

Applied to files:

  • Cargo.toml
🪛 actionlint (1.7.12)
.github/workflows/npm-release.yml

[error] 50-50: 1st argument of function call is not assignable. "bool" cannot be assigned to "string". called function type is "fromJSON(string) -> any"

(expression)

🪛 ast-grep (0.42.1)
site/build.ts

[warning] 107-110: Regular expression constructed from variable input detected. This can lead to Regular Expression Denial of Service (ReDoS) attacks if the variable contains malicious patterns. Use libraries like 'recheck' to validate regex safety or use static patterns.
Context: new RegExp(
<script[^>]+src="\\.?/?(?:${[...emptyChunks].join("|")})"[^>]*></script>,
"g",
)
Note: [CWE-1333] Inefficient Regular Expression Complexity [REFERENCES]
- https://owasp.org/www-community/attacks/Regular_expression_Denial_of_Service_-_ReDoS
- https://cwe.mitre.org/data/definitions/1333.html

(regexp-from-variable)

🪛 Betterleaks (1.1.2)
site/build.ts

[high] 16-16: Detected a Generic API Key, potentially exposing access to various services and sensitive operations.

(generic-api-key)

🪛 LanguageTool
CHANGELOG.md

[uncategorized] ~35-~35: The official name of this software platform is spelled with a capital “H”.
Context: ...ake zero API calls (tag resolved from github.action_ref); version: latest trigg...

(GITHUB)


[grammar] ~90-~90: Ensure spelling is correct
Context: ...he runner-run npm version and the MIT licence. ### Changed - Cloudflare Web Analytics beac...

(QB_NEW_EN_ORTHOGRAPHY_ERROR_IDS_1)

🪛 Stylelint (17.10.0)
site/src/styles/base.css

[error] 25-25: Expected empty line before declaration (declaration-empty-line-before)

(declaration-empty-line-before)


[error] 14-14: Expected "Menlo" to be "menlo" (value-keyword-case)

(value-keyword-case)


[error] 14-14: Expected "Consolas" to be "consolas" (value-keyword-case)

(value-keyword-case)

site/src/styles/index.css

[error] 298-298: Deprecated keyword "break-word" for property "word-break" (declaration-property-value-keyword-no-deprecated)

(declaration-property-value-keyword-no-deprecated)


[error] 14-14: Deprecated property "clip" (property-no-deprecated)

(property-no-deprecated)

🪛 YAMLlint (1.38.0)
.github/workflows/release-dryrun.yml

[error] 47-47: too many spaces inside braces

(braces)


[error] 47-47: too many spaces inside braces

(braces)


[error] 60-60: too many spaces inside braces

(braces)


[error] 60-60: too many spaces inside braces

(braces)


[error] 85-85: too many spaces inside braces

(braces)


[error] 85-85: too many spaces inside braces

(braces)


[error] 92-92: too many spaces inside braces

(braces)


[error] 92-92: too many spaces inside braces

(braces)


[error] 97-97: too many spaces inside braces

(braces)


[error] 97-97: too many spaces inside braces

(braces)


[error] 102-102: too many spaces inside braces

(braces)


[error] 102-102: too many spaces inside braces

(braces)


[error] 135-135: too many spaces inside braces

(braces)


[error] 135-135: too many spaces inside braces

(braces)

.github/workflows/npm-release.yml

[warning] 10-10: truthy value should be one of [false, true]

(truthy)


[error] 11-11: too many spaces inside braces

(braces)


[error] 11-11: too many spaces inside braces

(braces)


[error] 14-14: too many spaces inside braces

(braces)


[error] 14-14: too many spaces inside braces

(braces)


[error] 65-65: too many spaces inside braces

(braces)


[error] 65-65: too many spaces inside braces

(braces)

🔇 Additional comments (12)
.zed/settings.json (1)

15-16: Aye, this be a clean config change.

lsp closes properly, comma placement is correct, and the new file_types mapping is valid JSON.

.github/workflows/npm-release.yml (1)

94-95: Nice cleanup: single-source config for Node version and registry.

Using env.NODE_VERSION and env.REGISTRY here reduces drift across steps. Good hardening for maintainability.

src/complete/grouped.zsh (1)

85-92: Nice catch on the zsh local leak.

Aye, explicitly zeroing __runner_g here is the right fix; it stops the stray redisplay junk without touching the completion grouping logic.

justfile (1)

17-18: Belay that concern—the alias be checked in proper.

The cargo config already defines i = ["install", "--path", "."] in the repository, so cargo i will work fine as coded. No need to be changin' nothin' here. The plank stays steady, me hearties.

			> Likely an incorrect or invalid review comment.
README.md (1)

3-4: Badges and public links are aligned, matey.

These updates are clean and consistent with the runner-run package naming.

Also applies to: 8-10

src/cmd/list.rs (1)

45-45: Cargo alias source is wired into list rendering correctly, arr.

Grouping and source-path resolution both include TaskSource::CargoAliases, so list output coverage is complete.

Also applies to: 164-164

site/wrangler.jsonc (1)

1-17: Wrangler static-assets config looks shipshape, matey.

assets.directory, route, and compatibility settings are set up coherently for deployment.

src/detect.rs (1)

272-274: Cargo alias extraction integration is solid, matey.

Nice touch handling alias-read failures as DetectionWarning instead of aborting task detection.

Also applies to: 277-300

src/cmd/run.rs (1)

136-136: Run-path support for Cargo aliases is properly threaded through, arr.

Both source-directory selection and command construction now cover TaskSource::CargoAliases.

Also applies to: 252-252

site/package.json (1)

12-30: Site tooling manifest is well organized, matey.

Script coverage for build/dev/deploy/check flows looks complete and consistent.

site/src/app/copy.ts (1)

1-23: Clipboard interaction flow is clean and resilient, arr.

Success/failure feedback and temporary button state are handled clearly.

.github/workflows/crates-release.yml (1)

64-111: Security gating here is clean and release-safe.

Explicit tag namespacing, tag↔version verification, mandatory dry-run, and token preflight make the publish path robust.

RUN_ID: ${{ case(contains(github.event_name, 'workflow_dispatch'), inputs.run-id, github.event.workflow_run.id) }}
EVENT_NAME: ${{ github.event_name }}
INPUT_DIST_TAG: ${{ inputs.dist-tag }}
INPUT_DRY_RUN: ${{ fromJSON(inputs.dry-run) }}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "1) Confirm the problematic expression exists:"
rg -n -C2 'INPUT_DRY_RUN:\s*\$\{\{\s*fromJSON\(inputs\.dry-run\)\s*\}\}' .github/workflows/npm-release.yml || true

echo
echo "2) Run actionlint (if available) to verify the type error:"
if command -v actionlint >/dev/null 2>&1; then
  actionlint -oneline .github/workflows/npm-release.yml
else
  echo "actionlint not found in this environment. Install it and rerun:"
  echo "  actionlint -oneline .github/workflows/npm-release.yml"
fi

Repository: kjanat/runner

Length of output: 506


Ahoy! INPUT_DRY_RUN is typoed to Davy Jones' locker — Line 50 will keel over.

inputs.dry-run be a boolean from workflow_dispatch, but fromJSON() be expectin' a string. Actionlint be hollerin' the error clear as cannon fire: "bool" cannot be assigned to "string". This'll break yer workflow evaluation faster than a ship without a rudder.

Fix: Hoist the boolean straight up without the fromJSON() wrapper:

🏴‍☠️ Correct fix
-  INPUT_DRY_RUN: ${{ fromJSON(inputs.dry-run) }}
+  INPUT_DRY_RUN: ${{ inputs.dry-run }}

Or if ye be needin' null-safety on non-dispatch events:

-  INPUT_DRY_RUN: ${{ fromJSON(inputs.dry-run) }}
+  INPUT_DRY_RUN: ${{ inputs.dry-run || false }}

Verified with actionlint — the type error be real as Blackbeard's beard. Get this shiplapped and squared away, matey.

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
INPUT_DRY_RUN: ${{ fromJSON(inputs.dry-run) }}
INPUT_DRY_RUN: ${{ inputs.dry-run }}
🧰 Tools
🪛 actionlint (1.7.12)

[error] 50-50: 1st argument of function call is not assignable. "bool" cannot be assigned to "string". called function type is "fromJSON(string) -> any"

(expression)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/npm-release.yml at line 50, The workflow currently wraps
inputs.dry-run with fromJSON in the INPUT_DRY_RUN env (INPUT_DRY_RUN: ${{
fromJSON(inputs.dry-run) }}), causing a type mismatch because inputs.dry-run is
already a boolean; remove the fromJSON wrapper and assign the boolean directly
(use inputs.dry-run as-is for INPUT_DRY_RUN), or if you need null-safety for
non-dispatch runs add a conditional/default around inputs.dry-run rather than
calling fromJSON.

Comment on lines 107 to 115
- name: build (taiki-e dry-run)
if: matrix.build-tool == 'cargo' || matrix.build-tool == 'cross' || matrix.build-tool == 'cargo-cross-toolchain'
if: contains(fromJSON('["cargo", "cross", "cargo-cross-toolchain"]'), matrix.build-tool)
uses: taiki-e/upload-rust-binary-action@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
token: ${{ github.token }}
bin: runner,run
target: ${{ matrix.target }}
build-tool: ${{ matrix.build-tool == 'cargo-cross-toolchain' && 'cargo' || matrix.build-tool }}
build-tool: ${{ case(matrix.build-tool == 'cargo-cross-toolchain', 'cargo', matrix.build-tool) }}
archive: runner-$tag-$target
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

cat -n .github/workflows/release-dryrun.yml

Repository: kjanat/runner

Length of output: 6093


🌐 Web query:

taiki-e/upload-rust-binary-action event support workflow_dispatch tag variable

💡 Result:

taiki-e/upload-rust-binary-action supports choosing the Git tag/release target via the action input ref (a fully-formed tag ref like refs/tags/<tag>), which you can set from a workflow_dispatch input variable. Key points (from the action’s docs/code): 1) The action has an input named ref described as “Fully-formed tag ref for this release”, and the README shows using it like ref: refs/tags/my_tag. [1] 2) Internally, the action uses ${{ inputs.ref }} to derive tag (it slices off the refs/tags/ prefix). [2] 3) Therefore, if your workflow is triggered by workflow_dispatch, you can define a workflow_dispatch input (e.g., tag) and pass it into the action by constructing ref: refs/tags/${{ github.event.inputs.tag }} (or equivalent expression). This is consistent with the action documentation that accepts ref for uploading to arbitrary tags beyond push tag events. [1] Minimal example workflow snippet: on: workflow_dispatch: inputs: tag: required: true type: string jobs: upload: runs-on: ubuntu-latest steps: - uses: actions/checkout@v6 - uses: taiki-e/upload-rust-binary-action@v1 with: bin: my-binary token: ${{ secrets.GITHUB_TOKEN }} # Upload assets to an arbitrary tag ref: refs/tags/${{ github.event.inputs.tag }} Note: Without specifying ref, the action defaults to GITHUB_REF (via ref=${INPUT_REF:-"${GITHUB_REF:-}"} in main.sh). [2]

Citations:


This action won't work right on manual dispatch without passin' ref—and $tag ain't even defined.

Aye, the action does support workflow_dispatch, but only if ye feed it an explicit ref parameter (like ref: refs/tags/${{ github.event.inputs.tag }}). Without it, the action falls back to GITHUB_REF, which'll be a branch ref on manual runs, not a tag. Plus, that archive: runner-$tag-$target be usin' a literal string $tag that's never defined anywhere in this workflow—it won't expand, just sit there like dead weight in the archive name. With dry-run: true, it won't break spectacularly, but ye'll get archives named runner-$tag-$target instead of somethin' sane. Either pass a proper ref input to the action for manual runs or remove the $tag reference from archive and let the action handle its own naming. (github.com)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/release-dryrun.yml around lines 107 - 115, The
taiki-e/upload-rust-binary-action step ("build (taiki-e dry-run)" using
taiki-e/upload-rust-binary-action@v1) is missing an explicit ref for manual
workflow_dispatch runs and the archive name uses an undefined literal $tag; fix
by adding a ref input to the action (e.g., set ref to a tag input like
refs/tags/${{ github.event.inputs.tag }} or to github.ref_name when appropriate)
and either define a workflow_dispatch input named tag or replace the archive
value to use a valid expression (e.g., a derived/defined tag variable or
github.ref_name) so archive no longer contains the literal $tag.

Comment thread bin/runner
Comment on lines +20 to +23
exec env CARGO_TERM_QUIET=true \
cargo run --release \
--manifest-path="${MANIFEST}" \
-- "$@"
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Keep the wrapper on the locked dependency graph.

Aye, every release path in this PR builds with a locked resolve, but this wrapper does not. That leaves local bin/runner invocations free to drift from CI/release behavior and to rewrite Cargo.lock when the graph changes.

🛠 Proposed fix
 exec env CARGO_TERM_QUIET=true \
 	cargo run --release \
+	--locked \
 	--manifest-path="${MANIFEST}" \
 	-- "$@"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
exec env CARGO_TERM_QUIET=true \
cargo run --release \
--manifest-path="${MANIFEST}" \
-- "$@"
exec env CARGO_TERM_QUIET=true \
cargo run --release \
--locked \
--manifest-path="${MANIFEST}" \
-- "$@"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@bin/runner` around lines 20 - 23, The wrapper script bin/runner invokes cargo
without the --locked flag, allowing local runs to update Cargo.lock and diverge
from CI/release builds; update the cargo invocation in the exec line (the
command starting with exec env CARGO_TERM_QUIET=true \ cargo run --release
--manifest-path="${MANIFEST}" -- "$@") to include the --locked flag so cargo run
is invoked with --locked (e.g., cargo run --release
--manifest-path="${MANIFEST}" --locked -- "$@"), ensuring local runs use the
locked dependency graph.

Comment thread justfile
Comment on lines +6 to +7
npm-pkg-name := `cargo metadata --format-version 1 | jq -r '.packages[] | select(.name == "runner").metadata.npm.name'`
npm-pkg-scope := `cargo metadata --format-version 1 | jq -r '.packages[] | select(.name == "runner").metadata.npm.subpkgscope'`
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

cargo metadata --no-deps --format-version 1 | jq -r '
{
  workspace_default_members,
  package_names: [.packages[].name],
  selected_runner: ([.packages[] | select(.name == "runner")][0].name // "<none>"),
  selected_runner_run: ([.packages[] | select(.name == "runner-run")][0].name // "<none>"),
  default_member_npm_name: (
    .workspace_default_members[0] as $id
    | [.packages[] | select(.id == $id) | .metadata.npm.name][0] // "<none>"
  ),
  default_member_npm_scope: (
    .workspace_default_members[0] as $id
    | [.packages[] | select(.id == $id) | .metadata.npm.subpkgscope][0] // "<none>"
  )
}'

Repository: kjanat/runner

Length of output: 542


🏁 Script executed:

head -n 10 justfile | cat -n

Repository: kjanat/runner

Length of output: 602


Arr, them jq selectors be ghost-hunting the old crate name, matey.

After the rename to runner-run, that select(.name == "runner") be coming back empty as a dead man's coffers. Leaves npm-pkg-name and npm-pkg-scope blank as bleached bones, which means test-release starts walking through bad npm/dist/... paths and the whole thing goes belly-up.

Time to fix this proper-like: resolve the workspace default member here, just like build-packages.ts does.

Suggested fix
-npm-pkg-name := `cargo metadata --format-version 1 | jq -r '.packages[] | select(.name == "runner").metadata.npm.name'`
-npm-pkg-scope := `cargo metadata --format-version 1 | jq -r '.packages[] | select(.name == "runner").metadata.npm.subpkgscope'`
+npm-pkg-name := `cargo metadata --no-deps --format-version 1 | jq -r '.workspace_default_members[0] as $id | .packages[] | select(.id == $id) | .metadata.npm.name'`
+npm-pkg-scope := `cargo metadata --no-deps --format-version 1 | jq -r '.workspace_default_members[0] as $id | .packages[] | select(.id == $id) | .metadata.npm.subpkgscope'`
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
npm-pkg-name := `cargo metadata --format-version 1 | jq -r '.packages[] | select(.name == "runner").metadata.npm.name'`
npm-pkg-scope := `cargo metadata --format-version 1 | jq -r '.packages[] | select(.name == "runner").metadata.npm.subpkgscope'`
npm-pkg-name := `cargo metadata --no-deps --format-version 1 | jq -r '.workspace_default_members[0] as $id | .packages[] | select(.id == $id) | .metadata.npm.name'`
npm-pkg-scope := `cargo metadata --no-deps --format-version 1 | jq -r '.workspace_default_members[0] as $id | .packages[] | select(.id == $id) | .metadata.npm.subpkgscope'`
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@justfile` around lines 6 - 7, The jq selectors in justfile currently look up
a package by the old crate name ("runner") and return empty after the rename;
change npm-pkg-name and npm-pkg-scope to first resolve the workspace default
member (same approach as build-packages.ts) and then select the package whose id
matches that default member, extracting .metadata.npm.name and
.metadata.npm.subpkgscope; specifically, run cargo metadata and use jq to get
the workspace default member id, then select the package with that id (matching
.id) and return its metadata.npm.name / metadata.npm.subpkgscope instead of
select(.name == "runner").

Comment thread justfile
Comment on lines +47 to 49
pkg="$(jq -r --arg t '{{ host-triple }}' '.targets[] | select(.rust == $t) | .pkg' npm/targets.json)"
echo "→ host: {{ host-triple }} (${pkg})"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Fail fast if the host triple has no target mapping.

If that jq query returns an empty string, the script keeps sailing: build-packages gets only="", which means “build everything”, and the later symlink step goes off the rails with a blank package name. Guard pkg right after the lookup.

Suggested fix
     pkg="$(jq -r --arg t '{{ host-triple }}' '.targets[] | select(.rust == $t) | .pkg' npm/targets.json)"
+    if [[ -z "${pkg}" ]]; then
+        echo "✗ no npm target mapping for host triple {{ host-triple }}"
+        exit 1
+    fi
     echo "→ host: {{ host-triple }} (${pkg})"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
pkg="$(jq -r --arg t '{{ host-triple }}' '.targets[] | select(.rust == $t) | .pkg' npm/targets.json)"
echo "→ host: {{ host-triple }} (${pkg})"
pkg="$(jq -r --arg t '{{ host-triple }}' '.targets[] | select(.rust == $t) | .pkg' npm/targets.json)"
if [[ -z "${pkg}" ]]; then
echo "✗ no npm target mapping for host triple {{ host-triple }}"
exit 1
fi
echo "→ host: {{ host-triple }} (${pkg})"
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@justfile` around lines 47 - 49, The pkg lookup using jq can return an empty
string which later causes build-packages and the symlink step to fail silently;
after the jq assignment to pkg (the variable pkg in the justfile), immediately
check if pkg is empty and, if so, print a clear error message including the
host-triple and exit non‑zero to fail fast (so build-packages doesn't get
only=""). Ensure you reference the same pkg variable and the host-triple
placeholder in the message.

Comment thread site/src/index.html
Comment on lines +40 to +43
<button class="copy" type="button" data-cmd="cargo install --git={{repo}}/ runner">
<span class="label">cargo · from source</span>
<span class="cmd">cargo install --git={{repo}}/ runner</span>
<span class="toast" aria-hidden="true">copied</span>
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Update the cargo install command to the renamed crate package.

Lines 40-43 still copy/install runner; this PR renames the published package to runner-run, so the current button command is stale.

Suggested fix
-					<button class="copy" type="button" data-cmd="cargo install --git={{repo}}/ runner">
+					<button class="copy" type="button" data-cmd="cargo install --git {{repo}} runner-run">
 						<span class="label">cargo · from source</span>
-						<span class="cmd">cargo install --git={{repo}}/ runner</span>
+						<span class="cmd">cargo install --git {{repo}} runner-run</span>
 						<span class="toast" aria-hidden="true">copied</span>
 					</button>
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
<button class="copy" type="button" data-cmd="cargo install --git={{repo}}/ runner">
<span class="label">cargo · from source</span>
<span class="cmd">cargo install --git={{repo}}/ runner</span>
<span class="toast" aria-hidden="true">copied</span>
<button class="copy" type="button" data-cmd="cargo install --git {{repo}} runner-run">
<span class="label">cargo · from source</span>
<span class="cmd">cargo install --git {{repo}} runner-run</span>
<span class="toast" aria-hidden="true">copied</span>
</button>
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@site/src/index.html` around lines 40 - 43, The cargo install command shown in
the copy button still references the old crate name "runner"; update the command
string in the button with data-cmd and the visible span (span class="cmd") to
the new package name "runner-run" so both the data-cmd attribute value and the
displayed command text change from "cargo install --git={{repo}}/ runner" /
"runner" to "cargo install --git={{repo}}/ runner-run" / "runner-run" (locate
the button element with class="copy" and the nested span.cmd to make the edits).

Comment thread site/src/styles/index.css
Comment on lines +5 to +15
.visually-hidden {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
white-space: nowrap;
border: 0;
clip: rect(0, 0, 0, 0);
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Ye’ve got a lint blocker in the visually-hidden utility.

Line 14 uses deprecated clip, and Stylelint is already flagging it as an error. Swap to the modern clipping pattern so this sheet passes lint cleanly.

Suggested fix
 .visually-hidden {
 	position: absolute;
 	width: 1px;
 	height: 1px;
 	padding: 0;
 	margin: -1px;
 	overflow: hidden;
 	white-space: nowrap;
 	border: 0;
-	clip: rect(0, 0, 0, 0);
+	clip-path: inset(50%);
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
.visually-hidden {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
white-space: nowrap;
border: 0;
clip: rect(0, 0, 0, 0);
}
.visually-hidden {
position: absolute;
width: 1px;
height: 1px;
padding: 0;
margin: -1px;
overflow: hidden;
white-space: nowrap;
border: 0;
clip-path: inset(50%);
}
🧰 Tools
🪛 Stylelint (17.10.0)

[error] 14-14: Deprecated property "clip" (property-no-deprecated)

(property-no-deprecated)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@site/src/styles/index.css` around lines 5 - 15, The .visually-hidden utility
is using the deprecated clip property (clip: rect(...)), which Stylelint flags;
replace that deprecated rule with the modern equivalent by removing clip:
rect(0, 0, 0, 0) and adding a clip-path based solution (e.g., clip-path:
inset(50%)) so the .visually-hidden rule uses clip-path instead of clip while
keeping the existing positioning, size, overflow, and white-space semantics.

Comment thread site/src/styles/index.css
Comment on lines +295 to +299
.copy .cmd {
overflow-x: visible;
font-size: 0.85rem;
word-break: break-word;
overflow-wrap: anywhere;
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

word-break: break-word be deprecated here too.

Line 298 is another Stylelint error. overflow-wrap: anywhere already handles the wrap behavior in this block, so keep word-break at its default or drop it entirely.

Suggested fix
 	.copy .cmd {
 		overflow-x: visible;
 		font-size: 0.85rem;
-		word-break: break-word;
 		overflow-wrap: anywhere;
 		white-space: normal;
 	}
🧰 Tools
🪛 Stylelint (17.10.0)

[error] 298-298: Deprecated keyword "break-word" for property "word-break" (declaration-property-value-keyword-no-deprecated)

(declaration-property-value-keyword-no-deprecated)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@site/src/styles/index.css` around lines 295 - 299, The CSS rule for the
selector ".copy .cmd" contains the deprecated declaration "word-break:
break-word"; remove that property (or reset it to the default) since
"overflow-wrap: anywhere" already provides the intended wrapping behavior—update
the ".copy .cmd" block to drop "word-break: break-word" so Stylelint stops
flagging it.

Comment thread src/tool/cargo_aliases.rs
Comment on lines +47 to +50
impl ExtractedAlias {
/// Render the expansion as a space-separated string for descriptions.
pub(crate) fn display_command(&self) -> String {
self.expansion.join(" ")
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

The rendered command loses quoting.

tokenize() preserves a single token like a b, but display_command() prints it as a b with no quoting, so the description can show the wrong argv shape. Quote/escape whitespace-bearing tokens before joining so runner list matches reality.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/tool/cargo_aliases.rs` around lines 47 - 50, display_command currently
joins tokens with spaces and loses quoting for tokens that contain whitespace
(mismatching what tokenize() produces); update ExtractedAlias::display_command
to quote/escape any token that contains whitespace or special characters before
joining so the rendered string reflects the real argv shape (e.g., wrap
space-containing tokens in quotes and escape internal quotes/backslashes), using
the same tokenization assumptions as tokenize() to ensure symmetry between
tokenize() and display_command().

Comment thread src/tool/cargo_aliases.rs
Comment on lines +157 to +163
let aliases =
read_alias_table(path).with_context(|| format!("reading {}", path.display()))?;
for (name, value) in aliases {
let Some(tokens) = tokenize(&value) else {
continue;
};
merged.insert(name, tokens);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Don’t silently drop busted aliases.

If tokenize() fails here, the alias just vanishes from detection/listing and the caller never gets a warning. Bubble an error with the alias name/path instead of continue-ing, or ye’ll make invalid configs look like “no alias found”.

Suggested fix
-            let Some(tokens) = tokenize(&value) else {
-                continue;
-            };
+            let tokens = tokenize(&value)
+                .with_context(|| format!("invalid cargo alias `{name}` in {}", path.display()))?;
             merged.insert(name, tokens);
-fn tokenize(value: &AliasValue) -> Option<Vec<String>> {
+fn tokenize(value: &AliasValue) -> anyhow::Result<Vec<String>> {
     match value {
-        AliasValue::Arr(tokens) => (!tokens.is_empty()).then(|| tokens.clone()),
+        AliasValue::Arr(tokens) if !tokens.is_empty() => Ok(tokens.clone()),
+        AliasValue::Arr(_) => anyhow::bail!("alias expansion must not be empty"),
         AliasValue::Str(raw) => {
-            let split = shlex::split(raw)?;
-            (!split.is_empty()).then_some(split)
+            let split = shlex::split(raw).ok_or_else(|| anyhow::anyhow!("invalid shell quoting"))?;
+            if split.is_empty() {
+                anyhow::bail!("alias expansion must not be empty");
+            }
+            Ok(split)
         }
     }
 }

Also applies to: 198-204

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/tool/cargo_aliases.rs` around lines 157 - 163, The loop that iterates
aliases from read_alias_table(path) currently silently skips failures from
tokenize(&value), causing invalid aliases to disappear; instead, when tokenize
returns None or an error, return/bubble an error with context including the
alias name and the config path. Replace the `continue` in the for loop (and the
analogous site around lines 198-204) so that failures from tokenize(&value)
produce an anyhow::Error (or use bail!/Context) that includes the alias `name`
and `path.display()` before returning, rather than inserting nothing into
`merged`.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants