Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
61 changes: 3 additions & 58 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,64 +39,9 @@ jobs:

- run: yarn install --immutable

- name: Get changed files
id: changed
run: |
files=$(git diff --name-only --diff-filter=ACMR origin/main...HEAD | tr '\n' ' ')
echo "files=$files" >> "$GITHUB_OUTPUT"
lint_files=$(git diff --name-only --diff-filter=ACMR origin/main...HEAD \
| grep -E '\.(ts|js|mts|mjs|tsx|jsx)$' | tr '\n' ' ')
echo "lint_files=$lint_files" >> "$GITHUB_OUTPUT"

- name: Get changed codemods
id: codemods
run: |
candidates=$(git diff --name-only origin/main...HEAD -- 'codemods/' \
| awk -F/ 'NF>=3 {print $1"/"$2"/"$3}' | sort -u)
dirs=""
for d in $candidates; do
[ -f "$d/package.json" ] && dirs="$dirs $d"
done
echo "dirs=$(echo $dirs | xargs)" >> "$GITHUB_OUTPUT"

- name: Format check (changed files)
if: steps.changed.outputs.files != ''
run: yarn format:check ${{ steps.changed.outputs.files }}

- name: Lint (changed files)
if: steps.changed.outputs.lint_files != ''
run: yarn lint ${{ steps.changed.outputs.lint_files }}

