diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 2a89f45..a838412 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -11,14 +11,14 @@ jobs: id-token: write # < REQUIRED FOR OIDC steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: fetch-depth: 0 - name: git config run: | git config --global user.name 'Invertase Publisher' git config --global user.email 'oss@invertase.io' - - uses: actions/setup-node@v6 + - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6 with: node-version: "lts/*" registry-url: "https://registry.npmjs.org" diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 43af83b..8f0639c 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: - "**" @@ -18,18 +19,18 @@ jobs: timeout-minutes: 15 runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 with: fetch-depth: 1 - - uses: actions/setup-node@v6 + - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6 with: node-version: 24 - name: Configure JDK - uses: actions/setup-java@v5 + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 with: distribution: "temurin" java-version: "25" - - uses: actions/cache/restore@v5 + - uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5 name: Yarn Cache Restore id: yarn-cache with: @@ -37,7 +38,7 @@ jobs: key: ${{ runner.os }}-yarn-v1-${{ hashFiles('yarn.lock') }} restore-keys: ${{ runner.os }}-yarn-v1 - name: Yarn Install - uses: nick-fields/retry@v3 + uses: nick-fields/retry@ce71cc2ab81d554ebbe88c79ab5975992d79ba08 # v3 with: timeout_minutes: 15 retry_wait_seconds: 30 @@ -45,7 +46,7 @@ jobs: command: yarn - name: Lint run: ./test.sh - - uses: actions/cache/save@v5 + - uses: actions/cache/save@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5 name: Yarn Cache Save if: "${{ github.ref == 'refs/heads/main' }}" with: diff --git a/.github/workflows/update-google-java-format.yml b/.github/workflows/update-google-java-format.yml new file mode 100644 index 0000000..de2ad98 --- /dev/null +++ b/.github/workflows/update-google-java-format.yml @@ -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@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6 + with: + fetch-depth: 0 + + - uses: actions/setup-node@53b83947a5a98c8d113130e565377fae1a50d02f # v6 + with: + node-version: 24 + + - name: Configure JDK + uses: actions/setup-java@be666c2fcd27ec809703dec50e508c2fdc7f6654 # v5 + with: + distribution: "temurin" + java-version: "25" + + - uses: actions/cache/restore@668228422ae6a00e4ad889ee87cd7109ec5666a7 # 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@ce71cc2ab81d554ebbe88c79ab5975992d79ba08 # 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@668228422ae6a00e4ad889ee87cd7109ec5666a7 # 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@c0f553fe549906ede9cf27b5156039d195d2ece0 # v8.1.0 + 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` 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); +});