name: Sync Renovate changeset on: pull_request_target: paths: - '.github/workflows/sync_renovate-changesets.yml' - '**/yarn.lock' jobs: generate-changeset: runs-on: ubuntu-latest if: github.actor == 'renovate[bot]' && github.repository == 'backstage/backstage' steps: - name: Harden Runner uses: step-security/harden-runner@63c24ba6bd7ba022e95695ff85de572c04a18142 # v2.7.0 with: egress-policy: audit - name: Checkout uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1 with: fetch-depth: 2 ref: ${{ github.head_ref }} token: ${{ secrets.GH_SERVICE_ACCOUNT_TOKEN }} - name: Configure Git run: | git config --global user.email noreply@backstage.io git config --global user.name 'Github changeset workflow' - name: Generate changeset uses: actions/github-script@60a0d83039c74a4aee543508d2ffcb1c3799cdea # v7.0.1 with: script: | const { promises: fs } = require("fs"); // Parses package.json files and returns the package names async function getPackagesNames(files) { const names = []; for (const file of files) { const data = JSON.parse(await fs.readFile(file, "utf8")); if (!data.private) { names.push(data.name); } } return names; } async function createChangeset(fileName, packageBumps, packages) { let message = ""; for (const [pkg, bump] of packageBumps) { message = message + `Updated dependency \`${pkg}\` to \`${bump}\`.\n`; } const pkgs = packages.map((pkg) => `'${pkg}': patch`).join("\n"); const body = `---\n${pkgs}\n---\n\n${message.trim()}\n`; await fs.writeFile(fileName, body); } async function getBumps(files) { const bumps = new Map(); for (const file of files) { const { stdout: changes } = await exec.getExecOutput("git", [ "show", file, ]); for (const change of changes.split("\n")) { if (!change.startsWith("+ ")) { continue; } const match = change.match(/"(.*?)"/g); bumps.set(match[0].replace(/"/g, ""), match[1].replace(/"/g, "")); } } return bumps; } const branch = await exec.getExecOutput("git branch --show-current"); if (!branch.stdout.startsWith("renovate/")) { console.log("Not a renovate branch, skipping"); return; } const diffOutput = await exec.getExecOutput("git diff --name-only HEAD~1"); const diffFiles = diffOutput.stdout.split("\n"); if (diffFiles.find((f) => f.startsWith(".changeset"))) { console.log("Changeset already exists, skipping"); return; } const files = diffFiles .filter((file) => file !== "package.json") // skip root package.json .filter((file) => file.includes("package.json")); const packageNames = await getPackagesNames(files); if (!packageNames.length) { console.log("No package.json changes to published packages, skipping"); return; } const { stdout: shortHash } = await exec.getExecOutput( "git rev-parse --short HEAD" ); const fileName = `.changeset/renovate-${shortHash.trim()}.md`; const packageBumps = await getBumps(files); await createChangeset(fileName, packageBumps, packageNames); await exec.exec("git", ["add", fileName]); await exec.exec("git commit -C HEAD --amend --no-edit"); await exec.exec("git push --force");