Skip to content
Draft
Show file tree
Hide file tree
Changes from 10 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
102 changes: 92 additions & 10 deletions .github/workflows/lint-build-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -64,11 +64,13 @@
validate-changelog:
name: Validate changelog
runs-on: ubuntu-latest
needs: prepare
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 +89,57 @@
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")
# Fall back to all packages if no packages changed (e.g. CI-only PRs),
# to ensure required status checks are not skipped.
if [[ "$PACKAGES" == "[]" ]]; then
PACKAGES=$(yarn workspaces list --no-private --json | jq --slurp --raw-output 'map(.name) | @json')
fi
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
Comment on lines +93 to +137

validate-changelog-diffs:
name: Validate changelog diffs
if: github.event_name == 'pull_request' || github.event_name == 'merge_group'
Expand All @@ -102,7 +155,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 +168,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 +227,12 @@
test-18:
name: Test (18.x)
runs-on: ubuntu-latest
needs: prepare
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 +254,12 @@
test-20:
name: Test (20.x)
runs-on: ubuntu-latest
needs: prepare
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 +281,12 @@
test-22:
name: Test (22.x)
runs-on: ubuntu-latest
needs: prepare
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
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
Loading