Skip to content
Merged
11 changes: 9 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,9 @@ jobs:
needs: version-bump
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: dtolnay/rust-toolchain@6c977a6ca4077a0ceb28ffbe03f59d46e9ac8772
with:
toolchain: 1.98.0
components: clippy, rustfmt
- name: Configure shared Cargo cache
run: |
Expand Down Expand Up @@ -125,13 +126,19 @@ jobs:

- name: verify_mapping
run: ./scripts/verify_mapping.sh
- name: verify_toolchain
run: ./scripts/verify_toolchain.sh
- name: test_release_guards
run: ./scripts/test_release_guards.sh
coverage:
name: Coverage
runs-on: ubuntu-latest
needs: gate
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: dtolnay/rust-toolchain@6c977a6ca4077a0ceb28ffbe03f59d46e9ac8772
with:
toolchain: 1.98.0
- name: Configure shared Cargo cache
run: |
# The runner image pre-sets CARGO_HOME=$HOME/.cargo; drop the
Expand Down
52 changes: 34 additions & 18 deletions .github/workflows/publish.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ on:
push:
tags:
- 'v*'
workflow_dispatch:

permissions:
contents: write
Expand All @@ -14,8 +13,17 @@ jobs:
name: Publish Crate
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: dtolnay/rust-toolchain@stable
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
persist-credentials: false
- uses: dtolnay/rust-toolchain@6c977a6ca4077a0ceb28ffbe03f59d46e9ac8772
with:
toolchain: 1.98.0
components: clippy, rustfmt
- name: Verify toolchain consistency
run: ./scripts/verify_toolchain.sh
- name: Test release guards
run: ./scripts/test_release_guards.sh
- name: Verify version and tag match upstream
run: scripts/verify_upstream_version.sh "$GITHUB_REF_NAME"
- name: Configure shared Cargo cache
Expand All @@ -35,49 +43,56 @@ jobs:
} >> "$GITHUB_ENV"
- name: Test shared Cargo cache env
run: scripts/test-cargo-env.sh
- uses: actions/setup-go@v5
- uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5
with:
go-version: 'stable'
- name: Fetch upstream Go source (pinned)
run: |
git clone --quiet https://github.com/charmbracelet/bubbletea.git upstream-go
cd upstream-go && git checkout --quiet v2.0.8
git clone --quiet --no-tags https://github.com/charmbracelet/bubbletea.git upstream-go
cd upstream-go
git checkout --quiet fc707bb7ea0161405bb6c653ec93f6a9c6a72fe1
- name: Fetch sibling rusty-bubbles (pinned)
uses: actions/checkout@v4
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
repository: coderbants/rusty-bubbles
path: siblings/rusty-bubbles
ref: dev
ref: 2a88f46cb8179388adb1951adb0f4fdbdcd633c0
persist-credentials: false
- name: Fetch sibling rusty-colorprofile (pinned)
uses: actions/checkout@v4
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
repository: coderbants/rusty-colorprofile
path: siblings/rusty-colorprofile
ref: dev
ref: 2d505e7821f72f133c17d6cc7afd314e03eddb66
persist-credentials: false
- name: Fetch sibling rusty-lipgloss (pinned)
uses: actions/checkout@v4
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
repository: coderbants/rusty-lipgloss
path: siblings/rusty-lipgloss
ref: dev
ref: 685b25da2af226b6ab2b9ce70f149eb9e4fb54ee
persist-credentials: false
- name: Fetch sibling rusty-testkit (pinned)
uses: actions/checkout@v4
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
repository: coderbants/rusty-testkit
path: siblings/rusty-testkit
ref: dev
ref: 163cf16399662da7bf2b17fb6637b466c6402fb2
persist-credentials: false
- name: Fetch sibling rusty-ultraviolet (pinned)
uses: actions/checkout@v4
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
repository: coderbants/rusty-ultraviolet
path: siblings/rusty-ultraviolet
ref: dev
ref: ad29f896f95e1bcd850d69580152edfac3289eec
persist-credentials: false
- name: Fetch sibling rusty-x-ansi (pinned)
uses: actions/checkout@v4
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
with:
repository: coderbants/rusty-x-ansi
path: siblings/rusty-x-ansi
ref: dev
ref: 971128e8566ad37991cd9cc50ff4f4c8a4b09c29
persist-credentials: false
- name: Place sibling crates
run: |
mkdir -p ../siblings && true
Expand Down Expand Up @@ -130,6 +145,7 @@ jobs:
# rejects re-publishing an existing version, so the version-bump gate
# in ci.yml keeps every release on a fresh, unreleased version.
- name: Publish to crates.io
if: startsWith(github.ref, 'refs/tags/v')
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
run: |
Expand Down
22 changes: 6 additions & 16 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ workflow.

