Skip to content

Commit af30f19

Browse files
Crash0v3rrid3claude
andcommitted
fix(DEVA11Y-657): use CLI 1.5.0 --pr-scan contract (fixes 'unknown option --head')
The action called a nonexistent `scan` subcommand with --head/--changed-files/ --output and then minted OIDC + POSTed itself. Published CLI 1.5.0 does the whole PR-scan via `--pr-scan`: resolves the base..head diff locally (git), gets linter-server to normalize + HMAC-sign, mints the Actions OIDC token, and submits to the ci-review-service (POST <ci-service-url>/v1/findings) which posts as the App. - scan step -> `--pr-scan --include '**/*' --base <sha> --head-sha <sha> --repo <owner/name> --pr-number <n> --ci-service-url https://devtools-a11y-linter.browserstack.com` (--head-sha, not --head; drop --changed-files/--output/--format — the CLI derives the diff and submits itself). fail-on-severity=none -> --non-strict. - Remove the now-redundant `oidc` and `submit` steps (the CLI does both). - `report` gates on the CLI exit code (0 pass; 1/3/4/other fail). Known 1.5.0 limits (documented inline): --pr-scan does not forward the comment/ inline/sarif/remediation options or return per-severity counts / comment URL — the ci-review-service applies defaults; those outputs need a CLI enhancement. NOTE: --ci-service-url uses the shared host, so POST /v1/findings must be routed to ci-review-service on devtools-a11y-linter.browserstack.com (currently only /github is routed) for the submit to land. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 56301a6 commit af30f19

1 file changed

Lines changed: 64 additions & 160 deletions

File tree

action.yml

Lines changed: 64 additions & 160 deletions
Original file line numberDiff line numberDiff line change
@@ -281,198 +281,102 @@ runs:
281281
node-version: '20'
282282

