From 314557191bb5bb7b81f12fe26907f90fb4996655 Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Fri, 27 Mar 2026 15:30:14 +0000 Subject: [PATCH 1/2] feat: workflow to automatically update java format + create PR --- .github/workflows/test.yml | 1 + .../workflows/update-google-java-format.yml | 164 ++++++++++++++++++ 2 files changed, 165 insertions(+) create mode 100644 .github/workflows/update-google-java-format.yml diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 43af83b..79835b4 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -1,6 +1,7 @@ name: Code Quality Checks on: + workflow_dispatch: pull_request: branches: - "**" diff --git a/.github/workflows/update-google-java-format.yml b/.github/workflows/update-google-java-format.yml new file mode 100644 index 0000000..280ac75 --- /dev/null +++ b/.github/workflows/update-google-java-format.yml @@ -0,0 +1,164 @@ +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: Check latest upstream version + id: check + shell: bash + run: | + set -euo pipefail + + current_jar="$(ls lib/google-java-format-*-all-deps.jar)" + current_version="${current_jar##*/google-java-format-}" + current_version="${current_version%-all-deps.jar}" + + release_json="$(curl -fsSL -H "Accept: application/vnd.github+json" "https://api.github.com/repos/google/google-java-format/releases/latest")" + + latest_version="$( + printf '%s' "$release_json" | node -e ' + const fs = require("fs"); + const release = JSON.parse(fs.readFileSync(0, "utf8")); + const asset = (release.assets || []).find((entry) => + /google-java-format-\d+\.\d+\.\d+-all-deps\.jar$/.test(entry.name) + ); + if (!asset) { + throw new Error("Unable to find all-deps jar asset in latest release"); + } + process.stdout.write(asset.name.replace(/^google-java-format-/, "").replace(/-all-deps\.jar$/, "")); + ' + )" + download_url="$( + printf '%s' "$release_json" | node -e ' + const fs = require("fs"); + const release = JSON.parse(fs.readFileSync(0, "utf8")); + const asset = (release.assets || []).find((entry) => + /google-java-format-\d+\.\d+\.\d+-all-deps\.jar$/.test(entry.name) + ); + if (!asset) { + throw new Error("Unable to find all-deps jar asset in latest release"); + } + process.stdout.write(asset.browser_download_url); + ' + )" + + echo "current_version=$current_version" >> "$GITHUB_OUTPUT" + echo "latest_version=$latest_version" >> "$GITHUB_OUTPUT" + echo "download_url=$download_url" >> "$GITHUB_OUTPUT" + + if [[ "$current_version" == "$latest_version" ]]; then + echo "update_available=false" >> "$GITHUB_OUTPUT" + exit 0 + fi + + echo "update_available=true" >> "$GITHUB_OUTPUT" + + - name: Download new formatter jar + if: steps.check.outputs.update_available == 'true' + shell: bash + run: | + set -euo pipefail + + rm -f lib/google-java-format-*-all-deps.jar + curl -fsSL \ + "${{ steps.check.outputs.download_url }}" \ + -o "lib/google-java-format-${{ steps.check.outputs.latest_version }}-all-deps.jar" + + - name: Update hardcoded jar path + if: steps.check.outputs.update_available == 'true' + shell: bash + run: | + set -euo pipefail + + node - <<'EOF' + const fs = require("fs"); + const indexPath = "index.js"; + const latestVersion = process.env.LATEST_VERSION; + const oldSource = fs.readFileSync(indexPath, "utf8"); + const newSource = oldSource.replace( + /google-java-format-\d+\.\d+\.\d+-all-deps\.jar/g, + `google-java-format-${latestVersion}-all-deps.jar` + ); + + if (oldSource === newSource) { + throw new Error("Failed to update google-java-format jar path in index.js"); + } + + fs.writeFileSync(indexPath, newSource); + EOF + env: + LATEST_VERSION: ${{ steps.check.outputs.latest_version }} + + - name: Run tests + if: steps.check.outputs.update_available == 'true' + run: ./test.sh + + - uses: actions/cache/save@v5 + name: Yarn Cache Save + if: ${{ steps.check.outputs.update_available == 'true' && github.ref == 'refs/heads/main' }} + with: + path: .yarn/cache + key: ${{ runner.os }}-yarn-v1-${{ hashFiles('yarn.lock') }} + + - name: Create pull request + if: steps.check.outputs.update_available == 'true' + uses: peter-evans/create-pull-request@v7 + with: + token: ${{ github.token }} + commit-message: "feat: adopt upstream google-java-format [${{ steps.check.outputs.latest_version }}]" + branch: ci/google-java-format-update-${{ steps.check.outputs.latest_version }} + delete-branch: true + title: "feat: adopt upstream google-java-format [${{ steps.check.outputs.latest_version }}]" + body: | + Updates bundled google-java-format from `${{ steps.check.outputs.current_version }}` to `${{ steps.check.outputs.latest_version }}`. + + - Replaces the jar in `lib/` + - Updates the hardcoded jar path in `index.js` + - Runs `./test.sh` From 50fff5880d37cabb2be7ee9ed929310eeb405fe7 Mon Sep 17 00:00:00 2001 From: russellwheatley Date: Fri, 27 Mar 2026 16:01:27 +0000 Subject: [PATCH 2/2] chore: allow running scripts manually --- .../workflows/update-google-java-format.yml | 102 ++---------- package.json | 1 + scripts/update-google-java-format.js | 151 ++++++++++++++++++ 3 files changed, 162 insertions(+), 92 deletions(-) create mode 100644 scripts/update-google-java-format.js diff --git a/.github/workflows/update-google-java-format.yml b/.github/workflows/update-google-java-format.yml index 280ac75..b815ff9 100644 --- a/.github/workflows/update-google-java-format.yml +++ b/.github/workflows/update-google-java-format.yml @@ -50,114 +50,32 @@ jobs: max_attempts: 3 command: yarn - - name: Check latest upstream version - id: check - shell: bash - run: | - set -euo pipefail - - current_jar="$(ls lib/google-java-format-*-all-deps.jar)" - current_version="${current_jar##*/google-java-format-}" - current_version="${current_version%-all-deps.jar}" - - release_json="$(curl -fsSL -H "Accept: application/vnd.github+json" "https://api.github.com/repos/google/google-java-format/releases/latest")" - - latest_version="$( - printf '%s' "$release_json" | node -e ' - const fs = require("fs"); - const release = JSON.parse(fs.readFileSync(0, "utf8")); - const asset = (release.assets || []).find((entry) => - /google-java-format-\d+\.\d+\.\d+-all-deps\.jar$/.test(entry.name) - ); - if (!asset) { - throw new Error("Unable to find all-deps jar asset in latest release"); - } - process.stdout.write(asset.name.replace(/^google-java-format-/, "").replace(/-all-deps\.jar$/, "")); - ' - )" - download_url="$( - printf '%s' "$release_json" | node -e ' - const fs = require("fs"); - const release = JSON.parse(fs.readFileSync(0, "utf8")); - const asset = (release.assets || []).find((entry) => - /google-java-format-\d+\.\d+\.\d+-all-deps\.jar$/.test(entry.name) - ); - if (!asset) { - throw new Error("Unable to find all-deps jar asset in latest release"); - } - process.stdout.write(asset.browser_download_url); - ' - )" - - echo "current_version=$current_version" >> "$GITHUB_OUTPUT" - echo "latest_version=$latest_version" >> "$GITHUB_OUTPUT" - echo "download_url=$download_url" >> "$GITHUB_OUTPUT" - - if [[ "$current_version" == "$latest_version" ]]; then - echo "update_available=false" >> "$GITHUB_OUTPUT" - exit 0 - fi - - echo "update_available=true" >> "$GITHUB_OUTPUT" - - - name: Download new formatter jar - if: steps.check.outputs.update_available == 'true' - shell: bash - run: | - set -euo pipefail - - rm -f lib/google-java-format-*-all-deps.jar - curl -fsSL \ - "${{ steps.check.outputs.download_url }}" \ - -o "lib/google-java-format-${{ steps.check.outputs.latest_version }}-all-deps.jar" - - - name: Update hardcoded jar path - if: steps.check.outputs.update_available == 'true' - shell: bash - run: | - set -euo pipefail - - node - <<'EOF' - const fs = require("fs"); - const indexPath = "index.js"; - const latestVersion = process.env.LATEST_VERSION; - const oldSource = fs.readFileSync(indexPath, "utf8"); - const newSource = oldSource.replace( - /google-java-format-\d+\.\d+\.\d+-all-deps\.jar/g, - `google-java-format-${latestVersion}-all-deps.jar` - ); - - if (oldSource === newSource) { - throw new Error("Failed to update google-java-format jar path in index.js"); - } - - fs.writeFileSync(indexPath, newSource); - EOF - env: - LATEST_VERSION: ${{ steps.check.outputs.latest_version }} + - name: Update formatter files + id: update + run: node ./scripts/update-google-java-format.js - name: Run tests - if: steps.check.outputs.update_available == 'true' + if: steps.update.outputs.updated == 'true' run: ./test.sh - uses: actions/cache/save@v5 name: Yarn Cache Save - if: ${{ steps.check.outputs.update_available == 'true' && github.ref == 'refs/heads/main' }} + 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.check.outputs.update_available == 'true' + 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.check.outputs.latest_version }}]" - branch: ci/google-java-format-update-${{ steps.check.outputs.latest_version }} + 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.check.outputs.latest_version }}]" + title: "feat: adopt upstream google-java-format [${{ steps.update.outputs.latest_version }}]" body: | - Updates bundled google-java-format from `${{ steps.check.outputs.current_version }}` to `${{ steps.check.outputs.latest_version }}`. + 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` diff --git a/package.json b/package.json index 60fc28c..3f81b65 100644 --- a/package.json +++ b/package.json @@ -17,6 +17,7 @@ ], "scripts": { "test": "bash ./test.sh", + "update-google-java-format": "node ./scripts/update-google-java-format.js", "shipit": "release-it" }, "contributors": [ diff --git a/scripts/update-google-java-format.js b/scripts/update-google-java-format.js new file mode 100644 index 0000000..3485e38 --- /dev/null +++ b/scripts/update-google-java-format.js @@ -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); +});