|
| 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