diff --git a/.github/scripts/.gitignore b/.github/scripts/.gitignore new file mode 100644 index 000000000..504afef81 --- /dev/null +++ b/.github/scripts/.gitignore @@ -0,0 +1,2 @@ +node_modules/ +package-lock.json diff --git a/.github/scripts/commit-and-tag.cjs b/.github/scripts/commit-and-tag.cjs new file mode 100644 index 000000000..5a238b9ae --- /dev/null +++ b/.github/scripts/commit-and-tag.cjs @@ -0,0 +1,71 @@ +// Copyright 2025 New Vector Ltd. +// +// SPDX-License-Identifier: AGPL-3.0-only +// Please see LICENSE in the repository root for full details. + +// @ts-check + +/** @param {import('@actions/github-script').AsyncFunctionArguments} AsyncFunctionArguments */ +module.exports = async ({ github, context }) => { + const fs = require("node:fs/promises"); + const { owner, repo } = context.repo; + const version = process.env.VERSION; + const parent = context.sha; + if (!version) throw new Error("VERSION is not defined"); + + const files = [ + "Cargo.toml", + "Cargo.lock", + "tools/syn2mas/package.json", + "tools/syn2mas/package-lock.json", + ]; + + /** @type {{path: string, mode: "100644", type: "blob", sha: string}[]} */ + const tree = []; + for (const file of files) { + const content = await fs.readFile(file); + const blob = await github.rest.git.createBlob({ + owner, + repo, + content: content.toString("base64"), + encoding: "base64", + }); + console.log(`Created blob for ${file}:`, blob.data.url); + + tree.push({ + path: file, + mode: "100644", + type: "blob", + sha: blob.data.sha, + }); + } + + const treeObject = await github.rest.git.createTree({ + owner, + repo, + tree, + base_tree: parent, + }); + console.log("Created tree:", treeObject.data.url); + + const commit = await github.rest.git.createCommit({ + owner, + repo, + message: version, + parents: [parent], + tree: treeObject.data.sha, + }); + console.log("Created commit:", commit.data.url); + + const tag = await github.rest.git.createTag({ + owner, + repo, + tag: `v${version}`, + message: version, + type: "commit", + object: commit.data.sha, + }); + console.log("Created tag:", tag.data.url); + + return { commit: commit.data.sha, tag: tag.data.sha }; +}; diff --git a/.github/scripts/create-release-branch.cjs b/.github/scripts/create-release-branch.cjs new file mode 100644 index 000000000..c7c0018a3 --- /dev/null +++ b/.github/scripts/create-release-branch.cjs @@ -0,0 +1,22 @@ +// Copyright 2025 New Vector Ltd. +// +// SPDX-License-Identifier: AGPL-3.0-only +// Please see LICENSE in the repository root for full details. + +// @ts-check + +/** @param {import('@actions/github-script').AsyncFunctionArguments} AsyncFunctionArguments */ +module.exports = async ({ github, context }) => { + const { owner, repo } = context.repo; + const branch = process.env.BRANCH; + const sha = process.env.SHA; + if (!sha) throw new Error("SHA is not defined"); + + await github.rest.git.createRef({ + owner, + repo, + ref: `refs/heads/${branch}`, + sha, + }); + console.log(`Created branch ${branch} from ${sha}`); +}; diff --git a/.github/scripts/create-version-tag.cjs b/.github/scripts/create-version-tag.cjs new file mode 100644 index 000000000..97536c5ff --- /dev/null +++ b/.github/scripts/create-version-tag.cjs @@ -0,0 +1,24 @@ +// Copyright 2025 New Vector Ltd. +// +// SPDX-License-Identifier: AGPL-3.0-only +// Please see LICENSE in the repository root for full details. + +// @ts-check + +/** @param {import('@actions/github-script').AsyncFunctionArguments} AsyncFunctionArguments */ +module.exports = async ({ github, context }) => { + const { owner, repo } = context.repo; + const version = process.env.VERSION; + const tagSha = process.env.TAG_SHA; + + if (!version) throw new Error("VERSION is not defined"); + if (!tagSha) throw new Error("TAG_SHA is not defined"); + + const tag = await github.rest.git.createRef({ + owner, + repo, + ref: `refs/tags/v${version}`, + sha: tagSha, + }); + console.log("Created tag ref:", tag.data.url); +}; diff --git a/.github/scripts/merge-back.cjs b/.github/scripts/merge-back.cjs new file mode 100644 index 000000000..30b08329d --- /dev/null +++ b/.github/scripts/merge-back.cjs @@ -0,0 +1,60 @@ +// Copyright 2025 New Vector Ltd. +// +// SPDX-License-Identifier: AGPL-3.0-only +// Please see LICENSE in the repository root for full details. + +// @ts-check + +/** @param {import('@actions/github-script').AsyncFunctionArguments} AsyncFunctionArguments */ +module.exports = async ({ github, context }) => { + const { owner, repo } = context.repo; + const sha = process.env.SHA; + const branch = `ref-merge/${sha}`; + if (!sha) throw new Error("SHA is not defined"); + + await github.rest.git.createRef({ + owner, + repo, + ref: `refs/heads/${branch}`, + sha, + }); + console.log(`Created branch ${branch} to ${sha}`); + + // Create a PR to merge the branch back to main + const pr = await github.rest.pulls.create({ + owner, + repo, + head: branch, + base: "main", + title: "Automatic merge back to main", + body: "This pull request was automatically created by the release workflow. It merges the release branch back to main.", + maintainer_can_modify: true, + }); + console.log( + `Created pull request #${pr.data.number} to merge the release branch back to main`, + ); + console.log(`PR URL: ${pr.data.html_url}`); + + // Add the `T-Task` label to the PR + await github.rest.issues.addLabels({ + owner, + repo, + issue_number: pr.data.number, + labels: ["T-Task"], + }); + + // Enable auto-merge on the PR + await github.graphql( + ` + mutation AutoMerge($id: ID!) { + enablePullRequestAutoMerge(input: { + pullRequestId: $id, + mergeMethod: MERGE, + }) { + clientMutationId + } + } + `, + { id: pr.data.node_id }, + ); +}; diff --git a/.github/scripts/package.json b/.github/scripts/package.json new file mode 100644 index 000000000..8fc6ec2cc --- /dev/null +++ b/.github/scripts/package.json @@ -0,0 +1,7 @@ +{ + "private": true, + "devDependencies": { + "@actions/github-script": "github:actions/github-script", + "typescript": "^5.7.3" + } +} diff --git a/.github/scripts/update-release-branch.cjs b/.github/scripts/update-release-branch.cjs new file mode 100644 index 000000000..0a94aa217 --- /dev/null +++ b/.github/scripts/update-release-branch.cjs @@ -0,0 +1,22 @@ +// Copyright 2025 New Vector Ltd. +// +// SPDX-License-Identifier: AGPL-3.0-only +// Please see LICENSE in the repository root for full details. + +// @ts-check + +/** @param {import('@actions/github-script').AsyncFunctionArguments} AsyncFunctionArguments */ +module.exports = async ({ github, context }) => { + const { owner, repo } = context.repo; + const branch = process.env.BRANCH; + const sha = process.env.SHA; + if (!sha) throw new Error("SHA is not defined"); + + await github.rest.git.updateRef({ + owner, + repo, + ref: `heads/${branch}`, + sha, + }); + console.log(`Updated branch ${branch} to ${sha}`); +}; diff --git a/.github/scripts/update-unstable-tag.cjs b/.github/scripts/update-unstable-tag.cjs new file mode 100644 index 000000000..1958adcec --- /dev/null +++ b/.github/scripts/update-unstable-tag.cjs @@ -0,0 +1,21 @@ +// Copyright 2025 New Vector Ltd. +// +// SPDX-License-Identifier: AGPL-3.0-only +// Please see LICENSE in the repository root for full details. + +// @ts-check + +/** @param {import('@actions/github-script').AsyncFunctionArguments} AsyncFunctionArguments */ +module.exports = async ({ github, context }) => { + const { owner, repo } = context.repo; + const sha = context.sha; + + const tag = await github.rest.git.updateRef({ + owner, + repo, + force: true, + ref: "tags/unstable", + sha, + }); + console.log("Updated tag ref:", tag.data.url); +}; diff --git a/.github/workflows/build.yaml b/.github/workflows/build.yaml index d35b30e33..ee734f69f 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -444,21 +444,18 @@ jobs: path: artifacts merge-multiple: true + - name: Checkout the code + uses: actions/checkout@v4.2.2 + with: + sparse-checkout: | + .github/scripts + - name: Update unstable git tag uses: actions/github-script@v7.0.1 with: script: | - const [owner, repo] = process.env.GITHUB_REPOSITORY.split("/"); - const sha = process.env.GITHUB_SHA; - - const tag = await github.rest.git.updateRef({ - owner, - repo, - force: true, - ref: 'tags/unstable', - sha, - }); - console.log("Updated tag ref:", tag.data.url); + const script = require('./.github/scripts/update-unstable-tag.cjs'); + await script({ core, github, context }); - name: Update unstable release uses: softprops/action-gh-release@v2 diff --git a/.github/workflows/merge-back.yaml b/.github/workflows/merge-back.yaml index 5c1166bfd..5c80ddac7 100644 --- a/.github/workflows/merge-back.yaml +++ b/.github/workflows/merge-back.yaml @@ -14,7 +14,16 @@ jobs: name: Merge back the reference to main runs-on: ubuntu-24.04 + permissions: + contents: read + steps: + - name: Checkout the code + uses: actions/checkout@v4 + with: + sparse-checkout: | + .github/scripts + - name: Push branch and open a PR uses: actions/github-script@v7.0.1 env: @@ -22,51 +31,5 @@ jobs: with: github-token: ${{ secrets.BOT_GITHUB_TOKEN }} script: | - const [owner, repo] = process.env.GITHUB_REPOSITORY.split('/'); - const sha = process.env.SHA; - const branch = `ref-merge/${sha}`; - const ref = `heads/${branch}`; - - await github.rest.git.createRef({ - owner, - repo, - ref, - sha, - }); - console.log(`Created branch ${branch} to ${sha}`); - - // Create a PR to merge the branch back to main - const pr = await github.rest.pulls.create({ - owner, - repo, - head: branch, - base: 'main', - title: `Automatic merge back to main`, - body: `This pull request was automatically created by the release workflow. It merges the release branch back to main.`, - maintainer_can_modify: true, - }); - console.log(`Created pull request #${pr.data.number} to merge the release branch back to main`); - console.log(`PR URL: ${pr.data.html_url}`); - - // Add the `T-Task` label to the PR - await github.rest.issues.addLabels({ - owner, - repo, - issue_number: pr.data.number, - labels: ['T-Task'], - }); - - // Enable auto-merge on the PR - await github.graphql( - ` - mutation AutoMerge($id: ID!) { - enablePullRequestAutoMerge(input: { - pullRequestId: $id, - mergeMethod: MERGE, - }) { - clientMutationId - } - } - `, - { id: pr.data.node_id }, - ); + const script = require('./.github/scripts/merge-back.js'); + await script({ core, github, context }); diff --git a/.github/workflows/release-branch.yaml b/.github/workflows/release-branch.yaml index 5b56dcc29..087402d66 100644 --- a/.github/workflows/release-branch.yaml +++ b/.github/workflows/release-branch.yaml @@ -89,6 +89,12 @@ jobs: needs: [tag, compute-version, localazy] steps: + - name: Checkout the code + uses: actions/checkout@v4.2.2 + with: + sparse-checkout: | + .github/scripts + - name: Create a new release branch uses: actions/github-script@v7.0.1 env: @@ -97,15 +103,5 @@ jobs: with: github-token: ${{ secrets.BOT_GITHUB_TOKEN }} script: | - const [owner, repo] = process.env.GITHUB_REPOSITORY.split('/'); - const branch = process.env.BRANCH; - const sha = process.env.SHA; - const ref = `refs/heads/${branch}`; - - await github.rest.git.createRef({ - owner, - repo, - ref, - sha, - }); - console.log(`Created branch ${branch} from ${sha}`); + const script = require('./.github/scripts/create-release-branch.js'); + await script({ core, github, context }); diff --git a/.github/workflows/release-bump.yaml b/.github/workflows/release-bump.yaml index 898033182..b2ee17d80 100644 --- a/.github/workflows/release-bump.yaml +++ b/.github/workflows/release-bump.yaml @@ -70,6 +70,12 @@ jobs: needs: [tag, compute-version] steps: + - name: Checkout the code + uses: actions/checkout@v4.2.2 + with: + sparse-checkout: | + .github/scripts + - name: Update the release branch uses: actions/github-script@v7.0.1 env: @@ -78,15 +84,5 @@ jobs: with: github-token: ${{ secrets.BOT_GITHUB_TOKEN }} script: | - const [owner, repo] = process.env.GITHUB_REPOSITORY.split('/'); - const branch = process.env.BRANCH; - const sha = process.env.SHA; - const ref = `heads/${branch}`; - - await github.rest.git.updateRef({ - owner, - repo, - ref, - sha, - }); - console.log(`Updated branch ${branch} to ${sha}`); + const script = require('./.github/scripts/update-release-branch.cjs'); + await script({ core, github, context }); diff --git a/.github/workflows/tag.yaml b/.github/workflows/tag.yaml index 68aeedc4b..b86579c1d 100644 --- a/.github/workflows/tag.yaml +++ b/.github/workflows/tag.yaml @@ -53,65 +53,8 @@ jobs: # Commit & tag with the actions token, so that they get signed # This returns the commit sha and the tag object sha script: | - const fs = require("fs/promises"); - const [owner, repo] = process.env.GITHUB_REPOSITORY.split("/"); - const version = process.env.VERSION; - const parent = context.sha; - - const files = [ - "Cargo.toml", - "Cargo.lock", - "tools/syn2mas/package.json", - "tools/syn2mas/package-lock.json", - ]; - - const tree = []; - for (const file of files) { - const content = await fs.readFile(file); - const blob = await github.rest.git.createBlob({ - owner, - repo, - content: content.toString("base64"), - encoding: "base64", - }); - console.log(`Created blob for ${file}:`, blob.data.url); - - tree.push({ - path: file, - mode: "100644", - type: "blob", - sha: blob.data.sha, - }); - } - - const treeObject = await github.rest.git.createTree({ - owner, - repo, - tree, - base_tree: parent, - }); - console.log("Created tree:", treeObject.data.url); - - const commit = await github.rest.git.createCommit({ - owner, - repo, - message: version, - parents: [parent], - tree: treeObject.data.sha, - }); - console.log("Created commit:", commit.data.url); - - const tag = await github.rest.git.createTag({ - owner, - repo, - tag: `v${version}`, - message: version, - type: "commit", - object: commit.data.sha, - }); - console.log("Created tag:", tag.data.url); - - return { commit: commit.data.sha, tag: tag.data.sha }; + const script = require('./.github/scripts/commit-and-tag.cjs'); + return await script({ core, github, context }); - name: Update the refs uses: actions/github-script@v7.0.1 @@ -123,16 +66,5 @@ jobs: # Update the refs with the bot token, so that workflows are triggered github-token: ${{ secrets.BOT_GITHUB_TOKEN }} script: | - const [owner, repo] = process.env.GITHUB_REPOSITORY.split("/"); - const version = process.env.VERSION; - const commit = process.env.COMMIT_SHA; - const tagSha = process.env.TAG_SHA; - const branch = process.env.GITHUB_REF_NAME; - - const tag = await github.rest.git.createRef({ - owner, - repo, - ref: `refs/tags/v${version}`, - sha: tagSha, - }); - console.log("Created tag ref:", tag.data.url); + const script = require('./.github/scripts/create-version-tag.cjs'); + await script({ core, github, context });