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
95 changes: 95 additions & 0 deletions .github/workflows/cargo-publish.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
name: Cargo trusted publishing

on:
workflow_dispatch:
inputs:
expected_sha:
description: Exact main commit whose CI has succeeded
required: true
type: string
version:
description: Exact package version in the selected source
required: true
type: string
publish:
description: Publish a new version (false only verifies packaging and OIDC)
required: true
default: false
type: boolean

permissions:
contents: read
actions: read

concurrency:
group: cargo-trusted-publishing
cancel-in-progress: false

defaults:
run:
shell: bash

jobs:
publish:
if: github.ref == 'refs/heads/main'
runs-on: ubuntu-24.04
timeout-minutes: 20
permissions:
contents: read
actions: read
id-token: write
env:
EXPECTED_SHA: ${{ inputs.expected_sha }}
EXPECTED_VERSION: ${{ inputs.version }}
PACKAGE_NAME: stack-diagram-cli
PUBLISH: ${{ inputs.publish }}
steps:
- name: Reject unexpected dispatch context
run: |
test "$GITHUB_REPOSITORY" = stack-sh/cli
test "$GITHUB_REF" = refs/heads/main
[[ "$EXPECTED_SHA" =~ ^[0-9a-f]{40}$ ]]
test "$GITHUB_SHA" = "$EXPECTED_SHA"
[[ "$EXPECTED_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]
case "$PACKAGE_NAME" in stack-diagram-cli) ;; *) exit 1 ;; esac
case "$PUBLISH" in true|false) ;; *) exit 1 ;; esac

- name: Check out exact main source without persisted credentials
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7
with:
ref: ${{ github.sha }}
persist-credentials: false

- name: Install minimum supported Rust
run: rustup toolchain install 1.85.0 --profile minimal

- name: Verify package, successful CI, and immutable registry state
env:
GH_TOKEN: ${{ github.token }}
run: |
cargo +1.85.0 metadata --no-deps --locked --format-version 1 > "$RUNNER_TEMP/package.json"
gh run list --repo stack-sh/cli --workflow ci.yaml --event push --branch main --commit "$EXPECTED_SHA" --limit 1 --json status,conclusion,headSha > "$RUNNER_TEMP/ci.json"
node scripts/cargo-publish-context.mjs "$RUNNER_TEMP/package.json" "$RUNNER_TEMP/ci.json"

- name: Verify package without registry credentials
run: cargo +1.85.0 publish --package "$PACKAGE_NAME" --registry crates-io --locked --dry-run

- name: Exchange GitHub OIDC identity for a short-lived registry token
id: auth
uses: rust-lang/crates-io-auth-action@c6f97d42243bad5fab37ca0427f495c86d5b1a18 # v1.0.5

- name: Verify OIDC exchange without publishing
if: inputs.publish == false
env:
CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }}
run: |
test -n "$CARGO_REGISTRY_TOKEN"
echo "OIDC exchange verified; no crate was published."

- name: Publish the previously verified new version
if: inputs.publish == true
env:
CARGO_REGISTRY_TOKEN: ${{ steps.auth.outputs.token }}
run: |
test -n "$CARGO_REGISTRY_TOKEN"
cargo +1.85.0 publish --package "$PACKAGE_NAME" --registry crates-io --locked
2 changes: 1 addition & 1 deletion .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ jobs:
- name: Check out repository
uses: actions/checkout@v7
- name: Validate initial Cargo publication guards
run: node --test scripts/initial-publish-context.test.mjs
run: node --test scripts/initial-publish-context.test.mjs scripts/cargo-publish-context.test.mjs

- name: Read supported specification revision
id: specification
Expand Down
19 changes: 14 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

`stack-sh/cli` is the open-source native Rust `stack` command for Stack architecture diagrams.

