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
54 changes: 54 additions & 0 deletions .github/workflows/cli.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
name: CLI CI

on:
push:
branches: [main, develop]
paths:
- 'cli/**'
- '.github/workflows/cli.yml'
pull_request:
branches: [main, develop]
paths:
- 'cli/**'
- '.github/workflows/cli.yml'

env:
CARGO_TERM_COLOR: always

concurrency:
group: cli-${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true

jobs:
cli:
name: CLI Validation
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v4

- name: Setup Rust
uses: dtolnay/rust-toolchain@stable
with:
components: rustfmt, clippy

- name: Cache dependencies
uses: Swatinem/rust-cache@v2
with:
workspaces: "cli -> target"

- name: Check formatting
working-directory: ./cli
run: cargo fmt --check

- name: Run clippy
working-directory: ./cli
run: cargo clippy --all-targets --all-features -- -D warnings

- name: Run tests
working-directory: ./cli
run: cargo test

- name: Build CLI
working-directory: ./cli
run: cargo build
164 changes: 164 additions & 0 deletions .github/workflows/publish-cli.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,164 @@
name: Publish CLI Crate

on:
push:
branches: [main]
paths:
- 'cli/**'
- '.github/workflows/publish-cli.yml'
workflow_dispatch:
inputs:
publish:
description: 'Publish the current cougr-cli version to crates.io'
required: true
type: boolean
default: false

env:
CARGO_TERM_COLOR: always

permissions:
contents: write

concurrency:
group: publish-cli-${{ github.ref }}
cancel-in-progress: false

jobs:
package:
name: Package CLI crate
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v4

- name: Setup Rust
uses: dtolnay/rust-toolchain@stable

- name: Cache dependencies
uses: Swatinem/rust-cache@v2
with:
workspaces: "cli -> target"

- name: Check package
working-directory: ./cli
run: cargo package

publish:
name: Publish CLI to crates.io
needs: package
if: github.ref == 'refs/heads/main' || (github.event_name == 'workflow_dispatch' && inputs.publish)
runs-on: ubuntu-latest
timeout-minutes: 15
steps:
- uses: actions/checkout@v4

- name: Setup Rust
uses: dtolnay/rust-toolchain@stable

- name: Cache dependencies
uses: Swatinem/rust-cache@v2
with:
workspaces: "cli -> target"

- name: Read crate metadata
id: crate
working-directory: ./cli
run: |
metadata="$(cargo metadata --no-deps --format-version 1)"
echo "name=$(echo "$metadata" | jq -r '.packages[0].name')" >> "$GITHUB_OUTPUT"
echo "version=$(echo "$metadata" | jq -r '.packages[0].version')" >> "$GITHUB_OUTPUT"

- name: Check crates.io version
id: crates
run: |
# crates.io rejects requests with no descriptive User-Agent (403)
response="$(curl -fsSL \
-H "User-Agent: cougr-cli-publish-workflow (+https://github.com/salazarsebas/Cougr)" \
"https://crates.io/api/v1/crates/${{ steps.crate.outputs.name }}")" || true
published=false

# If curl succeeded and the version is in the response, it's published.
# If curl failed (e.g. 404 because the crate is totally new), jq will fail, leaving published=false.
if echo "$response" | jq -e --arg version "${{ steps.crate.outputs.version }}" '.versions[]? | select(.num == $version)' >/dev/null 2>&1; then
published=true
fi

echo "published=$published" >> "$GITHUB_OUTPUT"

- name: Publish crate
if: steps.crates.outputs.published != 'true'
working-directory: ./cli
env:
CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
run: cargo publish

- name: Skip already published version
if: steps.crates.outputs.published == 'true'
run: echo "${{ steps.crate.outputs.name }} ${{ steps.crate.outputs.version }} is already published on crates.io"

release:
name: Create GitHub Release
needs: publish
if: github.ref == 'refs/heads/main' || (github.event_name == 'workflow_dispatch' && inputs.publish)
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0

- name: Read crate version
id: crate
working-directory: ./cli
run: |
version="$(grep -m1 '^version' Cargo.toml | sed 's/.*= *"\(.*\)"/\1/')"
echo "version=$version" >> "$GITHUB_OUTPUT"
echo "tag=cli-v$version" >> "$GITHUB_OUTPUT"

- name: Check if release already exists
id: check
env:
GH_TOKEN: ${{ github.token }}
run: |
if gh release view "${{ steps.crate.outputs.tag }}" &>/dev/null; then
echo "exists=true" >> "$GITHUB_OUTPUT"
else
echo "exists=false" >> "$GITHUB_OUTPUT"
fi

- name: Extract changelog section
if: steps.check.outputs.exists != 'true'
id: changelog
run: |
version="${{ steps.crate.outputs.version }}"
notes="$(awk "/^## $version/{found=1; next} found && /^## /{exit} found{print}" CHANGELOG.md)"
if [ -z "$notes" ]; then
notes="See [CHANGELOG.md](CHANGELOG.md) for details."
fi
{
echo "notes<<EOF"
echo "$notes"
echo "EOF"
} >> "$GITHUB_OUTPUT"

- name: Ensure tag exists
if: steps.check.outputs.exists != 'true'
run: |
tag="${{ steps.crate.outputs.tag }}"
if ! git rev-parse "$tag" &>/dev/null; then
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git tag "$tag"
git push origin "$tag"
fi

- name: Create release
if: steps.check.outputs.exists != 'true'
env:
GH_TOKEN: ${{ github.token }}
run: |
gh release create "${{ steps.crate.outputs.tag }}" \
--title "cougr-cli v${{ steps.crate.outputs.version }}" \
--notes "${{ steps.changelog.outputs.notes }}" \
--generate-notes
Loading