From b1f49d5b4f2fce34190ef0f7c05f9c2b3b4eb115 Mon Sep 17 00:00:00 2001 From: leohara Date: Wed, 8 Apr 2026 14:12:44 +0900 Subject: [PATCH] chore(ci): add reusable GitHub Actions workflow template --- .github/scripts/run-ci-checks.sh | 53 ++++++++++++++++++++++++++++++++ .github/workflows/ci.yml | 40 ++++++++++++++++++++++++ README.md | 12 ++++++++ package.json | 2 +- 4 files changed, 106 insertions(+), 1 deletion(-) create mode 100644 .github/scripts/run-ci-checks.sh create mode 100644 .github/workflows/ci.yml diff --git a/.github/scripts/run-ci-checks.sh b/.github/scripts/run-ci-checks.sh new file mode 100644 index 0000000..7392eff --- /dev/null +++ b/.github/scripts/run-ci-checks.sh @@ -0,0 +1,53 @@ +#!/usr/bin/env bash + +set -euo pipefail + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)" +cd "$ROOT_DIR" + +if [[ ! -f package.json ]]; then + echo "package.json was not found in $ROOT_DIR" + exit 1 +fi + +choose_script() { + node - "$@" <<'NODE' +const fs = require('fs'); + +const pkg = JSON.parse(fs.readFileSync('package.json', 'utf8')); +const scripts = pkg.scripts ?? {}; +const candidates = process.argv.slice(2); + +const match = candidates.find((name) => + Object.prototype.hasOwnProperty.call(scripts, name), +); + +if (match) { + process.stdout.write(match); + process.exit(0); +} + +process.exit(1); +NODE +} + +run_check() { + local label="$1" + shift + + local selected_script + if selected_script="$(choose_script "$@")"; then + echo "::group::${label} (${selected_script})" + pnpm run "$selected_script" + echo "::endgroup::" + return 0 + fi + + echo "Skipping ${label}: no matching package.json script found (${*})" +} + +run_check "Lint" "lint:ci" "lint" +run_check "Format" "fmt:check" "format:check" "prettier:check" "check-format" +run_check "Type check" "type-check" "typecheck" "check-types" "type-check:ci" +run_check "Test" "test:ci" "test" +run_check "Build" "build:ci" "build" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..f884ae6 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,40 @@ +name: CI + +on: + pull_request: + push: + +permissions: + contents: read + +concurrency: + group: ci-${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + checks: + name: lint-format-typecheck-test-build + runs-on: ubuntu-latest + timeout-minutes: 20 + env: + CI: true + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup pnpm + uses: pnpm/action-setup@v4 + with: + run_install: false + + - name: Setup Node.js + uses: actions/setup-node@v4 + with: + node-version: 22 + cache: pnpm + + - name: Install dependencies + run: pnpm install --frozen-lockfile + + - name: Run repository checks + run: bash ./.github/scripts/run-ci-checks.sh diff --git a/README.md b/README.md index 8a40a8a..bd4da66 100644 --- a/README.md +++ b/README.md @@ -12,6 +12,18 @@ - `pnpm run test:e2e`: run end-to-end tests with `Playwright` - `pnpm run test:e2e:headed`: run `Playwright` in headed mode +## GitHub Actions + +- `.github/workflows/ci.yml` runs on `push` and `pull_request`. +- The workflow installs dependencies with `pnpm install --frozen-lockfile`. +- Repository checks are dispatched by `.github/scripts/run-ci-checks.sh`. +- Lint uses the first matching script: `lint:ci`, `lint`. +- Format uses the first matching script: `fmt:check`, `format:check`, `prettier:check`, `check-format`. +- Type checking uses the first matching script: `type-check`, `typecheck`, `check-types`, `type-check:ci`. +- Tests use the first matching script: `test:ci`, `test`. +- Build uses the first matching script: `build:ci`, `build`. +- Missing scripts are skipped, so the template can be adopted incrementally. + ## Commits - `Husky` runs a `commit-msg` hook that enforces Conventional Commits via `commitlint`. diff --git a/package.json b/package.json index dafd06d..5480e58 100644 --- a/package.json +++ b/package.json @@ -19,8 +19,8 @@ "devDependencies": { "@commitlint/cli": "^20.5.0", "@commitlint/config-conventional": "^20.5.0", - "@types/node": "^24.5.2", "@playwright/test": "^1.55.0", + "@types/node": "^24.5.2", "husky": "^9.1.7", "oxfmt": "^0.44.0", "oxlint": "^1.59.0",