Skip to content

Commit d1af2ed

Browse files
committed
ci: fail lint when source files aren't covered by build-apps filter
1 parent 2c68b80 commit d1af2ed

3 files changed

Lines changed: 72 additions & 0 deletions

File tree

.github/workflows/build-apps.yml

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,9 @@
1+
# Per-app build matrix gated by `dorny/paths-filter`. Adding a new model
2+
# directory, controller, or top-level module under packages/react-native-executorch/
3+
# probably means updating the filter block below — `core-shared` if it affects every
4+
# app, or one of the per-app `<app>-pkg` anchors otherwise. CI runs
5+
# `scripts/check-ci-filter-coverage.js` (in ci.yml's lint job) to flag uncovered
6+
# files so this stays accurate.
17
name: Example apps build check
28
on:
39
push:

.github/workflows/ci.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,9 @@ jobs:
3333
- name: Lint files
3434
run: yarn lint
3535

36+
- name: Check CI filter coverage
37+
run: node scripts/check-ci-filter-coverage.js
38+
3639
- name: Typecheck files
3740
run: yarn workspaces foreach --all --topological-dev run prepare && yarn typecheck
3841

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
#!/usr/bin/env node
2+
// Verifies every source file under packages/react-native-executorch/ is matched by
3+
// at least one filter in .github/workflows/build-apps.yml. Prevents new files (e.g.
4+
// a new model directory or controller) from silently slipping past CI's per-app
5+
// path triggers.
6+
7+
const fs = require('fs');
8+
const cp = require('child_process');
9+
const yaml = require('js-yaml');
10+
const picomatch = require('picomatch');
11+
12+
const WORKFLOW = '.github/workflows/build-apps.yml';
13+
const PACKAGE_ROOT = 'packages/react-native-executorch/';
14+
15+
// Files that legitimately don't belong to any per-app or shared filter.
16+
const ALLOWLIST = new Set([
17+
'packages/react-native-executorch/.gitignore',
18+
'packages/react-native-executorch/.watchmanconfig',
19+
'packages/react-native-executorch/tsconfig.doc.json',
20+
]);
21+
22+
const flatten = (x) => (Array.isArray(x) ? x.flatMap(flatten) : [x]);
23+
24+
const wf = yaml.load(fs.readFileSync(WORKFLOW, 'utf8'));
25+
const filtersStr = wf.jobs['detect-changes'].steps.find(
26+
(s) => s.id === 'filter'
27+
).with.filters;
28+
const filters = yaml.load(filtersStr);
29+
30+
const patterns = new Set();
31+
for (const v of Object.values(filters)) {
32+
flatten(v)
33+
.filter((p) => typeof p === 'string')
34+
.forEach((p) => patterns.add(p));
35+
}
36+
const matchers = [...patterns].map((p) => picomatch(p, { dot: true }));
37+
const matchAny = (file) => matchers.some((m) => m(file));
38+
39+
const tracked = cp
40+
.execSync('git ls-files', { encoding: 'utf8' })
41+
.trim()
42+
.split('\n');
43+
const orphans = tracked
44+
.filter((f) => f.startsWith(PACKAGE_ROOT))
45+
.filter((f) => !ALLOWLIST.has(f))
46+
.filter((f) => !matchAny(f));
47+
48+
if (orphans.length > 0) {
49+
console.error(
50+
`\n${WORKFLOW} does not cover ${orphans.length} file(s) under ${PACKAGE_ROOT}:\n`
51+
);
52+
orphans.forEach((f) => console.error(' ' + f));
53+
console.error(
54+
`\nAdd them to the appropriate filter (core-shared, llm-pkg, cv-pkg, speech-pkg,\n` +
55+
`text-embeddings-pkg, or one of the platform-shared blocks). If the file is\n` +
56+
`genuinely build-irrelevant, add it to ALLOWLIST in scripts/check-ci-filter-coverage.js.`
57+
);
58+
process.exit(1);
59+
}
60+
61+
console.log(
62+
`OK: every file under ${PACKAGE_ROOT} is covered by build-apps.yml.`
63+
);

0 commit comments

Comments
 (0)