Skip to content
Merged
Show file tree
Hide file tree
Changes from 2 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
1 change: 1 addition & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
name: Code Quality Checks

on:
workflow_dispatch:
pull_request:
branches:
- "**"
Expand Down
82 changes: 82 additions & 0 deletions .github/workflows/update-google-java-format.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
name: Update google-java-format

on:
workflow_dispatch:
schedule:
- cron: "0 6 1 * *"

permissions:
contents: write
pull-requests: write

concurrency:
group: ${{ github.workflow }}
cancel-in-progress: true

jobs:
update:
name: Check for upstream formatter update
runs-on: ubuntu-latest
timeout-minutes: 20

steps:
- uses: actions/checkout@v6
with:
fetch-depth: 0

- uses: actions/setup-node@v6
with:
node-version: 24

- name: Configure JDK
uses: actions/setup-java@v5
with:
distribution: "temurin"
java-version: "25"

- uses: actions/cache/restore@v5
name: Yarn Cache Restore
id: yarn-cache
with:
path: .yarn/cache
key: ${{ runner.os }}-yarn-v1-${{ hashFiles('yarn.lock') }}
restore-keys: ${{ runner.os }}-yarn-v1

- name: Yarn Install
uses: nick-fields/retry@v3
with:
timeout_minutes: 15
retry_wait_seconds: 30
max_attempts: 3
command: yarn

- name: Update formatter files
id: update
run: node ./scripts/update-google-java-format.js

- name: Run tests
if: steps.update.outputs.updated == 'true'
run: ./test.sh

- uses: actions/cache/save@v5
name: Yarn Cache Save
if: ${{ steps.update.outputs.updated == 'true' && github.ref == 'refs/heads/main' }}
with:
path: .yarn/cache
key: ${{ runner.os }}-yarn-v1-${{ hashFiles('yarn.lock') }}

- name: Create pull request
if: steps.update.outputs.updated == 'true'
uses: peter-evans/create-pull-request@v7
with:
token: ${{ github.token }}
commit-message: "feat: adopt upstream google-java-format [${{ steps.update.outputs.latest_version }}]"
branch: ci/google-java-format-update-${{ steps.update.outputs.latest_version }}
delete-branch: true
title: "feat: adopt upstream google-java-format [${{ steps.update.outputs.latest_version }}]"
body: |
Updates bundled google-java-format from `${{ steps.update.outputs.current_version }}` to `${{ steps.update.outputs.latest_version }}`.

- Replaces the jar in `lib/`
- Updates the hardcoded jar path in `index.js`
- Runs `./test.sh`
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@
],
"scripts": {
"test": "bash ./test.sh",
"update-google-java-format": "node ./scripts/update-google-java-format.js",
"shipit": "release-it"
},
"contributors": [
Expand Down
151 changes: 151 additions & 0 deletions scripts/update-google-java-format.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
#!/usr/bin/env node
"use strict";

const fs = require("fs");
const path = require("path");

const REPO_ROOT = path.resolve(__dirname, "..");
const LIB_DIR = path.join(REPO_ROOT, "lib");
const INDEX_PATH = path.join(REPO_ROOT, "index.js");
const RELEASE_URL =
"https://api.github.com/repos/google/google-java-format/releases/latest";
const JAR_PATTERN = /^google-java-format-(\d+\.\d+\.\d+)-all-deps\.jar$/;

function setOutput(name, value) {
if (!process.env.GITHUB_OUTPUT) {
return;
}

fs.appendFileSync(process.env.GITHUB_OUTPUT, `${name}=${String(value)}\n`);
}

function getCurrentJar() {
const jarFiles = fs.readdirSync(LIB_DIR).filter((fileName) => {
return JAR_PATTERN.test(fileName);
});

if (jarFiles.length !== 1) {
throw new Error(
`Expected exactly one google-java-format jar in lib/, found ${jarFiles.length}.`
);
}

const jarName = jarFiles[0];
const match = jarName.match(JAR_PATTERN);

return {
name: jarName,
version: match[1],
path: path.join(LIB_DIR, jarName),
};
}

async function fetchLatestRelease() {
const response = await fetch(RELEASE_URL, {
headers: {
Accept: "application/vnd.github+json",
"User-Agent": "nodejs-google-java-format-updater",
},
});

if (!response.ok) {
throw new Error(
`Failed to fetch latest release: ${response.status} ${response.statusText}`
);
}

return response.json();
}

function getLatestJarAsset(release) {
const asset = (release.assets || []).find((entry) => {
return JAR_PATTERN.test(entry.name);
});

if (!asset) {
throw new Error("Unable to find google-java-format all-deps jar asset.");
}

const match = asset.name.match(JAR_PATTERN);

return {
name: asset.name,
version: match[1],
downloadUrl: asset.browser_download_url,
};
}

async function downloadJar(downloadUrl, destinationPath) {
const response = await fetch(downloadUrl, {
headers: {
"User-Agent": "nodejs-google-java-format-updater",
},
});

if (!response.ok) {
throw new Error(
`Failed to download jar: ${response.status} ${response.statusText}`
);
}

const arrayBuffer = await response.arrayBuffer();
fs.writeFileSync(destinationPath, Buffer.from(arrayBuffer));
}

function updateIndexJarPath(nextVersion) {
const source = fs.readFileSync(INDEX_PATH, "utf8");
const updatedSource = source.replace(
/google-java-format-\d+\.\d+\.\d+-all-deps\.jar/g,
`google-java-format-${nextVersion}-all-deps.jar`
);

if (source === updatedSource) {
throw new Error("Failed to update google-java-format jar path in index.js.");
}

fs.writeFileSync(INDEX_PATH, updatedSource);
}

async function main() {
const currentJar = getCurrentJar();
const latestRelease = await fetchLatestRelease();
const latestJar = getLatestJarAsset(latestRelease);

setOutput("current_version", currentJar.version);
setOutput("latest_version", latestJar.version);
setOutput("download_url", latestJar.downloadUrl);
setOutput(
"update_available",
currentJar.version !== latestJar.version ? "true" : "false"
);

if (currentJar.version === latestJar.version) {
setOutput("updated", "false");
console.log(
`google-java-format is already up to date at ${currentJar.version}.`
);
return;
}

const targetJarPath = path.join(LIB_DIR, latestJar.name);
const tempJarPath = `${targetJarPath}.download`;

try {
await downloadJar(latestJar.downloadUrl, tempJarPath);
fs.rmSync(currentJar.path, { force: true });
fs.renameSync(tempJarPath, targetJarPath);
updateIndexJarPath(latestJar.version);
} finally {
fs.rmSync(tempJarPath, { force: true });
}

setOutput("updated", "true");
console.log(
`Updated google-java-format from ${currentJar.version} to ${latestJar.version}.`
);
}

main().catch((error) => {
console.error(error);
process.exit(1);
});
Loading