Skip to content

Commit 4b07993

Browse files
authored
Merge pull request #5564 from NomicFoundation/galargh/github-actions-laziness
ci(v3): test and lint v-next packages lazily
2 parents 47eae51 + f8dc86c commit 4b07993

File tree

3 files changed

+113
-3
lines changed

3 files changed

+113
-3
lines changed

.github/workflows/v-next-ci.yml

+34-3
Original file line numberDiff line numberDiff line change
@@ -8,11 +8,13 @@ on:
88
- ".github/workflows/v-next-ci.yml"
99
- "v-next/**"
1010
- "config-v-next/**"
11+
- "pnpm-lock.yaml"
1112
pull_request:
1213
paths:
1314
- ".github/workflows/v-next-ci.yml"
1415
- "v-next/**"
1516
- "config-v-next/**"
17+
- "pnpm-lock.yaml"
1618
workflow_dispatch:
1719

1820
concurrency:
@@ -46,16 +48,43 @@ jobs:
4648
name: List packages
4749
runs-on: ubuntu-latest
4850
outputs:
49-
packages: ${{ steps.list.outputs.packages }}
51+
packages: ${{ steps.filter.outputs.changes }}
5052
steps:
5153
- uses: actions/checkout@v4
52-
- id: list
54+
- uses: actions/setup-node@v4
55+
with:
56+
node-version: 22
57+
- run: yq -p yaml -o json pnpm-lock.yaml | tee pnpm-lock.json
58+
- id: generate
59+
env:
60+
PACKAGE_IGNORE: |
61+
[
62+
".",
63+
"packages/",
64+
"v-next/example-project"
65+
]
66+
COMMON_FILTERS: |
67+
[
68+
".github/workflows/v-next-ci.yml",
69+
"config-v-next/**",
70+
"pnpm-lock.yaml"
71+
]
5372
run: |
54-
echo "packages=$(ls -d v-next/* | xargs -n 1 basename | grep -v example-project | jq -R -s -c 'split("\n")[:-1]')" >> $GITHUB_OUTPUT
73+
echo "filters<<EOF" >> $GITHUB_OUTPUT
74+
node scripts/generate-filters.js |
75+
yq -Po yaml 'with_entries(.key |= sub("^v-next/", ""))' |
76+
tee -a $GITHUB_OUTPUT
77+
echo "EOF" >> $GITHUB_OUTPUT
78+
- id: filter
79+
uses: dorny/paths-filter@de90cc6fb38fc0963ad72b210f1f284cd68cea36 # v3.0.2
80+
with:
81+
filters: ${{ steps.generate.outputs.filters }}
5582

5683
lint:
5784
needs: list-packages
5885

86+
if: needs.list-packages.outputs.packages != '[]'
87+
5988
strategy:
6089
fail-fast: false
6190
matrix:
@@ -82,6 +111,8 @@ jobs:
82111
ci:
83112
needs: list-packages
84113

114+
if: needs.list-packages.outputs.packages != '[]'
115+
85116
strategy:
86117
fail-fast: false
87118
matrix:

.gitignore

+3
Original file line numberDiff line numberDiff line change
@@ -100,3 +100,6 @@ Brewfile.lock.json
100100

101101
# Performance profiling application
102102
.clinic
103+
104+
# Input to scripts/generate-filters.js; generated in CI
105+
pnpm-lock.json

scripts/generate-filters.js

+76
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
// @ts-check
2+
/**
3+
* This script generates filters for https://github.com/dorny/paths-filter
4+
*
5+
* It is used in CI to find all the packages which have been modified or
6+
* have a dependency (including transitive) that has been modified.
7+
*
8+
* This enables running checks only for the packages affected by the changes.
9+
*/
10+
const fs = require("fs");
11+
const path = require("path");
12+
13+
function main() {
14+
const packageIgnore = JSON.parse(process.env.PACKAGE_IGNORE || "[]");
15+
const commonFilters = JSON.parse(process.env.COMMON_FILTERS || "[]");
16+
17+
const pnpmLockfilePath = path.join(__dirname, "..", "pnpm-lock.json");
18+
if (!fs.existsSync(pnpmLockfilePath)) {
19+
console.warn(
20+
`${pnpmLockfilePath} doesn't exist, please run: yq -p yaml -o json pnpm-lock.yaml | tee pnpm-lock.json`
21+
);
22+
process.exit(1);
23+
}
24+
25+
const pnpmLockfile = JSON.parse(fs.readFileSync(pnpmLockfilePath, "utf8"));
26+
27+
// Find all direct internal dependencies for all packages
28+
const internalDependenciesMap = {};
29+
for (const [package, allDependencies] of Object.entries(
30+
pnpmLockfile.importers
31+
)) {
32+
const internalDependencies = Object.values(allDependencies)
33+
.flatMap((dependencies) => Object.values(dependencies))
34+
.map((dependency) => dependency.version)
35+
.filter((version) => version.startsWith("link:"))
36+
.map((version) => version.replace("link:", ""))
37+
.map((version) => path.join(package, version));
38+
internalDependenciesMap[package] = internalDependencies;
39+
}
40+
41+
// Add transitive internal dependencies
42+
for (const dependencies of Object.values(internalDependenciesMap)) {
43+
const dependencyQueue = [...dependencies];
44+
while (dependencyQueue.length !== 0) {
45+
const dependency = dependencyQueue.pop();
46+
for (const transitiveDependency of internalDependenciesMap[dependency]) {
47+
if (!dependencies.includes(transitiveDependency)) {
48+
dependencies.push(transitiveDependency);
49+
dependencyQueue.push(transitiveDependency);
50+
}
51+
}
52+
}
53+
}
54+
55+
// Generate filters
56+
const filters = {};
57+
for (const [package, dependencies] of Object.entries(
58+
internalDependenciesMap
59+
)) {
60+
// Ignore packages that start with one of the prefixes in PACKAGE_IGNORE
61+
if (packageIgnore.some((prefix) => package.startsWith(prefix))) {
62+
continue;
63+
}
64+
// Calculate glob patterns for the package and its dependencies
65+
const packageFilters = [package, ...dependencies].map((dependency) =>
66+
path.join(dependency, "**")
67+
);
68+
// Set filters for the package
69+
filters[package] = [...commonFilters, ...packageFilters];
70+
}
71+
72+
// Pretty print the filters
73+
console.log(JSON.stringify(filters, null, 2));
74+
}
75+
76+
main();

0 commit comments

Comments
 (0)