From 2b6e8a884f2f593f7782896fbc868ed20ac0392c Mon Sep 17 00:00:00 2001 From: Tarpan Pathak Date: Thu, 21 May 2026 21:34:38 -0700 Subject: [PATCH 1/2] ci: lint workflows and composite actions on PRs Add a CI workflow that runs actionlint over the repo's own workflow files (release.yml, this ci.yml) and shellcheck over the composite actions' run: blocks (which actionlint does not inspect). Pins actions/checkout to a SHA and actionlint to v1.7.12. This is the gate we'll require on main once it lands. --- .github/workflows/ci.yml | 67 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 67 insertions(+) create mode 100644 .github/workflows/ci.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..64aeffc --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,67 @@ +name: CI + +# Lints this repository's own workflows and composite actions on every PR and +# push to main. actionlint covers the workflow files (and runs shellcheck on +# their run: blocks); a follow-up step runs shellcheck over the composite +# actions' run: blocks, which actionlint does not inspect. + +on: + pull_request: + push: + branches: [main] + +permissions: + contents: read + +jobs: + lint: + name: lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 + + - name: Install actionlint + run: bash <(curl -fsSL https://raw.githubusercontent.com/rhysd/actionlint/v1.7.12/scripts/download-actionlint.bash) 1.7.12 + + - name: Run actionlint (workflows) + run: ./actionlint -color + + - name: Shellcheck composite action run blocks (informational) + continue-on-error: true + run: | + python3 - <<'PY' + import glob, re, subprocess, tempfile, os, sys + try: + import yaml + except ImportError: + subprocess.run([sys.executable, "-m", "pip", "install", "-q", "pyyaml"], check=True) + import yaml + # GitHub expressions aren't valid shell; replace them with a token so + # shellcheck parses the script. Ignore SC2154 (env-injected vars) and + # SC2086 (intentional unquoted runner words like `npm run`). + EXPR = re.compile(r"\$\{\{.*?\}\}") + flagged = 0 + for path in sorted(glob.glob("actions/*/action.yml")): + with open(path) as fh: + doc = yaml.safe_load(fh) + steps = (doc.get("runs") or {}).get("steps") or [] + for i, st in enumerate(steps): + run = st.get("run") + if not run: + continue + script = "#!/bin/bash\n" + EXPR.sub("GHA_EXPR", run) + with tempfile.NamedTemporaryFile("w", suffix=".sh", delete=False) as tf: + tf.write(script) + fn = tf.name + res = subprocess.run( + ["shellcheck", "-S", "warning", "-e", "SC2154,SC2086", fn], + capture_output=True, text=True, + ) + if res.returncode != 0: + flagged += 1 + print(f"::group::{path} - step {i} ({st.get('name', '')})") + print(res.stdout) + print("::endgroup::") + os.unlink(fn) + print(f"shellcheck flagged {flagged} composite step(s) (informational)") + PY From 0585f41fc101294863e2308dc5f57605013f78e8 Mon Sep 17 00:00:00 2001 From: Tarpan Pathak Date: Thu, 21 May 2026 21:40:57 -0700 Subject: [PATCH 2/2] ci: rewrite Expose version step as if-form (shellcheck SC2015) actionlint flagged the '[ -f ] && echo || true' one-liner (SC2015: A && B || C is not if-then-else). Rewrite to an explicit if so the repo is lint-clean and the new lint gate can be made required. Behavior is unchanged. --- .github/workflows/release.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6d77103..fd354af 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -65,7 +65,8 @@ jobs: - name: Expose version id: version - run: '[ -f .version ] && echo "version=$(cat .version)" >> "$GITHUB_OUTPUT" || true' + run: | + if [ -f .version ]; then echo "version=$(cat .version)" >> "$GITHUB_OUTPUT"; fi - name: Commit changelog if: inputs.changelog