283283
# ---------------------------------------------------------------------------
284-
# 7. Run the PINNED published CLI in PR-scan mode over the diff.
285-
# IMPORTANT: the CLI version is pinned by THIS Action release (no @latest at
286-
# runtime — ADR-0035 §2, review #4). Update CLI_VERSION when cutting a new
287-
# Action release. Pinned to the published 1.5.0 (npmjs `latest`), which ships PR-scan mode.
288-
# We run the pinned package directly with npx --yes so no project install /
289-
# postinstall of the PR head executes.
284+
# 7. Run the pinned, published CLI in PR-scan mode.
285+
# In --pr-scan the CLI does the whole flow itself: resolves the base..head
286+
# diff LOCALLY (git), sends only the changed files' contents to linter-server
287+
# (which normalizes + HMAC-signs the findings as a scanProof), mints the
288+
# Actions OIDC token from ACTIONS_ID_TOKEN_REQUEST_* (needs id-token: write),
289+
# and submits to the ci-review-service (POST <ci-service-url>/v1/findings),
290+
# which verifies origin + content and posts AS THE APP. No project build /
291+
# postinstall of PR-head code ever runs.
292+
#
293+
# CLI_VERSION is pinned by THIS Action release (never @latest at runtime).
290294
# ---------------------------------------------------------------------------
291295
- id: scan
292296
if: steps.preflight.outputs.ready == 'true'
293297
shell: bash
294298
env:
295-
# SA creds — passed to the CLI, which authenticates over WSS to linter-server.
299+
# SA creds — the CLI authenticates over WSS to linter-server with these.
296300
BROWSERSTACK_USERNAME: ${{ inputs.username }}
297301
BROWSERSTACK_ACCESS_KEY: ${{ inputs.access-key }}
298-
CHANGED_FILES: ${{ steps.pr.outputs.changed-files }}
302+
# scanProof context: {repo, prNumber, headSha}. repo/pr-number are required
303+
# by --pr-scan; base is the diff baseline (CI checkouts can't infer it);
304+
# head-sha binds the proof to this PR head (defaults to git HEAD otherwise).
305+
REPO: ${{ github.repository }}
306+
PR_NUMBER: ${{ steps.pr.outputs.pr-number }}
299307
BASE_SHA: ${{ steps.pr.outputs.base-sha }}
300308
HEAD_SHA: ${{ steps.pr.outputs.head-sha }}
309+
FAIL_ON: ${{ inputs.fail-on-severity }}
310+
# ACTIONS_ID_TOKEN_REQUEST_URL/TOKEN are inherited from the job env
311+
# (permissions: id-token: write); the CLI reads them to mint the OIDC token.
301312
run: |
302313
set -euo pipefail
303-
# Pinned published CLI version (npmjs latest = 1.5.0; includes PR-scan mode). Do NOT use @latest.
314+
# Pinned published CLI (npmjs latest = 1.5.0; ships --pr-scan). Do NOT use @latest.
304315
CLI_VERSION="1.5.0"
305-
FINDINGS_OUT="${RUNNER_TEMP}/findings.json"
306316
307-
echo "Running @browserstack/accessibility-devtools@${CLI_VERSION} in PR-scan mode (diff scope)…"
308-
# --scope=diff (ADR-0022): scan only changed ranges over base…head.
309-
# The CLI reads file contents; it does NOT execute the project.
310-
npx --yes "@browserstack/accessibility-devtools@${CLI_VERSION}" scan \
311-
--scope=diff \
317+
# fail-on-severity=none -> --non-strict: report findings but never fail the
318+
# job. `error` is the CLI default (errors -> exit 1). NOTE: CLI 1.5.0 has no
319+
# separate "warning" gate, so `warning` currently behaves like `error`.
320+
NON_STRICT=()
321+
if [ "${FAIL_ON}" = "none" ]; then NON_STRICT=(--non-strict); fi
322+
323+
echo "Running @browserstack/accessibility-devtools@${CLI_VERSION} --pr-scan (diff scope)…"
324+
set +e
325+
npx --yes "@browserstack/accessibility-devtools@${CLI_VERSION}" \
326+
--pr-scan \
327+
--include '**/*' \
312328
--base "${BASE_SHA}" \
313-
--head "${HEAD_SHA}" \
314-
--changed-files "${CHANGED_FILES}" \
315-
--output "${FINDINGS_OUT}" \
316-
--format json
317-
318-
echo "findings-file=${FINDINGS_OUT}" >> "$GITHUB_OUTPUT"
329+
--head-sha "${HEAD_SHA}" \
330+
--repo "${REPO}" \
331+
--pr-number "${PR_NUMBER}" \
332+
--ci-service-url "https://devtools-a11y-linter.browserstack.com" \
333+
"${NON_STRICT[@]}"
334+
code=$?
335+
set -e
336+
echo "scan-exit=${code}" >> "$GITHUB_OUTPUT"
337+
echo "CLI exited ${code}"
319338
320339
# ---------------------------------------------------------------------------
321-
# 8. Request the Actions OIDC token with the DevA11y audience (ADR-0035 §5).
322-
# Proves "this request came from a real Actions run in repo X" — repo
323-
# granularity, no user resolved. Audience matches the deployed
324-
# ci-review-service verifier config.
325-
# ---------------------------------------------------------------------------
326-
- id: oidc
327-
if: steps.preflight.outputs.ready == 'true'
328-
shell: bash
329-
run: |
330-
set -euo pipefail
331-
# DevA11y OIDC audience — matches the ci-review-service verifier config (oidc.audience).
332-
AUDIENCE="browserstack-accessibility-devtools"
333-
token="$(curl -sf \
334-
-H "Authorization: bearer ${ACTIONS_ID_TOKEN_REQUEST_TOKEN}" \
335-
"${ACTIONS_ID_TOKEN_REQUEST_URL}&audience=${AUDIENCE}" | jq -r '.value')"
336-
if [ -z "$token" ] || [ "$token" = "null" ]; then
337-
echo "::error::Failed to mint the Actions OIDC token."
338-
exit 1
339-
fi
340-
# Mask so it never lands in logs.
341-
echo "::add-mask::$token"
342-
echo "token=$token" >> "$GITHUB_OUTPUT"
343-
344-
# ---------------------------------------------------------------------------
345-
# 9. Submit findings + scanProof + OIDC token to the ci-review-service.
346-
# The service verifies origin (OIDC) + content (scanProof) and posts AS THE
347-
# APP. The Action never posts to GitHub directly (ADR-0035 §3, §5).
348-
# The service base URL is fixed (global ingress), NOT a customer input.
349-
# ---------------------------------------------------------------------------
350-
- id: submit
351-
if: steps.preflight.outputs.ready == 'true'
352-
shell: bash
353-
env:
354-
OIDC_TOKEN: ${{ steps.oidc.outputs.token }}
355-
FINDINGS_FILE: ${{ steps.scan.outputs.findings-file }}
356-
REPO: ${{ github.repository }}
357-
PR_NUMBER: ${{ steps.pr.outputs.pr-number }}
358-
HEAD_SHA: ${{ steps.pr.outputs.head-sha }}
359-
OPT_COMMENT: ${{ inputs.comment }}
360-
OPT_CHECK_GATE: ${{ inputs.check-gate }}
361-
OPT_FAIL_ON: ${{ inputs.fail-on-severity }}
362-
OPT_INLINE: ${{ inputs.inline-suggestions }}
363-
OPT_SARIF: ${{ inputs.sarif }}
364-
OPT_COMMENT_MODE: ${{ inputs.comment-mode }}
365-
OPT_REMEDIATION: ${{ inputs.remediation }}
366-
OPT_AI_AGENT: ${{ inputs.ai-agent }}
367-
run: |
368-
set -euo pipefail
369-
# Service base URL (global ingress); server-side geo-routes per data-residency
370-
# region (ADR-0035 §7). NOT a customer input.
371-
SERVICE_URL="https://devtools-a11y-linter.browserstack.com"
372-
RESP_OUT="${RUNNER_TEMP}/service-response.json"
373-
374-
# The CLI emits { findings, scanProof } — findings are normalized and the
375-
# scanProof is HMAC-signed by linter-server (key never on the runner).
376-
findings="$(jq -c '.findings // .' "${FINDINGS_FILE}")"
377-
scan_proof="$(jq -r '.scanProof // ""' "${FINDINGS_FILE}")"
378-
379-
# Assemble the request body (ADR-0035 §5 wire shape).
380-
body="$(jq -cn \
381-
--arg repo "$REPO" \
382-
--argjson prNumber "$PR_NUMBER" \
383-
--arg headSha "$HEAD_SHA" \
384-
--argjson findings "$findings" \
385-
--arg scanProof "$scan_proof" \
386-
--arg oidcToken "$OIDC_TOKEN" \
387-
--arg comment "$OPT_COMMENT" \
388-
--arg checkGate "$OPT_CHECK_GATE" \
389-
--arg failOnSeverity "$OPT_FAIL_ON" \
390-
--arg inlineSuggestions "$OPT_INLINE" \
391-
--arg sarif "$OPT_SARIF" \
392-
--arg commentMode "$OPT_COMMENT_MODE" \
393-
--arg remediation "$OPT_REMEDIATION" \
394-
--arg aiAgent "$OPT_AI_AGENT" \
395-
'{
396-
platform: "github",
397-
repo: $repo,
398-
prNumber: $prNumber,
399-
headSha: $headSha,
400-
findings: $findings,
401-
scanProof: $scanProof,
402-
oidcToken: $oidcToken,
403-
options: {
404-
comment: ($comment == "true"),
405-
checkGate: ($checkGate == "true"),
406-
failOnSeverity: $failOnSeverity,
407-
inlineSuggestions: ($inlineSuggestions == "true"),
408-
sarif: ($sarif == "true"),
409-
commentMode: $commentMode,
410-
remediation: ($remediation == "true"),
411-
aiAgent: $aiAgent
412-
}
413-
}')"
414-
415-
http_code="$(curl -sS -o "${RESP_OUT}" -w '%{http_code}' \
416-
-X POST "${SERVICE_URL}/v1/findings" \
417-
-H "Content-Type: application/json" \
418-
-H "X-CI-Platform: github" \
419-
--data-binary "${body}")"
420-
421-
echo "ci-review-service responded HTTP ${http_code}"
422-
if [ "$http_code" -lt 200 ] || [ "$http_code" -ge 300 ]; then
423-
echo "::error::ci-review-service rejected the submission (HTTP ${http_code})."
424-
cat "${RESP_OUT}" || true
425-
exit 1
426-
fi
427-
echo "response-file=${RESP_OUT}" >> "$GITHUB_OUTPUT"
428-
429-
# ---------------------------------------------------------------------------
430-
# 10. Report: surface outputs from the service response and apply the gate.
340+
# 8. Report + gate. The CLI already submitted the findings and the App posts
341+
# them; here we only translate the CLI exit code into the job result and the
342+
# optional merge gate. (CLI 1.5.0 --pr-scan does not return per-severity
343+
# counts / comment URL to the runner, nor forward the comment/inline/sarif/
344+
# remediation options — the ci-review-service applies its defaults. Surfacing
345+
# those needs a CLI enhancement.)
431346
# ---------------------------------------------------------------------------
432347
- id: report
433348
if: always()
434349
shell: bash
435350
env:
436-
RESP_FILE: ${{ steps.submit.outputs.response-file }}
351+
SCAN_EXIT: ${{ steps.scan.outputs.scan-exit }}
437352
PROCEED: ${{ steps.guard.outputs.proceed }}
438353
ALLOWED: ${{ steps.permission.outputs.allowed }}
439354
READY: ${{ steps.preflight.outputs.ready }}
440355
OPT_CHECK_GATE: ${{ inputs.check-gate }}
441356
run: |
442357
set -euo pipefail
443-
444-
# Default (skipped / not-run) outputs.
445-
result="pass"; error_count="0"; warning_count="0"; findings_count="0"
446-
comment_url=""; sarif_file=""
447-
358+
result="pass"
448359
if [ "${PROCEED}" != "true" ] || [ "${ALLOWED:-false}" != "true" ] || [ "${READY:-false}" != "true" ]; then
449360
echo "::notice::Scan did not run (guard/permission/preflight skip); reporting a neutral pass."
450-
elif [ -n "${RESP_FILE:-}" ] && [ -f "${RESP_FILE}" ]; then
451-
result="$(jq -r '.result // "pass"' "${RESP_FILE}")"
452-
error_count="$(jq -r '.errorCount // 0' "${RESP_FILE}")"
453-
warning_count="$(jq -r '.warningCount // 0' "${RESP_FILE}")"
454-
findings_count="$(jq -r '.findingsCount // 0' "${RESP_FILE}")"
455-
comment_url="$(jq -r '.commentUrl // ""' "${RESP_FILE}")"
456-
sarif_file="$(jq -r '.sarifFile // ""' "${RESP_FILE}")"
457361
else
458-
echo "::warning::No service response available; reporting fail."
459-
result="fail"
362+
case "${SCAN_EXIT:-2}" in
363+
0) result="pass" ;;
364+
1) result="fail"; echo "::warning::Accessibility errors found." ;;
365+
3) result="fail"; echo "::error::Fair-Usage wait budget exhausted (rate limited)." ;;
366+
4) result="fail"; echo "::error::Permission denied by the server for this account/commenter." ;;
367+
*) result="fail"; echo "::error::Scan failed (CLI exit ${SCAN_EXIT:-unknown})." ;;
368+
esac
460369
fi
461-
462370
{
463-
echo "result=$result"
464-
echo "error-count=$error_count"
465-
echo "warning-count=$warning_count"
466-
echo "findings-count=$findings_count"
467-
echo "comment-url=$comment_url"
468-
echo "sarif-file=$sarif_file"
371+
echo "result=${result}"
372+
echo "error-count="
373+
echo "warning-count="
374+
echo "findings-count="
375+
echo "comment-url="
376+
echo "sarif-file="
469377
} >> "$GITHUB_OUTPUT"
470-
471-
echo "Result: ${result} (errors=${error_count}, warnings=${warning_count}, total=${findings_count})"
472-
[ -n "$comment_url" ] && echo "Comment: ${comment_url}" || true
473-
474-
# Gate: fail the job when check-gate is on and the service verdict is fail.
475-
if [ "${OPT_CHECK_GATE}" = "true" ] && [ "$result" = "fail" ]; then
476-
echo "::error::Accessibility check failed (fail-on-severity gate)."
378+
echo "Result: ${result} (CLI exit ${SCAN_EXIT:-n/a})"
379+
if [ "${OPT_CHECK_GATE}" = "true" ] && [ "${result}" = "fail" ]; then
380+
echo "::error::Accessibility check failed (check-gate)."
477381
exit 1
478382
fi

0 commit comments

Comments
 (0)