diff --git a/.github/actions/build-frontend/action.yml b/.github/actions/build-frontend/action.yml new file mode 100644 index 000000000..a7a1fe2c5 --- /dev/null +++ b/.github/actions/build-frontend/action.yml @@ -0,0 +1,20 @@ +name: Build the frontend assets +description: Installs Node.js and builds the frontend assets from the frontend directory + +runs: + using: composite + steps: + - name: Install Node + uses: actions/setup-node@v4.2.0 + with: + node-version: '22' + + - name: Install dependencies + run: npm ci + working-directory: ./frontend + shell: sh + + - name: Build the frontend assets + run: npm run build + working-directory: ./frontend + shell: sh diff --git a/.github/actions/build-policies/action.yml b/.github/actions/build-policies/action.yml new file mode 100644 index 000000000..dfe78917d --- /dev/null +++ b/.github/actions/build-policies/action.yml @@ -0,0 +1,15 @@ +name: Build the Open Policy Agent policies +description: Installs OPA and builds the policies + +runs: + using: composite + steps: + - name: Install Open Policy Agent + uses: open-policy-agent/setup-opa@v2.2.0 + with: + version: 0.70.0 + + - name: Build the policies + run: make + working-directory: ./policies + shell: sh 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 9ed4d39b9..ee734f69f 100644 --- a/.github/workflows/build.yaml +++ b/.github/workflows/build.yaml @@ -59,27 +59,8 @@ jobs: - name: Checkout the code uses: actions/checkout@v4.2.2 - - name: Setup OPA - uses: open-policy-agent/setup-opa@v2.2.0 - with: - version: 0.64.1 - - - name: Install frontend Node - uses: actions/setup-node@v4.2.0 - with: - node-version: 20 - - - name: Install frontend Node dependencies - working-directory: ./frontend - run: npm ci - - - name: Build frontend - working-directory: ./frontend - run: npm run build - - - name: Build policies - working-directory: ./policies - run: make + - uses: ./.github/actions/build-frontend + - uses: ./.github/actions/build-policies - name: Prepare assets artifact run: | @@ -463,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/ci.yaml b/.github/workflows/ci.yaml index e2e956d49..2e1aee21b 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -63,7 +63,7 @@ jobs: - name: Install Node uses: actions/setup-node@v4.2.0 with: - node-version: 20 + node-version: 22 - name: Install Node dependencies working-directory: ./frontend @@ -88,7 +88,7 @@ jobs: - name: Install Node uses: actions/setup-node@v4.2.0 with: - node-version: 20 + node-version: 22 - name: Install Node dependencies working-directory: ./frontend @@ -135,11 +135,10 @@ jobs: - name: Checkout the code uses: actions/checkout@v4.2.2 - - name: Install toolchain - run: | - rustup toolchain install nightly - rustup default nightly - rustup component add rustfmt + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@nightly + with: + components: rustfmt - name: Check style run: cargo fmt --all -- --check @@ -183,14 +182,7 @@ jobs: - name: Setup sccache uses: mozilla-actions/sccache-action@v0.0.7 - - name: Install Node - uses: actions/setup-node@v4.2.0 - with: - node-version: 20 - - - name: Install Node dependencies - working-directory: ./frontend - run: npm ci + - uses: ./.github/actions/build-frontend - name: Update the schemas run: sh ./misc/update.sh @@ -224,11 +216,10 @@ jobs: - name: Checkout the code uses: actions/checkout@v4.2.2 - - name: Install toolchain - run: | - rustup toolchain install 1.84.0 - rustup default 1.84.0 - rustup component add clippy + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@1.84.0 + with: + components: clippy - name: Setup OPA uses: open-policy-agent/setup-opa@v2.2.0 @@ -258,10 +249,8 @@ jobs: - name: Checkout uses: actions/checkout@v4.2.2 - - name: Install toolchain - run: | - rustup toolchain install stable - rustup default stable + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@stable - name: Install nextest uses: taiki-e/install-action@v2 @@ -320,27 +309,8 @@ jobs: with: tool: cargo-nextest - - name: Install Node - uses: actions/setup-node@v4.2.0 - with: - node-version: 20 - - - name: Install Node dependencies - working-directory: ./frontend - run: npm ci - - - name: Build the frontend - working-directory: ./frontend - run: npm run build - - - name: Setup OPA - uses: open-policy-agent/setup-opa@v2.2.0 - with: - version: 0.64.1 - - - name: Compile OPA policies - working-directory: ./policies - run: make + - uses: ./.github/actions/build-frontend + - uses: ./.github/actions/build-policies - name: Download archive uses: actions/download-artifact@v4 diff --git a/.github/workflows/coverage.yaml b/.github/workflows/coverage.yaml index 180295e99..a099dc887 100644 --- a/.github/workflows/coverage.yaml +++ b/.github/workflows/coverage.yaml @@ -54,18 +54,7 @@ jobs: - name: Checkout the code uses: actions/checkout@v4.2.2 - - name: Install Node - uses: actions/setup-node@v4.2.0 - with: - node-version: 20 - - - name: Install Node dependencies - working-directory: ./frontend - run: npm ci - - - name: Build the frontend - working-directory: ./frontend - run: npm run build + - uses: ./.github/actions/build-frontend env: CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} @@ -110,33 +99,10 @@ jobs: - name: Checkout the code uses: actions/checkout@v4.2.2 - - name: Install toolchain - run: | - rustup toolchain install stable - rustup default stable - rustup component add llvm-tools-preview - - - name: Install Node - uses: actions/setup-node@v4.2.0 - with: - node-version: 20 - - - name: Install Node dependencies - working-directory: ./frontend - run: npm ci - - - name: Build the frontend - working-directory: ./frontend - run: npm run build - - - name: Setup OPA - uses: open-policy-agent/setup-opa@v2.2.0 + - name: Install Rust toolchain + uses: dtolnay/rust-toolchain@stable with: - version: 0.64.1 - - - name: Compile OPA policies - working-directory: ./policies - run: make + components: llvm-tools-preview - name: Setup sccache uses: mozilla-actions/sccache-action@v0.0.7 @@ -146,6 +112,9 @@ jobs: with: tool: grcov + - uses: ./.github/actions/build-frontend + - uses: ./.github/actions/build-policies + - name: Run test suite with profiling enabled run: | cargo test --no-fail-fast --workspace diff --git a/.github/workflows/docs.yaml b/.github/workflows/docs.yaml index 1ff7ee3da..3a5bc8d9c 100644 --- a/.github/workflows/docs.yaml +++ b/.github/workflows/docs.yaml @@ -23,9 +23,7 @@ jobs: uses: actions/checkout@v4.2.2 - name: Install Rust toolchain - run: | - rustup toolchain install stable - rustup default stable + uses: dtolnay/rust-toolchain@stable - name: Setup sccache uses: mozilla-actions/sccache-action@v0.0.7 @@ -38,7 +36,7 @@ jobs: - name: Install Node uses: actions/setup-node@v4.2.0 with: - node-version: 20 + node-version: 22 - name: Build the documentation run: sh misc/build-docs.sh 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 5d9bdfe96..087402d66 100644 --- a/.github/workflows/release-branch.yaml +++ b/.github/workflows/release-branch.yaml @@ -23,9 +23,7 @@ jobs: uses: actions/checkout@v4.2.2 - name: Install Rust toolchain - run: | - rustup toolchain install stable - rustup default stable + uses: dtolnay/rust-toolchain@stable - name: Compute the new minor RC id: next @@ -52,7 +50,7 @@ jobs: - name: Install Node uses: actions/setup-node@v4.2.0 with: - node-version: 20 + node-version: 22 - name: Install Localazy CLI run: npm install -g @localazy/cli @@ -91,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: @@ -99,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 6280c9182..b2ee17d80 100644 --- a/.github/workflows/release-bump.yaml +++ b/.github/workflows/release-bump.yaml @@ -31,9 +31,7 @@ jobs: uses: actions/checkout@v4.2.2 - name: Install Rust toolchain - run: | - rustup toolchain install stable - rustup default stable + uses: dtolnay/rust-toolchain@stable - name: Extract the current version id: current @@ -72,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: @@ -80,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 5a86c5164..b86579c1d 100644 --- a/.github/workflows/tag.yaml +++ b/.github/workflows/tag.yaml @@ -28,9 +28,7 @@ jobs: uses: actions/checkout@v4.2.2 - name: Install Rust toolchain - run: | - rustup toolchain install stable - rustup default stable + uses: dtolnay/rust-toolchain@stable - name: Set the crates version env: @@ -55,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 @@ -125,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 }); diff --git a/.github/workflows/translations-download.yaml b/.github/workflows/translations-download.yaml index eb70afc14..474f3f69b 100644 --- a/.github/workflows/translations-download.yaml +++ b/.github/workflows/translations-download.yaml @@ -19,7 +19,7 @@ jobs: - name: Install Node uses: actions/setup-node@v4.2.0 with: - node-version: 20 + node-version: 22 - name: Install Localazy CLI run: npm install -g @localazy/cli diff --git a/.github/workflows/translations-upload.yaml b/.github/workflows/translations-upload.yaml index 986dc5f0e..c92b1fef9 100644 --- a/.github/workflows/translations-upload.yaml +++ b/.github/workflows/translations-upload.yaml @@ -18,7 +18,7 @@ jobs: - name: Install Node uses: actions/setup-node@v4.2.0 with: - node-version: 20 + node-version: 22 - name: Install Localazy CLI run: npm install -g @localazy/cli