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
5 changes: 5 additions & 0 deletions .claude/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"enabledPlugins": {
"typescript-lsp@claude-plugins-official": true
}
}
15 changes: 15 additions & 0 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
version: 2
updates:
- package-ecosystem: "npm"
directory: "/"
schedule:
interval: "weekly"
groups:
actions-toolkit:
patterns:
- "@actions/*"

- package-ecosystem: "github-actions"
directory: "/"
schedule:
interval: "weekly"
38 changes: 38 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
name: CI

on:
push:
branches: [main]
pull_request:

permissions:
contents: read

jobs:
check:
name: Typecheck / Lint / Test / Build
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- uses: actions/setup-node@v4
with:
node-version: 24
cache: npm

- run: npm ci

- run: npm run typecheck
- run: npm run lint
- run: npm run test
- run: npm run build

# The committed dist/ must match a fresh build, otherwise the action
# would run stale code. Fail if `npm run build` produced a diff.
- name: Verify dist/ is up to date
run: |
if [ -n "$(git status --porcelain dist)" ]; then
echo "::error::dist/ is out of date. Run 'npm run build' and commit the result."
git --no-pager diff --stat dist
exit 1
fi
70 changes: 70 additions & 0 deletions .github/workflows/self-test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
name: Self-test

on:
push:
branches: [main]
pull_request:

permissions:
contents: read

jobs:
matrix:
name: ${{ matrix.os }} / version=${{ matrix.version }}
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-latest, windows-latest]
version: ["latest", "3.x", "3.51.1"]
steps:
- uses: actions/checkout@v4

# The action runs from dist/, so build it first in CI.
- uses: actions/setup-node@v4
with:
node-version: 24
cache: npm
- run: npm ci
- run: npm run build

- name: Run setup-task
id: setup
uses: ./
with:
version: ${{ matrix.version }}

- name: Verify task is on PATH and reports a version
shell: bash
run: |
installed="$(task --version)"
echo "task --version => ${installed}"
echo "action output version => ${RESOLVED}"
echo "action output cache-hit => ${CACHE_HIT}"
case "${installed}" in
*"${RESOLVED}"*) echo "OK: versions match" ;;
*) echo "::error::version mismatch: '${installed}' does not contain '${RESOLVED}'"; exit 1 ;;
esac
env:
RESOLVED: ${{ steps.setup.outputs.version }}
CACHE_HIT: ${{ steps.setup.outputs.cache-hit }}

checksum-failure:
Comment thread
yk-lab marked this conversation as resolved.
name: Checksum mismatch must fail
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: 24
cache: npm
- run: npm ci
- run: npm run build

# Sanity: skip-checksum should still install successfully.
- name: Install with checksum skipped
uses: ./
with:
version: "3.51.1"
skip-checksum: "true"
- run: task --version
10 changes: 10 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
node_modules/
coverage/
*.log
.DS_Store

# Personal Claude Code overrides (shared .claude/settings.json IS committed)
.claude/settings.local.json

# NOTE: dist/ is intentionally committed — GitHub Actions run the bundled
# dist/index.js directly from the repo, so it must be checked in.
54 changes: 54 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# CLAUDE.md

This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.

## What this is

