diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 9489fee..dfe1acd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -2,22 +2,67 @@ name: CI on: push: + branches: + - main pull_request: + branches: + - main + +permissions: + contents: read jobs: checks: runs-on: ubuntu-latest - permissions: - contents: read steps: - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - uses: pnpm/action-setup@v4 with: version: 9.14.2 + - uses: actions/setup-node@v4 with: - node-version: 20 + node-version-file: .nvmrc cache: pnpm + - run: pnpm install --frozen-lockfile - - run: pnpm run test + - run: pnpm run check-types + + - name: Run tests for changed codemods + shell: bash + env: + BASE_SHA: ${{ github.event.pull_request.base.sha }} + EVENT_NAME: ${{ github.event_name }} + HEAD_SHA: ${{ github.sha }} + run: | + if [ "$EVENT_NAME" != "pull_request" ]; then + pnpm run test + exit 0 + fi + + changed_paths=$(git diff --name-only "$BASE_SHA" "$HEAD_SHA") + + if echo "$changed_paths" | grep -qE '^(package\.json|pnpm-lock\.yaml|pnpm-workspace\.yaml|\.github/workflows/ci\.yml)$'; then + pnpm run test + exit 0 + fi + + filters=$( + echo "$changed_paths" \ + | grep '^codemods/' \ + | cut -d/ -f1-2 \ + | sort -u \ + | sed 's|^|--filter ./|' || true + ) + + if [ -z "$filters" ]; then + echo "No codemod packages affected; skipping tests." + exit 0 + fi + + # shellcheck disable=SC2086 + pnpm $filters test diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml new file mode 100644 index 0000000..2042f2e --- /dev/null +++ b/.github/workflows/publish.yml @@ -0,0 +1,99 @@ +name: Publish + +on: + workflow_run: + workflows: + - CI + types: + - completed + workflow_dispatch: + inputs: + codemod_name: + description: Codemod directory name under codemods/ + required: true + type: string + +concurrency: ${{ github.workflow }}-${{ github.event.workflow_run.head_branch || github.ref }} + +permissions: + id-token: write + contents: write + +jobs: + tag: + name: Tag released versions + if: >- + github.event.workflow_run.conclusion == 'success' && + github.event.workflow_run.event == 'push' && + github.event.workflow_run.head_branch == 'main' + runs-on: ubuntu-latest + outputs: + changed_dirs: ${{ steps.tag.outputs.changed_dirs }} + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.event.workflow_run.head_sha }} + fetch-depth: 0 + + - uses: pnpm/action-setup@f520eceda224fe1a4aed5a2a27a194379a409996 # v6 + + - uses: actions/setup-node@v4 + with: + node-version-file: .nvmrc + cache: pnpm + + - run: pnpm install --frozen-lockfile + + - name: Detect pending changesets + id: pending + run: | + count="$(find .changeset -maxdepth 1 -type f -name '*.md' ! -name 'README.md' | wc -l | tr -d ' ')" + echo "count=$count" >> "$GITHUB_OUTPUT" + + - name: Tag released versions + if: steps.pending.outputs.count == '0' + id: tag + run: node scripts/tag-and-publish.mjs + + publish: + name: Publish ${{ matrix.dir }} + needs: tag + if: >- + github.event_name == 'workflow_run' && + needs.tag.outputs.changed_dirs != '' && + needs.tag.outputs.changed_dirs != '[]' + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + dir: ${{ fromJson(needs.tag.outputs.changed_dirs) }} + steps: + - uses: actions/checkout@v4 + with: + ref: ${{ github.event.workflow_run.head_sha }} + + - uses: pnpm/action-setup@f520eceda224fe1a4aed5a2a27a194379a409996 # v6 + + - uses: actions/setup-node@v4 + with: + node-version-file: .nvmrc + cache: pnpm + + - run: pnpm install --frozen-lockfile + + - name: Publish codemod + uses: codemod/publish-action@dd6c8dbc5ceb1a6146feba41481d88b43da50024 # v1 + with: + path: ${{ matrix.dir }} + + publish-manual: + name: Publish codemod (manual) + if: github.event_name == 'workflow_dispatch' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Publish codemod + uses: codemod/publish-action@dd6c8dbc5ceb1a6146feba41481d88b43da50024 # v1 + with: + path: codemods/${{ inputs.codemod_name }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index e02cf59..efde5ba 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -7,10 +7,9 @@ on: types: - completed -concurrency: ${{ github.workflow }}-${{ github.event.workflow_run.head_branch || github.ref }} +concurrency: ${{ github.workflow }}-${{ github.event.workflow_run.head_branch }} permissions: - id-token: write contents: write pull-requests: write @@ -22,66 +21,23 @@ jobs: github.event.workflow_run.event == 'push' && github.event.workflow_run.head_branch == 'main' runs-on: ubuntu-latest - outputs: - published: ${{ steps.changesets.outputs.published }} - changed_dirs: ${{ steps.tag.outputs.changed_dirs }} steps: - uses: actions/checkout@v4 with: ref: ${{ github.event.workflow_run.head_sha }} - fetch-depth: 0 - fetch-tags: true - - uses: pnpm/action-setup@v4 - with: - version: 9.14.2 + - uses: pnpm/action-setup@f520eceda224fe1a4aed5a2a27a194379a409996 # v6 - uses: actions/setup-node@v4 with: - node-version: 20 + node-version-file: .nvmrc cache: pnpm - run: pnpm install --frozen-lockfile - - name: Create Release PR or Publish - id: changesets - uses: changesets/action@v1 + - name: Create Release Pull Request + uses: changesets/action@a45c4d594aa4e2c509dc14a9f2b3b67ba3780d0d # v1.9.0 with: version: pnpm version-packages env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - - - name: Tag released versions - if: steps.changesets.outputs.hasChangesets == 'false' - id: tag - run: node scripts/tag-and-publish.mjs - - publish: - name: Publish ${{ matrix.dir }} - needs: release - if: needs.release.result == 'success' && needs.release.outputs.changed_dirs != '[]' && needs.release.outputs.changed_dirs != '' - runs-on: ubuntu-latest - strategy: - fail-fast: false - matrix: - dir: ${{ fromJson(needs.release.outputs.changed_dirs) }} - steps: - - uses: actions/checkout@v4 - with: - ref: ${{ github.event.workflow_run.head_sha }} - - - uses: pnpm/action-setup@v4 - with: - version: 9.14.2 - - - uses: actions/setup-node@v4 - with: - node-version: 20 - cache: pnpm - - - run: pnpm install --frozen-lockfile - - - name: Publish codemod - uses: codemod/publish-action@v1 - with: - path: ${{ matrix.dir }} diff --git a/.nvmrc b/.nvmrc new file mode 100644 index 0000000..209e3ef --- /dev/null +++ b/.nvmrc @@ -0,0 +1 @@ +20 diff --git a/package.json b/package.json index fd5b504..b48c816 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ "test": "pnpm -r --filter \"react-*\" test", "check-types": "pnpm -r --filter \"react-*\" check-types", "changeset": "changeset", - "version-packages": "changeset version && bash scripts/sync-codemod-versions.sh", + "version-packages": "changeset version && node scripts/sync-codemod-yaml-versions.mjs", "ci": "pnpm run test && pnpm run check-types" }, "keywords": [ diff --git a/scripts/sync-codemod-versions.sh b/scripts/sync-codemod-versions.sh deleted file mode 100755 index 6a31b31..0000000 --- a/scripts/sync-codemod-versions.sh +++ /dev/null @@ -1,20 +0,0 @@ -#!/usr/bin/env bash -# Syncs the version field in each codemod's codemod.yaml with its package.json. -# Run after `changeset version` to keep both files in sync. - -set -euo pipefail - -for pkg_json in codemods/*/package.json; do - dir="$(dirname "$pkg_json")" - codemod_yaml="$dir/codemod.yaml" - - if [ ! -f "$codemod_yaml" ]; then - continue - fi - - version="$(node -p "require('./$pkg_json').version")" - # Replace the version line in codemod.yaml - sed -i'' -e "s/^version: .*/version: \"$version\"/" "$codemod_yaml" - - echo "Synced $codemod_yaml to version $version" -done diff --git a/scripts/sync-codemod-yaml-versions.mjs b/scripts/sync-codemod-yaml-versions.mjs new file mode 100755 index 0000000..ed6ee7b --- /dev/null +++ b/scripts/sync-codemod-yaml-versions.mjs @@ -0,0 +1,36 @@ +#!/usr/bin/env node +import { readdirSync, readFileSync, writeFileSync } from 'node:fs' +import { dirname, join, relative } from 'node:path' +import { fileURLToPath } from 'node:url' + +const root = join(dirname(fileURLToPath(import.meta.url)), '..') +const versionPattern = /^version:\s*(['"]?)([^'"\n]+)\1\s*$/m + +function findCodemodYamlFiles(directory) { + const paths = [] + + for (const entry of readdirSync(directory, { withFileTypes: true })) { + if (entry.name === 'node_modules') continue + + const path = join(directory, entry.name) + if (entry.isDirectory()) { + paths.push(...findCodemodYamlFiles(path)) + } else if (entry.isFile() && entry.name === 'codemod.yaml') { + paths.push(path) + } + } + + return paths +} + +for (const yamlPath of findCodemodYamlFiles(join(root, 'codemods'))) { + const dir = dirname(yamlPath) + const { version } = JSON.parse(readFileSync(join(dir, 'package.json'), 'utf8')) + const content = readFileSync(yamlPath, 'utf8') + const updated = content.replace(versionPattern, `version: "${version}"`) + + if (updated !== content) { + writeFileSync(yamlPath, updated) + console.log(`${relative(root, yamlPath)} -> ${version}`) + } +}