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
4 changes: 4 additions & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
# CI, release workflows, and the Action contract define what passes and what publishes.
/.github/ @Teakowa
/action.yml @Teakowa
/AGENTS.md @Teakowa
94 changes: 94 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
name: CI

on:
pull_request:
push:
branches: [main]

permissions:
contents: read

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

env:
# Exact Wright release the smoke jobs install. Bump deliberately.
WRIGHT_SMOKE_VERSION: 0.29.0

jobs:
lint:
name: actionlint and shellcheck
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Checkout
uses: actions/checkout@v7

# The image bundles shellcheck, so actionlint also checks `run:` scripts.
- name: actionlint
run: docker run --rm -v "$PWD:/repo" -w /repo rhysd/actionlint:1.7.12 -color

- name: shellcheck
run: shellcheck -S warning scripts/*.sh

test:
name: Helper tests
runs-on: ubuntu-latest
timeout-minutes: 5
steps:
- name: Checkout
uses: actions/checkout@v7

- name: Test the helper script
run: scripts/test-setup-wright.sh

smoke:
name: Smoke (${{ matrix.os }})
runs-on: ${{ matrix.os }}
timeout-minutes: 15
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-15, windows-latest]
steps:
- name: Checkout
uses: actions/checkout@v7

- name: Install Wright through the Action
id: first
uses: ./
with:
version: ${{ env.WRIGHT_SMOKE_VERSION }}

- name: Move the installed copy aside so the cache must restore it
shell: bash
run: mv "$RUNNER_TEMP/setup-wright" "$RUNNER_TEMP/setup-wright-first"

- name: Install Wright again from the cache
id: second
uses: ./
with:
version: ${{ env.WRIGHT_SMOKE_VERSION }}

# A fork PR has a read-only cache token, so it cannot seed the cache.
- name: Check the second install used the cache
if: github.event.pull_request.head.repo.fork != true
shell: bash
env:
CACHE_HIT: ${{ steps.second.outputs.cache-hit }}
run: |
[[ "$CACHE_HIT" == true ]] || { echo "::error::expected a cache hit"; exit 1; }

- name: Check the installed version
shell: bash
run: |
wright --version | tee "$RUNNER_TEMP/version.txt"
grep -Fq "$WRIGHT_SMOKE_VERSION" "$RUNNER_TEMP/version.txt"

- name: Run Wright as an ordinary step
shell: bash
run: |
printf 'rule ("setup smoke") {\n event {\n Ongoing - Global;\n }\n actions {\n Disable Inspector Recording;\n }\n}\n' > "$RUNNER_TEMP/smoke.ws"
wright check "$RUNNER_TEMP/smoke.ws"
grep -Fq 'PASS' "$GITHUB_STEP_SUMMARY"
37 changes: 37 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
name: Release

# release-please maintains the Release PR. Merging it creates the exact vX.Y.Z
# tag and GitHub Release. The same job then moves the major tag (for example
# v1) to that tag, so consumers can use `wrightkit/setup-wright@v1`. The
# built-in token suffices: no downstream workflow starts from these tags.
on:
push:
branches: [main]

permissions:
contents: write
pull-requests: write

concurrency:
group: release
cancel-in-progress: false

jobs:
release:
name: Release
runs-on: ubuntu-latest
timeout-minutes: 10
steps:
- uses: googleapis/release-please-action@45996ed1f6d02564a971a2fa1b5860e934307cf7 # v5.0.0
id: release
with:
config-file: release-please-config.json
manifest-file: .release-please-manifest.json

- name: Move the major tag
if: steps.release.outputs.release_created == 'true'
env:
GH_TOKEN: ${{ github.token }}
MAJOR_TAG: v${{ steps.release.outputs.major }}
SHA: ${{ steps.release.outputs.sha }}
run: gh api -X PATCH "repos/$GITHUB_REPOSITORY/git/refs/tags/$MAJOR_TAG" -f sha="$SHA" -F force=true || gh api -X POST "repos/$GITHUB_REPOSITORY/git/refs" -f ref="refs/tags/$MAJOR_TAG" -f sha="$SHA"
1 change: 1 addition & 0 deletions .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{}
11 changes: 11 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# setup-wright

Cached `setup-wright` GitHub composite Action: installs the Wright CLI and caches the verified executable. Read the organization routing in [`wrightkit/.github`](https://github.com/wrightkit/.github/blob/main/AGENTS.md) first. This file adds only what is specific to this repository.

- Wright owns the release archives, checksums, and the `latest/version` pointer. This repository only consumes them. Do not change that contract here.
- Keep the Action to setup. No command wrappers, no provider/project/build caching, no write token or secrets.
- Keep the cache key exact. Never add a restore prefix. Verify SHA-256 before cache save and before use, and never execute an unverified cached binary.
- `action.yml` is the public contract (inputs, outputs, `uses: wrightkit/setup-wright@v1`). Change it only with `README.md` and the helper tests in the same PR.
- Run `scripts/test-setup-wright.sh`, `actionlint`, and `shellcheck -S warning scripts/*.sh` before a PR. The `smoke` CI job is the real consuming-workflow check and pins an exact Wright version.
- Never create tags or releases by hand. `.github/workflows/release.yml` owns `vX.Y.Z` and the moving `v1`.
- Pin third-party actions to a full commit SHA.
77 changes: 77 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# setup-wright

A GitHub Action that installs the [Wright](https://github.com/wrightkit/wright) CLI and caches the verified executable. It only sets up Wright. Command selection, arguments, and gating stay in ordinary `run: wright ...` steps, so annotations, step summaries, and exit codes are Wright's own.

## Usage

Set up once, then run Wright as often as needed:

```yaml
jobs:
wright:
runs-on: ubuntu-latest
permissions:
contents: read
steps:
- uses: actions/checkout@v7
- uses: wrightkit/setup-wright@v1
with:
version: 0.29.0
- run: wright check
- run: wright lint
- run: wright analyze
```

| Input | Default | Meaning |
| --- | --- | --- |
| `version` | `latest` | Exact Wright version such as `0.29.0` (a leading `v` is accepted), or `latest`. |
| `cache` | `true` | Cache the verified executable. `false` downloads on every run. |

| Output | Meaning |
| --- | --- |
| `version` | Concrete Wright version installed. |
| `cache-hit` | `true` when a valid cached executable avoided the release download. |

Supported runners: Linux x86_64, macOS x86_64, macOS arm64, and Windows x86_64, the targets with published Wright release archives. Any other platform fails with an `unsupported platform` error.

## Versioning

The Action has its own versions, independent of Wright's. Use `@v1` for the moving major tag, an exact tag such as `@v1.0.0`, or a commit SHA. The `@ref` selects the Action's code. The `version` input selects the Wright binary.

## Version pinning

* An exact `version` is deterministic and, on a cache hit, makes no request to the release distribution.
* `latest` first reads the small `latest/version` pointer, then uses the concrete version for every later step. A cache hit for that version never re-downloads the archive. A new pointer value selects a new cache entry and installs the new binary.
* Pin an exact version in workflows that must be reproducible.

## Cache identity

Only the `wright` executable and a SHA-256 sidecar are cached, with the Actions cache service of the consuming repository. The key is exact:

```text
setup-wright-v1-<version>-<target-triple>
```

* There is no restore prefix, so a different version, OS, or architecture never restores into the wrong executable.
* `v1` is the cache layout. Changing the layout bumps it and abandons old entries.
* Actions caches are scoped to the consuming repository and ref. A pull request writes to its own scope, not to the default branch, and a fork pull request with a read-only token only restores. The Action needs no secrets and no permissions beyond `contents: read`.

On a miss the Action downloads the archive and its `.sha256` from the immutable release route, verifies the checksum before extraction, and only then saves the executable.

After every restore the Action recomputes the executable's SHA-256 against the sidecar and requires `wright --version` to report the requested version. A failing entry is deleted, a warning is printed, and the release is downloaded and verified again. An invalid entry keeps its key until the cache service evicts it, because Actions cache keys are immutable, so each run downloads again until then. The sidecar detects corruption and a wrong version. It does not authenticate the cache against a writer who can alter both files. That trust boundary is the consuming repository.

## What is not cached

Provider downloads, project sources, analysis or lint results, and build output are never cached. Providers remain managed by Wright itself. Set `cache: false` to skip the cache service entirely.

## Why a separate repository

The Action started inside `wrightkit/wright` (`uses: wrightkit/wright/.github/actions/setup-wright@<release-ref>`). The owner moved it here because that call path is awkward, and because consumers need a stable major tag that is independent of Wright's release refs.

## Development

* `scripts/test-setup-wright.sh` runs the helper against a mock release distribution: cache hit and miss (zero requests on a hit), version and `latest` resolution, key separation, corrupt and wrong-version caches, checksum mismatch, a missing release, and unsupported platforms.
* CI runs `actionlint`, `shellcheck`, those tests, and a smoke job that uses the Action as `uses: ./` on Linux, macOS, and Windows with a published Wright release. It installs twice and requires a cache hit on the second run.
* Releases follow [release-please](.github/workflows/release.yml): merge the Release PR to create the `vX.Y.Z` tag and move the major tag.

License: MIT.
81 changes: 81 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
name: Set up Wright
description: Install the Wright CLI from the verified release distribution and cache only the executable

inputs:
version:
description: Exact Wright version (for example 0.28.0) or `latest`
required: false
default: latest
cache:
description: Cache the verified Wright executable with the Actions cache service
required: false
default: 'true'

outputs:
version:
description: Concrete Wright version that was installed
value: ${{ steps.resolve.outputs.version }}
cache-hit:
description: '`true` when a valid cached executable avoided the release download'
value: ${{ steps.verify.outputs.valid }}

runs:
using: composite
steps:
- name: Resolve Wright release
id: resolve
shell: bash
env:
WRIGHT_VERSION: ${{ inputs.version }}
run: |
"$GITHUB_ACTION_PATH/scripts/setup-wright.sh" resolve

- name: Restore Wright executable
id: restore
if: inputs.cache == 'true'
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ${{ steps.resolve.outputs.cache-dir }}
key: ${{ steps.resolve.outputs.cache-key }}

- name: Verify cached executable
id: verify
shell: bash
env:
WRIGHT_SETUP_DIR: ${{ steps.resolve.outputs.cache-dir }}
WRIGHT_SETUP_VERSION: ${{ steps.resolve.outputs.version }}
WRIGHT_SETUP_TARGET: ${{ steps.resolve.outputs.target }}
CACHE_HIT: ${{ steps.restore.outputs.cache-hit }}
run: |
if [[ "$CACHE_HIT" == true ]]; then
"$GITHUB_ACTION_PATH/scripts/setup-wright.sh" verify-cache
else
echo "valid=false" >> "$GITHUB_OUTPUT"
fi

- name: Download and verify Wright release
if: steps.verify.outputs.valid != 'true'
shell: bash
env:
WRIGHT_SETUP_DIR: ${{ steps.resolve.outputs.cache-dir }}
WRIGHT_SETUP_VERSION: ${{ steps.resolve.outputs.version }}
WRIGHT_SETUP_TARGET: ${{ steps.resolve.outputs.target }}
WRIGHT_SETUP_EXT: ${{ steps.resolve.outputs.archive-ext }}
run: |
"$GITHUB_ACTION_PATH/scripts/setup-wright.sh" install

# Only a freshly verified executable on a true miss is saved. An invalid
# entry under the same immutable key is not overwritten; see README.md.
- name: Save Wright executable
if: inputs.cache == 'true' && steps.restore.outputs.cache-hit != 'true'
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: ${{ steps.resolve.outputs.cache-dir }}
key: ${{ steps.resolve.outputs.cache-key }}

- name: Add Wright to PATH
shell: bash
env:
WRIGHT_SETUP_DIR: ${{ steps.resolve.outputs.cache-dir }}
run: |
"$GITHUB_ACTION_PATH/scripts/setup-wright.sh" activate
11 changes: 11 additions & 0 deletions release-please-config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"$schema": "https://raw.githubusercontent.com/googleapis/release-please/main/schemas/config.json",
"initial-version": "1.0.0",
"packages": {
".": {
"release-type": "simple",
"include-component-in-tag": false,
"changelog-path": "CHANGELOG.md"
}
}
}
Loading
Loading