## Development setup

- A recent stable Rust toolchain (`rustup default stable`).
- Rust 1.98.0, selected automatically by the checked-in `rust-toolchain.toml`.
- Go (for the upstream parity scripts and the pinned `upstream-go/` checkout).
- No other system dependencies; there are no C build steps.

Expand All @@ -26,7 +26,7 @@ cargo test --all-targets
- `examples/` — executable Rust ports of upstream Go examples.
- `tests/` — Rust integration tests ported from upstream `*_test.go` suites.
- `upstream-go/` — the pinned upstream Go checkout (git-ignored, never commit it).
- `scripts/` — parity and mapping verification helpers.
- `scripts/` — parity, mapping, and toolchain consistency verification helpers.
- `UPSTREAM_MAPPING.md` — the authoritative 1:1 account of every upstream file.

## The cleanroom porting workflow
Expand Down Expand Up @@ -76,20 +76,10 @@ cargo test --all-targets

## Versioning

Every release that matches an upstream version uses the upstream `MAJOR.MINOR.PATCH` plus a
fourth dot-separated iteration number that internally tracks which deployed release of this
port it is for that upstream version:

- `v2.0.8.0` — first port release of upstream `v2.0.8`
- `v2.0.8.1` — a hotfix iteration for `v2.0.8` (bug fix released without an upstream
version bump)

The iteration increments whenever we publish a new release of our port without an upstream
version bump (e.g. a bug fix that upstream has not yet released). The git tag and GitHub
release carry the full four-part version (`v2.0.8.1`). `Cargo.toml` keeps the upstream
`X.Y.Z` (`2.0.8`), since crates.io only accepts `MAJOR.MINOR.PATCH`; iteration hotfixes
publish under the same `X.Y.Z` on crates.io, replacing the previous deployment (iterations
are only used for bug fixes, so the contents differ only in fixes).
Every release uses the tracked upstream `MAJOR.MINOR.PATCH` version exactly. The crate version,
release tag, and GitHub release must all match the upstream version; there is no fourth-part
iteration or republishing of an existing crates.io version. If upstream has not advanced beyond
the currently published version, do not create a release—wait for the next upstream release.

## Contribution guidelines

Expand Down
2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ name = "rusty-bubbletea"
exclude = ["POLICIES.md"]
version = "2.0.8"
edition = "2021"
rust-version = "1.91"
rust-version = "1.98.0"
description = "Cleanroom Rust port of Charmbracelet's Bubble Tea (v2.0.8) TUI Elm architecture framework"
license = "MIT"

Expand Down
2 changes: 1 addition & 1 deletion UPSTREAM_MAPPING.md
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ in the Support Files section.
| `README.md` | `README.md` | Documented Rust port header with graphics & links |
| Repository lifecycle guide | `docs/src/lib.rs` | User-facing documentation anchor for `ProgramHandle`, headless options, cancellation, and graceful versus error shutdown |
| `UPGRADE_GUIDE_V2.md` | `README.md` (notes) | v1 -> v2 migration guidance summarized in README |
| `go.mod` / `go.sum` | `Cargo.toml` | Dependency manifest (Go modules -> Cargo crates); candidate declares the supported Rust 1.91 toolchain floor |
| `go.mod` / `go.sum` | `Cargo.toml` | Dependency manifest (Go modules -> Cargo crates); candidate declares the supported Rust 1.98.0 toolchain floor |
| `examples/go.mod` / `examples/go.sum` / `tutorials/go.mod` / `tutorials/go.sum` | `Cargo.toml` | Example-module manifests (deps like bubbles, glamour, harmonica are example-only) |
| `examples/*/README.md` and `examples/*/*.gif` | `examples/` docs | Per-example docs/assets; retained as upstream documentation references |
| `examples/isbn-form/isbn-form.tape` | (asset) | VHS recording asset; not applicable to the Rust crate |
Expand Down
4 changes: 4 additions & 0 deletions rust-toolchain.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
[toolchain]
channel = "1.98.0"
profile = "minimal"
components = ["clippy", "rustfmt"]
90 changes: 90 additions & 0 deletions scripts/test_release_guards.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
#!/usr/bin/env bash
# Exercises the release-policy guards with both valid and invalid inputs.
set -u

cd "$(dirname "$0")/.."
fail=0

if ! ./scripts/verify_toolchain.sh >/dev/null; then
echo "ERROR: toolchain/release structure verification failed" >&2
fail=1
fi

