From 38e03e8011bbd3724e215f23baae2b9728e31b8e Mon Sep 17 00:00:00 2001 From: James Prevett Date: Thu, 16 Jan 2025 21:25:52 -0600 Subject: [PATCH] Improve check runs --- .github/workflows/ci.yaml | 23 +++++++------------- package.json | 3 ++- scripts/ci-cli.js | 44 +++++++++++++++++++++++++++++++++++++++ scripts/ci.js | 28 ++++++++++++++++++++----- scripts/test.js | 6 +++--- tests/fetch/run.sh | 2 +- 6 files changed, 81 insertions(+), 25 deletions(-) create mode 100644 scripts/ci-cli.js diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 42311d6a..8f7db2bf 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -19,6 +19,8 @@ jobs: defaults: run: shell: bash + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: - name: Checkout uses: actions/checkout@v4 @@ -31,44 +33,35 @@ jobs: - name: Install dependencies run: npm install + - name: Create checks + run: npx zenfs-ci --create Formatting Build Linting "Unit tests (common)" "Unit tests (InMemory)" "Unit tests (contexts)" "Unit tests (Index)" "Unit tests (Port)" "Unit tests (Overlay+Fetch)" + - name: Formatting - run: npm run format:check + run: npx zenfs-ci Formatting -R "npm run format:check" - name: Build - run: npm run build + run: npx zenfs-ci Build -R "npm run build" - name: Linting - run: npm run lint + run: npx zenfs-ci Linting -R "npm run lint" - name: Unit tests (common) run: npx zenfs-test -pfw --common --ci "Unit tests (common)" - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Unit tests (InMemory) run: npx zenfs-test -pfw tests/setup/memory.ts --ci "Unit tests (InMemory)" - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Unit tests (contexts) run: npx zenfs-test -pfw tests/setup/context.ts --ci "Unit tests (contexts)" - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Unit tests (Index) run: npx zenfs-test -pfw tests/setup/index.ts --ci "Unit tests (Index)" - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Unit tests (Port) run: npx zenfs-test -pfw tests/setup/port.ts --ci "Unit tests (Port)" - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Unit tests (Overlay+Fetch) run: tests/fetch/run.sh -w --ci "Unit tests (Overlay+Fetch)" - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Report coverage run: npx zenfs-test --report diff --git a/package.json b/package.json index 05ca38df..5073f3fc 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,8 @@ ], "bin": { "make-index": "scripts/make-index.js", - "zenfs-test": "scripts/test.js" + "zenfs-test": "scripts/test.js", + "zenfs-ci": "scripts/ci-cli.js" }, "files": [ "dist", diff --git a/scripts/ci-cli.js b/scripts/ci-cli.js new file mode 100644 index 00000000..85e296e7 --- /dev/null +++ b/scripts/ci-cli.js @@ -0,0 +1,44 @@ +#!/usr/bin/env node +import { parseArgs } from 'node:util'; +import * as ci from './ci.js'; + +const { values: options, positionals } = parseArgs({ + options: { + create: { type: 'boolean', default: false }, + start: { type: 'boolean', default: false }, + complete: { type: 'string' }, + help: { short: 'h', type: 'boolean', default: false }, + exit: { short: 'e', type: 'boolean', default: false }, + }, + allowPositionals: true, +}); + +if (options.help) { + console.log(`Usage: zenfs-ci [options] + +Options: + -R, --run Run a command and use that for statuses. Implies --exit + --create Create check(s) in a queued state for all positional check names + --start Move check(s) from queued to in_progress + --complete Complete the check(s) with a conclusion: success, failure, etc. + -e, --exit Set the exit code based on the status of --complete + -h, --help Show this help message`); + process.exit(); +} + +for (const name of positionals) { + if (options.create) await ci.createCheck(name); + if (options.start) await ci.startCheck(name); + if (options.complete) await ci.completeCheck(name, options.complete); + if (options.complete && options.exit) process.exitCode = +(options.complete == 'failure'); + if (options.run) { + await ci.startCheck(name); + + const result = spawnSync(options.run, { shell: true, stdio: 'inherit' }); + const exitCode = result.status; + + await ci.completeCheck(name, exitCode ? 'failure' : 'success'); + + if (!exitCode) process.exitCode = exitCode; + } +} diff --git a/scripts/ci.js b/scripts/ci.js index 6fa61c35..931d9732 100644 --- a/scripts/ci.js +++ b/scripts/ci.js @@ -1,11 +1,16 @@ import { Octokit } from '@octokit/action'; +import { mkdirSync } from 'node:fs'; +import { join } from 'node:path/posix'; +import { JSONFileMap } from 'utilium/fs.js'; const octokit = new Octokit(); const [owner, repo] = process.env.GITHUB_REPOSITORY.split('/'); const head_sha = process.env.GITHUB_SHA; -let check_run_id; +mkdirSync(join(import.meta.dirname, '../tmp'), { recursive: true }); + +const runIDs = new JSONFileMap(join(import.meta.dirname, '../tmp/checks.json')); /** Create a new GitHub check run */ export async function createCheck(name) { @@ -14,19 +19,32 @@ export async function createCheck(name) { repo, name, head_sha, - status: 'in_progress', + status: 'queued', started_at: new Date().toISOString(), }); - check_run_id = response.data.id; + runIDs.set(name, response.data.id); +} + +/** + * Move an existing check run from "queued" to "in_progress". + */ +export async function startCheck(name) { + await octokit.request('PATCH /repos/{owner}/{repo}/check-runs/{check_run_id}', { + owner, + repo, + check_run_id: runIDs.get(name), + status: 'in_progress', + started_at: new Date().toISOString(), + }); } /** Complete a check run */ -export async function completeCheck(conclusion, title = '', summary = '') { +export async function completeCheck(name, conclusion, title = '', summary = '') { await octokit.request('PATCH /repos/{owner}/{repo}/check-runs/{check_run_id}', { owner, repo, - check_run_id, + check_run_id: runIDs.get(name), status: 'completed', completed_at: new Date().toISOString(), conclusion, diff --git a/scripts/test.js b/scripts/test.js index 76d74e7f..945c2e39 100755 --- a/scripts/test.js +++ b/scripts/test.js @@ -115,7 +115,7 @@ function color(text, code) { async function status(name) { const start = performance.now(); - if (options.ci) await ci.createCheck(options.ci); + if (options.ci) await ci.startCheck(options.ci); const time = () => { let delta = Math.round(performance.now() - start), @@ -132,11 +132,11 @@ async function status(name) { return { async pass() { if (!options.quiet) console.log(`${color('passed', 32)}: ${name} ${time()}`); - if (options.ci) await ci.completeCheck('success'); + if (options.ci) await ci.completeCheck(options.ci, 'success'); }, async fail() { console.error(`${color('failed', '1;31')}: ${name} ${time()}`); - if (options.ci) await ci.completeCheck('failure'); + if (options.ci) await ci.completeCheck(options.ci, 'failure'); process.exitCode = 1; if (options['exit-on-fail']) process.exit(); }, diff --git a/tests/fetch/run.sh b/tests/fetch/run.sh index e3222977..afa37dcd 100755 --- a/tests/fetch/run.sh +++ b/tests/fetch/run.sh @@ -11,6 +11,6 @@ until nc -z localhost 26514; do sleep 0.5 done -npx zenfs-test $SCRIPT_DIR/setup.ts --preserve --force $@ +npx zenfs-test $SCRIPT_DIR/setup.ts --preserve --force "$@" kill $PID