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
53 changes: 53 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,13 @@ on:
branches: [main]
pull_request:
branches: [main]
# Daily canary: the action installs from neokapi/neokapi releases, so it can
# break without a commit here (a renamed release asset did exactly that for
# 1.2.0). The schedule catches that class of breakage; the prerelease job
# below catches it while it is still an RC.
schedule:
- cron: "17 5 * * *"
workflow_dispatch:

jobs:
test-latest:
Expand Down Expand Up @@ -77,6 +84,52 @@ jobs:
echo "Expected: ${EXPECTED}, Actual: ${ACTUAL}"
test "${EXPECTED}" = "${ACTUAL}"

# Canary against the newest CLI prerelease. Release packaging changes land
# on RCs first (the kapi_ → kapi-cli_ archive rename shipped on 1.2.0-rc1),
# so installing the newest prerelease here fails weeks before the same
# breakage would reach a stable release and every consumer workflow.
test-prerelease:
name: "Prerelease canary"
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v6

- name: Resolve newest CLI prerelease
id: prerelease
shell: bash
env:
GH_TOKEN: ${{ secrets.NEOKAPI_GITHUB_TOKEN }}
# Same tag discipline as resolve-version.sh: only vX.Y.Z-… CLI tags,
# never plugin/app tags (check-v…, asr-v…, bowrain-v…).
run: |
VERSION=$(
gh api "repos/neokapi/neokapi/releases?per_page=100" --paginate \
--jq '.[] | select(.draft == false and .prerelease == true) | .tag_name' |
grep -E '^v[0-9]+\.[0-9]+\.[0-9]+-' |
sed 's/^v//' |
sort -V |
tail -n 1
) || true
if [ -z "${VERSION}" ]; then
echo "No CLI prerelease found — nothing to canary."
else
echo "Newest CLI prerelease: ${VERSION}"
fi
echo "version=${VERSION}" >> "$GITHUB_OUTPUT"

- name: Setup kapi (prerelease)
if: steps.prerelease.outputs.version != ''
id: setup
uses: ./
with:
token: ${{ secrets.NEOKAPI_GITHUB_TOKEN }}
version: ${{ steps.prerelease.outputs.version }}

- name: Verify kapi is on PATH
if: steps.prerelease.outputs.version != ''
shell: bash
run: kapi version

test-cache:
name: "Cache hit (${{ matrix.os }})"
runs-on: ${{ matrix.os }}
Expand Down
12 changes: 7 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

