docs: streamline README structure #170
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 |