Skip to content

docs: streamline README structure #170

docs: streamline README structure

docs: streamline README structure #170

Workflow file for this run

name: CI/CD Pipeline
on:
push:
branches: [main]
tags:
- "v*"
pull_request:
branches: [main]
# It’s triggered when you publish a release in GitHub’s web UI or API.
# That means:
# Go to repository’s "Releases" tab.
# Click "Draft a new release".
# Choose a tag (or create a new one).
# Click "Publish release".
release:
types: [published]
# This is manual execution from GitHub Actions UI:
# Go to your repo → Actions tab → Select this workflow.
# You’ll see a "Run workflow" button.
# When you click it:
# GitHub will ask you to choose a branch (from the options you defined: main).
# That choice becomes an input accessible inside the workflow.
workflow_dispatch:
inputs:
branch:
description: "Branch to build from"
required: true
default: "main"
type: choice
options:
- main
env:
GO_VERSION: "1.25.0"
jobs:
test:
name: Test
runs-on: ubuntu-latest
strategy:
matrix:
go-version: [1.25.0]
os: [ubuntu-latest, macos-latest, windows-latest]
steps:
# This pulls your repository’s code into the runner’s working directory so subsequent commands can access it.
# Without this, the VM starts empty — no code files at all.
- name: Checkout code
uses: actions/checkout@v4
# Installs the Go version for that matrix run.
# This makes go available in the runner’s environment.
- name: Set up Go ${{ matrix.go-version }}
uses: actions/setup-go@v4
with:
go-version: ${{ matrix.go-version }}
# Speeds up future runs by reusing:
# ~/.cache/go-build → compiled build cache
# ~/go/pkg/mod → downloaded dependencies
# Key:
# Based on OS, Go version, and go.sum hash.
# If go.sum changes (dependencies updated), the cache is rebuilt.
- name: Cache Go modules
uses: actions/cache@v4
with:
path: |
~/.cache/go-build
~/go/pkg/mod
key: ${{ runner.os }}-go-${{ matrix.go-version }}-${{ hashFiles('**/go.sum') }}
restore-keys: |
${{ runner.os }}-go-${{ matrix.go-version }}-
- name: Install dependencies
run: go mod download
# -v → verbose output (shows each test name & result).
# -coverprofile=coverage.txt → writes code coverage stats to a file.
# -covermode=atomic → precise coverage tracking, even if tests run in parallel.
# ./... → runs tests in all subdirectories recursively.
- name: Run tests
run: |
go test -v -coverprofile=coverage.txt -covermode=atomic ./...
build:
name: Build
runs-on: ubuntu-latest
# This means build will not run unless test passes.
needs: test
# Even if test passes, the build job only runs for:
if: github.event_name == 'push' || github.event_name == 'release' || github.event_name == 'workflow_dispatch'
steps:
# This pulls your repository’s code into the runner’s working directory so subsequent commands can access it.
# Without this, the VM starts empty — no code files at all.
- name: Checkout code
uses: actions/checkout@v4
# Uses the global GO_VERSION from env: (instead of the matrix).
# This makes the build job single-version — usually the "official" release version.
- name: Set up Go
uses: actions/setup-go@v4
with:
go-version: ${{ env.GO_VERSION }}
# git describe --tags --always --dirty → Nearest tag if available. (--dirty if there are uncommitted changes.)
- name: Get version
id: get_version
run: |
if [ "${GITHUB_EVENT_NAME}" = "release" ]; then
echo "version=${GITHUB_REF_NAME}" >> $GITHUB_OUTPUT
elif [[ "${GITHUB_REF}" == refs/tags/* ]]; then
echo "version=${GITHUB_REF_NAME}" >> $GITHUB_OUTPUT
else
echo "version=$(git describe --tags --always --dirty)" >> $GITHUB_OUTPUT
fi
# CGO_ENABLED=0 → disables Cgo for pure Go builds (better portability, no C toolchain needed).
# GOOS / GOARCH → target OS & architecture.
# -ldflags:
# -s -w → strip debug info (smaller binaries).
# -X main.Version=... → set a variable in your code at compile time (often declared like var Version string).
# -o dist/... → output binary to dist directory.
- name: Build for multiple platforms
run: |
# Create build directory
mkdir -p dist
# Get build information
BUILD_TIME=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
GIT_COMMIT=$(git rev-parse --short HEAD)
VERSION="${{ steps.get_version.outputs.version }}"
# Build flags with all version information
LDFLAGS="-s -w -X main.Version=${VERSION} -X main.BuildTime=${BUILD_TIME} -X main.GitCommit=${GIT_COMMIT}"
# Build for different architectures (CGO disabled for cross-platform builds)
CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build -ldflags="${LDFLAGS}" -o dist/leetsolv-linux-amd64
CGO_ENABLED=0 GOOS=linux GOARCH=arm64 go build -ldflags="${LDFLAGS}" -o dist/leetsolv-linux-arm64
CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build -ldflags="${LDFLAGS}" -o dist/leetsolv-darwin-amd64
CGO_ENABLED=0 GOOS=darwin GOARCH=arm64 go build -ldflags="${LDFLAGS}" -o dist/leetsolv-darwin-arm64
CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -ldflags="${LDFLAGS}" -o dist/leetsolv-windows-amd64.exe
CGO_ENABLED=0 GOOS=windows GOARCH=arm64 go build -ldflags="${LDFLAGS}" -o dist/leetsolv-windows-arm64.exe
# Generates SHA256 hashes for all built binaries.
# Allows users to verify download integrity (especially important for security).
# A checksum (like SHA256) is a fingerprint of the file. If even 1 byte changes, the checksum changes completely.
# If a user downloads leetsolv-linux-amd64, they can also download checksums.txt and run:
# `sha256sum --check checksums.txt`
# If the output says OK, the file is intact and untampered.
- name: Create checksums
run: |
cd dist
sha256sum leetsolv-* > checksums.txt
# Saves the compiled binaries & checksums to GitHub’s artifact storage.
- name: Upload build artifacts
uses: actions/upload-artifact@v4
with:
name: build-artifacts
path: dist/
retention-days: 30
release:
name: Create Release
runs-on: ubuntu-latest
# This job will only start when the `test` and `build` job succeed.
needs: [test, build]
# Even if test passes, the build job only runs for:
if: github.event_name == 'release'
# Required so this job can upload assets to repo's releases.
permissions:
contents: write
steps:
# This pulls your repository’s code into the runner’s working directory so subsequent commands can access it.
# This is mainly needed so softprops/action-gh-release can work with metadata from your repo (tags, commit messages, etc.).
- name: Checkout code
uses: actions/checkout@v4
# Fetches the binaries created in the build job.
# The `name` must exactly match what build uploaded
- name: Download build artifacts
uses: actions/download-artifact@v4
with:
name: build-artifacts
path: dist/
# softprops/action-gh-release:
# Talks to the GitHub Releases API.
# Adds the downloaded binaries as release assets.
# files: — the assets to attach:
# All dist/leetsolv-* binaries.
# The checksum file.
# draft: false — release is immediately public.
# prerelease: false — marks it as a stable release.
# generate_release_notes: true — auto-generate a changelog based on merged PRs & commits since the last tag.
- name: Create Release
uses: softprops/action-gh-release@v1
with:
files: |
dist/leetsolv-*
dist/checksums.txt
draft: false
prerelease: false
generate_release_notes: true
update-homebrew:
name: Update Homebrew Formula
runs-on: ubuntu-latest
needs: release
if: github.event_name == 'release'
steps:
- name: Checkout homebrew-tap
uses: actions/checkout@v4
with:
repository: eannchen/homebrew-tap
token: ${{ secrets.HOMEBREW_TAP_TOKEN }}
path: homebrew-tap
- name: Download checksums
run: |
CHECKSUM_URL="https://github.com/eannchen/leetsolv/releases/download/${{ github.ref_name }}/checksums.txt"
echo "Downloading checksums from: ${CHECKSUM_URL}"
if ! curl -fsSL "${CHECKSUM_URL}" -o checksums.txt; then
echo "::error::Failed to download checksums.txt from ${CHECKSUM_URL}"
exit 1
fi
echo "Downloaded checksums.txt:"
cat checksums.txt
- name: Update formula
run: |
VERSION="${{ github.ref_name }}"
FORMULA="homebrew-tap/Formula/leetsolv.rb"
# Extract SHA256 for each platform from checksums.txt
SHA_DARWIN_ARM64=$(grep "leetsolv-darwin-arm64$" checksums.txt | awk '{print $1}')
SHA_DARWIN_AMD64=$(grep "leetsolv-darwin-amd64$" checksums.txt | awk '{print $1}')
SHA_LINUX_ARM64=$(grep "leetsolv-linux-arm64$" checksums.txt | awk '{print $1}')
SHA_LINUX_AMD64=$(grep "leetsolv-linux-amd64$" checksums.txt | awk '{print $1}')
# Validate that all SHA256 values were extracted
if [ -z "$SHA_DARWIN_ARM64" ] || [ -z "$SHA_DARWIN_AMD64" ] || \
[ -z "$SHA_LINUX_ARM64" ] || [ -z "$SHA_LINUX_AMD64" ]; then
echo "::error::Failed to extract SHA256 checksums. Contents of checksums.txt:"
cat checksums.txt
exit 1
fi
echo "Updating to version: ${VERSION}"
echo "SHA256 darwin-arm64: ${SHA_DARWIN_ARM64}"
echo "SHA256 darwin-amd64: ${SHA_DARWIN_AMD64}"
echo "SHA256 linux-arm64: ${SHA_LINUX_ARM64}"
echo "SHA256 linux-amd64: ${SHA_LINUX_AMD64}"
# Update version
sed -i "s|version \"v[^\"]*\"|version \"${VERSION}\"|g" "$FORMULA"
# Update all download URLs (version in path)
sed -i "s|/releases/download/v[^/]*/|/releases/download/${VERSION}/|g" "$FORMULA"
# Update SHA256 hashes using the URL as context (awk replaces the sha256 line after matching URL)
awk -v sha="$SHA_DARWIN_ARM64" '/leetsolv-darwin-arm64"/{found=1} found && /sha256/{sub(/sha256 "[^"]*"/, "sha256 \""sha"\""); found=0} 1' "$FORMULA" > tmp && mv tmp "$FORMULA"
awk -v sha="$SHA_DARWIN_AMD64" '/leetsolv-darwin-amd64"/{found=1} found && /sha256/{sub(/sha256 "[^"]*"/, "sha256 \""sha"\""); found=0} 1' "$FORMULA" > tmp && mv tmp "$FORMULA"
awk -v sha="$SHA_LINUX_ARM64" '/leetsolv-linux-arm64"/{found=1} found && /sha256/{sub(/sha256 "[^"]*"/, "sha256 \""sha"\""); found=0} 1' "$FORMULA" > tmp && mv tmp "$FORMULA"
awk -v sha="$SHA_LINUX_AMD64" '/leetsolv-linux-amd64"/{found=1} found && /sha256/{sub(/sha256 "[^"]*"/, "sha256 \""sha"\""); found=0} 1' "$FORMULA" > tmp && mv tmp "$FORMULA"
echo "Updated formula:"
cat "$FORMULA"
- name: Commit and push
run: |
cd homebrew-tap
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add Formula/leetsolv.rb
git diff --staged --quiet || git commit -m "leetsolv ${{ github.ref_name }}"
git push
update-scoop:
name: Update Scoop Manifest
runs-on: ubuntu-latest
needs: release
if: github.event_name == 'release'
steps:
- name: Checkout scoop-bucket
uses: actions/checkout@v4
with:
repository: eannchen/scoop-bucket
token: ${{ secrets.SCOOP_BUCKET_TOKEN }}
path: scoop-bucket
- name: Download checksums
run: |
CHECKSUM_URL="https://github.com/eannchen/leetsolv/releases/download/${{ github.ref_name }}/checksums.txt"
echo "Downloading checksums from: ${CHECKSUM_URL}"
if ! curl -fsSL "${CHECKSUM_URL}" -o checksums.txt; then
echo "::error::Failed to download checksums.txt from ${CHECKSUM_URL}"
exit 1
fi
echo "Downloaded checksums.txt:"
cat checksums.txt
- name: Update Scoop manifest
run: |
VERSION="${{ github.ref_name }}"
MANIFEST="scoop-bucket/bucket/leetsolv.json"
# Extract SHA256 for Windows platforms from checksums.txt
SHA_WINDOWS_AMD64=$(grep "leetsolv-windows-amd64.exe$" checksums.txt | awk '{print $1}')
SHA_WINDOWS_ARM64=$(grep "leetsolv-windows-arm64.exe$" checksums.txt | awk '{print $1}')
# Validate that all SHA256 values were extracted
if [ -z "$SHA_WINDOWS_AMD64" ] || [ -z "$SHA_WINDOWS_ARM64" ]; then
echo "::error::Failed to extract Windows SHA256 checksums. Contents of checksums.txt:"
cat checksums.txt
exit 1
fi
echo "Updating Scoop manifest to version: ${VERSION}"
echo "SHA256 windows-amd64: ${SHA_WINDOWS_AMD64}"
echo "SHA256 windows-arm64: ${SHA_WINDOWS_ARM64}"
# Create updated manifest using jq for proper JSON formatting
# Strip "v" prefix for Scoop version field (v1.0.0 -> 1.0.0)
SCOOP_VERSION="${VERSION#v}"
jq -n \
--arg version "${SCOOP_VERSION}" \
--arg url_amd64 "https://github.com/eannchen/leetsolv/releases/download/${VERSION}/leetsolv-windows-amd64.exe" \
--arg url_arm64 "https://github.com/eannchen/leetsolv/releases/download/${VERSION}/leetsolv-windows-arm64.exe" \
--arg hash_amd64 "${SHA_WINDOWS_AMD64}" \
--arg hash_arm64 "${SHA_WINDOWS_ARM64}" \
'{
version: $version,
description: "A CLI tool for DSA problem revision with spaced repetition, powered by a customized SuperMemo 2 algorithm.",
homepage: "https://github.com/eannchen/leetsolv",
license: "MIT",
architecture: {
"64bit": {
url: $url_amd64,
hash: $hash_amd64,
bin: [["leetsolv-windows-amd64.exe", "leetsolv"]]
},
arm64: {
url: $url_arm64,
hash: $hash_arm64,
bin: [["leetsolv-windows-arm64.exe", "leetsolv"]]
}
},
checkver: "github",
autoupdate: {
architecture: {
"64bit": {
url: "https://github.com/eannchen/leetsolv/releases/download/v$version/leetsolv-windows-amd64.exe"
},
arm64: {
url: "https://github.com/eannchen/leetsolv/releases/download/v$version/leetsolv-windows-arm64.exe"
}
}
}
}' > "$MANIFEST"
echo "Updated manifest:"
cat "$MANIFEST"
- name: Commit and push
run: |
cd scoop-bucket
git config user.name "github-actions[bot]"
git config user.email "github-actions[bot]@users.noreply.github.com"
git add bucket/leetsolv.json
git diff --staged --quiet || git commit -m "leetsolv ${{ github.ref_name }}"
git push