GitHub Action for installing the [kapi CLI](https://github.com/neokapi/neokapi) in CI workflows.

Downloads the correct binary for the runner platform, verifies SHA-256 checksums, caches between runs, and optionally installs kapi plugins (such as the bowrain plugin, `kapi-bowrain`) and configures server authentication.
Downloads the correct binary for the runner platform, verifies SHA-256 checksums, caches between runs, installs kapi plugins (the bowrain plugin by default), and configures server authentication.

## Usage

Expand Down Expand Up @@ -33,13 +33,15 @@ steps:
- run: kapi run
```

### With the bowrain plugin and server auth
### With server auth

The bowrain plugin is installed by default, so a server-connected project only
needs credentials:

```yaml
steps:
- uses: neokapi/setup-kapi@v1
with:
plugins: kapi-bowrain
auth-token: ${{ secrets.BOWRAIN_AUTH_TOKEN }}
server: https://your.bowrain.server

Expand All @@ -54,7 +56,7 @@ steps:
|-------|-------------|---------|----------|
| `version` | Kapi CLI version (e.g. `1.1.0`), or `latest` for the newest stable CLI release | `latest` | No |
| `token` | GitHub token for release downloads and API rate limits | `${{ github.token }}` | No |
| `plugins` | Newline- or comma-separated plugin refs to install (e.g. `kapi-bowrain`) | — | No |
| `plugins` | Newline- or comma-separated plugin refs to install, as the registry names them (`bowrain`, `okapi-bridge`; a `kapi-` prefix is stripped). Pass `''` to install nothing | `bowrain` | No |
| `auth-token` | Bowrain server JWT, exported as `BOWRAIN_AUTH_TOKEN` | — | No |
| `server` | Bowrain server URL, exported as `BOWRAIN_SERVER_URL` | — | No |

Expand All @@ -72,7 +74,7 @@ steps:
3. **Download + verify** (on cache miss) — downloads the archive and `checksums.txt` from the GitHub release, verifies the SHA-256 checksum, and extracts the binary.
4. **Add to PATH** — makes `kapi` available to all subsequent steps.
5. **Configure auth** (optional) — exports `BOWRAIN_AUTH_TOKEN`/`BOWRAIN_SERVER_URL` when `auth-token` is set.
6. **Install plugins** (optional) — installs each ref in `plugins` via `kapi plugins install`, cached keyed on the plugin set + OS + arch.
6. **Install plugins** — installs each ref in `plugins` (default: `bowrain`) via `kapi plugins install`, cached keyed on the plugin set + OS + arch. Refs use the registry names; a `kapi-` binary prefix is stripped (`kapi-bowrain` → `bowrain`).

## Caching

Expand Down
16 changes: 12 additions & 4 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,14 @@ inputs:
required: false
default: ${{ github.token }}
plugins:
description: "Newline- or comma-separated plugin refs to install (e.g. 'kapi-bowrain')"
description: >-
Newline- or comma-separated plugin refs to install, as the registry
names them (e.g. 'bowrain', 'okapi-bridge'). The 'kapi-' binary prefix
is accepted and stripped ('kapi-bowrain' → 'bowrain'). Defaults to
'bowrain' so server-connected projects work out of the box; pass '' to
install nothing.
required: false
default: ""
default: "bowrain"
auth-token:
description: "Bowrain server JWT auth token (exported as BOWRAIN_AUTH_TOKEN)"
required: false
Expand Down Expand Up @@ -151,8 +156,11 @@ runs:
env:
INPUT_PLUGINS: ${{ inputs.plugins }}
run: |
# Normalize: replace commas with newlines, trim whitespace, skip empty lines
PLUGINS=$(echo "${INPUT_PLUGINS}" | tr ',' '\n' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//' | grep -v '^$')
# Normalize: replace commas with newlines, trim whitespace, skip empty
# lines, and strip the 'kapi-' binary prefix — the registry names the
# plugin 'bowrain' while its binary and archives are 'kapi-bowrain',
# so both spellings should install.
PLUGINS=$(echo "${INPUT_PLUGINS}" | tr ',' '\n' | sed 's/^[[:space:]]*//;s/[[:space:]]*$//;s/^kapi-//' | grep -v '^$')
while IFS= read -r plugin; do
echo "Installing plugin: ${plugin}"
kapi plugins install "${plugin}"
Expand Down
30 changes: 26 additions & 4 deletions scripts/download-verify.sh
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,43 @@ ARCH="${3:?}"
EXT="${4:?}"
DEST="${5:?}"

ARCHIVE="kapi_${VERSION}_${OS}_${ARCH}.${EXT}"
TAG="v${VERSION}"
TMPDIR=$(mktemp -d)
trap 'rm -rf "${TMPDIR}"' EXIT

# The CLI archive was renamed for 1.2.0: kapi_<ver>_<os>_<arch>.<ext> became
# kapi-cli_<ver>_<os>_<arch>.<ext> (scripts/package-cli.sh mirrors the
# Homebrew naming, where the CLI toolchain is kapi-cli). Resolve the actual
# name from checksums.txt — the release's own asset manifest — so the action
# installs both eras: 1.1.0 and earlier ship kapi_, 1.2.0-rc1 and later ship
# kapi-cli_.
echo "Fetching checksums.txt from release ${TAG}..."
gh release download "${TAG}" \
--repo neokapi/neokapi \
--pattern "checksums.txt" \
--dir "${TMPDIR}"

cd "${TMPDIR}"
ARCHIVE=""
for candidate in "kapi-cli_${VERSION}_${OS}_${ARCH}.${EXT}" "kapi_${VERSION}_${OS}_${ARCH}.${EXT}"; do
if awk -v name="${candidate}" '$2 == name { found = 1 } END { exit !found }' checksums.txt; then
ARCHIVE="${candidate}"
break
fi
done
if [ -z "${ARCHIVE}" ]; then
echo "::error::Release ${TAG} has neither kapi-cli_${VERSION}_${OS}_${ARCH}.${EXT} nor kapi_${VERSION}_${OS}_${ARCH}.${EXT} in checksums.txt" >&2
exit 1
fi

echo "Downloading ${ARCHIVE} from release ${TAG}..."
gh release download "${TAG}" \
--repo neokapi/neokapi \
--pattern "${ARCHIVE}" \
--pattern "checksums.txt" \
--dir "${TMPDIR}"

# Verify checksum
cd "${TMPDIR}"
EXPECTED=$(grep "${ARCHIVE}" checksums.txt | awk '{print $1}')
EXPECTED=$(awk -v name="${ARCHIVE}" '$2 == name { print $1 }' checksums.txt)
if [ -z "${EXPECTED}" ]; then
echo "::error::Archive ${ARCHIVE} not found in checksums.txt" >&2
exit 1
Expand Down