From cffe1382ca75befc1927cdd073b20caaec7064ae Mon Sep 17 00:00:00 2001 From: Jonathan Zhang Date: Fri, 1 May 2026 09:53:09 -0700 Subject: [PATCH 1/3] chore(ci): make lint.yml drop-into-existing-repo friendly MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Two fixes for the install-lint.sh fanout caught by the first real-world target (whois-api-llc/wxa-jake-ai PR #181): 1. actionlint default: shellcheck OFF. actionlint's shellcheck integration defaults to severity=info, which means dropping the workflow into a repo with N existing `run:` blocks surfaces N info-level style warnings (SC2086 quote-variables etc.) and fails the install PR. Forcing every new repo to clean up shellcheck noise before they can ship a single PR is hostile. Real GHA bugs (typos in `uses:`, undefined steps refs, deprecated runners) are caught by actionlint's native checks — those still run. New input `run_shellcheck` (default false) lets normalized repos opt in. 2. prettier installs target deps when a package.json exists. Repos with custom prettier configs (e.g. wxa-jake-ai's prettier-plugin-svelte) need their plugins to resolve. We now run `npm ci` (or `npm install` if no lockfile) before prettier when package.json is present, then invoke the project's prettier with its plugins. Falls back to npx prettier@3 for markdown-only repos. New input `install_node_deps` (default true) for explicit control. Both inputs default to the "least surprise on first install" choice. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/lint.yml | 62 ++++++++++++++++++++++++++++++++++---- 1 file changed, 56 insertions(+), 6 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 0d032c1..2050c08 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -13,12 +13,19 @@ name: Lint # See callers/lint.yml in the templates dir for a copy-paste-ready caller. # # What it checks: -# - actionlint on every .github/workflows/*.yml (catches GHA-specific bugs + -# embedded shellcheck for `run:` blocks) +# - actionlint on every .github/workflows/*.yml (catches GHA-specific bugs) # - prettier --check on the markdown glob (default **/*.md) # -# Why no husky/lint-staged: this is meant to plug into repos with mixed -# tooling (or none). CI-only enforcement keeps the dependency surface flat. +# Defaults chosen for "drop into an existing repo without breaking it": +# - actionlint runs WITHOUT shellcheck. Shellcheck integration in actionlint +# defaults to severity=info, which surfaces stylistic suggestions +# (SC2086 "double quote variables", SC2034 "unused variable", etc.) on +# every existing `run:` block in the target repo. New repos rarely need +# to fix all of those before they can ship a single PR. Set +# `run_shellcheck: true` to opt in once the repo is normalized. +# - prettier auto-installs the target repo's deps if a package.json exists, +# so prettier plugins (prettier-plugin-svelte etc.) resolve correctly. +# Skip with `install_node_deps: false` for plain markdown-only repos. on: pull_request: @@ -41,6 +48,16 @@ on: required: false type: boolean default: true + run_shellcheck: + description: "Enable actionlint's shellcheck integration. Default false to avoid info-level style noise on first install." + required: false + type: boolean + default: false + install_node_deps: + description: "Run `npm ci` before prettier so plugins (prettier-plugin-svelte etc.) resolve. Default true. Skipped automatically when no package.json present." + required: false + type: boolean + default: true permissions: contents: read @@ -59,7 +76,16 @@ jobs: shell: bash - name: Run actionlint - run: ${{ steps.get_actionlint.outputs.executable }} -color + # `-shellcheck=` (empty) disables the shellcheck integration. We default + # to off because info-level shellcheck warnings (SC2086 etc.) are + # noise on most repos and would block the install PR until every + # existing `run:` block is normalized. + run: | + if [ "${{ inputs.run_shellcheck != true && 'true' || 'false' }}" = "true" ]; then + ${{ steps.get_actionlint.outputs.executable }} -color -shellcheck= + else + ${{ steps.get_actionlint.outputs.executable }} -color + fi shell: bash prettier: @@ -72,6 +98,30 @@ jobs: - uses: actions/setup-node@v4 with: node-version: "20" + cache: ${{ hashFiles('**/package-lock.json') != '' && 'npm' || '' }} + + # If the target repo has a prettier config that references plugins + # (e.g. prettier-plugin-svelte), prettier needs the project's + # node_modules to resolve them. Run `npm ci` (preferred, exact lockfile) + # or fall back to `npm install` if no lockfile. + - name: Install target repo's deps (for prettier plugins) + if: ${{ (github.event_name != 'workflow_call' || inputs.install_node_deps) && hashFiles('**/package.json') != '' }} + run: | + if [ -f package-lock.json ]; then + npm ci --ignore-scripts --no-audit --no-fund + else + npm install --ignore-scripts --no-audit --no-fund + fi + shell: bash - name: prettier --check ${{ inputs.markdown_glob || '**/*.md' }} - run: npx --yes prettier@3 --check "${{ inputs.markdown_glob || '**/*.md' }}" + # Use the project's prettier (with its plugins) when node_modules is + # populated; otherwise fall back to a one-off prettier@3 install. + run: | + GLOB="${{ inputs.markdown_glob || '**/*.md' }}" + if [ -x node_modules/.bin/prettier ]; then + node_modules/.bin/prettier --check "$GLOB" + else + npx --yes prettier@3 --check "$GLOB" + fi + shell: bash From 4fcdcafc4480c4a1e60eceb67d517856427d5efa Mon Sep 17 00:00:00 2001 From: Jonathan Zhang Date: Fri, 1 May 2026 10:03:40 -0700 Subject: [PATCH 2/3] fix(lint): correct skip-condition for reusable inputs MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `github.event_name` in a reusable workflow returns the caller's triggering event (e.g. 'pull_request'), NOT 'workflow_call'. So the previous condition if: ${{ github.event_name != 'workflow_call' || inputs.run_X }} was always true in both self-test AND reusable-call contexts — inputs.run_actionlint and inputs.run_prettier were silently ignored. Verified: wxa-jake-ai#181 set run_prettier: false but the prettier job still ran. Use `inputs == null` to distinguish self-test (no workflow_call ⇒ inputs is null) from reusable invocation. New condition: if: ${{ inputs == null || inputs.run_X }} - self-test (pull_request): inputs is null → first clause true → run - workflow_call run_X: false: inputs is object, run_X false → skip - workflow_call run_X: true: inputs is object, run_X true → run Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/lint.yml | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 2050c08..47aa166 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -65,7 +65,11 @@ permissions: jobs: actionlint: name: actionlint - if: ${{ github.event_name != 'workflow_call' || inputs.run_actionlint }} + # `inputs` is null for self-test (pull_request trigger) and an object for + # workflow_call invocations — that's the only reliable signal that + # distinguishes the two contexts (github.event_name in a reusable returns + # the caller's event, not 'workflow_call'). + if: ${{ inputs == null || inputs.run_actionlint }} runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -90,7 +94,7 @@ jobs: prettier: name: prettier (markdown) - if: ${{ github.event_name != 'workflow_call' || inputs.run_prettier }} + if: ${{ inputs == null || inputs.run_prettier }} runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 From f74f2fa1a93d16d431d39a474a07de665eb8d251 Mon Sep 17 00:00:00 2001 From: Jonathan Zhang Date: Fri, 1 May 2026 10:06:12 -0700 Subject: [PATCH 3/3] fix(lint): correct null check for self-test inputs context MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The previous fix used `inputs == null` to distinguish self-test (pull_request) from workflow_call. Wrong — GHA initializes the inputs context as `{}` (empty object) on non-workflow_call triggers, not null. So `inputs == null` was always false and both jobs got skipped on self-test. Use `inputs. == null` instead — that's null on self-test (no schema-default applied) and a real value on workflow_call (default-filled or caller-provided). Also corrected the run_shellcheck branch logic that was inverted in the previous commit, and applied the same null-check pattern to the nested install_node_deps condition in the prettier job. Co-Authored-By: Claude Opus 4.7 (1M context) --- .github/workflows/lint.yml | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 47aa166..5225f44 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -65,11 +65,12 @@ permissions: jobs: actionlint: name: actionlint - # `inputs` is null for self-test (pull_request trigger) and an object for - # workflow_call invocations — that's the only reliable signal that - # distinguishes the two contexts (github.event_name in a reusable returns - # the caller's event, not 'workflow_call'). - if: ${{ inputs == null || inputs.run_actionlint }} + # Skip-logic for the run_actionlint input. github.event_name returns the + # CALLER's event in a reusable workflow (not 'workflow_call'), and the + # `inputs` object is `{}` (not null) on non-workflow_call triggers — so + # the only reliable check is "input is null OR input is truthy". Null on + # self-test (no workflow_call), false only when caller explicitly opts out. + if: ${{ inputs.run_actionlint == null || inputs.run_actionlint }} runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -83,18 +84,20 @@ jobs: # `-shellcheck=` (empty) disables the shellcheck integration. We default # to off because info-level shellcheck warnings (SC2086 etc.) are # noise on most repos and would block the install PR until every - # existing `run:` block is normalized. + # existing `run:` block is normalized. inputs.run_shellcheck defaults + # to false, but on self-test the inputs object is `{}` (not null + # via the schema), so we check for `== true` to opt in explicitly. run: | - if [ "${{ inputs.run_shellcheck != true && 'true' || 'false' }}" = "true" ]; then - ${{ steps.get_actionlint.outputs.executable }} -color -shellcheck= - else + if [ "${{ inputs.run_shellcheck == true && 'true' || 'false' }}" = "true" ]; then ${{ steps.get_actionlint.outputs.executable }} -color + else + ${{ steps.get_actionlint.outputs.executable }} -color -shellcheck= fi shell: bash prettier: name: prettier (markdown) - if: ${{ inputs == null || inputs.run_prettier }} + if: ${{ inputs.run_prettier == null || inputs.run_prettier }} runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -109,7 +112,7 @@ jobs: # node_modules to resolve them. Run `npm ci` (preferred, exact lockfile) # or fall back to `npm install` if no lockfile. - name: Install target repo's deps (for prettier plugins) - if: ${{ (github.event_name != 'workflow_call' || inputs.install_node_deps) && hashFiles('**/package.json') != '' }} + if: ${{ (inputs.install_node_deps == null || inputs.install_node_deps) && hashFiles('**/package.json') != '' }} run: | if [ -f package-lock.json ]; then npm ci --ignore-scripts --no-audit --no-fund