merge-gitignore.mjs
1.88 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
// lib/merge-gitignore.mjs
// 合并两份 .gitignore,对**规则行**逐行判重并集合并;注释行透传(相邻去重),空行丢弃(节由注释头承担)。
// 之所以不对注释去重:两段分组各自的同名注释头(如多次出现的 `# generated`)是分节标题,
// 全局去重会把第二段的标题吞掉,让 add 文件的规则被并入第一段的注释下、破坏分节语义。
export function mergeGitignore(baseText, addText) {
const seenRules = new Set()
const out = []
const push = (line) => {
const trimmed = line.trim()
if (!trimmed) return // drop blank lines (输出靠 join('\n')+尾换行;分节由注释行承担)
if (trimmed.startsWith('#')) {
// 注释:仅折叠**相邻**完全相同的注释,避免分节标题被吞
if (out.length && out[out.length - 1].trim() === trimmed) return
out.push(line)
return
}
// 规则行:全局去重(含 negation `!pattern`,按原文比对,顺序保留首次出现位置)
if (seenRules.has(trimmed)) return
seenRules.add(trimmed)
out.push(line)
}
for (const l of baseText.split('\n')) push(l)
for (const l of addText.split('\n')) push(l)
let text = out.join('\n').replace(/\n+$/, '') + '\n'
return text
}
// CLI entry: node lib/merge-gitignore.mjs <basePath> <addPath>
// Use pathToFileURL so the guard matches even when the path contains spaces or
// non-ASCII chars (import.meta.url is percent-encoded; process.argv[1] is raw).
const { pathToFileURL } = await import('node:url')
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
const [basePath, addPath] = process.argv.slice(2)
const { readFileSync, writeFileSync } = await import('node:fs')
const base = readFileSync(basePath, 'utf8')
const add = readFileSync(addPath, 'utf8')
writeFileSync(basePath, mergeGitignore(base, add))
}