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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 26 additions & 4 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -106,9 +106,33 @@ jobs:
publish:
needs: build
runs-on: ubuntu-latest
permissions:
# Trusted publishing (OIDC). `id-token: write` is the whole auth story:
# npm exchanges this short-lived, workflow-scoped GitHub identity for
# publish rights, so there is no NPM_TOKEN to expire — which is exactly
# how v0.2.11 and v0.3.0 failed to publish.
#
# Each of the five packages names alleneubank/linear-cli + release.yml as
# its trusted publisher on npmjs.com. RENAMING THIS FILE BREAKS PUBLISHING
# until every package's trusted-publisher config is updated to match.
#
# Declared per-job, which drops the workflow-level `contents: write` here:
# publishing needs to read the repo, not write it.
contents: read
id-token: write
steps:
- uses: actions/checkout@v4
- uses: oven-sh/setup-bun@v2
- uses: actions/setup-node@v4
with:
# Trusted publishing needs Node >= 22.14.0 and npm >= 11.5.1.
node-version: "24"
registry-url: "https://registry.npmjs.org"
- name: Ensure npm supports trusted publishing
# Node 24 already ships npm 11.x; this pins the floor rather than
# trusting whatever the runner image happens to bundle.
run: |
npm install -g npm@latest
npm --version
- uses: actions/download-artifact@v4
with:
name: npm-packages
Expand All @@ -117,6 +141,4 @@ jobs:
# upload-artifact does not preserve the mode; the packaged binary must
# stay executable or the npm wrapper spawns a non-executable file.
run: chmod +x npm/linear-cli-*/linear
- env:
NPM_CONFIG_TOKEN: ${{ secrets.NPM_TOKEN }}
run: ./scripts/publish-npm.sh ${GITHUB_REF_NAME#v}
- run: ./scripts/publish-npm.sh ${GITHUB_REF_NAME#v}
20 changes: 20 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,26 @@
Notable changes per release. Versions before 0.3.0 are recorded in the
[GitHub releases](https://github.com/alleneubank/linear-cli/releases).

## Unreleased

### Release engineering

- npm publishing moves to **trusted publishing (OIDC)**. The release workflow
presents a short-lived, workflow-scoped GitHub identity via
`id-token: write` and npm exchanges it for publish rights, so there is no
long-lived `NPM_TOKEN`. The token had gone stale and silently broke npm
publishing for two releases (v0.2.11 and v0.3.0 both failed with a registry
404), while the tag, the GitHub release, and the manifests all looked
correct.
- `scripts/publish-npm.sh` uses `npm publish` rather than `bun publish`: bun
cannot present an OIDC identity (oven-sh/bun#22423) and fails with "missing
authentication". `bunfig.toml` existed only to feed bun the token and is
removed.
- Publishing from a public repo with OIDC also produces provenance
attestations automatically.
- `flake.nix` provides a dev shell pinned to Zig 0.16.0 with `ziglint` and
`jq`, so `zig build lint` works instead of failing with `FileNotFound`.

## 0.3.0

The first release since v0.2.11, covering the Zig 0.16 migration, a
Expand Down
2 changes: 2 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,4 +36,6 @@
- Flags must reach the API or not exist. `issue delete --reason` was removed rather than deprecated because `issueDelete` takes only `(id, permanentlyDelete)` — the value was echoed into this CLI's own output and read like an audit trail in Linear. Do not add a flag whose only effect is on local output while naming something the API owns.
- GraphQL client already handles HTTP status + GraphQL errors; retries only for 5xx with small backoff. Preserve explicit error messaging.
- Releases are cut by pushing a `v*` tag. `build.zig` derives `--version` from `git describe --tags`, so the tag is the only source of truth for the binary; the four hand-maintained manifests (`package.json`, `.claude-plugin/plugin.json`, `npm/*/package.json` including its `optionalDependencies` pins) must agree with it, and `scripts/check-versions.sh` is the gate that proves it — it runs in CI on every push and again in the release workflow *before* anything is published. Bump all four together or the gate fails closed. One tag drives two channels: npm (`scripts/publish-npm.sh`) and GitHub release assets. The assets are `linear-<version>-<os>-<arch>.tar.gz` + `.sha256`, macOS/Linux on both arches, Linux statically linked against musl; the naming mirrors zmx and `alleneubank/agent-statusline` because that is the shape mise's `github:` backend resolves per platform. Do not add an `asset_pattern` on the consuming side — it replaces mise's per-arch matching instead of narrowing it, collapsing every platform onto whichever asset sorts first.
- npm publishing is **trusted publishing (OIDC)**, not a token. The release workflow's `publish` job declares `id-token: write` and npm exchanges that short-lived, workflow-scoped GitHub identity for publish rights; each of the five packages names `alleneubank/linear-cli` + the workflow **filename** as its trusted publisher on npmjs.com. Renaming `.github/workflows/release.yml` therefore breaks publishing until every package's config is updated to match — the filename is part of the trust relationship, not an implementation detail. It must be `npm publish`, never `bun publish`: bun cannot present an OIDC identity (oven-sh/bun#22423) and fails with "missing authentication", which is why `bunfig.toml` is gone. Do not reintroduce an `NPM_TOKEN` fallback — a stale token is precisely what silently broke publishing for v0.2.11 and v0.3.0 while every other signal looked green, and a fallback would re-arm that failure mode instead of surfacing it.
- `flake.nix` is the dev shell: Zig pinned to `zig_0_16` (matching CI and the README — the toolchain is not interchangeable), plus `ziglint` and `jq`. `zig build lint` shells out to `ziglint` and dies with `FileNotFound` without it, which is why the step is absent from CI; run it from `nix develop`. ziglint findings are advisory and are not a release gate.
- Tests: offline unit coverage exists for config, flag parsing, printer; keep them passing. Online tests are gated by env and should remain optional.
2 changes: 0 additions & 2 deletions bunfig.toml

This file was deleted.

61 changes: 61 additions & 0 deletions flake.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

36 changes: 36 additions & 0 deletions flake.nix
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
description = "linear-cli — a single-binary Linear client built with Zig";

inputs = {
nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
flake-utils.url = "github:numtide/flake-utils";
};

outputs =
{ self, nixpkgs, flake-utils }:
flake-utils.lib.eachDefaultSystem (
system:
let
pkgs = nixpkgs.legacyPackages.${system};
in
{
devShells.default = pkgs.mkShell {
packages = with pkgs; [
# Pinned, not `zig`: 0.15.2 cannot link on macOS 26 and the 0.16
# std.Io migration is not backward compatible, so the shell has to
# agree with .github/workflows/*.yml and the README.
zig_0_16

# `zig build lint` shells out to this binary and fails with
# FileNotFound when it is absent — which is why the step was left
# out of CI. .ziglint.zon holds the rule set.
ziglint

# scripts/check-versions.sh and scripts/publish-npm.sh both parse
# the package manifests with jq.
jq
];
};
}
);
}
28 changes: 22 additions & 6 deletions scripts/publish-npm.sh
Original file line number Diff line number Diff line change
@@ -1,24 +1,40 @@
#!/bin/bash
# Publish the five npm packages for a release.
#
# Auth is npm TRUSTED PUBLISHING (OIDC), not a token: the workflow grants
# `id-token: write`, npm exchanges that short-lived GitHub identity for publish
# rights, and each package on npmjs.com names this repo + workflow filename as
# its trusted publisher. There is no NPM_TOKEN to leak, rotate, or let expire —
# which is what broke v0.2.11 and v0.3.0.
#
# `npm publish`, not `bun publish`: bun cannot present an OIDC identity
# (oven-sh/bun#22423, open since 2025-09) and fails with "missing
# authentication". Do not switch this back for speed.
#
# Requires npm >= 11.5.1 and Node >= 22.14.0. Publishing from a public repo
# also gets provenance attestations for free.
set -euo pipefail

VERSION="${1:?Usage: publish-npm.sh <version>}"

# Auth configured via bunfig.toml (reads $NPM_CONFIG_TOKEN)

# Update versions
# Update versions. .github/workflows/release.yml has already run
# scripts/check-versions.sh --expect "$VERSION" against the committed
# manifests, so this only restates what the gate proved.
for f in npm/*/package.json; do
jq --arg v "$VERSION" '.version = $v' "$f" > tmp && mv tmp "$f"
done
jq --arg v "$VERSION" '.optionalDependencies |= with_entries(.value = $v)' \
npm/linear-cli/package.json > tmp && mv tmp npm/linear-cli/package.json

# Publish platform packages first, then main
# Platform packages first: the wrapper pins them as optionalDependencies, so
# publishing it first would leave a window where `npm install` resolves a
# wrapper whose binaries do not exist yet.
for p in darwin-arm64 darwin-x64 linux-x64 linux-arm64; do
echo "Publishing @0xbigboss/linear-cli-${p}..."
(cd "npm/linear-cli-${p}" && bun publish --access public)
(cd "npm/linear-cli-${p}" && npm publish --access public)
done

echo "Publishing @0xbigboss/linear-cli..."
(cd npm/linear-cli && bun publish --access public)
(cd npm/linear-cli && npm publish --access public)

echo "Done"
Loading