-
Notifications
You must be signed in to change notification settings - Fork 14
feat: automatic dependabot pr changesets #897
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
kylemcd
merged 5 commits into
main
from
kyle-kno-11961-automate-changeset-for-dependabot-dependencies
Mar 11, 2026
Merged
Changes from all commits
Commits
Show all changes
5 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,113 @@ | ||
| /** | ||
| * Creates a changeset file for Dependabot PRs that update production | ||
| * dependencies in published @knocklabs/* packages. | ||
| * | ||
| * Skips devDependency-only changes since those don't require a release. | ||
| * | ||
| * This script is only run from a workflow gated to dependabot[bot], | ||
| * so we trust that the changes are dependency updates. | ||
| * | ||
| * Environment variables: | ||
| * PR_TITLE - The pull request title (used as the changeset description) | ||
| * PR_NUMBER - The pull request number (used in the changeset filename) | ||
| * GITHUB_OUTPUT - Path to the GitHub Actions output file | ||
| */ | ||
|
|
||
| const { execSync } = require("child_process"); | ||
| const fs = require("fs"); | ||
| const path = require("path"); | ||
|
|
||
| const prTitle = process.env.PR_TITLE; | ||
| const prNumber = process.env.PR_NUMBER; | ||
| const githubOutput = process.env.GITHUB_OUTPUT; | ||
|
|
||
| if (!prTitle || !prNumber || !githubOutput) { | ||
| console.error( | ||
| "Missing required environment variables: PR_TITLE, PR_NUMBER, GITHUB_OUTPUT", | ||
| ); | ||
| process.exit(1); | ||
| } | ||
|
|
||
| const changesetFile = path.join(".changeset", `dependabot-pr-${prNumber}.md`); | ||
|
|
||
| function setOutput(key, value) { | ||
| fs.appendFileSync(githubOutput, `${key}=${value}\n`); | ||
| } | ||
|
|
||
| // If changeset already exists, skip | ||
| if (fs.existsSync(changesetFile)) { | ||
| console.log("Changeset already exists, skipping"); | ||
| setOutput("created", "false"); | ||
| process.exit(0); | ||
| } | ||
|
|
||
| // Find all package.json files changed in the latest commit | ||
| const diffOutput = execSync( | ||
| "git diff --name-only HEAD~1 HEAD -- '**/package.json'", | ||
| { encoding: "utf-8" }, | ||
| ).trim(); | ||
|
|
||
| if (!diffOutput) { | ||
| console.log("No package.json files changed"); | ||
| setOutput("created", "false"); | ||
| process.exit(0); | ||
| } | ||
|
|
||
| const changedFiles = diffOutput.split("\n").filter(Boolean); | ||
| const packages = []; | ||
|
|
||
| for (const pkgFile of changedFiles) { | ||
| const content = JSON.parse(fs.readFileSync(pkgFile, "utf-8")); | ||
| const pkgName = content.name || ""; | ||
|
|
||
| if (content.private || !pkgName.startsWith("@knocklabs/")) { | ||
| continue; | ||
| } | ||
|
|
||
| // Only include packages where production dependencies changed, | ||
| // not devDependency-only updates | ||
| let oldDeps = {}; | ||
| try { | ||
| const oldContent = execSync(`git show "HEAD~1:${pkgFile}"`, { | ||
| encoding: "utf-8", | ||
| }); | ||
| oldDeps = JSON.parse(oldContent).dependencies || {}; | ||
| } catch { | ||
| // File may not exist in previous commit | ||
| } | ||
|
|
||
| const newDeps = content.dependencies || {}; | ||
| const sortedOld = JSON.stringify(oldDeps, Object.keys(oldDeps).sort()); | ||
| const sortedNew = JSON.stringify(newDeps, Object.keys(newDeps).sort()); | ||
|
|
||
| if (sortedOld === sortedNew) { | ||
| console.log(`Only devDependency changes in ${pkgName}, skipping`); | ||
| continue; | ||
| } | ||
|
|
||
| packages.push(pkgName); | ||
| } | ||
|
|
||
| const uniquePackages = [...new Set(packages)].sort(); | ||
|
|
||
| if (uniquePackages.length === 0) { | ||
| console.log("No published @knocklabs packages were affected"); | ||
| setOutput("created", "false"); | ||
| process.exit(0); | ||
| } | ||
|
|
||
| // Generate changeset file | ||
| const lines = [ | ||
| "---", | ||
| ...uniquePackages.map((pkg) => `"${pkg}": patch`), | ||
| "---", | ||
| "", | ||
| prTitle, | ||
| "", | ||
| ]; | ||
|
|
||
| fs.writeFileSync(changesetFile, lines.join("\n")); | ||
|
|
||
| console.log(`Created changeset: ${changesetFile}`); | ||
| console.log(fs.readFileSync(changesetFile, "utf-8")); | ||
| setOutput("created", "true"); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,77 @@ | ||
| name: Dependabot Changeset | ||
|
|
||
| on: | ||
| pull_request_target: | ||
| types: [opened] | ||
| workflow_dispatch: | ||
| inputs: | ||
| pr-number: | ||
| description: "Pull request number to add a changeset for" | ||
| required: true | ||
| type: string | ||
|
|
||
| jobs: | ||
| changeset: | ||
| name: Add changeset for dependency update | ||
| runs-on: ubuntu-latest | ||
| if: github.actor == 'dependabot[bot]' || github.event_name == 'workflow_dispatch' | ||
|
|
||
| steps: | ||
| - name: Get PR metadata | ||
| id: pr | ||
| env: | ||
| GH_TOKEN: ${{ secrets.KNOCK_ENG_BOT_GITHUB_TOKEN }} | ||
| EVENT_NAME: ${{ github.event_name }} | ||
| INPUT_PR_NUMBER: ${{ inputs.pr-number }} | ||
| REPO: ${{ github.repository }} | ||
| PR_NUMBER_FROM_EVENT: ${{ github.event.pull_request.number }} | ||
| PR_TITLE_FROM_EVENT: ${{ github.event.pull_request.title }} | ||
| PR_REF_FROM_EVENT: ${{ github.event.pull_request.head.ref }} | ||
| run: | | ||
| if [ "$EVENT_NAME" = "workflow_dispatch" ]; then | ||
| PR_JSON=$(gh pr view "$INPUT_PR_NUMBER" --repo "$REPO" --json title,headRefName) | ||
| echo "number=$INPUT_PR_NUMBER" >> "$GITHUB_OUTPUT" | ||
| echo "title=$(echo "$PR_JSON" | jq -r '.title')" >> "$GITHUB_OUTPUT" | ||
| echo "ref=$(echo "$PR_JSON" | jq -r '.headRefName')" >> "$GITHUB_OUTPUT" | ||
| else | ||
| echo "number=$PR_NUMBER_FROM_EVENT" >> "$GITHUB_OUTPUT" | ||
| echo "title=$PR_TITLE_FROM_EVENT" >> "$GITHUB_OUTPUT" | ||
| echo "ref=$PR_REF_FROM_EVENT" >> "$GITHUB_OUTPUT" | ||
| fi | ||
|
|
||
| # Checkout the base branch to get the trusted version of the script, | ||
| # then checkout the PR branch on top to get the package.json changes. | ||
| - name: Checkout base branch | ||
| uses: actions/checkout@v4 | ||
| with: | ||
| ref: main | ||
| token: ${{ secrets.KNOCK_ENG_BOT_GITHUB_TOKEN }} | ||
| path: base | ||
|
|
||
| - name: Checkout PR branch | ||
| uses: actions/checkout@v4 | ||
| with: | ||
| ref: ${{ steps.pr.outputs.ref }} | ||
| token: ${{ secrets.KNOCK_ENG_BOT_GITHUB_TOKEN }} | ||
| fetch-depth: 2 | ||
|
|
||
| - name: Setup Node.js | ||
| uses: actions/setup-node@v4 | ||
| with: | ||
| node-version-file: "package.json" | ||
|
|
||
| - name: Detect affected packages and create changeset | ||
| id: changeset | ||
| env: | ||
| PR_TITLE: ${{ steps.pr.outputs.title }} | ||
| PR_NUMBER: ${{ steps.pr.outputs.number }} | ||
| run: node base/.github/scripts/dependabot-changeset.js | ||
|
|
||
| - name: Commit and push changeset | ||
| if: steps.changeset.outputs.created == 'true' | ||
| run: | | ||
| git config user.name "knock-eng-bot" | ||
| git config user.email "knock-eng-bot@users.noreply.github.com" | ||
| git add .changeset/ | ||
| git commit -m "chore(deps): add changeset for dependency update" | ||
| git push | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.