Skip to content
Open
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
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