// Source guard helper: every file under `paths` whose current checkout differs from `ref`. // `git diff --name-only ` alone misses files that were never added (a new internal module, a new component), so // the guards also list untracked files. Ignored files (.gitignore: dist, storybook-static, node_modules, *.log) stay out. // Each entry: { path, status, staged }. status: M modified, A added (tracked), D deleted, T type change, // R renamed (both sides are listed, `from` / `to` name the other side), U unmerged, ? untracked. // staged: the index already holds the change (git add / git rm / git mv). Run from the repository root, like the guards. import { execFileSync } from 'node:child_process' const git = args => execFileSync('git', args, { encoding: 'utf8', maxBuffer: 1 << 26 }).split('\0').filter(Boolean) export const sourceChanges = (ref, paths) => { const staged = new Set(git(['diff', '--cached', '--name-only', '--no-renames', '-z', ref, '--', ...paths])) const entries = [], diff = git(['diff', '--name-status', '-M', '--no-ext-diff', '-z', ref, '--', ...paths]) for (let i = 0; i < diff.length;) { const status = diff[i++][0] if (status === 'R' || status === 'C') { const from = diff[i++], to = diff[i++] if (status === 'R') entries.push({ path: from, status, to, staged: staged.has(from) }) entries.push({ path: to, status, from, staged: staged.has(to) }) } else { const file = diff[i++] entries.push({ path: file, status, staged: staged.has(file) }) } } for (const file of git(['ls-files', '--others', '--exclude-standard', '--full-name', '-z', '--', ...paths])) entries.push({ path: file, status: '?', staged: false }) return entries.sort((a, b) => a.path.localeCompare(b.path)) } // Changes outside the explicitly approved file list (exact repository paths). An approved file moved to a new path is // still reported at the new path; an unapproved file renamed onto nothing approved is reported at both paths. export const unapprovedChanges = (ref, paths, approved) => sourceChanges(ref, paths).filter(entry => !approved.includes(entry.path))