if ! ./scripts/verify_upstream_version.sh v2.0.8 >/dev/null; then
echo "ERROR: valid upstream release tag was rejected" >&2
fail=1
fi

if UPSTREAM_VERSION_SOURCE=rust-toolchain.toml ./scripts/verify_upstream_version.sh v2.0.8 >/dev/null 2>&1; then
echo "ERROR: missing upstream header was accepted" >&2
fail=1
else
echo "OK: missing upstream header is rejected"
fi

fixture_root="$(mktemp -d)"
trap 'rm -rf "${fixture_root}"' EXIT
mkdir -p "${fixture_root}/.github/workflows" "${fixture_root}/scripts"
cp Cargo.toml rust-toolchain.toml CONTRIBUTING.md UPSTREAM_MAPPING.md "${fixture_root}/"
cp .github/workflows/ci.yml .github/workflows/publish.yml "${fixture_root}/.github/workflows/"
cp scripts/verify_toolchain.sh "${fixture_root}/scripts/"

perl -0pi -e 's/actions\/checkout\@11bd71901bbe5b1630ceea73d27597364c9af683/actions\/checkout\@v4/' "${fixture_root}/.github/workflows/publish.yml"
if RELEASE_ROOT="${fixture_root}" "${fixture_root}/scripts/verify_toolchain.sh" >/dev/null 2>&1; then
echo "ERROR: mutable checkout action pin was accepted" >&2
fail=1
else
echo "OK: mutable checkout action pin is rejected"
fi

cp .github/workflows/publish.yml "${fixture_root}/.github/workflows/publish.yml"
perl -0pi -e 's/ref: [0-9a-f]{40}/ref: dev/' "${fixture_root}/.github/workflows/publish.yml"
if RELEASE_ROOT="${fixture_root}" "${fixture_root}/scripts/verify_toolchain.sh" >/dev/null 2>&1; then
echo "ERROR: mutable sibling ref was accepted" >&2
fail=1
else
echo "OK: mutable sibling ref is rejected"
fi

cp .github/workflows/publish.yml "${fixture_root}/.github/workflows/publish.yml"
perl -0pi -e 's/persist-credentials: false/persist-credentials: true/' "${fixture_root}/.github/workflows/publish.yml"
if RELEASE_ROOT="${fixture_root}" "${fixture_root}/scripts/verify_toolchain.sh" >/dev/null 2>&1; then
echo "ERROR: persisted checkout credentials were accepted" >&2
fail=1
else
echo "OK: persisted checkout credentials are rejected"
fi

cp .github/workflows/publish.yml "${fixture_root}/.github/workflows/publish.yml"
perl -0pi -e 's/(uses: actions\/checkout\@11bd71901bbe5b1630ceea73d27597364c9af683\n with:\n )persist-credentials: false/${1}persist-credentials: true/' "${fixture_root}/.github/workflows/publish.yml"
perl -0pi -e 's/(toolchain: 1\.98\.0\n)/$1 persist-credentials: false\n/' "${fixture_root}/.github/workflows/publish.yml"
if RELEASE_ROOT="${fixture_root}" "${fixture_root}/scripts/verify_toolchain.sh" >/dev/null 2>&1; then
echo "ERROR: credentials compensated in another step were accepted" >&2
fail=1
else
echo "OK: cross-step credential compensation is rejected"
fi

cp .github/workflows/publish.yml "${fixture_root}/.github/workflows/publish.yml"
perl -0pi -e 's/fc707bb7ea0161405bb6c653ec93f6a9c6a72fe1/v2.0.8/' "${fixture_root}/.github/workflows/publish.yml"
if RELEASE_ROOT="${fixture_root}" "${fixture_root}/scripts/verify_toolchain.sh" >/dev/null 2>&1; then
echo "ERROR: mutable upstream ref was accepted" >&2
fail=1
else
echo "OK: mutable upstream ref is rejected"
fi

cp .github/workflows/publish.yml "${fixture_root}/.github/workflows/publish.yml"
perl -0pi -e 's/ CARGO_REGISTRY_TOKEN:/ CARGO_REGISTRY_TOKEN:/' "${fixture_root}/.github/workflows/publish.yml"
if RELEASE_ROOT="${fixture_root}" "${fixture_root}/scripts/verify_toolchain.sh" >/dev/null 2>&1; then
echo "ERROR: job-scoped registry credentials were accepted" >&2
fail=1
else
echo "OK: job-scoped registry credentials are rejected"
fi

if [ "${fail}" -ne 0 ]; then
exit 1
fi

echo "OK: release guards reject unsafe inputs and accept the valid release path"
Loading
Loading