From d73eef60d839f16d609043c468e25071e3c9f0f1 Mon Sep 17 00:00:00 2001 From: hmmhmmhm Date: Tue, 6 Oct 2026 09:48:59 +0900 Subject: [PATCH] =?UTF-8?q?ci:=20=EB=A1=9C=EC=BB=AC=20=EB=9F=AC=EB=84=88?= =?UTF-8?q?=EC=99=80=20=EC=98=A4=ED=94=84=EB=9D=BC=EC=9D=B8=20=EC=A0=84?= =?UTF-8?q?=ED=99=98=20=EA=B5=AC=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/local-runner-smoke.yml | 56 +++++++++++++++ .github/workflows/publish.yml | 39 +++++++++++ .github/workflows/select-local-runner.yml | 84 +++++++++++++++++++++++ 3 files changed, 179 insertions(+) create mode 100644 .github/workflows/local-runner-smoke.yml create mode 100644 .github/workflows/select-local-runner.yml diff --git a/.github/workflows/local-runner-smoke.yml b/.github/workflows/local-runner-smoke.yml new file mode 100644 index 0000000..5337f43 --- /dev/null +++ b/.github/workflows/local-runner-smoke.yml @@ -0,0 +1,56 @@ +name: Local runner smoke + +on: + push: + branches: [local-runner-fallback] + paths: + - .github/workflows/select-local-runner.yml + - .github/workflows/local-runner-smoke.yml + workflow_dispatch: + inputs: + force-cloud: + description: Exercise the cloud runner without stopping the Mac service + type: boolean + default: false + probe-local: + description: Owner-only installation check that requires the Mac to be online + type: boolean + default: false + +permissions: + contents: read + +jobs: + select-runner: + uses: ./.github/workflows/select-local-runner.yml + with: + force-cloud: ${{ inputs.force-cloud || false }} + secrets: + status-token: ${{ secrets.LOCAL_RUNNER_STATUS_TOKEN }} + smoke: + needs: select-runner + runs-on: ${{ inputs.probe-local && github.actor == github.repository_owner && fromJSON('["self-hosted", "macOS", "ARM64", "personal-mac"]') || fromJSON(needs.select-runner.outputs.runner) }} + timeout-minutes: 10 + steps: + - name: Check selected runner + shell: bash + env: + RUNNER_REASON: ${{ needs.select-runner.outputs.reason }} + EXPECT_LOCAL: ${{ (inputs.probe-local && github.actor == github.repository_owner) || needs.select-runner.outputs.local == 'true' }} + RUNNER_ENVIRONMENT: ${{ runner.environment }} + ACTUAL_RUNNER_NAME: ${{ runner.name }} + ACTUAL_RUNNER_OS: ${{ runner.os }} + ACTUAL_RUNNER_ARCH: ${{ runner.arch }} + run: | + echo "Selection: $RUNNER_REASON" + echo "Runner: $ACTUAL_RUNNER_NAME ($ACTUAL_RUNNER_OS/$ACTUAL_RUNNER_ARCH)" + if [ "$EXPECT_LOCAL" = "true" ]; then + test "$RUNNER_ENVIRONMENT" = "self-hosted" + test "$ACTUAL_RUNNER_OS" = "macOS" + test "$ACTUAL_RUNNER_ARCH" = "ARM64" + else + test "$RUNNER_ENVIRONMENT" = "github-hosted" + fi + printf 'Selection: %s\nRunner: %s (%s/%s)\n' \ + "$RUNNER_REASON" "$ACTUAL_RUNNER_NAME" "$ACTUAL_RUNNER_OS" "$ACTUAL_RUNNER_ARCH" \ + >> "$GITHUB_STEP_SUMMARY" diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index cbbd64c..9f1be0b 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -11,7 +11,46 @@ permissions: id-token: write jobs: + select-runner: + uses: ./.github/workflows/select-local-runner.yml + with: + cloud-runner: ubuntu-latest + secrets: + status-token: ${{ secrets.LOCAL_RUNNER_STATUS_TOKEN }} + + verify: + needs: select-runner + runs-on: ${{ fromJSON(needs.select-runner.outputs.runner) }} + permissions: + contents: read + steps: + - uses: actions/checkout@v7 + + - uses: actions/setup-node@v6 + with: + node-version: "24" + registry-url: "https://registry.npmjs.org" + package-manager-cache: false + + - run: npm install -g npm@latest + + - run: corepack enable + + - run: pnpm install --frozen-lockfile + + - run: pnpm build + + - run: pnpm exec vitest run tests/cli.test.ts tests/readme.test.ts --testNamePattern "auto search|Google search|known challenge providers|README" + + - name: Check tag matches package version + if: startsWith(github.ref, 'refs/tags/v') + run: | + expected="v$(node -p 'require("./package.json").version')" + test "$expected" = "$GITHUB_REF_NAME" + + publish: + needs: verify runs-on: ubuntu-latest steps: diff --git a/.github/workflows/select-local-runner.yml b/.github/workflows/select-local-runner.yml new file mode 100644 index 0000000..a84727e --- /dev/null +++ b/.github/workflows/select-local-runner.yml @@ -0,0 +1,84 @@ +name: Select local runner + +on: + workflow_call: + inputs: + cloud-runner: + type: string + default: ubuntu-latest + force-cloud: + type: boolean + default: false + secrets: + status-token: + required: false + outputs: + runner: + value: ${{ jobs.select.outputs.runner }} + local: + value: ${{ jobs.select.outputs.local }} + reason: + value: ${{ jobs.select.outputs.reason }} + +permissions: + contents: read + +jobs: + select: + runs-on: ubuntu-latest + timeout-minutes: 5 + outputs: + runner: ${{ steps.runner.outputs.runner }} + local: ${{ steps.runner.outputs.local }} + reason: ${{ steps.runner.outputs.reason }} + steps: + - name: Select local Mac when online + id: runner + uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8 + env: + CLOUD_RUNNER: ${{ inputs.cloud-runner }} + FORCE_CLOUD: ${{ inputs.force-cloud }} + HAS_STATUS_TOKEN: ${{ secrets.status-token != '' }} + with: + github-token: ${{ secrets.status-token || github.token }} + script: | + const localLabels = ['self-hosted', 'macOS', 'ARM64', 'personal-mac']; + let labels = [process.env.CLOUD_RUNNER]; + let reason = 'cloud'; + const pr = context.payload.pull_request; + const trusted = context.actor === context.repo.owner && + (!pr || (pr.user.login === context.repo.owner && + pr.head.repo?.full_name === `${context.repo.owner}/${context.repo.repo}`)); + + if (process.env.FORCE_CLOUD === 'true') { + reason = 'forced-cloud'; + } else if (!trusted) { + reason = 'untrusted-event'; + } else if (process.env.HAS_STATUS_TOKEN !== 'true') { + reason = 'status-token-missing'; + core.warning('LOCAL_RUNNER_STATUS_TOKEN is missing; using GitHub-hosted runner.'); + } else { + try { + const runners = await github.paginate(github.rest.actions.listSelfHostedRunnersForRepo, { + ...context.repo, per_page: 100, + }); + const available = runners.some(runner => + runner.name === `personal-mac-${context.repo.repo}` && + runner.status === 'online' && + localLabels.every(label => runner.labels.some(item => item.name === label))); + if (available) { + labels = localLabels; + reason = 'local-online'; + } else { + reason = 'local-offline-or-absent'; + } + } catch (error) { + reason = 'status-api-unavailable'; + core.warning(`Runner status query failed (${error.status || 'network error'}); using GitHub-hosted runner.`); + } + } + + core.info(`Runner selection: ${reason} -> ${JSON.stringify(labels)}`); + core.setOutput('runner', JSON.stringify(labels)); + core.setOutput('local', String(reason === 'local-online')); + core.setOutput('reason', reason);