Files
HMDesign/packages/sw/tests/source-changes.mjs
T
shkim4 1e05eaf1de test(sw): detect untracked source files and public API changes
- source-changes.mjs: source guards list untracked, staged, deleted and renamed files, not only git diff --name-only.
- overview / storybook-unused-removal: production source guards use the helper (assertions, paths, approved list unchanged).
- public-api.check.mjs + public-api.approved.json: the built @hm-design/sw entry must export exactly the approved
  names and kinds, keep the package.json export settings and keep every declaration reachable from dist/index.d.ts
  byte-identical. The approved surface was written from a build of 553e4df (approvedMainCommit).
2026-10-21 06:08:16 +09:00

32 lines
2.1 KiB
JavaScript

// Source guard helper: every file under `paths` whose current checkout differs from `ref`.
// `git diff --name-only <ref>` 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))