- name: Package name length check
if: steps.codemods.outputs.dirs != ''
run: |
failed=0
for dir in ${{ steps.codemods.outputs.dirs }}; do
name=$(node -p "require('./$dir/package.json').name")
len=${#name}
if [ "$len" -gt 50 ]; then
echo "::error::Package name '$name' is $len chars (max 50 for Codemod registry)"
failed=1
fi
done
[ "$failed" -eq 0 ] || exit 1

- name: Test (changed codemods)
if: steps.codemods.outputs.dirs != ''
run: |
for dir in ${{ steps.codemods.outputs.dirs }}; do
(cd "$dir" && yarn test)
done

- name: README freshness check
if: steps.codemods.outputs.dirs != ''
run: |
yarn readme
if ! git diff --quiet README.md; then
echo "::error::README.md is out of date. Run 'yarn readme' and commit the result."
git diff README.md
exit 1
fi
# Shared with local husky pre-push (scripts/check-changed.sh) so gates cannot drift.
- name: Check changed files
run: bash scripts/check-changed.sh origin/main

changeset-check:
name: Changeset Check
Expand Down
9 changes: 8 additions & 1 deletion .husky/pre-commit
100644 → 100755
Original file line number Diff line number Diff line change
@@ -1 +1,8 @@
npx lint-staged
#!/usr/bin/env sh
set -e
# Fail loud if deps aren't installed — silent hook skips are how CI-only failures sneak in.
if [ ! -x node_modules/.bin/lint-staged ]; then
echo "error: lint-staged not found. Run 'yarn install' in this worktree before committing." >&2
exit 1
fi
yarn lint-staged
12 changes: 12 additions & 0 deletions .husky/pre-push
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
#!/usr/bin/env sh
set -e
# Same gates as CI (.github/workflows/ci.yml → scripts/check-changed.sh).
if [ ! -f scripts/check-changed.sh ]; then
echo "error: scripts/check-changed.sh missing." >&2
exit 1
fi
if [ ! -d node_modules ]; then
echo "error: node_modules missing. Run 'yarn install' in this worktree before pushing." >&2
exit 1
fi
bash scripts/check-changed.sh origin/main
7 changes: 4 additions & 3 deletions .lintstagedrc.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
{
"*.{ts,js,mts,mjs}": ["yarn format", "yarn lint:fix"],
"*.{json,yaml,yml,md}": ["yarn format"],
"codemods/**/scripts/*.ts": "bash scripts/test-staged.sh"
"*.{ts,tsx,js,jsx,mts,mjs}": ["yarn format", "yarn lint"],
"*.{json,yaml,yml,md,css}": ["yarn format"],
"codemods/**/scripts/*.{ts,tsx}": "bash scripts/test-staged.sh",
"codemods/**/codemod.yaml": "bash scripts/readme-staged.sh"
}
11 changes: 8 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,11 +28,16 @@ yarn lint:fix
yarn test
```

## Pre-commit hook
## Git hooks (husky)

A pre-commit hook runs automatically after `yarn install`. It uses lint-staged to run oxfmt and oxlint on staged files before each commit. If a file fails formatting or linting, the commit is blocked until the issues are fixed.
After `yarn install`, the `prepare` script runs `husky` and sets `core.hooksPath` to `.husky/_`. Hooks fail loudly if `node_modules` is missing (so worktrees cannot silently skip checks).

The hook only inspects **staged** files. Format-only changes elsewhere can still fail CI — run `yarn format:check` before pushing.
| Hook | What runs |
| -------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| **pre-commit** | `yarn lint-staged` — format + lint staged files; package tests for staged `codemods/**/scripts/*`; regenerate `README.md` when `codemods/**/codemod.yaml` is staged ([`.lintstagedrc.json`](./.lintstagedrc.json)) |
| **pre-push** | `yarn check:changed` → [`scripts/check-changed.sh`](./scripts/check-changed.sh) — the **same** format / lint / package-name / test / README gates as CI |

CI (`.github/workflows/ci.yml`) calls that same script, so local and remote gates cannot drift. Run `yarn check:changed` anytime to reproduce CI locally.

## Making changes

Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,8 @@
"prepare": "husky",
"changeset": "changeset",
"version-packages": "changeset version && bash scripts/sync-codemod-versions.sh",
"readme": "bash scripts/generate-readme-codemods.sh"
"readme": "bash scripts/generate-readme-codemods.sh",
"check:changed": "bash scripts/check-changed.sh"
},
"devDependencies": {
"@changesets/cli": "^2.31.0",
Expand Down
82 changes: 82 additions & 0 deletions scripts/check-changed.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
#!/usr/bin/env bash
#
# Shared quality gates for local hooks and CI.
# Checks the merge-base diff against BASE (default: origin/main).
#
# Usage: bash scripts/check-changed.sh [BASE]
#
set -euo pipefail

BASE="${1:-origin/main}"

if ! git rev-parse --verify "$BASE" >/dev/null 2>&1; then
echo "error: base ref '$BASE' not found. Fetch it first (e.g. git fetch origin main)." >&2
exit 1
fi

# Portable (macOS bash 3.2 + Linux): build arrays without mapfile.
ALL_FILES=()
while IFS= read -r f; do
[ -n "$f" ] && ALL_FILES+=("$f")
done < <(git diff --name-only --diff-filter=ACMR "$BASE"...HEAD)

LINT_FILES=()
while IFS= read -r f; do
[ -n "$f" ] && LINT_FILES+=("$f")
done < <(
git diff --name-only --diff-filter=ACMR "$BASE"...HEAD | grep -E '\.(ts|js|mts|mjs|tsx|jsx)$' || true
)

CODEMOD_DIRS=()
while IFS= read -r candidate; do
[ -n "$candidate" ] || continue
[ -f "$candidate/package.json" ] && CODEMOD_DIRS+=("$candidate")
done < <(
git diff --name-only "$BASE"...HEAD -- 'codemods/' |
awk -F/ 'NF>=3 {print $1"/"$2"/"$3}' |
sort -u
)

if [ "${#ALL_FILES[@]}" -gt 0 ]; then
echo "::group::Format check (changed files)"
yarn format:check "${ALL_FILES[@]}"
echo "::endgroup::"
fi

if [ "${#LINT_FILES[@]}" -gt 0 ]; then
echo "::group::Lint (changed files)"
yarn lint "${LINT_FILES[@]}"
echo "::endgroup::"
fi

if [ "${#CODEMOD_DIRS[@]}" -gt 0 ]; then
echo "::group::Package name length check"
failed=0
for dir in "${CODEMOD_DIRS[@]}"; do
name=$(node -p "require('./$dir/package.json').name")
len=${#name}
if [ "$len" -gt 50 ]; then
echo "::error::Package name '$name' is $len chars (max 50 for Codemod registry)"
failed=1
fi
done
[ "$failed" -eq 0 ]
echo "::endgroup::"

echo "::group::Test (changed codemods)"
for dir in "${CODEMOD_DIRS[@]}"; do
(cd "$dir" && yarn test)
done
echo "::endgroup::"

echo "::group::README freshness check"
yarn readme
if ! git diff --quiet README.md; then
echo "::error::README.md is out of date. Run 'yarn readme' and commit the result."
git diff README.md
exit 1
fi
echo "::endgroup::"
fi

echo "All changed-file checks passed (base: $BASE)."
6 changes: 6 additions & 0 deletions scripts/readme-staged.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
#!/usr/bin/env bash
# Regenerate README.md when codemod manifests change (matches CI README freshness check).
set -euo pipefail

yarn readme
git add README.md