The repository contains native validation, formatting, and rendering commands. [Stack CLI 0.5.0](https://github.com/stack-sh/cli/releases/tag/v0.5.0) is the supported native binary release for macOS 13 or newer and glibc-based Linux 2.31 or newer, on arm64 and x86_64. GitHub Releases, Homebrew, and the owner-maintained Aqua registry are available; Cargo remains planned. Self-update was removed in 0.5.0; use the installation owner to upgrade. The target matrix, artifact names, verification material, channel ownership, and rollback rules are defined by the [distribution contract](./docs/distribution.md), with signing and verification procedures in the [supply-chain guide](./docs/supply-chain.md).
The repository contains native validation, formatting, and rendering commands. [Stack CLI 0.5.1](https://github.com/stack-sh/cli/releases/tag/v0.5.1) is the supported native binary release for macOS 13 or newer and glibc-based Linux 2.31 or newer, on arm64 and x86_64. GitHub Releases, Homebrew, the owner-maintained Aqua registry, and Cargo are available. Self-update was removed in 0.5.0; use the installation owner to upgrade. The target matrix, artifact names, verification material, channel ownership, and rollback rules are defined by the [distribution contract](./docs/distribution.md), with signing and verification procedures in the [supply-chain guide](./docs/supply-chain.md).

## Install

Expand All @@ -12,6 +12,15 @@ On supported Homebrew hosts, install the owner-maintained formula:
brew install stack-sh/tap/stack
```

With Rust 1.85 or newer and a native linker, install from crates.io:

```sh
cargo install stack-diagram-cli --version 0.5.1 --locked
stack --version
```

The package is `stack-diagram-cli`, not the unrelated `stack-cli`; the binary is `stack`. Cargo owns updates and uninstall, and does not automatically install completion or manual files. See [Cargo installation](./docs/distribution.md#cargo-installation).

For Aqua, copy the pinned configuration and policy from the [Aqua installation section](./docs/distribution.md#aqua-installation), review them, allow the policy, and install the checksum-locked package:

```sh
Expand All @@ -21,11 +30,11 @@ aqua install
stack --version
```

For a direct installation, download the archive for your target and its verification material from [GitHub Releases](https://github.com/stack-sh/cli/releases/tag/v0.5.0). Verify the checksum signature and both attestations by following the [supply-chain guide](./docs/supply-chain.md), then follow the [direct installation steps](./docs/distribution.md#direct-installation). The macOS artifacts are reproducibly ad-hoc signed, not Apple-notarized; Sigstore and GitHub attestations provide the publisher-identity check.
For a direct installation, download the archive for your target and its verification material from [GitHub Releases](https://github.com/stack-sh/cli/releases/tag/v0.5.1). Verify the checksum signature and both attestations by following the [supply-chain guide](./docs/supply-chain.md), then follow the [direct installation steps](./docs/distribution.md#direct-installation). The macOS artifacts are reproducibly ad-hoc signed, not Apple-notarized; Sigstore and GitHub attestations provide the publisher-identity check.

## Commands

The command inventory below is available in the published 0.5.0 binaries, including `stack doctor`, `stack config`, and structured JSON output for automation.
The command inventory below is available in the published 0.5.1 binaries, including `stack doctor`, `stack config`, and structured JSON output for automation.

```text
stack help
Expand Down Expand Up @@ -72,9 +81,9 @@ stack manpage

`stack config path` prints the selected `config.yaml` path without creating or reading the file. `stack config get default_icons_path` strictly reads the supported configuration and prints the effective icon-store path. `stack doctor` reports the CLI version, configuration path and source, configuration validity, effective icon-store source, and installed known-provider packs. It is read-only, emits actionable categories instead of configuration contents, exits `0` for healthy and warning-only reports, and exits `2` when it finds an operational problem. See the [configuration discovery and doctor contract](./docs/configuration.md).

`stack update` is removed in 0.5.0. Homebrew, Aqua, and future Cargo installations are updated through their package manager; direct downloads are updated manually after verification. See the [upgrade and migration guide](./docs/self-update.md).
`stack update` is removed in 0.5.0. Homebrew, Aqua, and Cargo installations are updated through their package manager; direct downloads are updated manually after verification. See the [upgrade and migration guide](./docs/self-update.md).

`stack completions <bash|zsh|fish>` and `stack manpage` generate deterministic shell integration and an offline roff manual from the CLI command metadata. The 0.5.0 release archives carry the exact generated files; Homebrew installs them into its managed completion and manual paths, while direct, Aqua, and future Cargo users can generate them into user-owned locations without modifying shell startup files. See the [completion and manual guide](./docs/completions.md).
`stack completions <bash|zsh|fish>` and `stack manpage` generate deterministic shell integration and an offline roff manual from the CLI command metadata. The 0.5.1 release archives carry the exact generated files; Homebrew installs them into its managed completion and manual paths, while direct, Aqua, and Cargo users can generate them into user-owned locations without modifying shell startup files. See the [completion and manual guide](./docs/completions.md).

`stack icons list [PROVIDER] [QUERY]` searches the asset-free catalog by ID, product name, or category. The catalog currently contains 1,051 IDs: 305 AWS, 45 Google Cloud, 639 Azure, and 62 curated developer and collaboration tool icons. This command reads only metadata embedded in the CLI.

Expand Down
10 changes: 5 additions & 5 deletions distribution/distribution-contract.json
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@
"product": {
"binary": "stack",
"sourceCargoPackage": "stack-diagram-cli",
"publishedCargoPackage": null,
"publishedCargoPackage": "stack-diagram-cli",
"sourceVersionFile": "Cargo.toml",
"currentSourceVersion": "0.5.1",
"minimumRustVersion": "1.85",
"currentReleaseVersion": "0.5.0"
"currentReleaseVersion": "0.5.1"
},
"availability": {
"state": "available",
"message": "Stack CLI 0.5.1 prepares the first Cargo distribution. GitHub Releases, Homebrew, and Aqua currently distribute 0.5.0; Cargo remains planned until registry publication and clean-install verification."
"message": "Stack CLI 0.5.1 is available through GitHub Releases, Homebrew, Aqua, and Cargo. Cargo installs the stack-diagram-cli source package using Rust 1.85 or newer and registry-only dependencies."
},
"versioning": {
"scheme": "Semantic Versioning",
Expand Down Expand Up @@ -142,14 +142,14 @@
},
{
"id": "cargo",
"state": "planned",
"state": "available",
"targets": [
"aarch64-apple-darwin",
"x86_64-apple-darwin",
"aarch64-unknown-linux-gnu",
"x86_64-unknown-linux-gnu"
],
"owns": "a future unambiguous crates.io source package and dependency graph; installs the stack binary with Rust 1.85 or newer",
"owns": "the stack-diagram-cli crates.io source package and registry-only locked dependency graph; installs the stack binary with Rust 1.85 or newer",
"source": "crates.io",
"updatePolicy": "Cargo owns upgrades; stack never replaces its own executable"
},
Expand Down
8 changes: 8 additions & 0 deletions docs/cargo-releasing.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,3 +9,11 @@ The initial publication creates `stack-diagram-cli` version `0.5.1`. The workflo
5. Remove the GitHub bootstrap secret and revoke the crates.io token. Configure a crates.io trusted publisher for the ongoing release workflow before any later publication. Do not reuse this initial workflow for updates or broaden the bootstrap token.

The token is supplied only to the publication step through `CARGO_REGISTRY_TOKEN`; the workflow never runs `cargo login` or writes a credentials file. It cannot configure trusted publishing on behalf of a crate owner. See the [Cargo publication reference](https://doc.rust-lang.org/cargo/commands/cargo-publish.html) for upload and timeout behavior.

## Ongoing trusted publishing

After initial publication, configure each crate's Settings → Trusted Publishing on crates.io with repository owner `stack-sh`, repository name `cli`, workflow filename `cargo-publish.yaml`, and no environment. The crate owner must save these settings; committing this workflow does not configure or prove registry trust. Follow the [crates.io instructions](https://crates.io/docs/trusted-publishing).

Dispatch `cargo-publish.yaml` from `main` with the full successful main CI commit and the exact package version. The default `publish: false` validates identity, registry state, and packaging, then checks the OIDC exchange **without uploading a crate**. This proves workflow authentication, not a new version's publication or every crate's owner configuration. The pinned authentication action revokes its short-lived token when the job ends; no long-lived repository secret or credentials file is used.

For an actual new release, merge the version change and all checks first, publish dependencies before consumers, then dispatch with `publish: true`. Existing versions, missing crates, non-main refs, version/SHA drift, and unsuccessful CI fail closed. Verify the downloaded archive checksum and source SHA after publication; a failed post-upload check does not undo an upload. Never rerun an upload without checking registry state. Keep the native release version/source identical and verify each package-manager channel separately.
4 changes: 2 additions & 2 deletions docs/completions.md
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,9 @@ The owner-maintained formula installs the archived files through Homebrew's `bas

No shell startup file is modified by the formula.

## Direct, Aqua, and future Cargo installs
## Direct, Aqua, and Cargo installs

Aqua's registry `files` mapping owns executable placement, not a user's shell startup or global manual database. A direct binary copy and a future Cargo install have the same boundary. Generate files into a user-owned location after installing the binary:
Aqua's registry `files` mapping owns executable placement, not a user's shell startup or global manual database. A direct binary copy and a Cargo install have the same boundary. Generate files into a user-owned location after installing the binary:

```sh
data_root="${XDG_DATA_HOME:-$HOME/.local/share}"
Expand Down
Loading