A GitHub Action (TypeScript, Node 24 runtime) that installs the [go-task](https://github.com/go-task/task) `task` binary onto `PATH`. It is a drop-in, hardened alternative to `arduino/setup-task`: authenticated downloads, SHA256 checksum verification, tool-cache reuse, and retry-with-backoff. The `version` / `repo-token` inputs are intentionally compatible with `arduino/setup-task`.

## Commands

```bash
npm run all # typecheck + lint + test + build — run before committing
npm run typecheck # tsc --noEmit
npm run lint # eslint . (lint:fix to autofix)
npm run test # vitest run
npm run test:watch # vitest in watch mode
npm run build # ncc bundles src/main.ts -> dist/index.js
npx vitest run tests/version.test.ts # run a single test file
npx vitest run -t "resolves a semver range" # run tests matching a name
```

## Critical: `dist/` is committed and must stay in sync

GitHub Actions runs the bundled `dist/index.js` directly (see `action.yml` → `main: dist/index.js`), **not** the TypeScript source. After any change under `src/`, run `npm run build` and commit the regenerated `dist/`. CI (`.github/workflows/ci.yml`) fails the build if `git status --porcelain dist` is non-empty — i.e. if the committed `dist/` doesn't match a fresh build.

## Architecture

`src/main.ts` is the orchestrator. Its `run()` executes a fixed 7-step pipeline, and the rest of `src/` is single-responsibility modules it calls:

1. **Resolve version** — `version.ts` turns a spec (`latest` / exact / semver range) into a concrete version.
2. **Tool-cache lookup** — `@actions/tool-cache.find()`; on hit, skip everything else.
3. **Download** — `download.ts` fetches the release asset (authenticated when a token is present).
4. **Checksum** — `checksum.ts` fetches `task_checksums.txt`, parses it, and verifies SHA256.
5. **Extract + cache** — `install.ts` unpacks; `tc.cacheDir()` stores it.
6. **chmod + `addPath`** — make executable (non-Windows) and expose on `PATH`.
7. **Outputs** — `version`, `task-path`, `cache-hit`.

Key design seams to preserve when editing:

- **`ReleaseApi` interface (`version.ts`)** is the network seam. `version.ts` holds only pure, testable resolution logic; `github.ts` (`createReleaseApi`) is the **only** module that performs HTTP. Keep network access out of `version.ts` so its tests stay HTTP-free.
- **Reliability core (`github.ts` `fetchJson`)**: rejects non-JSON bodies as errors. This is the project's reason for existing — unauthenticated GitHub returns HTML rate-limit pages that broke `arduino/setup-task`. Treat that case as *transient* (retryable) and surface a message pointing at `repo-token`.
- **Retry vs. permanent (`download.ts` `withRetry` + `errors.ts` `PermanentError`)**: `withRetry` does exponential backoff, but `PermanentError` (and HTTP 404, including tool-cache's `httpStatusCode === 404`) bail out immediately with no retry. When adding a new failure mode, decide deliberately which side it falls on — checksum mismatches and 404s are permanent; everything network-ish is transient.
- **Platform mapping (`platform.ts`)**: maps Node `process.platform`/`process.arch` to go-task's asset naming (`task_<os>_<arch>.<ext>`) and validates against a hardcoded `SUPPORTED` matrix mirroring go-task's published assets. Update `SUPPORTED` if upstream adds/drops a target.
- **`constants.ts`** centralizes the upstream repo (`go-task/task`), the `task_checksums.txt` asset name, URL builders, and retry tuning.

## Conventions

- **ESM + Node built-ins**: `import * as fs from 'node:fs'`, `target: ES2022`, `module: ESNext`. Strict TS with `noUnusedLocals`/`noUnusedParameters`.
- **`FR-N` comments** (e.g. `(FR-5)`) reference numbered functional requirements in `要求仕様書.md`. Keep these traceable when touching the behavior they annotate.
- **Tests cover pure logic only** — `version`, `platform`, `checksum` parsing. Network/IO modules (`github`, `download`, `install`, `main`) are validated end-to-end by `.github/workflows/self-test.yml`, which runs the built action across a Linux/macOS/Windows × `latest`/`3.x`/`3.51.1` matrix and asserts `task --version` matches the resolved output.

## Reference docs

`企画書.md` (planning) and `要求仕様書.md` (requirements, source of the `FR-N` numbers) capture the design rationale, in Japanese.
21 changes: 21 additions & 0 deletions LICENSE
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
MIT License

Copyright (c) 2026 yk-lab

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
77 changes: 77 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# setup-task

A GitHub Action that installs the [Task](https://github.com/go-task/task) (go-task) binary and puts it on `PATH`.

Built as a modern, reliable alternative to `arduino/setup-task`:

- 🟢 **Node 24 runtime** — not affected by the Node 20 action-runtime deprecation
- 🔐 **Authenticated by default** — uses `${{ github.token }}` so release lookups don't hit unauthenticated rate limits (the cause of intermittent "could not download" failures)
- 🛡️ **Checksum-verified** — every download is checked against the release `task_checksums.txt` (SHA256)
- ♻️ **Cached** — uses the runner tool cache to avoid re-downloading
- 🔁 **Resilient** — retries transient network failures with exponential backoff
- 🧩 **Drop-in** — `version` / `repo-token` inputs are compatible with `arduino/setup-task`

## Usage

```yaml
- uses: yk-lab/setup-task@v1
with:
version: 3.x # optional; default: latest
- run: task --version
```

### Migrating from `arduino/setup-task`

Replace the `uses:` line — the common inputs are compatible:

```diff
- - uses: arduino/setup-task@v2
+ - uses: yk-lab/setup-task@v1
with:
version: 3.x
repo-token: ${{ secrets.GITHUB_TOKEN }}
```

## Inputs

| Name | Default | Description |
|---|---|---|
| `version` | `latest` | Version to install: exact (`3.51.1`), semver range (`3.x`, `^3.50`), or `latest`. |
| `repo-token` | `${{ github.token }}` | Token for authenticating GitHub API/asset requests. |
| `architecture` | runner's arch | Override the CPU architecture (e.g. `amd64`, `arm64`). |
| `check-latest` | `false` | For ranges, always re-resolve the newest matching release. |
| `skip-checksum` | `false` | Disable SHA256 verification (not recommended). |

> **Tip:** pin a range like `3.x` (or an exact version) rather than `latest` for reproducible CI.

## Outputs

| Name | Description |
|---|---|
| `version` | The resolved version installed (e.g. `3.51.1`). |
| `task-path` | Absolute path to the `task` executable. |
| `cache-hit` | `true` if restored from the tool cache, else `false`. |

## Supported platforms

| OS | Architectures |
|---|---|
| Linux | `386`, `amd64`, `arm`, `arm64`, `riscv64` |
| macOS | `amd64`, `arm64` |
| Windows | `386`, `amd64`, `arm64` |
| FreeBSD | `386`, `amd64`, `arm`, `arm64` |

## Development

```bash
npm install
npm run all # typecheck + lint + test + build (bundles dist/)
```

`dist/` is committed because GitHub Actions run the bundled `dist/index.js` directly. CI fails if it is out of date.

See [`企画書.md`](./企画書.md) and [`要求仕様書.md`](./要求仕様書.md) for the design rationale and requirements.

## License

[MIT](./LICENSE)
62 changes: 62 additions & 0 deletions TODO.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# 残タスク / Backlog

要求仕様書(`要求仕様書.md`)・企画書(`企画書.md`)と実装の差分。
分類は **マイルストーン(リリース単位)× 優先度ラベル(`P0..P3`)** の2軸。進捗トラッカーは
GitHub の各マイルストーンページ。`(FR/NFR/§)` は要求仕様書の参照。

実装済み: FR-1〜FR-10 と主要 NFR。未達は主に「仕様で必須のテスト(§10)」と「リリース運用(M3/M4)」。

---

## マイルストーン `v1.0.0`(要求仕様書 §11 DoD・期日 2026-09-15)

### Issue 化済み(詳細は各 Issue / 進捗は milestone を参照)

| # | 概要 | 優先度 | 参照 |
|---|---|---|---|
| [#1](https://github.com/yk-lab/setup-task/issues/1) | `[security]` `repo-token` を `core.setSecret` で秘匿 | `P1: high` | NFR-1 |
| [#2](https://github.com/yk-lab/setup-task/issues/2) | `[test]` `withRetry` のユニットテスト | `P1: high` | §10.1 |
| [#3](https://github.com/yk-lab/setup-task/issues/3) | `[test]` `fetchJson` content-type ガードのテスト | `P1: high` | §10.1 / 企画 §1.1-3 |
| [#4](https://github.com/yk-lab/setup-task/issues/4) | `[test]` checksum 改ざん注入の self-test | `P1: high` | §10.3 |
| [#5](https://github.com/yk-lab/setup-task/issues/5) | `[test]` cache-hit 経路の self-test | `P2: medium` | §10.2 |
| [#7](https://github.com/yk-lab/setup-task/issues/7) | `[bug]` レンジ指定時に tool-cache より先に GitHub 解決する | `P2: medium` | §6.1 / NFR-3 / Codex |

### 未 Issue 化(リリース運用 / M3・M4)

- **リリース自動化 + `v1` ムービングタグ + Marketplace 公開**(`release`)
タグ push 時に `dist/` を検証して Release 発行 → `v1` を追従 → Marketplace 掲載。DoD §11 必須。
- **ローカルコードを `yk-lab/setup-task` へ push(remote 設定)**(`chore`)
現状 remote 未設定・リモートは空。`git remote add origin … && git push` で CI/self-test が初回実行。

---

## マイルストーン `Backlog`(任意 / 将来・仕様上 optional)

### Issue 化済み

| # | 概要 | 優先度 | ラベル |
|---|---|---|---|
| [#8](https://github.com/yk-lab/setup-task/issues/8) | `[ci]` Codecov でカバレッジを PR 表示(ネイティブ機能は個人アカウント不可) | `P3: low` | `ci` `test` |
| [#9](https://github.com/yk-lab/setup-task/issues/9) | `[chore]` lefthook で pre-push に `npm run all`(stale dist 防止) | `P3: low` | `chore` `ci` |

### 未 Issue 化

- **フォールバックソース(FR-11)** — npm `@go-task/cli` 等の代替取得元。v1 必須ではない。
- **ジョブサマリ出力(NFR-5・任意)** — `core.summary` に解決版/取得元/cache/検証結果を記録。
- **リトライ回数の input 化(FR-4「必要なら」)** — `DEFAULT_RETRIES`/`DEFAULT_RETRY_BASE_MS`(`src/constants.ts`)を input 化。
- **`platform.test.ts` を §9 全 os/arch 組合せに拡張(§10.1)** — riscv64 は PR #6 で対応済み。残りの全組合せ網羅 + 各 OS の非対応 arch 拒否は未。
- **ダウンロード先ホスト/リダイレクト検証(NFR-1)** — 取得 URL を go-task 公式に固定し、リダイレクト先を検証。
- **Biome 評価(見送り中)** — フォーマッタ不在を埋める余地のみ。現状 ESLint は痛んでおらず優先度低。

---

## 参考: 実装済みで確認済みの要求

- FR-1 バージョン解決(exact/range/latest, `v` 正規化, prerelease 除外)— `src/version.ts`
- FR-2 プラットフォーム判定 + `architecture` 上書き — `src/platform.ts`
- FR-3 認証付き DL + content-type ガード — `src/download.ts` / `src/github.ts`
- FR-4 指数バックオフ + 恒久エラー即 fail — `src/download.ts` / `src/errors.ts`
- FR-5 SHA256 検証(既定 ON, BSD `*name` 形式対応)— `src/checksum.ts`
- FR-6〜FR-9 展開 / chmod / tool-cache / PATH / outputs — `src/main.ts` / `src/install.ts`
- FR-10 inputs(`arduino/setup-task` 互換)— `action.yml`
- NFR-3 exact 指定時の一覧取得スキップ — `src/version.ts`
Loading
Loading