mirror of
https://github.com/Sea-Haven-Industries/.github.git
synced 2026-09-30 04:43:12 +00:00
feat(ci): add formatter presets to autofix (#147)
Callers can run prettier, eslint, ruff, and terraform without passing npm run lint -- --fix, which chains non-fixers in several apps.
This commit is contained in:
parent
fd41132410
commit
61f15d78b5
3 changed files with 125 additions and 21 deletions
134
.github/workflows/ci-autofix.yaml
vendored
134
.github/workflows/ci-autofix.yaml
vendored
|
|
@ -9,6 +9,12 @@ name: CI — Autofix
|
||||||
# head, then set output committed=true so the caller skips portions on SHA_old.
|
# head, then set output committed=true so the caller skips portions on SHA_old.
|
||||||
# Do not --no-verify. Do not push to main.
|
# Do not --no-verify. Do not push to main.
|
||||||
#
|
#
|
||||||
|
# Presets run first, then any format-command / lint-fix-command / extra-command.
|
||||||
|
# prettier npm ci + npm run format (requires package-lock.json)
|
||||||
|
# eslint npm ci + npx eslint . --fix (opt-in; do not call npm run lint)
|
||||||
|
# ruff ruff format . + ruff check --fix . (ruff 0.15.22)
|
||||||
|
# terraform terraform fmt -recursive in terraform-working-directory
|
||||||
|
#
|
||||||
# Caller example:
|
# Caller example:
|
||||||
# jobs:
|
# jobs:
|
||||||
# autofix:
|
# autofix:
|
||||||
|
|
@ -17,38 +23,52 @@ name: CI — Autofix
|
||||||
# permissions: { contents: write }
|
# permissions: { contents: write }
|
||||||
# secrets: inherit
|
# secrets: inherit
|
||||||
# with:
|
# with:
|
||||||
# format-command: npm run format
|
# presets: prettier,terraform
|
||||||
# lint-fix-command: npm run lint -- --fix
|
#
|
||||||
|
# Python callers pass presets: ruff,terraform. Add eslint only when that
|
||||||
|
# repo's CI lint step is ESLint itself and Prettier owns formatting.
|
||||||
|
# Do not pass `npm run lint -- --fix` (some apps chain Redocly into lint).
|
||||||
#
|
#
|
||||||
# Org secrets (names only): AUTOFMT_APP_ID, AUTOFMT_APP_PRIVATE_KEY.
|
# Org secrets (names only): AUTOFMT_APP_ID, AUTOFMT_APP_PRIVATE_KEY.
|
||||||
|
|
||||||
on:
|
on:
|
||||||
workflow_call:
|
workflow_call:
|
||||||
inputs:
|
inputs:
|
||||||
format-command:
|
presets:
|
||||||
description: "Write formatter command (e.g. npm run format, ruff format .)"
|
description: "Comma-separated presets: prettier, eslint, ruff, terraform"
|
||||||
type: string
|
type: string
|
||||||
required: true
|
required: false
|
||||||
|
default: ""
|
||||||
|
format-command:
|
||||||
|
description: "Optional write command run after presets (e.g. npm run format)"
|
||||||
|
type: string
|
||||||
|
required: false
|
||||||
|
default: ""
|
||||||
lint-fix-command:
|
lint-fix-command:
|
||||||
description: "Optional write lint-fix command (e.g. ruff check --fix .)"
|
description: "Optional write lint-fix command run after presets"
|
||||||
type: string
|
type: string
|
||||||
required: false
|
required: false
|
||||||
default: ""
|
default: ""
|
||||||
extra-command:
|
extra-command:
|
||||||
description: "Optional extra write command (e.g. terraform fmt -write)"
|
description: "Optional extra write command run after presets"
|
||||||
type: string
|
type: string
|
||||||
required: false
|
required: false
|
||||||
default: ""
|
default: ""
|
||||||
node-version:
|
node-version:
|
||||||
description: "Node.js version when package-lock.json is present"
|
description: "Node.js version for the prettier or eslint preset, or an npm command"
|
||||||
type: string
|
type: string
|
||||||
required: false
|
required: false
|
||||||
default: "24"
|
default: "24"
|
||||||
terraform-version:
|
terraform-version:
|
||||||
description: "Terraform version when extra-command mentions terraform"
|
description: "Terraform version for the terraform preset or an extra-command that runs terraform"
|
||||||
type: string
|
type: string
|
||||||
required: false
|
required: false
|
||||||
default: "1.16.0"
|
default: "1.16.0"
|
||||||
|
terraform-working-directory:
|
||||||
|
description: "Directory for the terraform preset (terraform fmt -recursive)"
|
||||||
|
type: string
|
||||||
|
required: false
|
||||||
|
default: "terraform"
|
||||||
outputs:
|
outputs:
|
||||||
committed:
|
committed:
|
||||||
description: "true when this job pushed a formatter commit"
|
description: "true when this job pushed a formatter commit"
|
||||||
|
|
@ -105,27 +125,93 @@ jobs:
|
||||||
ref: ${{ github.head_ref }}
|
ref: ${{ github.head_ref }}
|
||||||
persist-credentials: true
|
persist-credentials: true
|
||||||
|
|
||||||
|
- name: Resolve presets
|
||||||
|
id: presets
|
||||||
|
env:
|
||||||
|
SKIP_BOT: ${{ steps.skip-bot.outputs.skip }}
|
||||||
|
PRESETS: ${{ inputs.presets }}
|
||||||
|
FORMAT_COMMAND: ${{ inputs.format-command }}
|
||||||
|
LINT_FIX_COMMAND: ${{ inputs.lint-fix-command }}
|
||||||
|
EXTRA_COMMAND: ${{ inputs.extra-command }}
|
||||||
|
run: |
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
write_outputs() {
|
||||||
|
{
|
||||||
|
echo "prettier=$1"
|
||||||
|
echo "eslint=$2"
|
||||||
|
echo "ruff=$3"
|
||||||
|
echo "terraform=$4"
|
||||||
|
} >> "${GITHUB_OUTPUT}"
|
||||||
|
}
|
||||||
|
|
||||||
|
if [ "${SKIP_BOT}" = "true" ]; then
|
||||||
|
write_outputs false false false false
|
||||||
|
exit 0
|
||||||
|
fi
|
||||||
|
|
||||||
|
want_prettier=false
|
||||||
|
want_eslint=false
|
||||||
|
want_ruff=false
|
||||||
|
want_terraform=false
|
||||||
|
|
||||||
|
if [ -n "${PRESETS}" ]; then
|
||||||
|
IFS=',' read -ra parts <<< "${PRESETS}"
|
||||||
|
for raw in "${parts[@]}"; do
|
||||||
|
token=$(printf '%s' "${raw}" | tr -d '[:space:]')
|
||||||
|
case "${token}" in
|
||||||
|
"") ;;
|
||||||
|
prettier) want_prettier=true ;;
|
||||||
|
eslint) want_eslint=true ;;
|
||||||
|
ruff) want_ruff=true ;;
|
||||||
|
terraform) want_terraform=true ;;
|
||||||
|
*)
|
||||||
|
echo "Unknown preset: ${token}" >&2
|
||||||
|
exit 1
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
if { [ "${want_prettier}" = "true" ] || [ "${want_eslint}" = "true" ]; } && [ ! -f package-lock.json ]; then
|
||||||
|
echo "prettier and eslint presets require package-lock.json" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
if [ "${want_prettier}" = "false" ] \
|
||||||
|
&& [ "${want_eslint}" = "false" ] \
|
||||||
|
&& [ "${want_ruff}" = "false" ] \
|
||||||
|
&& [ "${want_terraform}" = "false" ] \
|
||||||
|
&& [ -z "${FORMAT_COMMAND}" ] \
|
||||||
|
&& [ -z "${LINT_FIX_COMMAND}" ] \
|
||||||
|
&& [ -z "${EXTRA_COMMAND}" ]; then
|
||||||
|
echo "Set presets or a format, lint-fix, or extra command." >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
write_outputs "${want_prettier}" "${want_eslint}" "${want_ruff}" "${want_terraform}"
|
||||||
|
|
||||||
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
|
||||||
if: ${{ steps.skip-bot.outputs.skip != 'true' && hashFiles('package-lock.json') != '' }}
|
if: ${{ steps.skip-bot.outputs.skip != 'true' && hashFiles('package-lock.json') != '' && (steps.presets.outputs.prettier == 'true' || steps.presets.outputs.eslint == 'true' || contains(inputs.format-command, 'npm') || contains(inputs.lint-fix-command, 'npm')) }}
|
||||||
with:
|
with:
|
||||||
node-version: ${{ inputs.node-version }}
|
node-version: ${{ inputs.node-version }}
|
||||||
cache: npm
|
cache: npm
|
||||||
|
|
||||||
- name: Install npm dependencies
|
- name: Install npm dependencies
|
||||||
if: ${{ steps.skip-bot.outputs.skip != 'true' && hashFiles('package-lock.json') != '' }}
|
if: ${{ steps.skip-bot.outputs.skip != 'true' && hashFiles('package-lock.json') != '' && (steps.presets.outputs.prettier == 'true' || steps.presets.outputs.eslint == 'true' || contains(inputs.format-command, 'npm') || contains(inputs.lint-fix-command, 'npm')) }}
|
||||||
run: npm ci
|
run: npm ci
|
||||||
|
|
||||||
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
|
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
|
||||||
if: ${{ steps.skip-bot.outputs.skip != 'true' && (contains(inputs.format-command, 'ruff') || contains(inputs.lint-fix-command, 'ruff')) }}
|
if: ${{ steps.skip-bot.outputs.skip != 'true' && (steps.presets.outputs.ruff == 'true' || contains(inputs.format-command, 'ruff') || contains(inputs.lint-fix-command, 'ruff') || contains(inputs.extra-command, 'ruff')) }}
|
||||||
with:
|
with:
|
||||||
python-version: "3.12"
|
python-version: "3.12"
|
||||||
|
|
||||||
- name: Install ruff
|
- name: Install ruff
|
||||||
if: ${{ steps.skip-bot.outputs.skip != 'true' && (contains(inputs.format-command, 'ruff') || contains(inputs.lint-fix-command, 'ruff')) }}
|
if: ${{ steps.skip-bot.outputs.skip != 'true' && (steps.presets.outputs.ruff == 'true' || contains(inputs.format-command, 'ruff') || contains(inputs.lint-fix-command, 'ruff') || contains(inputs.extra-command, 'ruff')) }}
|
||||||
run: pip install 'ruff==0.15.22'
|
run: pip install 'ruff==0.15.22'
|
||||||
|
|
||||||
- uses: hashicorp/setup-terraform@dfe3c3f87815947d99a8997f908cb6525fc44e9e # v4.0.1
|
- uses: hashicorp/setup-terraform@dfe3c3f87815947d99a8997f908cb6525fc44e9e # v4.0.1
|
||||||
if: ${{ steps.skip-bot.outputs.skip != 'true' && contains(inputs.extra-command, 'terraform') }}
|
if: ${{ steps.skip-bot.outputs.skip != 'true' && (steps.presets.outputs.terraform == 'true' || contains(inputs.extra-command, 'terraform')) }}
|
||||||
with:
|
with:
|
||||||
terraform_version: ${{ inputs.terraform-version }}
|
terraform_version: ${{ inputs.terraform-version }}
|
||||||
terraform_wrapper: false
|
terraform_wrapper: false
|
||||||
|
|
@ -133,12 +219,32 @@ jobs:
|
||||||
- name: Apply formatter
|
- name: Apply formatter
|
||||||
if: ${{ steps.skip-bot.outputs.skip != 'true' }}
|
if: ${{ steps.skip-bot.outputs.skip != 'true' }}
|
||||||
env:
|
env:
|
||||||
|
PRETTIER: ${{ steps.presets.outputs.prettier }}
|
||||||
|
ESLINT: ${{ steps.presets.outputs.eslint }}
|
||||||
|
RUFF: ${{ steps.presets.outputs.ruff }}
|
||||||
|
TERRAFORM: ${{ steps.presets.outputs.terraform }}
|
||||||
|
TERRAFORM_DIR: ${{ inputs.terraform-working-directory }}
|
||||||
FORMAT_COMMAND: ${{ inputs.format-command }}
|
FORMAT_COMMAND: ${{ inputs.format-command }}
|
||||||
LINT_FIX_COMMAND: ${{ inputs.lint-fix-command }}
|
LINT_FIX_COMMAND: ${{ inputs.lint-fix-command }}
|
||||||
EXTRA_COMMAND: ${{ inputs.extra-command }}
|
EXTRA_COMMAND: ${{ inputs.extra-command }}
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
if [ "${PRETTIER}" = "true" ]; then
|
||||||
|
npm run format
|
||||||
|
fi
|
||||||
|
if [ "${ESLINT}" = "true" ]; then
|
||||||
|
npx eslint . --fix
|
||||||
|
fi
|
||||||
|
if [ "${RUFF}" = "true" ]; then
|
||||||
|
ruff format .
|
||||||
|
ruff check --fix .
|
||||||
|
fi
|
||||||
|
if [ "${TERRAFORM}" = "true" ]; then
|
||||||
|
terraform -chdir="${TERRAFORM_DIR}" fmt -recursive
|
||||||
|
fi
|
||||||
|
if [ -n "${FORMAT_COMMAND}" ]; then
|
||||||
bash -euo pipefail -c "${FORMAT_COMMAND}"
|
bash -euo pipefail -c "${FORMAT_COMMAND}"
|
||||||
|
fi
|
||||||
if [ -n "${LINT_FIX_COMMAND}" ]; then
|
if [ -n "${LINT_FIX_COMMAND}" ]; then
|
||||||
bash -euo pipefail -c "${LINT_FIX_COMMAND}"
|
bash -euo pipefail -c "${LINT_FIX_COMMAND}"
|
||||||
fi
|
fi
|
||||||
|
|
|
||||||
|
|
@ -51,7 +51,7 @@ The formatter GitHub App is not on the main-branch bypass list.
|
||||||
|
|
||||||
**`.github/workflows/ci-terraform.yaml`** — Terraform `fmt -check`, `init -backend=false`, and `validate`. Default version `1.16.0`.
|
**`.github/workflows/ci-terraform.yaml`** — Terraform `fmt -check`, `init -backend=false`, and `validate`. Default version `1.16.0`.
|
||||||
|
|
||||||
**`.github/workflows/ci-autofix.yaml`** — Pull-request-only formatter. Mints a GitHub App token (`AUTOFMT_APP_ID`, `AUTOFMT_APP_PRIVATE_KEY`), runs the caller's write commands, and pushes `style: apply formatter` only when the tree is dirty. Output `committed` lets the caller skip portions on SHA_old. Does not `--no-verify` and does not push to `main`.
|
**`.github/workflows/ci-autofix.yaml`** — Pull-request-only formatter. Mints a GitHub App token (`AUTOFMT_APP_ID`, `AUTOFMT_APP_PRIVATE_KEY`), runs the requested presets and any optional write commands, and pushes `style: apply formatter` only when the tree is dirty. Presets are `prettier` (`npm run format`), `eslint` (`npx eslint . --fix`, opt-in), `ruff` (`ruff format .` and `ruff check --fix .`), and `terraform` (`terraform fmt -recursive` in `terraform-working-directory`, default `terraform`). Output `committed` lets the caller skip portions on SHA_old. Does not `--no-verify` and does not push to `main`.
|
||||||
|
|
||||||
**`.github/workflows/cd-hcp-fargate.yaml`** — HCP Fargate image CD. Checkout at `ref` (empty means `github.sha`), OIDC, SSM cluster/service/family/ecr/container/api-url, docker build+push tagged `$sha` and `$environment`, patch `GIT_SHA`, RegisterTaskDefinition + UpdateService + services-stable, poll health SHA. `environment` is a `with:` input. The reusable job owns `environment:`, concurrency, OIDC, and `vars.DEPLOY_ROLE_ARN`.
|
**`.github/workflows/cd-hcp-fargate.yaml`** — HCP Fargate image CD. Checkout at `ref` (empty means `github.sha`), OIDC, SSM cluster/service/family/ecr/container/api-url, docker build+push tagged `$sha` and `$environment`, patch `GIT_SHA`, RegisterTaskDefinition + UpdateService + services-stable, poll health SHA. `environment` is a `with:` input. The reusable job owns `environment:`, concurrency, OIDC, and `vars.DEPLOY_ROLE_ARN`.
|
||||||
|
|
||||||
|
|
@ -268,8 +268,7 @@ jobs:
|
||||||
permissions: { contents: write }
|
permissions: { contents: write }
|
||||||
secrets: inherit
|
secrets: inherit
|
||||||
with:
|
with:
|
||||||
format-command: npm run format
|
presets: prettier,terraform
|
||||||
lint-fix-command: npm run lint -- --fix
|
|
||||||
|
|
||||||
frontend:
|
frontend:
|
||||||
needs: autofix
|
needs: autofix
|
||||||
|
|
@ -304,7 +303,7 @@ jobs:
|
||||||
test "${TERRAFORM}" = success
|
test "${TERRAFORM}" = success
|
||||||
```
|
```
|
||||||
|
|
||||||
Python HCP callers pass `format-command: ruff format .` and `lint-fix-command: ruff check --fix .`. Optional `extra-command: terraform fmt -write` is available on `ci-autofix.yaml`. Do not run `eslint --fix` unless that repo's `lint` script is already fix-safe. Flip org ruleset membership in the same window as this merge: include on `CI complete`, exclude from `main branch protection`. Never require both `ci / ci` and `ci-complete`. Do not edit native GitHub merge-queue rulesets.
|
Python HCP callers pass `presets: ruff,terraform`. The `eslint` preset is opt-in and runs `npx eslint . --fix`. Do not pass `npm run lint -- --fix`: several apps chain Redocly into `lint`. Enable `eslint` only when that repo's CI lint step is ESLint itself and Prettier owns formatting. Optional `format-command`, `lint-fix-command`, and `extra-command` still run after the presets. Flip org ruleset membership in the same window as this merge: include on `CI complete`, exclude from `main branch protection`. Never require both `ci / ci` and `ci-complete`. Do not edit native GitHub merge-queue rulesets.
|
||||||
|
|
||||||
### 3. Add CD to a repo
|
### 3. Add CD to a repo
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -17,8 +17,7 @@ jobs:
|
||||||
contents: write
|
contents: write
|
||||||
secrets: inherit
|
secrets: inherit
|
||||||
with:
|
with:
|
||||||
format-command: npm run format
|
presets: prettier,terraform
|
||||||
lint-fix-command: "npm run lint -- --fix"
|
|
||||||
|
|
||||||
frontend:
|
frontend:
|
||||||
needs: autofix
|
needs: autofix
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue