(options: DiffOptions)
| 29 | await program.parseAsync(); |
| 30 | |
| 31 | async function diffSummary(options: DiffOptions): Promise<string> { |
| 32 | const max = Number.parseInt(options.max, 10); |
| 33 | const entries = await readdir(options.input, { withFileTypes: true }); |
| 34 | const yamlNames = entries |
| 35 | .filter((entry) => entry.isFile() && /\.ya?ml$/i.test(entry.name)) |
| 36 | .map((entry) => entry.name) |
| 37 | .sort(); |
| 38 | |
| 39 | const newSet = new Set(yamlNames); |
| 40 | const oldNames = await listFilesAtRef(options.ref, options.input); |
| 41 | |
| 42 | const allNames = new Set<string>([...newSet, ...oldNames]); |
| 43 | const changes: { id: string; delta: number; newCount: number }[] = []; |
| 44 | |
| 45 | for (const fileName of allNames) { |
| 46 | const id = fileName.replace(/\.ya?ml$/i, ""); |
| 47 | const relPath = path.join(options.input, fileName).replace(/\\/g, "/"); |
| 48 | const newCount = newSet.has(fileName) ? countRulesFromText(await readFile(relPath, "utf8")) : 0; |
| 49 | const oldRaw = await getFileAtRef(options.ref, relPath); |
| 50 | const oldCount = oldRaw ? countRulesFromText(oldRaw) : 0; |
| 51 | const delta = newCount - oldCount; |
| 52 | if (delta === 0) { |
| 53 | continue; |
| 54 | } |
| 55 | changes.push({ id, delta, newCount }); |
| 56 | } |
| 57 | |
| 58 | if (changes.length === 0) { |
| 59 | return "rule counts unchanged"; |
| 60 | } |
| 61 | |
| 62 | changes.sort((a, b) => Math.abs(b.delta) - Math.abs(a.delta)); |
| 63 | const head = changes.slice(0, max).map(({ id, delta }) => `${id} ${delta > 0 ? "+" : ""}${delta}`); |
| 64 | const tail = changes.length > max ? `, +${changes.length - max} more` : ""; |
| 65 | return head.join(", ") + tail; |
| 66 | } |
| 67 | |
| 68 | async function listFilesAtRef(ref: string, dir: string): Promise<Set<string>> { |
| 69 | const norm = dir.replace(/\\/g, "/"); |
no test coverage detected