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
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,8 @@ Replace the `uses:` line — the common inputs are compatible:
| `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). |
| `retries` | `3` | Max retries for transient network failures (version resolve, download, checksum fetch). |
| `retry-base-ms` | `1000` | Initial backoff delay between retries, in milliseconds (doubles each attempt). |

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

Expand Down
2 changes: 1 addition & 1 deletion TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@
| # | 概要 | 優先度 | ラベル | 備考 |
|---|---|---|---|---|
| [#39](https://github.com/yk-lab/setup-task/issues/39) | `[enhancement]` ジョブサマリに導入結果を出力(NFR-5) | `P3: low` | `enhancement` |
| [#40](https://github.com/yk-lab/setup-task/issues/40) | `[enhancement]` リトライ回数・間隔を input 化(FR-4) | `P3: low` | `enhancement` |
| [#41](https://github.com/yk-lab/setup-task/issues/41) | `[test]` platform.test.ts を §9 全 os/arch 組合せに拡張 | `P3: low` | `test` |
| [#42](https://github.com/yk-lab/setup-task/issues/42) | `[security]` ダウンロード先ホスト/リダイレクト先を検証(NFR-1) | `P2: medium` | `security` |
| [#43](https://github.com/yk-lab/setup-task/issues/43) | `[chore]` checksum 改ざんテストの重複ファイルを統合 | `P3: low` | `chore` `test` |
Expand All @@ -77,3 +76,4 @@ v1.0.0 実装中に作成・解決済みの Issue(参考)。
| [#5](https://github.com/yk-lab/setup-task/issues/5) | `[test]` cache-hit 経路の self-test | #29 |
| [#7](https://github.com/yk-lab/setup-task/issues/7) | `[bug]` レンジ指定時に tool-cache より先に GitHub 解決する | #30 |
| [#22](https://github.com/yk-lab/setup-task/issues/22) | dist/ freshness 管理 | #36(main から dist/ を外し、リリース時ビルド方式へ) |
| [#40](https://github.com/yk-lab/setup-task/issues/40) | `[enhancement]` リトライ回数・間隔を input 化(FR-4) | #51 |
10 changes: 9 additions & 1 deletion action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -24,9 +24,17 @@ inputs:
required: false
default: "false"
skip-checksum:
description: "Disable SHA256 checksum verification of the downloaded archive (NOT recommended)."
description: "Disable SHA256 verification of the downloaded archive (NOT recommended)."
required: false
default: "false"
retries:
description: "Maximum number of retries for transient network failures."
required: false
default: "3"
retry-base-ms:
description: "Initial backoff delay between retries, in milliseconds."
required: false
default: "1000"

outputs:
version:
Expand Down
15 changes: 15 additions & 0 deletions src/inputs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import * as core from '@actions/core';

/** Parse a non-negative integer input, warning and falling back on invalid values. */
export function parseRetryInput(input: string, defaultValue: number, name: string): number {
const trimmed = input.trim();
if (!trimmed) {
return defaultValue;
}
const parsed = Number(trimmed);
if (!Number.isInteger(parsed) || parsed < 0) {
core.warning(`Invalid ${name} "${input}"; using default ${defaultValue}.`);
return defaultValue;
}
return parsed;
}
43 changes: 27 additions & 16 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,9 @@ import * as fs from 'node:fs';
import * as path from 'node:path';
import * as core from '@actions/core';
import * as tc from '@actions/tool-cache';
import { DEFAULT_RETRIES, TOOL_NAME, releaseDownloadUrl } from './constants';
import { DEFAULT_RETRIES, DEFAULT_RETRY_BASE_MS, TOOL_NAME, releaseDownloadUrl } from './constants';
import { downloadAsset, withRetry } from './download';
import { parseRetryInput } from './inputs';
import { fetchChecksum, verifyChecksum } from './checksum';
import { errorMessage } from './errors';
import { createReleaseApi } from './github';
Expand All @@ -17,6 +18,8 @@ async function run(): Promise<void> {
const archOverride = core.getInput('architecture');
const checkLatest = core.getBooleanInput('check-latest');
const skipChecksum = core.getBooleanInput('skip-checksum');
const retries = parseRetryInput(core.getInput('retries'), DEFAULT_RETRIES, 'retries');
const retryBaseMs = parseRetryInput(core.getInput('retry-base-ms'), DEFAULT_RETRY_BASE_MS, 'retry-base-ms');

// Summary state collected during the run and written at the end (NFR-5).
const summary = {
Expand Down Expand Up @@ -53,7 +56,8 @@ async function run(): Promise<void> {
core.info(`Using cached go-task ${version} satisfying "${versionSpec}" (skipped network resolution).`);
} else {
version = await withRetry(() => resolveVersion(api, versionSpec, checkLatest), {
retries: DEFAULT_RETRIES,
retries,
baseMs: retryBaseMs,
name: 'resolve version',
});
core.info(`Resolved go-task version: ${version}`);
Expand All @@ -74,7 +78,8 @@ async function run(): Promise<void> {
// 3. Download (authenticated + retry, FR-3/FR-4).
core.info(`Downloading ${url}`);
const archivePath = await withRetry(() => downloadAsset(url, token || undefined), {
retries: DEFAULT_RETRIES,
retries,
baseMs: retryBaseMs,
name: 'download asset',
});

Expand All @@ -84,7 +89,8 @@ async function run(): Promise<void> {
core.warning('Checksum verification skipped (skip-checksum=true).');
} else {
const expected = await withRetry(() => fetchChecksum(tag, asset.assetName, token || undefined), {
retries: DEFAULT_RETRIES,
retries,
baseMs: retryBaseMs,
name: 'fetch checksums',
});
if (!expected) {
Expand Down Expand Up @@ -122,18 +128,23 @@ async function run(): Promise<void> {
core.info(`task ${version} is ready at ${binPath}`);

// Emit a job summary after the fixed pipeline completes (NFR-5).
await core.summary
.addHeading('Setup Task')
.addTable([
[{ data: 'Item', header: true }, { data: 'Value', header: true }],
['Version', summary.version],
['Asset', summary.asset],
['Source', summary.source],
['Cache', summary.cache],
['Checksum', summary.checksum],
['Executable', summary.path],
])
.write();
// Best-effort: a summary write failure must not fail the action.
try {
await core.summary
.addHeading('Setup Task')
.addTable([
[{ data: 'Item', header: true }, { data: 'Value', header: true }],
['Version', summary.version],
['Asset', summary.asset],
['Source', summary.source],
['Cache', summary.cache],
['Checksum', summary.checksum],
['Executable', summary.path],
])
.write();
} catch (err) {
core.warning(`Failed to write job summary: ${errorMessage(err)}`);
}
}

run().catch((err: unknown) => {
Expand Down
23 changes: 23 additions & 0 deletions tests/inputs.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { describe, expect, it, vi } from 'vitest';
import { parseRetryInput } from '../src/inputs';

// @actions/core.warning writes to the Actions log; silence it in tests.
vi.mock('@actions/core', () => ({ warning: vi.fn() }));

describe('parseRetryInput', () => {
it('returns the default for empty input', () => {
expect(parseRetryInput('', 3, 'retries')).toBe(3);
expect(parseRetryInput(' ', 1000, 'retry-base-ms')).toBe(1000);
});

it('parses a valid integer string', () => {
expect(parseRetryInput('5', 3, 'retries')).toBe(5);
expect(parseRetryInput('250', 1000, 'retry-base-ms')).toBe(250);
});

it('returns the default for invalid input and warns', () => {
expect(parseRetryInput('not-a-number', 3, 'retries')).toBe(3);
expect(parseRetryInput('-1', 3, 'retries')).toBe(3);
expect(parseRetryInput('3.5', 3, 'retries')).toBe(3);
});
});
2 changes: 2 additions & 0 deletions 要求仕様書.md
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,8 @@
| `architecture` | 任意 | ランナー自動判定 | CPU アーキテクチャ上書き(FR-2) |
| `check-latest` | 任意 | `false` | range 指定時に常に最新合致版を再解決するか |
| `skip-checksum` | 任意 | `false` | チェックサム未取得時のみ継続を許可(FR-5、非推奨) |
| `retries` | 任意 | `3` | 一時的ネットワーク失敗時の最大リトライ回数(FR-4) |
| `retry-base-ms` | 任意 | `1000` | リトライ間の初期バックオフ待機(ミリ秒、試行ごとに倍化、FR-4) |

### 6.2 Outputs

Expand Down
Loading