diff --git a/.github/workflows/auto-publish.yml b/.github/workflows/auto-publish.yml index ddc7926..587f514 100644 --- a/.github/workflows/auto-publish.yml +++ b/.github/workflows/auto-publish.yml @@ -11,6 +11,7 @@ on: jobs: auto-publish: runs-on: ubuntu-latest + environment: production steps: - name: Checkout code diff --git a/.github/workflows/pr-publish.yml b/.github/workflows/pr-publish.yml index 47d6174..413f395 100644 --- a/.github/workflows/pr-publish.yml +++ b/.github/workflows/pr-publish.yml @@ -1,34 +1,14 @@ -name: PR-based Auto-publish +name: PR Auto-publish Detection on: pull_request: types: [opened, synchronize, reopened] paths: - 'recipes/**' - issue_comment: - types: [created] - pull_request_review_comment: - types: [created] - workflow_dispatch: - inputs: - comment_id: - description: 'Comment ID to check for reactions' - required: true - type: string - test_publish: - description: 'Test the publish workflow' - required: false - type: boolean - default: false jobs: - detect-changes: - if: github.event_name == 'pull_request' + detect-and-comment: runs-on: ubuntu-latest - outputs: - codemods: ${{ steps.detect.outputs.codemods }} - has-changes: ${{ steps.detect.outputs.has-changes }} - publish-commands: ${{ steps.detect.outputs.publish-commands }} steps: - name: Checkout code @@ -58,52 +38,9 @@ jobs: echo "codemods=$CHANGED_CODEMODS" >> $GITHUB_OUTPUT echo "Changed codemods: $CHANGED_CODEMODS" - - # Generate publish commands for each changed codemod - PUBLISH_COMMANDS="" - for codemod_dir in $CHANGED_CODEMODS; do - if [ -f "$codemod_dir/codemod.yaml" ]; then - # Extract name and version from codemod.yaml - NAME=$(grep '^name:' "$codemod_dir/codemod.yaml" | sed 's/name: *//' | tr -d ' ') - VERSION=$(grep '^version:' "$codemod_dir/codemod.yaml" | sed 's/version: *//' | tr -d ' ') - - if [ -n "$NAME" ] && [ -n "$VERSION" ]; then - # Use codemod publish command instead of git tag - COMMAND="npx codemod@latest publish $codemod_dir" - PUBLISH_COMMANDS="${PUBLISH_COMMANDS}${COMMAND}\n" - fi - fi - done - - # Store commands as job output - echo "publish-commands=$PUBLISH_COMMANDS" >> $GITHUB_OUTPUT - comment-on-pr: - if: github.event_name == 'pull_request' && needs.detect-changes.outputs.has-changes == 'true' - needs: detect-changes - runs-on: ubuntu-latest - - steps: - - name: Checkout code - uses: actions/checkout@v4 - with: - fetch-depth: 0 # Need full history for diff - - - name: Setup pnpm - uses: pnpm/action-setup@v4 - with: - version: latest - - - name: Setup Node.js - uses: actions/setup-node@v4 - with: - node-version: '20.x' - cache: 'pnpm' - - - name: Install dependencies - run: pnpm install - - name: Comment on PR + if: steps.detect.outputs.has-changes == 'true' uses: actions/github-script@v7 with: script: | @@ -116,7 +53,7 @@ jobs: // Check if we already commented const existingComment = comments.find(comment => comment.user.type === 'Bot' && - comment.body.includes('Ready to Publish Codemods') + comment.body.includes('Auto-publish on merge') ); if (existingComment) { @@ -124,24 +61,23 @@ jobs: return; } - const codemods = '${{ needs.detect-changes.outputs.codemods }}'.split(' ').filter(Boolean); - const publishCommands = '${{ needs.detect-changes.outputs.publish-commands }}'; + const codemods = '${{ steps.detect.outputs.codemods }}'.split(' ').filter(Boolean); - const commentBody = `## 🚀 Ready to Publish Codemods + const commentBody = `## 🚀 Codemods Ready for Auto-Publish **Changed codemods:** ${codemods.map(c => `\`${c}\``).join(', ')} - To publish these codemods, a maintainer should comment with: \`/publish\` + These codemods will be **automatically published** to the Codemod Registry when this PR is merged to \`main\`. - **Commands that will be executed:** - \`\`\`bash - ${publishCommands} - \`\`\` + **What happens on merge:** + - ✅ Codemods are automatically published to the registry + - ✅ Version conflicts are auto-resolved (patch version bump) + - ✅ Publishing errors are handled gracefully - **Note:** Only comments from repository maintainers will trigger the publishing workflow. + **Note:** Make sure the \`CODEMOD_API_KEY\` secret is configured in the repository settings for auto-publishing to work. --- - *This comment was automatically generated by the PR-based auto-publish workflow.*`; + *This comment was automatically generated by the auto-publish detection workflow.*`; await github.rest.issues.createComment({ owner: context.repo.owner, @@ -149,306 +85,3 @@ jobs: issue_number: context.issue.number, body: commentBody }); - - handle-comment: - if: github.event_name == 'issue_comment' - runs-on: ubuntu-latest - - steps: - - name: Check if comment is a publish command - id: check-comment - run: | - COMMENT_BODY="${{ github.event.comment.body }}" - # Trim whitespace and check for /publish command - TRIMMED_COMMENT=$(echo "$COMMENT_BODY" | xargs) - if echo "$TRIMMED_COMMENT" | grep -q "^/publish$"; then - echo "is-publish-command=true" >> $GITHUB_OUTPUT - else - echo "is-publish-command=false" >> $GITHUB_OUTPUT - fi - - - name: Check if commenter is a maintainer - if: steps.check-comment.outputs.is-publish-command == 'true' - id: check-maintainer - uses: actions/github-script@v7 - with: - script: | - // Get repository collaborators to check if commenter is a maintainer - const { data: collaborators } = await github.rest.repos.listCollaborators({ - owner: context.repo.owner, - repo: context.repo.repo, - }); - - const maintainers = collaborators - .filter(collab => collab.permissions.admin || collab.permissions.maintain) - .map(collab => collab.login); - - const commenter = context.actor; - - if (maintainers.includes(commenter)) { - console.log('Maintainer approved with /publish command!'); - core.setOutput('approved', 'true'); - core.setOutput('approver', commenter); - } else { - console.log('Commenter is not a maintainer'); - core.setOutput('approved', 'false'); - } - - - name: Get PR branch - if: steps.check-maintainer.outputs.approved == 'true' - id: pr-branch - run: | - # Get the PR number from the issue number (for issue_comment events on PRs) - PR_NUMBER="${{ github.event.issue.number }}" - echo "PR number: $PR_NUMBER" - - # Get PR details to find the head ref - PR_DATA=$(curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ - "https://api.github.com/repos/${{ github.repository }}/pulls/$PR_NUMBER") - - HEAD_REF=$(echo "$PR_DATA" | jq -r '.head.ref') - HEAD_SHA=$(echo "$PR_DATA" | jq -r '.head.sha') - - echo "head_ref=$HEAD_REF" >> $GITHUB_OUTPUT - echo "head_sha=$HEAD_SHA" >> $GITHUB_OUTPUT - echo "PR branch: $HEAD_REF" - echo "PR SHA: $HEAD_SHA" - - - name: Checkout PR code - if: steps.check-maintainer.outputs.approved == 'true' - uses: actions/checkout@v4 - with: - ref: ${{ steps.pr-branch.outputs.head_sha }} - fetch-depth: 0 - - - name: Setup pnpm - if: steps.check-maintainer.outputs.approved == 'true' - uses: pnpm/action-setup@v4 - with: - version: latest - - - name: Setup Node.js - if: steps.check-maintainer.outputs.approved == 'true' - uses: actions/setup-node@v4 - with: - node-version: '20.x' - cache: 'pnpm' - - - name: Install dependencies - if: steps.check-maintainer.outputs.approved == 'true' - run: pnpm install - - - name: Check API key configuration - if: steps.check-maintainer.outputs.approved == 'true' - run: | - if [ -z "${{ secrets.CODEMOD_API_KEY }}" ]; then - echo "❌ CODEMOD_API_KEY secret is not configured" - echo "Please set up your API key in GitHub secrets" - exit 1 - fi - echo "✅ API key is configured" - - - name: Check for conflicts and auto-fix - if: steps.check-maintainer.outputs.approved == 'true' - id: conflict-check - run: | - # Get the bot's comment that contains the codemod information - # We need to find the bot's comment on this PR that contains "Ready to Publish Codemods" - echo "Finding bot's comment with codemod information..." - - # Get all comments on the PR - COMMENTS=$(curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ - "https://api.github.com/repos/${{ github.repository }}/issues/${{ github.event.issue.number }}/comments") - - # Find the bot's comment that contains "Ready to Publish Codemods" - BOT_COMMENT=$(echo "$COMMENTS" | jq -r '.[] | select(.user.type == "Bot" and .body | contains("Ready to Publish Codemods")) | .body') - - if [ -z "$BOT_COMMENT" ]; then - echo "❌ Could not find bot's comment with codemod information" - exit 1 - fi - - echo "Found bot's comment, extracting codemod directories..." - - # Extract codemod directories from the bot's comment - CODEMODS=$(echo "$BOT_COMMENT" | grep "Changed codemods:" | sed 's/.*Changed codemods: *//' | sed 's/To publish.*//' | tr -d '`*' | tr ',' ' ' | xargs) - - CONFLICTS_FOUND="false" - FIXED_CONFLICTS="" - - for codemod_dir in $CODEMODS; do - if [ -f "$codemod_dir/codemod.yaml" ]; then - echo "Checking $codemod_dir for conflicts..." - - # Try to publish with dry-run to detect conflicts - set +e - PUBLISH_OUTPUT=$(npx codemod@latest publish "$codemod_dir" --dry-run 2>&1) - PUBLISH_EXIT_CODE=$? - set -e - - # Check for different types of conflicts - if echo "$PUBLISH_OUTPUT" | grep -q "version.*already exists"; then - echo "Version conflict detected in $codemod_dir, auto-bumping..." - - # Extract current version - CURRENT_VERSION=$(grep '^version:' "$codemod_dir/codemod.yaml" | sed 's/version: *//' | tr -d ' ') - - # Bump patch version - NEW_VERSION=$(echo "$CURRENT_VERSION" | awk -F. '{$NF = $NF + 1;} 1' | sed 's/ /./g') - - # Update codemod.yaml - sed -i.bak "s/^version: .*/version: $NEW_VERSION/" "$codemod_dir/codemod.yaml" - - echo "✅ Bumped version from $CURRENT_VERSION to $NEW_VERSION in $codemod_dir" - FIXED_CONFLICTS="${FIXED_CONFLICTS}Version bumped in $codemod_dir (${CURRENT_VERSION} → ${NEW_VERSION})\n" - - elif echo "$PUBLISH_OUTPUT" | grep -q "name.*already exists\|already published\|name is taken"; then - echo "❌ Name conflict detected in $codemod_dir - codemod name is already taken by another author" - CONFLICTS_FOUND="true" - echo "conflict-details<> $GITHUB_OUTPUT - echo "Name conflict in $codemod_dir: The codemod name is already published by another author. Please change the 'name' field in $codemod_dir/codemod.yaml to a unique name." >> $GITHUB_OUTPUT - echo "EOF" >> $GITHUB_OUTPUT - - elif [ $PUBLISH_EXIT_CODE -ne 0 ]; then - echo "❌ Other publishing error in $codemod_dir" - CONFLICTS_FOUND="true" - echo "conflict-details<> $GITHUB_OUTPUT - echo "Publishing error in $codemod_dir: $PUBLISH_OUTPUT" >> $GITHUB_OUTPUT - echo "EOF" >> $GITHUB_OUTPUT - - else - echo "✅ No conflicts in $codemod_dir" - fi - fi - done - - echo "conflicts-found=$CONFLICTS_FOUND" >> $GITHUB_OUTPUT - echo "fixed-conflicts<> $GITHUB_OUTPUT - echo -e "$FIXED_CONFLICTS" >> $GITHUB_OUTPUT - echo "EOF" >> $GITHUB_OUTPUT - env: - CODEMOD_API_KEY: ${{ secrets.CODEMOD_API_KEY }} - CODEMOD_REGISTRY_SCOPE: ${{ vars.CODEMOD_REGISTRY_SCOPE || 'codemod' }} - CODEMOD_REGISTRY_URL: ${{ vars.CODEMOD_REGISTRY_URL || 'https://registry.codemod.com' }} - - - name: Extract and execute publish commands - if: steps.check-maintainer.outputs.approved == 'true' && steps.conflict-check.outputs.conflicts-found == 'false' - id: publish - run: | - # Get the bot's comment that contains the publish commands - echo "Finding bot's comment with publish commands..." - - # Get all comments on the PR - COMMENTS=$(curl -s -H "Authorization: token ${{ secrets.GITHUB_TOKEN }}" \ - "https://api.github.com/repos/${{ github.repository }}/issues/${{ github.event.issue.number }}/comments") - - # Find the bot's comment that contains "Ready to Publish Codemods" - BOT_COMMENT=$(echo "$COMMENTS" | jq -r '.[] | select(.user.type == "Bot" and .body | contains("Ready to Publish Codemods")) | .body') - - if [ -z "$BOT_COMMENT" ]; then - echo "❌ Could not find bot's comment with publish commands" - exit 1 - fi - - echo "Found bot's comment, extracting commands..." - - # Get the bash code block content from the bot's comment - COMMANDS=$(echo "$BOT_COMMENT" | sed -n '/```bash/,/```/p' | sed '1d;$d') - - echo "Executing commands:" - echo "$COMMANDS" - - # Execute commands with error handling - set +e # Don't exit on error - echo "$COMMANDS" | bash - PUBLISH_EXIT_CODE=$? - set -e # Re-enable exit on error - - if [ $PUBLISH_EXIT_CODE -eq 0 ]; then - echo "✅ Publishing commands executed successfully!" - echo "success=true" >> $GITHUB_OUTPUT - else - echo "❌ Publishing failed with exit code: $PUBLISH_EXIT_CODE" - echo "success=false" >> $GITHUB_OUTPUT - echo "exit_code=$PUBLISH_EXIT_CODE" >> $GITHUB_OUTPUT - fi - env: - CODEMOD_API_KEY: ${{ secrets.CODEMOD_API_KEY }} - CODEMOD_REGISTRY_SCOPE: ${{ vars.CODEMOD_REGISTRY_SCOPE || 'codemod' }} - CODEMOD_REGISTRY_URL: ${{ vars.CODEMOD_REGISTRY_URL || 'https://registry.codemod.com' }} - - - name: Update comment with result - if: steps.check-maintainer.outputs.approved == 'true' - uses: actions/github-script@v7 - with: - script: | - const approver = '${{ steps.check-maintainer.outputs.approver }}'; - const conflictsFound = '${{ steps.conflict-check.outputs.conflicts-found }}' === 'true'; - const conflictDetails = '${{ steps.conflict-check.outputs.conflict-details }}'; - const fixedConflicts = '${{ steps.conflict-check.outputs.fixed-conflicts }}'; - const success = '${{ steps.publish.outputs.success }}' === 'true'; - const exitCode = '${{ steps.publish.outputs.exit_code }}'; - - let commentBody; - - if (conflictsFound) { - commentBody = `## ⚠️ Publishing Blocked - Conflicts Detected - - **Approved by:** @${approver} - **Blocked at:** ${new Date().toISOString()} - - **Issues found:** - ${conflictDetails} - - **Auto-fixes applied:** - ${fixedConflicts || 'None'} - - **To resolve:** - 1. Fix the conflicts (e.g., change codemod name in \`codemod.yaml\`) - 2. Push new changes to this PR - 3. Comment \`/publish\` again - - --- - *This comment was automatically updated by the PR-based auto-publish workflow.*`; - } else if (success) { - commentBody = `## ✅ Published Successfully! - - **Approved by:** @${approver} - **Published at:** ${new Date().toISOString()} - - **Auto-fixes applied:** - ${fixedConflicts || 'None'} - - The codemods have been successfully published to the registry. - - --- - *This comment was automatically updated by the PR-based auto-publish workflow.*`; - } else { - commentBody = `## ❌ Publishing Failed - - **Approved by:** @${approver} - **Failed at:** ${new Date().toISOString()} - **Exit code:** ${exitCode} - - The publishing process encountered an error. Common issues: - - - **Invalid API key**: Check your \`CODEMOD_API_KEY\` secret - - **Version conflict**: The codemod version may already exist - try bumping the version in \`codemod.yaml\` - - **Network issues**: Registry may be temporarily unavailable - - **Invalid codemod**: Check that your codemod structure is correct - - **To retry:** - 1. Fix the issue (e.g., bump version in \`codemod.yaml\`) - 2. Push new changes to this PR - 3. Comment \`/publish\` again - - --- - *This comment was automatically updated by the PR-based auto-publish workflow.*`; - } - - await github.rest.issues.updateComment({ - owner: context.repo.owner, - repo: context.repo.repo, - comment_id: context.event.comment.id, - body: commentBody - }); diff --git a/AUTO_PUBLISH_SETUP.md b/AUTO_PUBLISH_SETUP.md index 528f41c..6774b61 100644 --- a/AUTO_PUBLISH_SETUP.md +++ b/AUTO_PUBLISH_SETUP.md @@ -1,39 +1,40 @@ # Auto-Publishing Setup -This repository includes two auto-publishing workflows for different use cases: - -1. **Direct Auto-Publish**: Automatically publishes when changes are pushed to `main` -2. **PR-based Auto-Publish**: Detects changes in PRs and waits for maintainer approval - -Both workflows publish codemods from the `recipes/` directory to the Codemod Registry. +This repository includes an auto-publishing workflow that automatically publishes codemods to the Codemod Registry when changes are merged to `main`. ## Quick Setup 1. **Get API key**: [https://app.codemod.com/api-keys](https://app.codemod.com/api-keys) -2. **Run setup script**: - ```bash - ./scripts/setup-auto-publish.sh - ``` - -The script will ask for your API key, registry scope (e.g., `your-org`), and registry URL. +2. **Create GitHub Environment** (required for secrets): + - Go to your repository → Settings → Environments + - Click "New environment" + - Name it `production` (or any name you prefer) + - Click "Configure environment" +3. **Add secrets to the environment**: + - In the environment settings, add these secrets: + - `CODEMOD_API_KEY`: Your API key from step 1 + - Optionally add these variables: + - `CODEMOD_REGISTRY_SCOPE`: Your organization scope (e.g., `your-org`) + - `CODEMOD_REGISTRY_URL`: Registry URL (defaults to `https://registry.codemod.com`) ## How It Works -### Direct Auto-Publish (Default) +### Auto-Publish Workflow - **Trigger**: Push to `main` branch -- **Process**: Automatically detects changes and publishes -- **Best for**: Solo maintainers or trusted teams +- **Process**: + 1. Automatically detects changes in `recipes/` directory + 2. Publishes new/updated codemods to the registry + 3. Unpublishes removed codemods + 4. Auto-resolves version conflicts (bumps patch version) -### PR-based Auto-Publish (Recommended for Open Source) +### PR Detection Workflow - **Trigger**: Pull request with codemod changes - **Process**: 1. Bot detects changed codemods in PR - 2. Posts comment with pre-filled publish commands - 3. Waits for maintainer approval (thumbs up reaction) - 4. Executes publishing commands when approved -- **Best for**: Open source projects with community contributions + 2. Posts comment: "Codemods will be auto-published on merge to main" + 3. No action required - just merge the PR -### Common Features +### Features - **New codemods**: Published for the first time - **Updated codemods**: Version bumped and republished - **Removed codemods**: Automatically unpublished @@ -51,40 +52,32 @@ The script will ask for your API key, registry scope (e.g., `your-org`), and reg - Never exposed in code, logs, or documentation - Only GitHub Actions can access the key -## Manual Setup - -If you prefer manual setup: - -1. **GitHub Secrets**: `CODEMOD_API_KEY` (your API key) -2. **GitHub Variables**: - - `CODEMOD_REGISTRY_SCOPE` (your organization scope) - - `CODEMOD_REGISTRY_URL` (registry URL, optional) - ## Examples -### Direct Auto-Publish +### Auto-Publish on Merge ```bash -# 1. Make changes +# 1. Make changes to codemod echo "console.log('test');" > recipes/my-codemod/tests/input.js -# 2. Commit and push +# 2. Create PR git add recipes/my-codemod/ git commit -m "Add test case" +git push origin feature-branch +# Create PR on GitHub + +# 3. Bot comments: "Codemods will be auto-published on merge to main" + +# 4. Merge PR to main +git checkout main +git merge feature-branch git push origin main -# 3. GitHub Actions automatically publishes to registry -# 4. Available at: https://registry.codemod.com/@your-scope/my-codemod +# 5. GitHub Actions automatically publishes to registry +# 6. Available at: https://registry.codemod.com/@your-scope/my-codemod ``` -### PR-based Auto-Publish -```bash -# 1. Community contributor opens PR with codemod changes -# 2. Bot automatically posts comment: -# "Show a thumbs up to this comment and I'll run: -# git tag -a v1.0.0@my-codemod -m 'Release version v1.0.0 for my-codemod' -# git push origin --tags" - -# 3. Maintainer reviews and gives thumbs up 👍 -# 4. Bot executes commands and updates comment with success -# 5. Codemod is published to registry -``` +### Environment Setup Screenshots +1. **Repository Settings** → **Environments** → **New environment** +2. **Name**: `production` → **Configure environment** +3. **Add secrets**: `CODEMOD_API_KEY` with your API key +4. **Add variables** (optional): `CODEMOD_REGISTRY_SCOPE`, `CODEMOD_REGISTRY_URL` diff --git a/README.md b/README.md index 2c82e6e..e36256d 100644 --- a/README.md +++ b/README.md @@ -30,12 +30,12 @@ This template repository comes pre-configured with a GitHub workflow that automa > > 6. Set up auto-publishing to [Codemod Registry](https://app.codemod.com/registry): > -> ```bash -> # Get API key from https://app.codemod.com/api-keys -> ./scripts/setup-auto-publish.sh -> ``` +> 1. **Get API key**: [https://app.codemod.com/api-keys](https://app.codemod.com/api-keys) +> 2. **Create GitHub Environment**: Repository Settings → Environments → New environment (name it `production`) +> 3. **Add secret**: `CODEMOD_API_KEY` with your API key +> 4. **Optional variables**: `CODEMOD_REGISTRY_SCOPE`, `CODEMOD_REGISTRY_URL` > -> This automatically publishes codemods when you push changes to `recipes/`. See [AUTO_PUBLISH_SETUP.md](./AUTO_PUBLISH_SETUP.md) for details. +> This automatically publishes codemods when you merge changes to `main`. See [AUTO_PUBLISH_SETUP.md](./AUTO_PUBLISH_SETUP.md) for details. ## Overview