Skip to content

Commit ed49f3b

Browse files
make inventory fresh and gitignore-aware
1 parent 7c0759e commit ed49f3b

1 file changed

Lines changed: 25 additions & 21 deletions

File tree

global-template/docgen/lib/inventory.mjs

Lines changed: 25 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import { ensureDir, git, loadConfig, now, posix, projectPaths, sha256, writeJson
55

66
const HARD_DIRS = new Set(['.git', '.docgen', '.commandcode', 'docs', 'node_modules', 'target', 'build', 'dist', 'coverage', 'vendor']);
77
const BINARY_EXTENSIONS = new Set(['.png','.jpg','.jpeg','.gif','.webp','.bmp','.ico','.tif','.tiff','.avif','.heic','.psd','.mp3','.wav','.flac','.aac','.ogg','.mp4','.mov','.avi','.mkv','.webm','.pdf','.doc','.docx','.xls','.xlsx','.ppt','.pptx','.zip','.gz','.tgz','.bz2','.xz','.7z','.rar','.tar','.jar','.war','.ear','.class','.dll','.exe','.so','.dylib','.o','.a','.lib','.woff','.woff2','.ttf','.otf','.eot','.bin','.dat','.db','.sqlite','.sqlite3','.p12','.pfx','.jks','.keystore','.apk','.ipa','.iso','.dmg','.img','.wasm','.pyc','.pyo']);
8+
const ruleCache = new Map();
89

910
function globRegex(pattern) {
1011
let source = posix(pattern.trim()).replace(/^\.\//, '');
@@ -18,29 +19,35 @@ function globRegex(pattern) {
1819
else if (c === '?') out += '[^/]';
1920
else out += c.replace(/[|\\{}()[\]^$+?.]/g, '\\$&');
2021
}
21-
return new RegExp(anchored ? `^${out}$` : `(?:^|/)${out}$`);
22+
return new RegExp(anchored ? `^${out}(?:/.*)?$` : `(?:^|/)${out}(?:/.*)?$`);
2223
}
2324

24-
function loadDocgenIgnore(root) {
25-
const file = path.join(root, '.docgenignore');
26-
if (!fs.existsSync(file)) return [];
27-
return fs.readFileSync(file, 'utf8').split(/\r?\n/).map((line) => line.trim()).filter((line) => line && !line.startsWith('#')).map((line) => ({ negate: line.startsWith('!'), regex: globRegex(line.replace(/^!/, '')), raw: line }));
25+
function loadRules(file) {
26+
if (ruleCache.has(file)) return ruleCache.get(file);
27+
const result = !fs.existsSync(file) ? [] : fs.readFileSync(file, 'utf8').split(/\r?\n/).map((line) => line.trim()).filter((line) => line && !line.startsWith('#')).map((line) => ({ negate: line.startsWith('!'), regex: globRegex(line.replace(/^!/, '')), raw: line }));
28+
ruleCache.set(file, result); return result;
2829
}
2930

30-
function ignoredByDocgen(rel, rules) {
31+
function ignoredByRules(rel, rules) {
3132
let ignored = false;
3233
for (const rule of rules) if (rule.regex.test(rel)) ignored = !rule.negate;
3334
return ignored;
3435
}
3536

37+
function ignoredByFallbackGitignore(root, rel) {
38+
const parts = rel.split('/'); let ignored = false;
39+
for (let depth = 0; depth < parts.length; depth++) {
40+
const base = parts.slice(0, depth).join('/'); const local = parts.slice(depth).join('/');
41+
for (const rule of loadRules(path.join(root, base, '.gitignore'))) if (rule.regex.test(local)) ignored = !rule.negate;
42+
}
43+
return ignored;
44+
}
45+
3646
function walk(root) {
3747
const out = []; const stack = [root];
3848
while (stack.length) {
3949
const dir = stack.pop();
4050
for (const entry of fs.readdirSync(dir, { withFileTypes: true })) {
41-
if (entry.name.startsWith('.') && entry.name !== '.env' && entry.name !== '.env.example') {
42-
if (entry.isDirectory() || ['.gitignore', '.docgenignore'].includes(entry.name) === false) continue;
43-
}
4451
const full = path.join(dir, entry.name); const rel = posix(path.relative(root, full));
4552
if (entry.isDirectory()) { if (!HARD_DIRS.has(entry.name)) stack.push(full); }
4653
else out.push(rel);
@@ -51,10 +58,10 @@ function walk(root) {
5158

5259
function candidatePaths(root) {
5360
const insideGit = git(root, ['rev-parse', '--is-inside-work-tree']) === 'true';
54-
if (!insideGit) return walk(root);
61+
if (!insideGit) return { paths: walk(root), insideGit: false };
5562
const result = spawnSync('git', ['-C', root, 'ls-files', '-co', '--exclude-standard', '-z'], { encoding: 'buffer', stdio: ['ignore', 'pipe', 'ignore'] });
56-
if (result.status !== 0) return walk(root);
57-
return result.stdout.toString('utf8').split('\0').filter(Boolean).map(posix).sort();
63+
if (result.status !== 0) return { paths: walk(root), insideGit: false };
64+
return { paths: result.stdout.toString('utf8').split('\0').filter(Boolean).map(posix).sort(), insideGit: true };
5865
}
5966

6067
function binaryMagic(buffer) {
@@ -80,17 +87,14 @@ export function classifyFile(root, rel, config = loadConfig(root)) {
8087
return { included: true, reason: null, size: stat.size, extension: ext || '<none>' };
8188
}
8289

83-
export function buildInventory(root, { force = false } = {}) {
84-
const paths = projectPaths(root); const config = loadConfig(root);
85-
if (!force && fs.existsSync(paths.inventory)) {
86-
const current = JSON.parse(fs.readFileSync(paths.inventory, 'utf8'));
87-
if (current.schemaVersion === '2.0') return current;
88-
}
89-
const rules = loadDocgenIgnore(root); const included = []; const excluded = [];
90-
for (const rel of candidatePaths(root)) {
90+
export function buildInventory(root) {
91+
ruleCache.clear();
92+
const paths = projectPaths(root); const config = loadConfig(root); const candidates = candidatePaths(root); const docgenRules = loadRules(path.join(root, '.docgenignore')); const included = []; const excluded = [];
93+
for (const rel of candidates.paths) {
9194
const top = rel.split('/')[0];
9295
if (HARD_DIRS.has(top)) { excluded.push({ path: rel, reason: 'hard-exclusion' }); continue; }
93-
if (ignoredByDocgen(rel, rules)) { excluded.push({ path: rel, reason: '.docgenignore' }); continue; }
96+
if (!candidates.insideGit && config.ignore?.useGitignore !== false && ignoredByFallbackGitignore(root, rel)) { excluded.push({ path: rel, reason: '.gitignore' }); continue; }
97+
if (config.ignore?.useDocgenignore !== false && ignoredByRules(rel, docgenRules)) { excluded.push({ path: rel, reason: '.docgenignore' }); continue; }
9498
const result = classifyFile(root, rel, config);
9599
if (!result.included) { excluded.push({ path: rel, reason: result.reason, size: result.size ?? 0 }); continue; }
96100
const content = fs.readFileSync(path.join(root, rel)); included.push({ path: rel, size: result.size, extension: result.extension, hash: sha256(content) });

0 commit comments

Comments
 (0)