diff --git a/.github/workflows/test-action.yml b/.github/workflows/test-action.yml index 42ae5b9..fffd26c 100644 --- a/.github/workflows/test-action.yml +++ b/.github/workflows/test-action.yml @@ -83,3 +83,53 @@ jobs: run: | echo "Cache hit: ${{ steps.setup.outputs.cache-hit }}" hm --version + + custom-binary: + name: Custom hm-path (dogfood pattern) + runs-on: ubuntu-latest + timeout-minutes: 15 + permissions: + contents: read + packages: write + steps: + - uses: actions/checkout@v4 + + - name: Create mock hm binary + run: | + mkdir -p /tmp/mock-hm + cat > /tmp/mock-hm/hm << 'SCRIPT' + #!/bin/bash + case "$1" in + --version) echo "mock-0.0.1" ;; + cache) + case "$2" in + save) + mkdir -p "$3" + echo '{"images":{}}' > "$3/manifest.json" + echo "abc123" + ;; + restore) echo "restored" ;; + esac + ;; + run) echo "ran pipeline: $2" ;; + esac + SCRIPT + chmod +x /tmp/mock-hm/hm + + - name: Cache restore (no manifest yet, cold start) + uses: ./cache-restore + with: + working-directory: tests/fixtures + + - name: Run with custom binary + working-directory: tests/fixtures + env: + HM_NONINTERACTIVE: '1' + run: /tmp/mock-hm/hm run hello + + - name: Cache save with custom binary + if: always() + uses: ./cache-save + with: + working-directory: tests/fixtures + hm-path: /tmp/mock-hm/hm diff --git a/README.md b/README.md index f5f1603..ada9e9b 100644 --- a/README.md +++ b/README.md @@ -1,29 +1,17 @@ # actions-hm -[![CI](https://img.shields.io/github/actions/workflow/status/harmont-dev/actions-hm/ci.yml?branch=main&logo=github&label=CI)](https://github.com/harmont-dev/actions-hm/actions) -[![GitHub release](https://img.shields.io/github/v/release/harmont-dev/actions-hm?logo=github)](https://github.com/harmont-dev/actions-hm/releases) -[![Marketplace](https://img.shields.io/badge/marketplace-harmont-purple?logo=github)](https://github.com/marketplace/actions/harmont) +> [!WARNING] +> +> This repo is currently considered experimental. -Run [harmont](https://harmont.dev) pipelines in GitHub Actions. One step. Automatic Docker image caching via your container registry. +Run [harmont](https://harmont.dev) pipelines in GitHub Actions. ```yaml -- uses: harmont-dev/actions-hm@v1 +- uses: harmont-dev/actions-hm@main with: pipeline: ci ``` -That's it. This installs `hm`, pulls cached Docker images from GHCR, runs your pipeline, and pushes updated images back — with automatic cleanup of stale cache entries. - -## Why - -You already define your CI with harmont. This action lets you run it on GitHub Actions without boilerplate: - -- **Zero config caching** — Docker images cached in GHCR with native layer deduplication -- **One step** — no separate setup, login, cache-restore, cache-save dance -- **Fast repeat runs** — `hm` binary cached between runs, images pulled only when changed -- **Auto cleanup** — stale registry images pruned automatically (configurable retention) -- **Granular control** — use sub-actions individually when you need custom steps between them - ## Usage ### Minimal (all-in-one) @@ -55,7 +43,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: harmont-dev/actions-hm@v1 + - uses: harmont-dev/actions-hm@main with: pipeline: lint @@ -63,7 +51,7 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - - uses: harmont-dev/actions-hm@v1 + - uses: harmont-dev/actions-hm@main with: pipeline: test parallelism: 4 @@ -95,14 +83,6 @@ jobs: if: always() ``` -### Pin to specific version - -```yaml -- uses: harmont-dev/actions-hm@v1 - with: - version: 0.5.0 -``` - ## Inputs | Input | Default | Description | @@ -116,6 +96,7 @@ jobs: | `cache-registry-prefix` | *(auto)* | Registry path prefix. Default: `ghcr.io///harmont-cache` | | `cache-cleanup` | `true` | Delete stale images from registry after save | | `cache-cleanup-keep` | `2` | Number of old image versions to keep per step | +| `hm-path` | | Path to a locally-built `hm` binary (skips install; used for dogfooding) | | `extra-args` | | Additional arguments passed to `hm run` | | `token` | `github.token` | GitHub token (needs `packages:write`, `packages:delete` for cleanup) | @@ -212,7 +193,7 @@ steps: ```yaml steps: - uses: actions/checkout@v4 - - uses: harmont-dev/actions-hm@v1 + - uses: harmont-dev/actions-hm@main with: pipeline: ci ``` @@ -234,7 +215,7 @@ macOS runners have Docker available via colima/lima. Windows runners are not cur Yes. Set `cache-registry` to your registry hostname and provide a token with push/pull access: ```yaml -- uses: harmont-dev/actions-hm@v1 +- uses: harmont-dev/actions-hm@main with: pipeline: ci cache-registry: registry.example.com @@ -244,7 +225,7 @@ Yes. Set `cache-registry` to your registry hostname and provide a token with pus ### How do I disable caching entirely? ```yaml -- uses: harmont-dev/actions-hm@v1 +- uses: harmont-dev/actions-hm@main with: pipeline: ci cache: 'false' diff --git a/action.yml b/action.yml index a2c11ec..6732199 100644 --- a/action.yml +++ b/action.yml @@ -54,6 +54,12 @@ inputs: description: Additional arguments passed to 'hm run' required: false default: '' + hm-path: + description: > + Path to the hm binary. Use when testing a locally-built binary + instead of an installed release. Overrides setup step. + required: false + default: '' token: description: > GitHub token (needs packages:write for cache, packages:delete for cleanup) @@ -70,6 +76,7 @@ runs: steps: # --- Setup --- - name: Setup Harmont + if: inputs.hm-path == '' id: setup uses: ./setup with: @@ -92,6 +99,7 @@ runs: working-directory: ${{ inputs.working-directory }} env: HM_NONINTERACTIVE: '1' + INPUT_HM_PATH: ${{ inputs.hm-path || 'hm' }} INPUT_PIPELINE: ${{ inputs.pipeline }} INPUT_PARALLELISM: ${{ inputs.parallelism }} INPUT_EXTRA_ARGS: ${{ inputs.extra-args }} @@ -107,7 +115,7 @@ runs: read -ra extra <<< "$INPUT_EXTRA_ARGS" args+=("${extra[@]}") fi - hm run "${args[@]}" + "$INPUT_HM_PATH" run "${args[@]}" # --- Cache Save --- - name: Save Docker cache @@ -119,4 +127,5 @@ runs: working-directory: ${{ inputs.working-directory }} cleanup: ${{ inputs.cache-cleanup }} cleanup-keep: ${{ inputs.cache-cleanup-keep }} + hm-path: ${{ inputs.hm-path || 'hm' }} token: ${{ inputs.token }} diff --git a/cache-save/action.yml b/cache-save/action.yml index 42572ae..cb26ae0 100644 --- a/cache-save/action.yml +++ b/cache-save/action.yml @@ -3,7 +3,6 @@ description: > Push Docker images to a container registry after a harmont pipeline run. Automatically cleans up stale images no longer in the current manifest. Use with 'if: always()' to save cache even when the pipeline fails. - inputs: registry: description: Container registry to push to @@ -36,7 +35,12 @@ inputs: Token for registry auth (needs packages:write, packages:delete for cleanup). required: false default: ${{ github.token }} - + hm-path: + description: > + Path to the hm binary. Defaults to 'hm' (assumes it's on PATH). + Use './target/debug/hm' or similar when testing a locally-built binary. + required: false + default: hm runs: using: composite steps: @@ -54,13 +58,14 @@ runs: env: INPUT_REGISTRY_PREFIX: ${{ inputs.registry-prefix }} INPUT_REGISTRY: ${{ inputs.registry }} + INPUT_HM_PATH: ${{ inputs.hm-path }} GITHUB_REPOSITORY: ${{ github.repository }} working-directory: ${{ inputs.working-directory }} run: | prefix="${INPUT_REGISTRY_PREFIX:-${INPUT_REGISTRY}/${GITHUB_REPOSITORY}/harmont-cache}" echo "::group::Exporting image manifest" - hm cache save .harmont-cache/ > /dev/null + "$INPUT_HM_PATH" cache save .harmont-cache/ > /dev/null echo "::endgroup::" if [ ! -f .harmont-cache/manifest.json ]; then diff --git a/docs/plans/2026-05-25-readme.md b/docs/plans/2026-05-25-readme.md deleted file mode 100644 index af3012c..0000000 --- a/docs/plans/2026-05-25-readme.md +++ /dev/null @@ -1,327 +0,0 @@ -# README Implementation Plan - -> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task. - -**Goal:** Write a README.md that makes `actions-hm` irresistible to adopt — instant comprehension, copy-paste first example, progressive disclosure. - -**Architecture:** Single-file documentation following the patterns of the most-starred GitHub Actions: value prop in one sentence, working example in the first scroll, inputs table for reference, FAQ for real questions. - -**Tech Stack:** Markdown, shields.io badges - -**Design principles (from research of hsrs, docker/build-push-action, setup-uv):** -- 3-second rule: "what does this do?" answered in one sentence -- Copy-paste-first: the minimal example IS a complete workflow -- Progressive disclosure: simple → granular → caching details → migration -- "That's it." confidence after the quick start -- Trust signals via badges -- FAQ preempts real support questions - ---- - -## Task 1: Write README.md - -**Files:** -- Create: `README.md` - -**Step 1: Write the full README** - -```markdown -# actions-hm - -[![CI](https://img.shields.io/github/actions/workflow/status/harmont-dev/actions-hm/ci.yml?branch=main&logo=github&label=CI)](https://github.com/harmont-dev/actions-hm/actions) -[![GitHub release](https://img.shields.io/github/v/release/harmont-dev/actions-hm?logo=github)](https://github.com/harmont-dev/actions-hm/releases) -[![Marketplace](https://img.shields.io/badge/marketplace-harmont-purple?logo=github)](https://github.com/marketplace/actions/harmont) - -Run [harmont](https://harmont.dev) pipelines in GitHub Actions. One step. Automatic Docker image caching via your container registry. - -```yaml -- uses: harmont-dev/actions-hm@v1 - with: - pipeline: ci -``` - -That's it. This installs `hm`, pulls cached Docker images from GHCR, runs your pipeline, and pushes updated images back — with automatic cleanup of stale cache entries. - -## Why - -You already define your CI with harmont. This action lets you run it on GitHub Actions without boilerplate: - -- **Zero config caching** — Docker images cached in GHCR with native layer deduplication -- **One step** — no separate setup, login, cache-restore, cache-save dance -- **Fast repeat runs** — `hm` binary cached between runs, images pulled only when changed -- **Auto cleanup** — stale registry images pruned automatically (configurable retention) -- **Granular control** — use sub-actions individually when you need custom steps between them - -## Usage - -### Minimal (all-in-one) - -```yaml -name: CI - -on: [push, pull_request] - -permissions: - contents: read - packages: write - -jobs: - ci: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: harmont-dev/actions-hm@v1 - with: - pipeline: ci -``` - -### Multiple pipelines - -```yaml -jobs: - lint: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: harmont-dev/actions-hm@v1 - with: - pipeline: lint - - test: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v4 - - uses: harmont-dev/actions-hm@v1 - with: - pipeline: test - parallelism: 4 -``` - -### Granular sub-actions - -For workflows that need custom steps between setup, cache, and run: - -```yaml -jobs: - ci: - runs-on: ubuntu-latest - permissions: - contents: read - packages: write - steps: - - uses: actions/checkout@v4 - - - uses: harmont-dev/actions-hm/setup@v1 - - - uses: harmont-dev/actions-hm/cache-restore@v1 - - - run: | - echo "Custom setup between cache restore and pipeline run" - hm run ci - - - uses: harmont-dev/actions-hm/cache-save@v1 - if: always() -``` - -### Pin to specific version - -```yaml -- uses: harmont-dev/actions-hm@v1 - with: - version: 0.5.0 -``` - -## Inputs - -| Input | Default | Description | -|-------|---------|-------------| -| `pipeline` | *(auto)* | Pipeline slug to run. Omit if repo has only one pipeline. | -| `version` | `latest` | `hm` CLI version (`latest` or semver like `0.5.0`) | -| `working-directory` | `.` | Path to repo root where `.harmont/` lives | -| `parallelism` | *(cpu count)* | Max concurrent pipeline chains | -| `cache` | `true` | Enable Docker image caching | -| `cache-registry` | `ghcr.io` | Container registry for image caching | -| `cache-registry-prefix` | *(auto)* | Registry path prefix. Default: `ghcr.io///harmont-cache` | -| `cache-cleanup` | `true` | Delete stale images from registry after save | -| `cache-cleanup-keep` | `2` | Number of old image versions to keep per step | -| `extra-args` | | Additional arguments passed to `hm run` | -| `token` | `github.token` | GitHub token (needs `packages:write`, `packages:delete` for cleanup) | - -## Outputs - -| Output | Description | -|--------|-------------| -| `hm-version` | Installed `hm` CLI version | - -## Sub-actions - -| Action | Purpose | -|--------|---------| -| `harmont-dev/actions-hm/setup@v1` | Install `hm` binary (cached between runs) | -| `harmont-dev/actions-hm/cache-restore@v1` | Pull cached Docker images from registry | -| `harmont-dev/actions-hm/cache-save@v1` | Push Docker images to registry + cleanup | - -## How caching works - -``` -┌─────────────────────────────────────────────────────────────┐ -│ GitHub Actions Runner │ -│ │ -│ 1. Pull manifest:latest from GHCR │ -│ 2. Pull each step image (layer dedup = fast) │ -│ 3. Re-tag as harmont-local/* so hm recognizes them │ -│ 4. hm run ci (uses cached images, skips rebuilds) │ -│ 5. Push changed images back to GHCR │ -│ 6. Prune images older than cleanup-keep │ -│ │ -│ Images stored at: │ -│ ghcr.io///harmont-cache/: │ -└─────────────────────────────────────────────────────────────┘ -``` - -**Why GHCR instead of `actions/cache`?** - -- No 10 GB size limit (GHCR storage is unlimited for public repos) -- Native Docker layer deduplication — shared base images stored once -- Per-image granularity — only changed images push/pull -- Faster for large images than tar/untar through GHA cache - -## Permissions - -The action needs `packages:write` on the `GITHUB_TOKEN` to push/pull cache images. For cleanup, it also needs `packages:delete`. - -```yaml -permissions: - contents: read - packages: write -``` - -> **Note:** `packages:delete` is included in `packages:write` for tokens with full `packages` scope. If using a fine-grained PAT, ensure both are granted. - -## Migrating from raw workflow steps - -If you currently have a manual harmont setup in your workflow: - -
-Before (manual setup) - -```yaml -steps: - - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@stable - - uses: Swatinem/rust-cache@v2 - - run: cargo build -p harmont-cli - - uses: actions/cache/restore@v4 - with: - path: .harmont-cache/ - key: harmont-v1-will-never-match - restore-keys: harmont-v1- - - run: ./target/debug/hm cache restore .harmont-cache/ - - run: ./target/debug/hm run ci - env: - HM_NONINTERACTIVE: '1' - - run: | - hash=$(./target/debug/hm cache save .harmont-cache/) - echo "key=harmont-v1-${hash}" >> "$GITHUB_OUTPUT" - id: cache-manifest - if: always() - - uses: actions/cache/save@v4 - if: always() - with: - path: .harmont-cache/ - key: ${{ steps.cache-manifest.outputs.key }} -``` - -
- -
-After (this action) - -```yaml -steps: - - uses: actions/checkout@v4 - - uses: harmont-dev/actions-hm@v1 - with: - pipeline: ci -``` - -
- -## FAQ - -### Do I need Docker on the runner? - -Yes. Harmont runs pipeline steps in Docker containers. Use `runs-on: ubuntu-latest` (Docker is pre-installed). - -### What about macOS / Windows runners? - -macOS runners have Docker available via colima/lima. Windows runners are not currently supported (harmont requires Linux containers). - -### Can I use a private registry instead of GHCR? - -Yes. Set `cache-registry` to your registry hostname and provide a token with push/pull access: - -```yaml -- uses: harmont-dev/actions-hm@v1 - with: - pipeline: ci - cache-registry: registry.example.com - token: ${{ secrets.REGISTRY_TOKEN }} -``` - -### How do I disable caching entirely? - -```yaml -- uses: harmont-dev/actions-hm@v1 - with: - pipeline: ci - cache: 'false' -``` - -### How do I force a clean cache rebuild? - -Delete the `harmont-cache` packages from your repo's GitHub Packages, or change `cache-registry-prefix` to a new path. - -### The first run is slow — is that expected? - -Yes. The first run has no cached images, so Docker pulls base images and builds from scratch. Subsequent runs reuse cached images and are significantly faster. - -### What permissions does cleanup need? - -`packages:delete` (part of the `packages: write` scope). If your token lacks this, set `cache-cleanup: 'false'` — images accumulate but nothing breaks. - -## License - -MIT -``` - -**Step 2: Validate all YAML code blocks in the README parse correctly** - -Run: `python3 -c "import yaml, re; content=open('README.md').read(); blocks=[b for b in re.findall(r'\`\`\`yaml\n(.*?)\`\`\`', content, re.DOTALL)]; [yaml.safe_load(b) for b in blocks]; print(f'{len(blocks)} YAML blocks valid')"` -Expected: All YAML blocks parse without error - -**Step 3: Commit** - -```bash -git add README.md -git commit -m "docs: add README with usage examples, inputs reference, and migration guide" -``` - ---- - -## Design Decisions - -### Why "That's it." after the first example? -Borrowed from hsrs. It signals confidence and tells the reader "no, really, that's all you need." It's the anti-enterprise-documentation move. - -### Why show the before/after migration in collapsible sections? -The 13-line "before" vs 3-line "after" is the most compelling visual argument for adopting the action. Collapsible keeps the README scannable for people who don't have an existing setup. - -### Why ASCII diagram instead of Mermaid for caching? -Mermaid doesn't render in all contexts (npm README, marketplace). ASCII art is universally rendered and fits the terminal-first personality of a CI tool. - -### Why FAQ instead of linking to issues? -Real questions deserve real answers in the README. Every FAQ entry is a support ticket that doesn't get filed. - -### Why no screenshot? -This action doesn't produce visible output (no job summary yet). A screenshot of a green CI check is generic and adds nothing. If/when harmont adds GHA job summaries, add a screenshot then.