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
51 changes: 10 additions & 41 deletions .github/scripts/create-verified-commit.js
Original file line number Diff line number Diff line change
@@ -1,65 +1,34 @@
const fs = require('fs');
const { execSync } = require('child_process');
const { createVerifiedCommit } = require('./git-utils');

module.exports = async ({ github, context }) => {
if (!context.commitMessage) {
throw new Error('commitMessage is required in context');
}

const headSha = execSync('git rev-parse HEAD').toString().trim();
const { owner, repo } = context.repo;

const changedFiles = execSync('git diff --name-only HEAD').toString().trim().split('\n').filter(f => f);
const commitSha = await createVerifiedCommit({ github, owner, repo, commitMessage: context.commitMessage });

if (changedFiles.length === 0) {
console.log('No changes to commit');
if (!commitSha) {
return;
}

const treeData = [];
for (const file of changedFiles) {
const content = fs.readFileSync(file, 'utf8');
treeData.push({
path: file,
mode: '100644',
type: 'blob',
content: content
});
}

const tree = await github.rest.git.createTree({
owner: context.repo.owner,
repo: context.repo.repo,
tree: treeData,
base_tree: headSha
});

const commit = await github.rest.git.createCommit({
owner: context.repo.owner,
repo: context.repo.repo,
message: context.commitMessage,
tree: tree.data.sha,
parents: [headSha]
});

let targetBranch;
const ref = context.ref || '';

if (ref.startsWith('refs/heads/')) {
targetBranch = context.ref.split('/').pop();
} else {
const repo = await github.rest.repos.get({
owner: context.repo.owner,
repo: context.repo.repo
});
targetBranch = repo.data.default_branch;
const repoData = await github.rest.repos.get({ owner, repo });
targetBranch = repoData.data.default_branch;
}

await github.rest.git.updateRef({
owner: context.repo.owner,
repo: context.repo.repo,
owner,
repo,
ref: `heads/${targetBranch}`,
sha: commit.data.sha
sha: commitSha
});

console.log(`Created verified commit: ${commit.data.sha} on branch ${targetBranch}`);
console.log(`Created verified commit: ${commitSha} on branch ${targetBranch}`);
};
40 changes: 40 additions & 0 deletions .github/scripts/create-verified-pr.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
const { createVerifiedCommit } = require('./git-utils');

module.exports = async ({ github, context }) => {
if (!context.commitMessage) {
throw new Error('commitMessage is required in context');
}
if (!context.branchName) {
throw new Error('branchName is required in context');
}

const { owner, repo } = context.repo;

const commitSha = await createVerifiedCommit({ github, owner, repo, commitMessage: context.commitMessage });

if (!commitSha) {
return;
}

const repoData = await github.rest.repos.get({ owner, repo });
const defaultBranch = repoData.data.default_branch;

await github.rest.git.createRef({
owner,
repo,
ref: `refs/heads/${context.branchName}`,
sha: commitSha
});

const pr = await github.rest.pulls.create({
owner,
repo,
title: context.prTitle || context.commitMessage,
head: context.branchName,
base: defaultBranch,
body: context.prBody || ''
});

console.log(`Created PR #${pr.data.number}: ${pr.data.html_url}`);
};

47 changes: 47 additions & 0 deletions .github/scripts/git-utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
const fs = require('fs');
const { execSync } = require('child_process');

/**
* Creates a verified commit from the current working tree changes using the GitHub API.
* Returns the commit SHA, or null if there are no changes.
*/
async function createVerifiedCommit({ github, owner, repo, commitMessage }) {
const headSha = execSync('git rev-parse HEAD').toString().trim();

const changedFiles = execSync('git diff --name-only HEAD').toString().trim().split('\n').filter(f => f);

if (changedFiles.length === 0) {
console.log('No changes to commit');
return null;
}

const treeData = [];
for (const file of changedFiles) {
const content = fs.readFileSync(file, 'utf8');
treeData.push({
path: file,
mode: '100644',
type: 'blob',
content: content
});
}

const tree = await github.rest.git.createTree({
owner,
repo,
tree: treeData,
base_tree: headSha
});

const commit = await github.rest.git.createCommit({
owner,
repo,
message: commitMessage,
tree: tree.data.sha,
parents: [headSha]
});

return commit.data.sha;
}

module.exports = { createVerifiedCommit };
50 changes: 50 additions & 0 deletions .github/workflows/update-changelog.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
name: "Update Changelog"

on:
release:
types: [released]

jobs:
update-changelog:
name: Update CHANGELOG.md
runs-on: ubuntu-latest
permissions:
contents: write
steps:
- name: Checkout code
uses: actions/checkout@v6

- name: Update Changelog
uses: stefanzweifel/changelog-updater-action@v1
with:
latest-version: ${{ github.event.release.tag_name }}
release-notes: ${{ github.event.release.body }}

- name: Check for changes
id: changes
run: |
if [ -n "$(git status --porcelain CHANGELOG.md)" ]; then
echo "has_changes=true" >> $GITHUB_OUTPUT
else
echo "has_changes=false" >> $GITHUB_OUTPUT
fi

- name: Create PR with changes
if: steps.changes.outputs.has_changes == 'true'
uses: actions/github-script@v8
with:
script: |
const script = require('${{ github.workspace }}/.github/scripts/create-verified-pr.js');
const [owner, repo] = process.env.GITHUB_REPOSITORY.split('/');
const tagName = '${{ github.event.release.tag_name }}';
await script({
github,
context: {
...context,
repo: { owner, repo },
commitMessage: `Update CHANGELOG.md for ${tagName}`,
branchName: `update-changelog-${tagName}`,
prTitle: `Update CHANGELOG.md for ${tagName}`,
prBody: `Automated update of CHANGELOG.md for release ${tagName}.`
}
});