Skip to content

Check Upstream Changes #3

Check Upstream Changes

Check Upstream Changes #3

name: Check Upstream Changes
on:
schedule:
- cron: '0 9 * * *' # Daily at 09:00 UTC
workflow_dispatch: # Manual trigger
permissions:
contents: read
issues: write
jobs:
check-upstream:
runs-on: ubuntu-latest
steps:
- name: Checkout with submodule
uses: actions/checkout@v4
with:
submodules: true
fetch-depth: 0
- name: Fetch upstream develop
run: git -C upstream fetch origin develop
- name: Compare submodule to upstream develop
id: compare
run: |
CURRENT=$(git -C upstream rev-parse HEAD)
LATEST=$(git -C upstream rev-parse origin/develop)
CURRENT_SHORT=${CURRENT:0:8}
LATEST_SHORT=${LATEST:0:8}
echo "current=$CURRENT" >> "$GITHUB_OUTPUT"
echo "latest=$LATEST" >> "$GITHUB_OUTPUT"
echo "current_short=$CURRENT_SHORT" >> "$GITHUB_OUTPUT"
echo "latest_short=$LATEST_SHORT" >> "$GITHUB_OUTPUT"
if [ "$CURRENT" = "$LATEST" ]; then
echo "Submodule is up to date with upstream develop."
echo "up_to_date=true" >> "$GITHUB_OUTPUT"
else
echo "Submodule is behind upstream develop ($CURRENT_SHORT → $LATEST_SHORT)."
echo "up_to_date=false" >> "$GITHUB_OUTPUT"
fi
- name: Diff watched paths
id: diff
if: steps.compare.outputs.up_to_date == 'false'
run: |
CURRENT=${{ steps.compare.outputs.current }}
LATEST=${{ steps.compare.outputs.latest }}
CHANGED=$(git -C upstream diff --name-only "$CURRENT" "$LATEST" -- \
"booklore-api/src/main/resources/db/migration/" \
"booklore-api/src/main/java/org/booklore/model/entity/" \
"booklore-api/src/main/java/org/booklore/repository/")
if [ -z "$CHANGED" ]; then
echo "No changes in watched paths."
echo "has_changes=false" >> "$GITHUB_OUTPUT"
else
echo "Changes detected in watched paths."
echo "has_changes=true" >> "$GITHUB_OUTPUT"
echo "$CHANGED" > /tmp/changed_files.txt
git -C upstream diff "$CURRENT" "$LATEST" -- \
"booklore-api/src/main/resources/db/migration/" \
"booklore-api/src/main/java/org/booklore/model/entity/" \
"booklore-api/src/main/java/org/booklore/repository/" \
> /tmp/upstream.patch
fi
- name: Check for existing open issue
id: existing
if: steps.diff.outputs.has_changes == 'true'
uses: actions/github-script@v7
with:
result-encoding: string
script: |
const sha = '${{ steps.compare.outputs.latest_short }}';
const { data: issues } = await github.rest.issues.listForRepo({
owner: context.repo.owner,
repo: context.repo.repo,
state: 'open',
labels: 'upstream-sync',
});
const duplicate = issues.find(i => i.title.includes(sha));
return duplicate ? String(duplicate.number) : '';
- name: Ensure upstream-sync label exists
if: steps.diff.outputs.has_changes == 'true' && steps.existing.outputs.result == ''
uses: actions/github-script@v7
with:
script: |
try {
await github.rest.issues.getLabel({
owner: context.repo.owner,
repo: context.repo.repo,
name: 'upstream-sync',
});
} catch (e) {
await github.rest.issues.createLabel({
owner: context.repo.owner,
repo: context.repo.repo,
name: 'upstream-sync',
color: '0075ca',
description: 'Upstream Booklore changes that need to be ported',
});
}
- name: Create issue
if: steps.diff.outputs.has_changes == 'true' && steps.existing.outputs.result == ''
uses: actions/github-script@v7
with:
script: |
const fs = require('fs');
const currentShort = '${{ steps.compare.outputs.current_short }}';
const latestShort = '${{ steps.compare.outputs.latest_short }}';
const upstreamUrl = 'https://github.com/booklore-app/booklore';
const changedFiles = fs.readFileSync('/tmp/changed_files.txt', 'utf8')
.trim().split('\n').filter(Boolean);
const migrations = changedFiles.filter(f => f.includes('/db/migration/'));
const entities = changedFiles.filter(f => f.includes('/model/entity/'));
const repositories = changedFiles.filter(f => f.includes('/repository/'));
let patch = fs.readFileSync('/tmp/upstream.patch', 'utf8');
const MAX = 50_000;
if (patch.length > MAX) {
patch = patch.slice(0, MAX) +
'\n\n… diff truncated. ' +
`[View full diff on GitHub](${upstreamUrl}/compare/${currentShort}...${latestShort})`;
}
const fileList = (files) =>
files.map(f => `- \`${f.split('/').pop()}\``).join('\n');
let body = `## Upstream changes detected\n\n`;
body += `The upstream \`develop\` branch has moved ahead of our pinned submodule commit.\n\n`;
body += `| | Commit |\n|---|---|\n`;
body += `| **Submodule (current)** | [\`${currentShort}\`](${upstreamUrl}/commit/${currentShort}) |\n`;
body += `| **Upstream develop (latest)** | [\`${latestShort}\`](${upstreamUrl}/commit/${latestShort}) |\n`;
body += `| **Full diff** | [Compare on GitHub](${upstreamUrl}/compare/${currentShort}...${latestShort}) |\n\n`;
if (migrations.length > 0) {
body += `### 🗄️ New / changed Flyway migrations (${migrations.length})\n\n`;
body += `Port these to Liquibase in \`src/main/resources/db/changelog/changelogs/\`.\n\n`;
body += fileList(migrations) + '\n\n';
}
if (entities.length > 0) {
body += `### 🏗️ Changed entities (${entities.length})\n\n`;
body += `Review for new fields that may require Liquibase columns or \`\${datetime.type}\` / \`\${id.type}\` substitutions.\n\n`;
body += fileList(entities) + '\n\n';
}
if (repositories.length > 0) {
body += `### 🔍 Changed repositories (${repositories.length})\n\n`;
body += `Check for new \`nativeQuery = true\` annotations — these need JPQL rewrites in our overlay for SQLite/PostgreSQL compatibility.\n\n`;
body += fileList(repositories) + '\n\n';
}
body += `### ✅ Checklist\n\n`;
body += `- [ ] Update submodule to \`${latestShort}\` (or the latest stable tag)\n`;
if (migrations.length > 0)
body += `- [ ] Port ${migrations.length} new Flyway migration(s) to Liquibase changelogs\n`;
if (entities.length > 0)
body += `- [ ] Review ${entities.length} changed entity file(s) for schema impact\n`;
if (repositories.length > 0)
body += `- [ ] Audit ${repositories.length} changed repository file(s) for new native queries\n`;
body += `- [ ] Diff upstream \`build.gradle\` for dependency changes\n`;
body += `- [ ] Build and run full test suite (\`./gradlew test\`)\n\n`;
body += `<details>\n<summary>Full diff (watched paths only)</summary>\n\n`;
body += `\`\`\`diff\n${patch}\n\`\`\`\n</details>\n`;
await github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title: `Upstream sync required: ${latestShort}`,
body,
assignees: ['binarymelon'],
labels: ['upstream-sync'],
});
console.log(`Issue created for upstream commit ${latestShort}`);