diff --git a/.github/scripts/pr-gate-policy.test.mjs b/.github/scripts/pr-gate-policy.test.mjs index bf1d64b..8443f9b 100644 --- a/.github/scripts/pr-gate-policy.test.mjs +++ b/.github/scripts/pr-gate-policy.test.mjs @@ -100,12 +100,12 @@ test("stale runs are cancelled per pull request", async () => { ); }); -test("token-authored commits preserve the pull request context on manual dispatch", async () => { +test("token-authored commits normalize manual dispatch PR numbers for every reusable workflow", async () => { const entry = await readWorkflow(ENTRY_WORKFLOW); assert.match(entry, /^\s{2}workflow_dispatch:\n\s{4}inputs:\n\s{6}pull_request_number:/m); assert.equal( - (entry.match(/pull_request_number: \$\{\{ inputs\.pull_request_number \|\| github\.event\.pull_request\.number \|\| 0 \}\}/g) ?? []).length, + (entry.match(/pull_request_number: \$\{\{ fromJSON\(format\('\{0\}', inputs\.pull_request_number \|\| github\.event\.pull_request\.number \|\| 0\)\) \}\}/g) ?? []).length, VALIDATION_WORKFLOWS.length, ); }); @@ -118,7 +118,7 @@ test("validation without a pull request number falls back to the full suite", as const repositoryQuality = await readWorkflow("repository-quality.yml"); assert.equal( - (entry.match(/\|\| github\.event\.pull_request\.number \|\| 0 \}\}/g) ?? []).length, + (entry.match(/\|\| github\.event\.pull_request\.number \|\| 0\)\) \}\}/g) ?? []).length, VALIDATION_WORKFLOWS.length, ); for (const gate of ["Require linked Issue"]) { diff --git a/.github/workflows/pr-validation.yml b/.github/workflows/pr-validation.yml index 9716a7a..a638ccf 100644 --- a/.github/workflows/pr-validation.yml +++ b/.github/workflows/pr-validation.yml @@ -33,12 +33,15 @@ permissions: issues: read pull-requests: write +# API dispatch로 받은 번호는 문자열일 수 있다. reusable workflow의 number 계약에 +# 맞춰 명시적으로 변환한다. PR 이벤트의 숫자와 번호 없는 경우의 0도 같은 경로를 쓴다. +# https://docs.github.com/en/actions/reference/workflows-and-actions/expressions#fromjson jobs: repository-quality: name: Repository Quality uses: ./.github/workflows/repository-quality.yml with: - pull_request_number: ${{ inputs.pull_request_number || github.event.pull_request.number || 0 }} + pull_request_number: ${{ fromJSON(format('{0}', inputs.pull_request_number || github.event.pull_request.number || 0)) }} static-analysis: name: Static Analysis @@ -46,7 +49,7 @@ jobs: if: ${{ !cancelled() }} uses: ./.github/workflows/lint.yml with: - pull_request_number: ${{ inputs.pull_request_number || github.event.pull_request.number || 0 }} + pull_request_number: ${{ fromJSON(format('{0}', inputs.pull_request_number || github.event.pull_request.number || 0)) }} run_ktlint: ${{ needs.repository-quality.result != 'success' || needs.repository-quality.outputs.ktlint_required != 'false' }} ktlint_tasks: ${{ needs.repository-quality.result == 'success' && needs.repository-quality.outputs.ktlint_tasks || 'ktlintCheck' }} run_android_lint: ${{ needs.repository-quality.result != 'success' || needs.repository-quality.outputs.android_lint_required != 'false' }} @@ -59,7 +62,7 @@ jobs: if: ${{ !cancelled() }} uses: ./.github/workflows/unit-test.yml with: - pull_request_number: ${{ inputs.pull_request_number || github.event.pull_request.number || 0 }} + pull_request_number: ${{ fromJSON(format('{0}', inputs.pull_request_number || github.event.pull_request.number || 0)) }} run_validation: ${{ needs.repository-quality.result != 'success' || needs.repository-quality.outputs.unit_test_required != 'false' }} run_node_tests: ${{ needs.repository-quality.result != 'success' || needs.repository-quality.outputs.run_node_tests != 'false' }} gradle_tasks: ${{ needs.repository-quality.result == 'success' && needs.repository-quality.outputs.unit_test_tasks || 'testDebugUnitTest :app:compileDebugAndroidTestKotlin' }} @@ -70,7 +73,7 @@ jobs: if: ${{ !cancelled() }} uses: ./.github/workflows/screenshot.yml with: - pull_request_number: ${{ inputs.pull_request_number || github.event.pull_request.number || 0 }} + pull_request_number: ${{ fromJSON(format('{0}', inputs.pull_request_number || github.event.pull_request.number || 0)) }} run_validation: ${{ needs.repository-quality.result != 'success' || needs.repository-quality.outputs.screenshot_required != 'false' }} gradle_tasks: ${{ needs.repository-quality.result == 'success' && needs.repository-quality.outputs.screenshot_tasks || ':app:validateScreenshotTest' }} screenshot_modules: ${{ needs.repository-quality.result == 'success' && needs.repository-quality.outputs.screenshot_modules || ':app' }}