Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
106 changes: 95 additions & 11 deletions .github/workflows/lint-build-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,14 @@
validate-changelog:
name: Validate changelog
runs-on: ubuntu-latest
needs: prepare
if: needs.get-changed-packages.outputs.package-names != '[]'
needs:
- prepare
- get-changed-packages
strategy:
matrix:
node-version: [24.x]
package-name: ${{ fromJson(needs.prepare.outputs.child-workspace-package-names) }}
package-name: ${{ fromJson(needs.get-changed-packages.outputs.package-names) }}
steps:
- name: Checkout and setup environment
uses: MetaMask/action-checkout-and-setup@v3
Expand All @@ -87,6 +90,52 @@
exit 1
fi

get-changed-packages:
name: Get changed packages
runs-on: ubuntu-latest
needs: prepare
outputs:
merge-base: ${{ steps.fetch-merge-base.outputs.merge-base }}
package-names: ${{ steps.packages.outputs.package-names }}
steps:
- name: Checkout and setup environment
uses: MetaMask/action-checkout-and-setup@v3
with:
is-high-risk-environment: false
persist-credentials: false
node-version: 24.x
- name: Fetch merge base
id: fetch-merge-base
if: github.base_ref != '' || github.event.merge_group.base_ref != ''
run: |
set -euo pipefail

PREFIXED_REF_REGEX='refs/heads/(.+)'
if [[ "$BASE_REF" =~ $PREFIXED_REF_REGEX ]]; then
BASE_REF="${BASH_REMATCH[1]}"
fi

MERGE_BASE=$(gh api "repos/$GITHUB_REPOSITORY/compare/$BASE_REF...$HEAD_SHA" --jq '.merge_base_commit.sha')
git fetch --unshallow --filter=blob:none --no-tags origin HEAD

echo "merge-base=$MERGE_BASE" >> "$GITHUB_OUTPUT"
env:
BASE_REF: ${{ github.event.pull_request.base.ref || github.event.merge_group.base_ref }}
HEAD_SHA: ${{ github.event.pull_request.head.sha || github.event.merge_group.head_sha }}
GH_TOKEN: ${{ github.token }}
- name: Get changed package names
id: packages
run: |
if [[ -n "$MERGE_BASE" ]]; then
PACKAGES=$(yarn tsx scripts/get-changed-workspaces.mts "$MERGE_BASE" "$HEAD_SHA")
else
PACKAGES=$(yarn workspaces list --no-private --json | jq --slurp --raw-output 'map(.name) | @json')
fi
echo "package-names=$PACKAGES" >> "$GITHUB_OUTPUT"
env:
MERGE_BASE: ${{ steps.fetch-merge-base.outputs.merge-base }}
HEAD_SHA: ${{ github.event.pull_request.head.sha || github.event.merge_group.head_sha }}

Check warning

Code scanning / zizmor

overly broad permissions: default permissions used due to no permissions: block Warning

overly broad permissions: default permissions used due to no permissions: block

