fix(scripts): report startup help stream errors when pipes fail (#102… #108
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Workflow Sanity | |
| on: | |
| pull_request: | |
| paths-ignore: | |
| - "CHANGELOG.md" | |
| push: | |
| branches: [main] | |
| paths-ignore: | |
| - "CHANGELOG.md" | |
| workflow_dispatch: | |
| permissions: | |
| contents: read | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.ref }} | |
| cancel-in-progress: true | |
| env: | |
| FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: "true" | |
| jobs: | |
| no-tabs: | |
| if: github.event_name != 'workflow_dispatch' | |
| runs-on: ubuntu-24.04 | |
| steps: | |
| - &workflow_sanity_checkout_step | |
| name: Checkout | |
| env: | |
| CHECKOUT_REPO: ${{ github.repository }} | |
| CHECKOUT_SHA: ${{ github.sha }} | |
| run: | | |
| set -euo pipefail | |
| git init "$GITHUB_WORKSPACE" | |
| git -C "$GITHUB_WORKSPACE" config gc.auto 0 | |
| git -C "$GITHUB_WORKSPACE" remote add origin "https://github.com/${CHECKOUT_REPO}.git" | |
| fetch_checkout_ref() { | |
| local fetch_status | |
| for attempt in 1 2 3; do | |
| timeout --signal=TERM --kill-after=10s 30s git -C "$GITHUB_WORKSPACE" \ | |
| -c protocol.version=2 \ | |
| fetch --no-tags --prune --no-recurse-submodules --depth=1 origin \ | |
| "+${CHECKOUT_SHA}:refs/remotes/origin/checkout" && return 0 | |
| fetch_status="$?" | |
| if [ "$fetch_status" != "124" ] && [ "$fetch_status" != "137" ]; then | |
| return "$fetch_status" | |
| fi | |
| if [ "$attempt" = "3" ]; then | |
| return "$fetch_status" | |
| fi | |
| echo "::warning::checkout fetch for '$CHECKOUT_SHA' timed out on attempt $attempt; retrying" | |
| sleep 5 | |
| done | |
| } | |
| fetch_checkout_ref | |
| git -C "$GITHUB_WORKSPACE" checkout --detach refs/remotes/origin/checkout | |
| - name: Fail on tabs in workflow files | |
| run: | | |
| python - <<'PY' | |
| from __future__ import annotations | |
| import pathlib | |
| import sys | |
| root = pathlib.Path(".github/workflows") | |
| bad: list[str] = [] | |
| for path in sorted(root.rglob("*.yml")): | |
| if b"\t" in path.read_bytes(): | |
| bad.append(str(path)) | |
| for path in sorted(root.rglob("*.yaml")): | |
| if b"\t" in path.read_bytes(): | |
| bad.append(str(path)) | |
| if bad: | |
| print("Tabs found in workflow file(s):") | |
| for path in bad: | |
| print(f"- {path}") | |
| sys.exit(1) | |
| PY | |
| actionlint: | |
| if: github.event_name != 'workflow_dispatch' | |
| runs-on: ubuntu-24.04 | |
| steps: | |
| - *workflow_sanity_checkout_step | |
| - name: Setup Python | |
| uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6 | |
| with: | |
| python-version: "3.12" | |
| - name: Prepare trusted workflow audit configs | |
| if: github.event_name == 'pull_request' | |
| env: | |
| BASE_REF: ${{ github.event.pull_request.base.ref }} | |
| BASE_SHA: ${{ github.event.pull_request.base.sha }} | |
| run: | | |
| set -euo pipefail | |
| trusted_config="$RUNNER_TEMP/pre-commit-base.yaml" | |
| trusted_zizmor_config="$RUNNER_TEMP/zizmor-base.yml" | |
| fetch_base_ref() { | |
| local ref="$1" | |
| local target="$2" | |
| local fetch_status | |
| for attempt in 1 2 3; do | |
| timeout --signal=TERM --kill-after=10s 30s git fetch --no-tags --depth=1 origin \ | |
| "+${ref}:${target}" && return 0 | |
| fetch_status="$?" | |
| if [ "$fetch_status" != "124" ] && [ "$fetch_status" != "137" ]; then | |
| return "$fetch_status" | |
| fi | |
| if [ "$attempt" = "3" ]; then | |
| return "$fetch_status" | |
| fi | |
| echo "::warning::trusted base fetch for '$ref' timed out on attempt $attempt; retrying" | |
| sleep 5 | |
| done | |
| } | |
| if ! git cat-file -e "${BASE_SHA}^{commit}" 2>/dev/null; then | |
| fetch_base_ref "$BASE_SHA" "refs/remotes/origin/security-base" || | |
| fetch_base_ref "refs/heads/${BASE_REF}" "refs/remotes/origin/${BASE_REF}" | |
| fi | |
| if git cat-file -e "${BASE_SHA}:.pre-commit-config.yaml" 2>/dev/null; then | |
| git show "${BASE_SHA}:.pre-commit-config.yaml" > "$trusted_config" | |
| elif git show "refs/remotes/origin/${BASE_REF}:.pre-commit-config.yaml" \ | |
| > "$trusted_config" 2>/dev/null; then | |
| echo "Base SHA ${BASE_SHA} does not expose .pre-commit-config.yaml; using origin/${BASE_REF} instead." | |
| else | |
| echo "::error title=trusted pre-commit config unavailable::Could not read .pre-commit-config.yaml from ${BASE_SHA} or origin/${BASE_REF}." | |
| exit 1 | |
| fi | |
| if git cat-file -e "${BASE_SHA}:.github/zizmor.yml" 2>/dev/null; then | |
| git show "${BASE_SHA}:.github/zizmor.yml" > "$trusted_zizmor_config" | |
| elif git show "refs/remotes/origin/${BASE_REF}:.github/zizmor.yml" \ | |
| > "$trusted_zizmor_config" 2>/dev/null; then | |
| echo "Base SHA ${BASE_SHA} does not expose .github/zizmor.yml; using origin/${BASE_REF} instead." | |
| else | |
| echo "::error title=trusted zizmor config unavailable::Could not read .github/zizmor.yml from ${BASE_SHA} or origin/${BASE_REF}." | |
| exit 1 | |
| fi | |
| python3 - "$trusted_config" "$trusted_zizmor_config" <<'PY' | |
| from pathlib import Path | |
| import sys | |
| config_path = Path(sys.argv[1]) | |
| zizmor_config_path = sys.argv[2] | |
| text = config_path.read_text() | |
| if ".github/zizmor.yml" not in text: | |
| raise SystemExit("trusted pre-commit config does not reference .github/zizmor.yml") | |
| config_path.write_text(text.replace(".github/zizmor.yml", zizmor_config_path)) | |
| PY | |
| echo "PRE_COMMIT_CONFIG_PATH=$trusted_config" >> "$GITHUB_ENV" | |
| - name: Install pre-commit | |
| run: python -m pip install --disable-pip-version-check pre-commit==4.2.0 | |
| - name: Install actionlint | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| ACTIONLINT_VERSION="1.7.11" | |
| archive="actionlint_${ACTIONLINT_VERSION}_linux_amd64.tar.gz" | |
| base_url="https://github.com/rhysd/actionlint/releases/download/v${ACTIONLINT_VERSION}" | |
| # GitHub release downloads occasionally return transient 5xx responses. | |
| # Retry all curl errors here so workflow-sanity does not fail closed on | |
| # a one-off release edge outage. | |
| curl --retry 5 --retry-delay 2 --retry-all-errors -sSfL -o "${archive}" "${base_url}/${archive}" | |
| curl --retry 5 --retry-delay 2 --retry-all-errors -sSfL -o checksums.txt "${base_url}/actionlint_${ACTIONLINT_VERSION}_checksums.txt" | |
| grep " ${archive}\$" checksums.txt | sha256sum -c - | |
| tar -xzf "${archive}" actionlint | |
| sudo install -m 0755 actionlint /usr/local/bin/actionlint | |
| - name: Lint workflows | |
| run: actionlint | |
| - name: Audit all workflows with zizmor | |
| shell: bash | |
| run: | | |
| set -euo pipefail | |
| mapfile -t workflow_files < <( | |
| find .github/workflows -maxdepth 1 -type f \( -name '*.yml' -o -name '*.yaml' \) | sort | |
| ) | |
| pre-commit run --config "${PRE_COMMIT_CONFIG_PATH:-.pre-commit-config.yaml}" zizmor --files "${workflow_files[@]}" | |
| - name: Disallow direct inputs interpolation in composite run blocks | |
| run: python3 scripts/check-composite-action-input-interpolation.py | |
| - name: Disallow tracked merge conflict markers | |
| run: node scripts/check-no-conflict-markers.mjs | |
| generated-doc-baselines: | |
| if: github.event_name == 'workflow_dispatch' | |
| runs-on: ubuntu-24.04 | |
| steps: | |
| - *workflow_sanity_checkout_step | |
| - name: Setup Node environment | |
| uses: ./.github/actions/setup-node-env | |
| with: | |
| install-bun: "false" | |
| - name: Check config docs drift statefile | |
| run: pnpm config:docs:check | |
| - name: Check plugin SDK API baseline drift | |
| run: pnpm plugin-sdk:api:check | |
| - name: Check plugin SDK surface budget | |
| run: pnpm plugin-sdk:surface:check |