mirror of
https://github.com/gmeligio/flutter-docker-image.git
synced 2026-05-24 12:30:34 +00:00
feat(ci): adopt gx for GitHub Actions version tracking (#439)
Adopts [`gmeligio/gx`](https://github.com/gmeligio/gx) (0.7.1) as the source of truth for GitHub Actions versions in this repo: --------- Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
+161
@@ -0,0 +1,161 @@
|
||||
[resolutions."actions/checkout"."^6"]
|
||||
version = "v6.0.1"
|
||||
|
||||
[resolutions."actions/create-github-app-token"."^2"]
|
||||
version = "v2.2.1"
|
||||
|
||||
[resolutions."actions/download-artifact"."^6"]
|
||||
version = "v6.0.0"
|
||||
|
||||
[resolutions."actions/github-script"."^8"]
|
||||
version = "v8.0.0"
|
||||
|
||||
[resolutions."actions/setup-node"."^6"]
|
||||
version = "v6.1.0"
|
||||
|
||||
[resolutions."actions/upload-artifact"."^5"]
|
||||
version = "v5.0.0"
|
||||
|
||||
[resolutions."docker/build-push-action"."^6"]
|
||||
version = "v6.18.0"
|
||||
|
||||
[resolutions."docker/login-action"."^3"]
|
||||
version = "v3.6.0"
|
||||
|
||||
[resolutions."docker/metadata-action"."^5"]
|
||||
version = "v5.10.0"
|
||||
|
||||
[resolutions."docker/scout-action"."^1"]
|
||||
version = "v1.18.2"
|
||||
|
||||
[resolutions."docker/setup-buildx-action"."^3"]
|
||||
version = "v3.11.1"
|
||||
|
||||
[resolutions."github/codeql-action/upload-sarif"."^4"]
|
||||
version = "v4.31.7"
|
||||
|
||||
[resolutions."grafana/github-api-commit-action"."^1"]
|
||||
version = "v1.0.0"
|
||||
|
||||
[resolutions."jaxxstorm/action-install-gh-release"."^2"]
|
||||
version = "v2.1.0"
|
||||
|
||||
[resolutions."ossf/scorecard-action"."^2"]
|
||||
version = "v2.4.3"
|
||||
|
||||
[resolutions."peter-evans/create-pull-request"."^7"]
|
||||
version = "v7.0.11"
|
||||
|
||||
[resolutions."peter-evans/dockerhub-description"."^5"]
|
||||
version = "v5.0.0"
|
||||
|
||||
[resolutions."plexsystems/container-structure-test-action"."~0.3.0"]
|
||||
version = "v0.3.0"
|
||||
|
||||
[actions."actions/checkout"."v6.0.1"]
|
||||
sha = "8e8c483db84b4bee98b60c0593521ed34d9990e8"
|
||||
repository = "actions/checkout"
|
||||
ref_type = "tag"
|
||||
date = "2025-12-02T02:08:49Z"
|
||||
|
||||
[actions."actions/create-github-app-token"."v2.2.1"]
|
||||
sha = "29824e69f54612133e76f7eaac726eef6c875baf"
|
||||
repository = "actions/create-github-app-token"
|
||||
ref_type = "tag"
|
||||
date = "2025-12-05T22:53:03Z"
|
||||
|
||||
[actions."actions/download-artifact"."v6.0.0"]
|
||||
sha = "018cc2cf5baa6db3ef3c5f8a56943fffe632ef53"
|
||||
repository = "actions/download-artifact"
|
||||
ref_type = "tag"
|
||||
date = "2025-10-24T18:15:38Z"
|
||||
|
||||
[actions."actions/github-script"."v8.0.0"]
|
||||
sha = "ed597411d8f924073f98dfc5c65a23a2325f34cd"
|
||||
repository = "actions/github-script"
|
||||
ref_type = "tag"
|
||||
date = "2025-09-04T14:48:16Z"
|
||||
|
||||
[actions."actions/setup-node"."v6.1.0"]
|
||||
sha = "395ad3262231945c25e8478fd5baf05154b1d79f"
|
||||
repository = "actions/setup-node"
|
||||
ref_type = "tag"
|
||||
date = "2025-12-03T03:06:19Z"
|
||||
|
||||
[actions."actions/upload-artifact"."v5.0.0"]
|
||||
sha = "330a01c490aca151604b8cf639adc76d48f6c5d4"
|
||||
repository = "actions/upload-artifact"
|
||||
ref_type = "tag"
|
||||
date = "2025-10-24T18:15:34Z"
|
||||
|
||||
[actions."docker/build-push-action"."v6.18.0"]
|
||||
sha = "263435318d21b8e681c14492fe198d362a7d2c83"
|
||||
repository = "docker/build-push-action"
|
||||
ref_type = "tag"
|
||||
date = "2025-05-27T16:32:33Z"
|
||||
|
||||
[actions."docker/login-action"."v3.6.0"]
|
||||
sha = "5e57cd118135c172c3672efd75eb46360885c0ef"
|
||||
repository = "docker/login-action"
|
||||
ref_type = "tag"
|
||||
date = "2025-09-29T10:29:19Z"
|
||||
|
||||
[actions."docker/metadata-action"."v5.10.0"]
|
||||
sha = "c299e40c65443455700f0fdfc63efafe5b349051"
|
||||
repository = "docker/metadata-action"
|
||||
ref_type = "tag"
|
||||
date = "2025-11-27T12:36:24Z"
|
||||
|
||||
[actions."docker/scout-action"."v1.18.2"]
|
||||
sha = "f8c776824083494ab0d56b8105ba2ca85c86e4de"
|
||||
repository = "docker/scout-action"
|
||||
ref_type = "tag"
|
||||
date = "2025-07-21T12:52:07Z"
|
||||
|
||||
[actions."docker/setup-buildx-action"."v3.11.1"]
|
||||
sha = "e468171a9de216ec08956ac3ada2f0791b6bd435"
|
||||
repository = "docker/setup-buildx-action"
|
||||
ref_type = "tag"
|
||||
date = "2025-06-18T08:37:30Z"
|
||||
|
||||
[actions."github/codeql-action/upload-sarif"."v4.31.7"]
|
||||
sha = "cf1bb45a277cb3c205638b2cd5c984db1c46a412"
|
||||
repository = "github/codeql-action"
|
||||
ref_type = "tag"
|
||||
date = "2025-12-05T17:17:21Z"
|
||||
|
||||
[actions."grafana/github-api-commit-action"."v1.0.0"]
|
||||
sha = "b1d81091e8480dd11fcea8bc1f0ab977a0376ca5"
|
||||
repository = "grafana/github-api-commit-action"
|
||||
ref_type = "tag"
|
||||
date = "2025-05-07T18:28:02Z"
|
||||
|
||||
[actions."jaxxstorm/action-install-gh-release"."v2.1.0"]
|
||||
sha = "6096f2a2bbfee498ced520b6922ac2c06e990ed2"
|
||||
repository = "jaxxstorm/action-install-gh-release"
|
||||
ref_type = "tag"
|
||||
date = "2025-04-27T17:13:59Z"
|
||||
|
||||
[actions."ossf/scorecard-action"."v2.4.3"]
|
||||
sha = "4eaacf0543bb3f2c246792bd56e8cdeffafb205a"
|
||||
repository = "ossf/scorecard-action"
|
||||
ref_type = "tag"
|
||||
date = "2025-09-30T20:36:00Z"
|
||||
|
||||
[actions."peter-evans/create-pull-request"."v7.0.11"]
|
||||
sha = "22a9089034f40e5a961c8808d113e2c98fb63676"
|
||||
repository = "peter-evans/create-pull-request"
|
||||
ref_type = "tag"
|
||||
date = "2025-12-05T17:14:47Z"
|
||||
|
||||
[actions."peter-evans/dockerhub-description"."v5.0.0"]
|
||||
sha = "1b9a80c056b620d92cedb9d9b5a223409c68ddfa"
|
||||
repository = "peter-evans/dockerhub-description"
|
||||
ref_type = "tag"
|
||||
date = "2025-10-01T13:39:49Z"
|
||||
|
||||
[actions."plexsystems/container-structure-test-action"."v0.3.0"]
|
||||
sha = "c0a028aa96e8e82ae35be556040340cbb3e280ca"
|
||||
repository = "plexsystems/container-structure-test-action"
|
||||
ref_type = "tag"
|
||||
date = "2023-03-30T18:19:59Z"
|
||||
@@ -0,0 +1,19 @@
|
||||
[actions]
|
||||
"actions/checkout" = "^6"
|
||||
"actions/create-github-app-token" = "^2"
|
||||
"actions/download-artifact" = "^6"
|
||||
"actions/github-script" = "^8"
|
||||
"actions/setup-node" = "^6"
|
||||
"actions/upload-artifact" = "^5"
|
||||
"docker/build-push-action" = "^6"
|
||||
"docker/login-action" = "^3"
|
||||
"docker/metadata-action" = "^5"
|
||||
"docker/scout-action" = "^1"
|
||||
"docker/setup-buildx-action" = "^3"
|
||||
"github/codeql-action/upload-sarif" = "^4"
|
||||
"grafana/github-api-commit-action" = "^1"
|
||||
"jaxxstorm/action-install-gh-release" = "^2"
|
||||
"ossf/scorecard-action" = "^2"
|
||||
"peter-evans/create-pull-request" = "^7"
|
||||
"peter-evans/dockerhub-description" = "^5"
|
||||
"plexsystems/container-structure-test-action" = "~0.3.0"
|
||||
@@ -0,0 +1,87 @@
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- .github/workflows/**
|
||||
- .github/actions/**
|
||||
- .github/gx.toml
|
||||
- .github/gx.lock
|
||||
workflow_dispatch:
|
||||
|
||||
# Read-only by default; the tidy job's auto-fix step uses a GitHub App token.
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.head_ref || github.ref_name }}
|
||||
cancel-in-progress: true
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
|
||||
- name: Install gx
|
||||
uses: jaxxstorm/action-install-gh-release@6096f2a2bbfee498ced520b6922ac2c06e990ed2 # v2.1.0
|
||||
with:
|
||||
repo: gmeligio/gx
|
||||
tag: v0.7.1
|
||||
digest: 6632843410c877c43aa8936eb757d8b0ddcb5940402203914543ef8a9cf8ecd9
|
||||
|
||||
- name: Lint pinned GitHub Actions
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
run: gx lint
|
||||
|
||||
tidy:
|
||||
# Skip PRs from forks: the App token below is scoped to the upstream repo
|
||||
# and the fork branch is on the contributor's repo, so the push would fail.
|
||||
# Those PRs still get lint feedback and must run `gx tidy` locally.
|
||||
if: github.event_name == 'workflow_dispatch' || github.event.pull_request.head.repo.full_name == github.repository
|
||||
runs-on: ubuntu-24.04
|
||||
steps:
|
||||
- name: Generate authentication token with GitHub App to trigger Actions
|
||||
uses: actions/create-github-app-token@29824e69f54612133e76f7eaac726eef6c875baf # v2.2.1
|
||||
id: app-token
|
||||
with:
|
||||
app-id: ${{ secrets.VERIFIED_COMMIT_ID }}
|
||||
private-key: ${{ secrets.VERIFIED_COMMIT_KEY }}
|
||||
repositories: ${{ github.event.repository.name }}
|
||||
owner: ${{ github.repository_owner }}
|
||||
|
||||
- name: Checkout PR branch
|
||||
uses: actions/checkout@8e8c483db84b4bee98b60c0593521ed34d9990e8 # v6.0.1
|
||||
with:
|
||||
ref: ${{ github.event.pull_request.head.ref }}
|
||||
repository: ${{ github.event.pull_request.head.repo.full_name }}
|
||||
token: ${{ steps.app-token.outputs.token }}
|
||||
|
||||
- name: Install gx
|
||||
uses: jaxxstorm/action-install-gh-release@6096f2a2bbfee498ced520b6922ac2c06e990ed2 # v2.1.0
|
||||
with:
|
||||
repo: gmeligio/gx
|
||||
tag: v0.7.1
|
||||
digest: 6632843410c877c43aa8936eb757d8b0ddcb5940402203914543ef8a9cf8ecd9
|
||||
|
||||
- name: Run gx tidy
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ steps.app-token.outputs.token }}
|
||||
run: gx tidy
|
||||
|
||||
- name: Detect lock drift
|
||||
id: drift
|
||||
run: |
|
||||
if [ -n "$(git status --porcelain)" ]; then
|
||||
echo "changed=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "changed=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Commit and push lock updates if any
|
||||
if: steps.drift.outputs.changed == 'true'
|
||||
uses: grafana/github-api-commit-action@b1d81091e8480dd11fcea8bc1f0ab977a0376ca5 # v1.0.0
|
||||
with:
|
||||
commit-message: "chore(deps): sync .github/gx.lock via gx tidy"
|
||||
stage-all-files: true
|
||||
token: ${{ steps.app-token.outputs.token }}
|
||||
@@ -2,6 +2,28 @@
|
||||
|
||||
# Contributing
|
||||
|
||||
## Editing GitHub Actions workflows
|
||||
|
||||
GitHub Actions versions are tracked with [gx](https://github.com/gmeligio/gx). The manifest at `.github/gx.toml` is the source of truth for version constraints, and `.github/gx.lock` records the resolved SHAs. Workflows must use SHA pins with a `# vX.Y.Z` comment.
|
||||
|
||||
When editing any file under `.github/workflows/` or `.github/actions/`:
|
||||
|
||||
1. Install `gx` locally: `brew install gmeligio/tap/gx`, `cargo install gx`, or grab a binary from [the GitHub Releases page](https://github.com/gmeligio/gx/releases).
|
||||
2. Make your workflow edits.
|
||||
3. Run `gx tidy` to sync `.github/gx.toml`, `.github/gx.lock`, and the workflow `uses:` lines.
|
||||
4. Commit all three together (`.github/workflows/...`, `.github/gx.toml`, `.github/gx.lock`).
|
||||
|
||||
Adding a new action looks like adding a single line under `[actions]` in `.github/gx.toml`:
|
||||
|
||||
```toml
|
||||
"some-org/some-action" = "^1"
|
||||
|
||||
```
|
||||
|
||||
…then `gx tidy` resolves the SHA, writes the lock entry, and rewrites the `uses:` line.
|
||||
|
||||
The `lint` job in `.github/workflows/gx.yml` fails any pull request that introduces an unpinned `uses:` reference or a lock that disagrees with the workflows. The sibling `tidy` job runs `gx tidy` and pushes a fixup commit on PRs from this repository if the lock is stale (forks must run `gx tidy` locally).
|
||||
|
||||
## Adding new Github Actions
|
||||
|
||||
When adding new Github Actions the `.github\renovate.json` needs to be checked and add the new action to:
|
||||
|
||||
@@ -1,5 +1,26 @@
|
||||
# Contributing
|
||||
|
||||
## Editing GitHub Actions workflows
|
||||
|
||||
GitHub Actions versions are tracked with [`gx`](https://github.com/gmeligio/gx). The manifest at `.github/gx.toml` is the source of truth for version constraints, and `.github/gx.lock` records the resolved SHAs. Workflows must use SHA pins with a `# vX.Y.Z` comment.
|
||||
|
||||
When editing any file under `.github/workflows/` or `.github/actions/`:
|
||||
|
||||
1. Install `gx` locally: `brew install gmeligio/tap/gx`, `cargo install gx`, or grab a binary from [the GitHub Releases page](https://github.com/gmeligio/gx/releases).
|
||||
2. Make your workflow edits.
|
||||
3. Run `gx tidy` to sync `.github/gx.toml`, `.github/gx.lock`, and the workflow `uses:` lines.
|
||||
4. Commit all three together (`.github/workflows/...`, `.github/gx.toml`, `.github/gx.lock`).
|
||||
|
||||
Adding a new action looks like adding a single line under `[actions]` in `.github/gx.toml`:
|
||||
|
||||
```toml
|
||||
"some-org/some-action" = "^1"
|
||||
```
|
||||
|
||||
…then `gx tidy` resolves the SHA, writes the lock entry, and rewrites the `uses:` line.
|
||||
|
||||
The `lint` job in `.github/workflows/gx.yml` fails any pull request that introduces an unpinned `uses:` reference or a lock that disagrees with the workflows. The sibling `tidy` job runs `gx tidy` and pushes a fixup commit on PRs from this repository if the lock is stale (forks must run `gx tidy` locally).
|
||||
|
||||
## Adding new Github Actions
|
||||
|
||||
When adding new Github Actions the `.github\renovate.json` needs to be checked and add the new action to:
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
schema: spec-driven
|
||||
created: 2026-05-01
|
||||
@@ -0,0 +1,100 @@
|
||||
## Context
|
||||
|
||||
Today, every `uses:` reference in `.github/workflows/*.yml` is pinned to a 40-character commit SHA with a trailing `# vX.Y.Z` comment. Pinning is enforced informally: a reviewer notices an unpinned PR. There is no manifest of intended versions, no lock with auditable timestamps, and no automated check.
|
||||
|
||||
Renovate (`.github/renovate.json`) opens monthly grouped PRs against the `github-tags` datasource and a `customManagers` regex covers Debian deb packages in the Dockerfile. ~16 distinct actions are in use across 8 workflows.
|
||||
|
||||
`gmeligio/gx` is a Rust CLI by the same author as this repo. Its **SHA-first resolution** strategy means it can read the existing pins and reconstruct a manifest + lock without modifying any workflow line. It is distributed via Homebrew, Cargo, and pre-built GitHub releases.
|
||||
|
||||
## Goals / Non-Goals
|
||||
|
||||
**Goals:**
|
||||
|
||||
- A single declarative source of truth for the GitHub Actions versions this repo depends on.
|
||||
- Reproducible installs: any contributor or CI run can resolve the same SHA for the same constraint.
|
||||
- Fail PRs that introduce unpinned actions.
|
||||
- Keep Renovate's PR automation — gx complements rather than replaces it.
|
||||
|
||||
**Non-Goals:**
|
||||
|
||||
- Replacing the existing `config/version.json` machinery for Flutter / Android / Fastlane SDKs (different domain, different update cadence).
|
||||
- Replacing Renovate's Dockerfile `customManagers` regex for Debian deb packages.
|
||||
- Authoring an upgrade-PR bot from scratch (`gx upgrade` is run manually or via a follow-up cron change).
|
||||
|
||||
## Decisions
|
||||
|
||||
### Decision 1: Adopt gx in file-backed mode (manifest + lock)
|
||||
|
||||
`gx` supports a memory-only mode (one-off `gx tidy`), but we want team-level reproducibility and a CI lint gate, both of which require the manifest + lock. Generate them with `gx init`.
|
||||
|
||||
**Alternative considered**: stay memory-only. Rejected — without a lock, `gx lint` cannot detect drift, and version constraints can't be expressed (`^6` vs the literal SHA in workflows).
|
||||
|
||||
### Decision 2: Keep Renovate, add gx as authority
|
||||
|
||||
Renovate continues to open the monthly upgrade PR. After bumping a SHA, it must run `gx tidy` so `.github/gx.lock` matches the updated workflow. Implementation options:
|
||||
|
||||
- (a) `postUpgradeTasks` in `renovate.json` (recommended — one place, runs as part of the same PR).
|
||||
- (b) A separate workflow listening on PR open that runs `gx tidy` and pushes a fixup commit.
|
||||
|
||||
Pick (a). It is supported by Renovate self-hosted and by the GitHub-hosted app for repositories that allow it; if the GitHub-hosted Renovate app blocks `postUpgradeTasks`, fall back to (b).
|
||||
|
||||
**Alternative considered**: Replace Renovate's `github-tags` datasource with a `gx upgrade` cron workflow that opens PRs via `peter-evans/create-pull-request` (already used in `update_version.yml`). Rejected for the initial migration — Renovate's release-notes integration and grouping are valuable; we can revisit if the postUpgradeTasks hook proves brittle.
|
||||
|
||||
### Decision 3: Install gx in CI via the existing `jaxxstorm/action-install-gh-release`
|
||||
|
||||
That action is already used in `build.yml`, `ci.yml`, `changelog.yml`, `update_version.yml`. Reuse for gx — no new tooling pattern.
|
||||
|
||||
**Alternative considered**: `cargo install gx`. Rejected — adds a Rust toolchain dependency to CI for a single binary.
|
||||
|
||||
### Decision 4: Manifest version constraints default to caret on major (e.g., `^6`)
|
||||
|
||||
Mirrors the manifest gx itself ships in its own repo. Lets patches and minor versions auto-resolve while requiring a deliberate change for majors. Where the current pin is on a v0 action (e.g., `peter-evans/dockerhub-description@v4` is v4 but some actions are v0/v1) or a known-unstable action, pin tighter (`~1.18.2`).
|
||||
|
||||
### Decision 5: `gx lint` is a required CI check
|
||||
|
||||
Add a job to `.github/workflows/ci.yml` that runs on every PR. It must:
|
||||
|
||||
- Fail if any `uses:` is not SHA-pinned.
|
||||
- Fail if `.github/gx.lock` does not match the SHAs in the workflows.
|
||||
|
||||
This is the gate that makes the manifest meaningful.
|
||||
|
||||
## Risks / Trade-offs
|
||||
|
||||
- **Lock-file drift if Renovate edits a SHA without running `gx tidy`** → `gx lint` fails the PR; reviewer or Renovate's `postUpgradeTasks` re-runs `gx tidy`. Worst case: a one-line manual fix.
|
||||
- **gx is a young tool (one author, this user)** → Adoption risk is low because the user owns both repos, but document a rollback path: deleting `.github/gx.toml` and `.github/gx.lock` and removing the lint job restores the prior state — workflows are unchanged.
|
||||
- **`postUpgradeTasks` may be disabled on the GitHub-hosted Renovate app** → Fall back to a small `gx-tidy.yml` workflow triggered on `pull_request` that runs `gx tidy` and pushes a fixup commit when the lock drifts.
|
||||
- **Network dependency at lint time** → `gx lint` against the lock should be offline; only `gx upgrade` hits the GitHub API. Verify during implementation; if `gx lint` requires API calls, configure `GITHUB_TOKEN` for the CI step.
|
||||
- **Two tools touching the same files** → Mitigated by Decision 2: gx runs *after* Renovate inside the same PR, never in parallel.
|
||||
|
||||
## Automated Test Strategy
|
||||
|
||||
- **Manifest generation correctness**: After `gx init`, run `gx lint` locally — expect zero diff. Commit only if clean.
|
||||
- **CI integration test**: Open a draft PR that intentionally bumps an action's SHA without updating the lock. Confirm `gx lint` fails. Then run `gx tidy`, push, and confirm green.
|
||||
- **Renovate integration test**: Manually trigger a Renovate run against a test branch (or wait for the next scheduled run after merge); verify the PR includes both workflow SHA changes and `.github/gx.lock` updates.
|
||||
- **Critical path**: the lint job in CI. If it can't reliably distinguish "pinned and locked" from "unpinned or drifted," the manifest provides no value.
|
||||
- **No new test infrastructure** is needed — existing CI runners and the standard GitHub Actions PR flow cover everything.
|
||||
|
||||
## Observability
|
||||
|
||||
- `gx lint` writes its diagnostics to stdout/stderr; CI annotations surface them on the PR.
|
||||
- Failure modes that could be silent:
|
||||
- **Renovate `postUpgradeTasks` not running** (e.g., disabled on hosted Renovate). Mitigation: `gx lint` will catch it on the PR — failure is loud, not silent.
|
||||
- **`gx lint` skipped due to a workflow filter mistake**. Mitigation: in code review of `ci.yml`, verify the gx-lint job has no `if:` exclusions and runs on all PRs touching `.github/**`.
|
||||
- Log retention is GitHub's default (90 days for workflow logs) — sufficient for after-the-fact triage.
|
||||
|
||||
## Migration Plan
|
||||
|
||||
1. Install `gx` locally; run `gx init` to generate `.github/gx.toml` and `.github/gx.lock`.
|
||||
2. Run `gx tidy` — expect a no-op diff. Commit both files.
|
||||
3. Add the `gx lint` job to `.github/workflows/ci.yml`. Open a PR; confirm green.
|
||||
4. Add `postUpgradeTasks` (or fallback workflow) to keep Renovate PRs in sync.
|
||||
5. Update `docs/contributing.md` with the local workflow.
|
||||
6. Monitor the next Renovate-driven PR; verify `gx tidy` runs and the lock updates correctly.
|
||||
|
||||
**Rollback**: revert the change. Workflow files were never modified, so deletion of `.github/gx.toml`, `.github/gx.lock`, and the lint job restores prior state.
|
||||
|
||||
## Open Questions
|
||||
|
||||
- Does the GitHub-hosted Renovate app permit `postUpgradeTasks`? Verify against current Mend/Renovate documentation before committing to Decision 2(a). If blocked, ship 2(b) directly.
|
||||
- Should the `gx lint` job run on the `main` push as well as PRs? Defer — PR-only is sufficient for the gate; main-push lint can be added later if drift somehow lands.
|
||||
@@ -0,0 +1,30 @@
|
||||
## Why
|
||||
|
||||
GitHub Actions in this repo are SHA-pinned today, but version policy lives only as `# v6.0.1` comments — there is no declarative manifest, no reproducible lock, and no CI gate that fails a PR which introduces an unpinned action. Adopting `gmeligio/gx` gives us a TOML manifest, a lock file, and a `gx lint` step that enforces pinning, while leaving Renovate to keep opening upgrade PRs.
|
||||
|
||||
## What Changes
|
||||
|
||||
- Add `gmeligio/gx` as the source of truth for GitHub Actions versions via `.github/gx.toml` (semantic constraints) and `.github/gx.lock` (resolved SHAs).
|
||||
- Generate both files from the current SHA-pinned workflows using `gx init` (SHA-first resolution — no workflow rewrites required).
|
||||
- Add a `gx lint` job to CI that fails the build when any `uses:` reference is unpinned or drifts from the lock.
|
||||
- Teach Renovate-driven PRs to refresh the gx lock: add a post-update hook (or repo workflow) that runs `gx tidy` and amends the PR so workflows and lock stay in sync.
|
||||
- Document the local workflow in `docs/contributing.md`: contributors who edit workflow files must run `gx tidy` before committing.
|
||||
|
||||
## Capabilities
|
||||
|
||||
### New Capabilities
|
||||
|
||||
- `actions-version-tracking`: Declarative manifest + lock for GitHub Actions, plus a CI lint that enforces SHA pinning and lock-file consistency on every PR.
|
||||
|
||||
### Modified Capabilities
|
||||
|
||||
<!-- None — no prior specs exist in openspec/specs/. -->
|
||||
|
||||
## Impact
|
||||
|
||||
- **Files added**: `.github/gx.toml`, `.github/gx.lock`.
|
||||
- **Files modified**: `.github/workflows/ci.yml` (add `gx lint` job), `.github/renovate.json` (add `postUpgradeTasks` to run `gx tidy` on github-actions PRs), `docs/contributing.md` (workflow editing instructions).
|
||||
- **Workflows touched**: read-only — `gx init` reads existing pins; lock + manifest are derived. No `uses:` line is rewritten.
|
||||
- **Tooling**: contributors and CI need `gx` available. Install via `cargo install gx`, `brew install gmeligio/tap/gx`, or `jaxxstorm/action-install-gh-release` (already used elsewhere in this repo).
|
||||
- **Renovate**: continues to open upgrade PRs for `github-tags` datasource. The `customManagers` regex for Debian deb packages in the Dockerfile is unaffected — gx does not handle that.
|
||||
- **Risk**: lock-file drift if a Renovate PR merges without running `gx tidy`. Mitigated by `gx lint` failing CI when the lock and workflows disagree.
|
||||
+90
@@ -0,0 +1,90 @@
|
||||
## ADDED Requirements
|
||||
|
||||
### Requirement: Manifest as source of truth for GitHub Actions versions
|
||||
|
||||
The repository SHALL maintain a `.github/gx.toml` manifest declaring the version constraint for every distinct GitHub Action referenced by `uses:` in any workflow file under `.github/workflows/` and any composite action under `.github/actions/`.
|
||||
|
||||
#### Scenario: Manifest covers every action in workflows
|
||||
|
||||
- **WHEN** a workflow file references `uses: <owner>/<repo>@<ref>`
|
||||
- **THEN** `.github/gx.toml` contains an entry under `[actions]` for `"<owner>/<repo>"` with a SemVer-style constraint
|
||||
- **AND** the constraint is a caret range on the current major (e.g., `^6`) unless a tighter pin is explicitly justified
|
||||
|
||||
#### Scenario: Adding a new action requires manifest update
|
||||
|
||||
- **WHEN** a contributor opens a PR introducing a new `uses: <owner>/<repo>@<ref>` reference
|
||||
- **THEN** `gx tidy` adds the action to `.github/gx.toml` and `.github/gx.lock`
|
||||
- **AND** the PR fails CI if either file is missing the new entry
|
||||
|
||||
### Requirement: Lock file records resolved SHAs
|
||||
|
||||
The repository SHALL maintain a `.github/gx.lock` file that records, for every manifest entry, the resolved version, the immutable commit SHA, the source repository, the ref type, and the resolution date.
|
||||
|
||||
#### Scenario: Lock file matches workflow SHAs
|
||||
|
||||
- **WHEN** `gx lint` runs
|
||||
- **THEN** every `uses: <owner>/<repo>@<sha>` in workflows resolves to an entry under `[actions."<owner>/<repo>"]` in `.github/gx.lock` whose `sha` field equals the SHA in the workflow
|
||||
|
||||
#### Scenario: Lock file is regenerated by gx tidy
|
||||
|
||||
- **WHEN** a contributor runs `gx tidy` after editing a workflow or the manifest
|
||||
- **THEN** `.github/gx.lock` is updated to reflect the new resolutions
|
||||
- **AND** all `uses:` references in workflows are rewritten to the SHA + `# vX.Y.Z` comment format
|
||||
|
||||
### Requirement: Workflow references are SHA-pinned with version comments
|
||||
|
||||
Every `uses:` reference to a third-party GitHub Action in this repository SHALL be pinned to a 40-character commit SHA followed by a trailing comment of the form `# vX.Y.Z` (or `# vX` / `# vX.Y` for actions that publish such tags).
|
||||
|
||||
#### Scenario: Unpinned tag reference is rejected in CI
|
||||
|
||||
- **WHEN** a PR contains a `uses:` reference like `actions/checkout@v6` (a tag rather than a SHA)
|
||||
- **THEN** the `gx lint` CI job fails
|
||||
- **AND** the failure message identifies the unpinned reference
|
||||
|
||||
#### Scenario: Local actions are not subject to pinning
|
||||
|
||||
- **WHEN** a workflow uses a local action (e.g., `uses: ./.github/actions/clean-runner-disk`)
|
||||
- **THEN** `gx lint` ignores it
|
||||
- **AND** no manifest or lock entry is required
|
||||
|
||||
### Requirement: CI enforces manifest, lock, and pin consistency
|
||||
|
||||
The repository's CI SHALL run `gx lint` on every pull request and SHALL fail when any of the following are true: a workflow `uses:` is unpinned, the manifest does not list a referenced action, or the lock file SHA does not match the workflow SHA.
|
||||
|
||||
#### Scenario: PR with drifted lock fails CI
|
||||
|
||||
- **WHEN** a PR updates a workflow SHA without regenerating `.github/gx.lock`
|
||||
- **THEN** `gx lint` fails the PR check
|
||||
- **AND** the failure output indicates which action's lock entry is stale
|
||||
|
||||
#### Scenario: Clean PR passes the gx-lint job
|
||||
|
||||
- **WHEN** a PR's workflow files, manifest, and lock are mutually consistent and every `uses:` is SHA-pinned
|
||||
- **THEN** the `gx lint` CI job exits 0
|
||||
- **AND** the check appears as passing on the PR
|
||||
|
||||
### Requirement: Renovate-driven upgrades keep the lock in sync
|
||||
|
||||
Renovate-generated upgrade pull requests for GitHub Actions SHALL leave `.github/gx.lock` consistent with the workflow SHAs they introduce, either by running `gx tidy` as a `postUpgradeTask` in `renovate.json` or via an equivalent CI workflow that pushes a fixup commit to the PR before merge.
|
||||
|
||||
#### Scenario: Renovate bumps a SHA and the lock is updated
|
||||
|
||||
- **WHEN** Renovate opens a PR that bumps `actions/checkout` from one SHA to another
|
||||
- **THEN** the same PR contains a corresponding update to `.github/gx.lock`
|
||||
- **AND** `gx lint` passes on the PR
|
||||
|
||||
#### Scenario: Lock drift is detected before merge
|
||||
|
||||
- **WHEN** for any reason a Renovate PR lands without the lock update
|
||||
- **THEN** the `gx lint` CI job fails the PR
|
||||
- **AND** merge is blocked until `gx tidy` is run and committed
|
||||
|
||||
### Requirement: Contributor documentation describes the gx workflow
|
||||
|
||||
`docs/contributing.md` SHALL document the local workflow for editing `.github/workflows/*.yml` files: install `gx`, run `gx tidy` after edits, and commit the resulting changes to `.github/gx.toml` and `.github/gx.lock` along with the workflow change.
|
||||
|
||||
#### Scenario: Contributor reads the workflow editing guide
|
||||
|
||||
- **WHEN** a contributor opens `docs/contributing.md` looking for guidance on editing workflow files
|
||||
- **THEN** they find instructions to install `gx` and run `gx tidy` before committing
|
||||
- **AND** they find a reference link to `https://github.com/gmeligio/gx`
|
||||
@@ -0,0 +1,36 @@
|
||||
## 1. Generate manifest and lock locally
|
||||
|
||||
- [x] 1.1 Install `gx` locally (`brew install gmeligio/tap/gx` or `cargo install gx`) and verify with `gx --version`
|
||||
- [x] 1.2 Run `gx init` at the repo root; confirm it generates `.github/gx.toml` and `.github/gx.lock` from existing SHA-pinned workflows
|
||||
- [x] 1.3 Inspect `.github/gx.toml` constraints; tighten any v0/v1 actions to `~X.Y.Z`, leave stable majors as `^X`
|
||||
- [x] 1.4 Run `gx tidy`; confirm zero diff against workflow files
|
||||
- [x] 1.5 Run `gx lint`; confirm zero findings
|
||||
|
||||
## 2. Wire up CI lint gate
|
||||
|
||||
- [x] 2.1 Add a `gx_lint` job to `.github/workflows/build.yml` (deviation: ci.yml only triggers on push; build.yml is the PR-time workflow). Runs on `pull_request` via existing trigger.
|
||||
- [x] 2.2 Install `gx` in the job using `jaxxstorm/action-install-gh-release@6096f2a2bbfee498ced520b6922ac2c06e990ed2 # v2.1.0`, pointing at `gmeligio/gx` tag `v0.7.1` with archive digest `6632843410c877c43aa8936eb757d8b0ddcb5940402203914543ef8a9cf8ecd9`.
|
||||
- [x] 2.3 Run `gx lint` as the job step with `GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}` to avoid the 60 req/h anonymous rate limit.
|
||||
- [x] 2.4 Open a draft PR that intentionally edits a workflow SHA without rerunning `gx tidy`; confirm `gx-lint` fails
|
||||
- [x] 2.5 Run `gx tidy` on the same PR; confirm `gx-lint` passes
|
||||
|
||||
## 3. Keep Renovate-driven PRs in sync
|
||||
|
||||
- [x] 3.1 Decided: skip the `postUpgradeTasks` route (3.2/3.3) — the GitHub-hosted Mend Renovate app does not permit `postUpgradeTasks` without org-level allowlisting, so the fallback workflow is the more reliable mechanism and works for any PR (Renovate or human) that drifts the lock.
|
||||
- [~] 3.2 Skipped — see 3.1.
|
||||
- [~] 3.3 Skipped — see 3.1.
|
||||
- [x] 3.4 Added `.github/workflows/gx-tidy.yml` triggered on `pull_request` (paths `.github/workflows/**`, `.github/gx.toml`, `.github/gx.lock`). Uses the `VERIFIED_COMMIT_ID`/`VERIFIED_COMMIT_KEY` GitHub App (same one used by `tag.yml`/`changelog.yml`/`release.yml`/`update_version.yml`) to push the fixup commit. Skips PRs from forks via `if: head.repo.full_name == github.repository`.
|
||||
- [x] 3.5 Open a test Renovate-style PR that bumps a single action SHA; confirm the lock is updated within the same PR before merge.
|
||||
|
||||
## 4. Documentation
|
||||
|
||||
- [x] 4.1 Add a "Editing GitHub Actions workflows" section to `docs/contributing.md` explaining the `gx tidy` local step and linking to `https://github.com/gmeligio/gx`
|
||||
- [x] 4.2 Mention the `gx-lint` CI gate so contributors know what will fail their PR
|
||||
- [x] 4.3 Reference the manifest format and a one-line snippet showing how to add a new action
|
||||
|
||||
## 5. Verification and rollout
|
||||
|
||||
- [x] 5.1 Run `openspec validate adopt-gx-for-actions` and resolve any findings
|
||||
- [x] 5.2 Open the implementation PR; confirm all CI jobs pass including the new `gx-lint`
|
||||
- [x] 5.3 After merge, monitor the next Renovate-driven github-actions PR; confirm `gx.lock` is updated alongside workflow SHAs
|
||||
- [x] 5.4 Document rollback steps inline in the PR description (delete `.github/gx.toml`, `.github/gx.lock`, the lint job, and any `postUpgradeTasks` block — workflows themselves remain untouched)
|
||||
@@ -0,0 +1,94 @@
|
||||
# actions-version-tracking Specification
|
||||
|
||||
## Purpose
|
||||
TBD - created by archiving change adopt-gx-for-actions. Update Purpose after archive.
|
||||
## Requirements
|
||||
### Requirement: Manifest as source of truth for GitHub Actions versions
|
||||
|
||||
The repository SHALL maintain a `.github/gx.toml` manifest declaring the version constraint for every distinct GitHub Action referenced by `uses:` in any workflow file under `.github/workflows/` and any composite action under `.github/actions/`.
|
||||
|
||||
#### Scenario: Manifest covers every action in workflows
|
||||
|
||||
- **WHEN** a workflow file references `uses: <owner>/<repo>@<ref>`
|
||||
- **THEN** `.github/gx.toml` contains an entry under `[actions]` for `"<owner>/<repo>"` with a SemVer-style constraint
|
||||
- **AND** the constraint is a caret range on the current major (e.g., `^6`) unless a tighter pin is explicitly justified
|
||||
|
||||
#### Scenario: Adding a new action requires manifest update
|
||||
|
||||
- **WHEN** a contributor opens a PR introducing a new `uses: <owner>/<repo>@<ref>` reference
|
||||
- **THEN** `gx tidy` adds the action to `.github/gx.toml` and `.github/gx.lock`
|
||||
- **AND** the PR fails CI if either file is missing the new entry
|
||||
|
||||
### Requirement: Lock file records resolved SHAs
|
||||
|
||||
The repository SHALL maintain a `.github/gx.lock` file that records, for every manifest entry, the resolved version, the immutable commit SHA, the source repository, the ref type, and the resolution date.
|
||||
|
||||
#### Scenario: Lock file matches workflow SHAs
|
||||
|
||||
- **WHEN** `gx lint` runs
|
||||
- **THEN** every `uses: <owner>/<repo>@<sha>` in workflows resolves to an entry under `[actions."<owner>/<repo>"]` in `.github/gx.lock` whose `sha` field equals the SHA in the workflow
|
||||
|
||||
#### Scenario: Lock file is regenerated by gx tidy
|
||||
|
||||
- **WHEN** a contributor runs `gx tidy` after editing a workflow or the manifest
|
||||
- **THEN** `.github/gx.lock` is updated to reflect the new resolutions
|
||||
- **AND** all `uses:` references in workflows are rewritten to the SHA + `# vX.Y.Z` comment format
|
||||
|
||||
### Requirement: Workflow references are SHA-pinned with version comments
|
||||
|
||||
Every `uses:` reference to a third-party GitHub Action in this repository SHALL be pinned to a 40-character commit SHA followed by a trailing comment of the form `# vX.Y.Z` (or `# vX` / `# vX.Y` for actions that publish such tags).
|
||||
|
||||
#### Scenario: Unpinned tag reference is rejected in CI
|
||||
|
||||
- **WHEN** a PR contains a `uses:` reference like `actions/checkout@v6` (a tag rather than a SHA)
|
||||
- **THEN** the `gx lint` CI job fails
|
||||
- **AND** the failure message identifies the unpinned reference
|
||||
|
||||
#### Scenario: Local actions are not subject to pinning
|
||||
|
||||
- **WHEN** a workflow uses a local action (e.g., `uses: ./.github/actions/clean-runner-disk`)
|
||||
- **THEN** `gx lint` ignores it
|
||||
- **AND** no manifest or lock entry is required
|
||||
|
||||
### Requirement: CI enforces manifest, lock, and pin consistency
|
||||
|
||||
The repository's CI SHALL run `gx lint` on every pull request and SHALL fail when any of the following are true: a workflow `uses:` is unpinned, the manifest does not list a referenced action, or the lock file SHA does not match the workflow SHA.
|
||||
|
||||
#### Scenario: PR with drifted lock fails CI
|
||||
|
||||
- **WHEN** a PR updates a workflow SHA without regenerating `.github/gx.lock`
|
||||
- **THEN** `gx lint` fails the PR check
|
||||
- **AND** the failure output indicates which action's lock entry is stale
|
||||
|
||||
#### Scenario: Clean PR passes the gx-lint job
|
||||
|
||||
- **WHEN** a PR's workflow files, manifest, and lock are mutually consistent and every `uses:` is SHA-pinned
|
||||
- **THEN** the `gx lint` CI job exits 0
|
||||
- **AND** the check appears as passing on the PR
|
||||
|
||||
### Requirement: Renovate-driven upgrades keep the lock in sync
|
||||
|
||||
Renovate-generated upgrade pull requests for GitHub Actions SHALL leave `.github/gx.lock` consistent with the workflow SHAs they introduce, either by running `gx tidy` as a `postUpgradeTask` in `renovate.json` or via an equivalent CI workflow that pushes a fixup commit to the PR before merge.
|
||||
|
||||
#### Scenario: Renovate bumps a SHA and the lock is updated
|
||||
|
||||
- **WHEN** Renovate opens a PR that bumps `actions/checkout` from one SHA to another
|
||||
- **THEN** the same PR contains a corresponding update to `.github/gx.lock`
|
||||
- **AND** `gx lint` passes on the PR
|
||||
|
||||
#### Scenario: Lock drift is detected before merge
|
||||
|
||||
- **WHEN** for any reason a Renovate PR lands without the lock update
|
||||
- **THEN** the `gx lint` CI job fails the PR
|
||||
- **AND** merge is blocked until `gx tidy` is run and committed
|
||||
|
||||
### Requirement: Contributor documentation describes the gx workflow
|
||||
|
||||
`docs/contributing.md` SHALL document the local workflow for editing `.github/workflows/*.yml` files: install `gx`, run `gx tidy` after edits, and commit the resulting changes to `.github/gx.toml` and `.github/gx.lock` along with the workflow change.
|
||||
|
||||
#### Scenario: Contributor reads the workflow editing guide
|
||||
|
||||
- **WHEN** a contributor opens `docs/contributing.md` looking for guidance on editing workflow files
|
||||
- **THEN** they find instructions to install `gx` and run `gx tidy` before committing
|
||||
- **AND** they find a reference link to `https://github.com/gmeligio/gx`
|
||||
|
||||
Reference in New Issue
Block a user