validate-changelog-diffs:
name: Validate changelog diffs
if: github.event_name == 'pull_request' || github.event_name == 'merge_group'
Expand All @@ -102,7 +151,9 @@
build:
name: Build
runs-on: ubuntu-latest
needs: prepare
needs:
- prepare
- get-changed-packages
strategy:
matrix:
node-version: [24.x]
Expand All @@ -113,7 +164,28 @@
is-high-risk-environment: false
persist-credentials: false
node-version: ${{ matrix.node-version }}
- run: yarn build
- name: Unshallow checkout
if: needs.get-changed-packages.outputs.merge-base != ''
run: |
# Unshallow so git can walk history back to the merge base for
# `git diff --name-only`. Using `--filter=blob:none` avoids
# downloading file content — only commit and tree objects are needed.
git fetch --unshallow --filter=blob:none --no-tags origin HEAD
- name: Build
run: |
if [[ -n "$MERGE_BASE" ]]; then
TSCONFIG=$(mktemp --tmpdir="$GITHUB_WORKSPACE" --suffix=.json)
yarn tsx scripts/generate-partial-build-tsconfig.mts "$MERGE_BASE" "$HEAD_SHA" > "$TSCONFIG"
if [[ -s "$TSCONFIG" ]]; then
yarn ts-bridge --project "$TSCONFIG" --verbose
fi
rm -f "$TSCONFIG"
else
yarn build
fi
env:
MERGE_BASE: ${{ needs.get-changed-packages.outputs.merge-base }}
HEAD_SHA: ${{ github.event.pull_request.head.sha || github.event.merge_group.head_sha }}
- name: Require clean working directory
shell: bash
run: |
Expand Down Expand Up @@ -151,10 +223,13 @@
test-18:
name: Test (18.x)
runs-on: ubuntu-latest
needs: prepare
if: needs.get-changed-packages.outputs.package-names != '[]'
needs:
- prepare
- get-changed-packages
strategy:
matrix:
package-name: ${{ fromJson(needs.prepare.outputs.child-workspace-package-names) }}
package-name: ${{ fromJson(needs.get-changed-packages.outputs.package-names) }}
steps:
- name: Checkout and setup environment
uses: MetaMask/action-checkout-and-setup@v3
Expand All @@ -176,10 +251,13 @@
test-20:
name: Test (20.x)
runs-on: ubuntu-latest
needs: prepare
if: needs.get-changed-packages.outputs.package-names != '[]'
needs:
- prepare
- get-changed-packages
strategy:
matrix:
package-name: ${{ fromJson(needs.prepare.outputs.child-workspace-package-names) }}
package-name: ${{ fromJson(needs.get-changed-packages.outputs.package-names) }}
steps:
- name: Checkout and setup environment
uses: MetaMask/action-checkout-and-setup@v3
Expand All @@ -201,10 +279,13 @@
test-22:
name: Test (22.x)
runs-on: ubuntu-latest
needs: prepare
if: needs.get-changed-packages.outputs.package-names != '[]'
needs:
- prepare
- get-changed-packages
strategy:
matrix:
package-name: ${{ fromJson(needs.prepare.outputs.child-workspace-package-names) }}
package-name: ${{ fromJson(needs.get-changed-packages.outputs.package-names) }}
steps:
- name: Checkout and setup environment
uses: MetaMask/action-checkout-and-setup@v3
Expand Down Expand Up @@ -246,7 +327,10 @@
test-wallet-cli-e2e:
name: Test wallet-cli daemon e2e (${{ matrix.node-version }})
runs-on: ubuntu-latest
needs: prepare
if: needs.get-changed-packages.outputs.package-names == '' || contains(fromJson(needs.get-changed-packages.outputs.package-names), '@metamask/wallet-cli')
needs:
- prepare
- get-changed-packages
strategy:
matrix:
node-version: [20.x, 22.x, 24.x]
Expand Down
50 changes: 50 additions & 0 deletions scripts/generate-partial-build-tsconfig.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import {
getTypeScriptWorkspaces,
computeChangedWorkspaces,
} from './lib/workspaces.mjs';

/**
* Generate a filtered tsconfig.build.json for partial CI builds.
*
* Given a merge base SHA, outputs a tsconfig that references only the
* TypeScript packages that changed since that commit plus their transitive
* dependants and dependencies. Pipe the output to a temp file and pass it
* to `ts-bridge --project`.
*
* Dependencies are always included because TypeScript project references
* require every referenced project's dist output to already exist on disk.
*
* Usage: `tsx scripts/generate-partial-build-tsconfig.mts <merge-base-sha> [<head-sha>]`
*/
async function main() {
const mergeBase = process.argv[2];
if (!mergeBase) {
console.error(
'Usage: generate-partial-build-tsconfig.mts <merge-base-sha> [<head-sha>]',
);
process.exitCode = 1;
return;
}

const headRef = process.argv[3] ?? 'HEAD';

const workspaces = await getTypeScriptWorkspaces();
const packagesToBuild = await computeChangedWorkspaces(
workspaces,
mergeBase,
headRef,
true,
);

const references = workspaces
.filter(({ name }) => packagesToBuild.has(name))
.map(({ location }) => ({ path: `./${location}/tsconfig.build.json` }));

if (references.length === 0) {
return;
}

console.log(JSON.stringify({ files: [], include: [], references }, null, 2));
}

await main();
43 changes: 43 additions & 0 deletions scripts/get-changed-workspaces.mts
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
import {
getAllWorkspaces,
computeChangedWorkspaces,
} from './lib/workspaces.mjs';

/**
* List workspace package names that need to be checked given a merge base.
*
* Outputs a JSON array of package names to stdout. Always includes packages
* that changed since the merge base plus their transitive dependants. Pass
* `--include-dependencies` to also include transitive dependencies (needed
* for TypeScript project reference builds where dist outputs must exist).
*
* Usage: `tsx scripts/get-changed-workspaces.mts <merge-base-sha> [<head-sha>] [--include-dependencies]`
*/
const args = process.argv.slice(2);
const includeDependencies = args.includes('--include-dependencies');
const positional = args.filter((arg) => !arg.startsWith('--'));

const mergeBase = positional[0];
if (!mergeBase) {
console.error(
'Usage: get-changed-workspaces.mts <merge-base-sha> [<head-sha>] [--include-dependencies]',
);
process.exitCode = 1;
process.exit();
}

const headRef = positional[1] ?? 'HEAD';

const workspaces = await getAllWorkspaces();
const changed = await computeChangedWorkspaces(
workspaces,
mergeBase,
headRef,
includeDependencies,
);

const names = workspaces
.filter(({ name }) => changed.has(name))
.map(({ name }) => name);

console.log(JSON.stringify(names));
Loading