mirror of
https://gh-proxy.org/https://github.com/klinsmaya/workdsh.git
synced 2026-11-08 01:17:05 +08:00
103 lines
3.6 KiB
JavaScript
103 lines
3.6 KiB
JavaScript
import { execFileSync } from 'node:child_process'
|
|
import { readFileSync } from 'node:fs'
|
|
import { basename, dirname, isAbsolute, relative, resolve } from 'node:path'
|
|
|
|
const HASH_PATTERN = /^[0-9a-f]{40}$/u
|
|
const DOCUMENT_PATTERN = /\.mdx?$/u
|
|
|
|
function normalizePath(path) {
|
|
return path.replaceAll('\\', '/')
|
|
}
|
|
|
|
function runGit(root, args) {
|
|
return execFileSync('git', args, {
|
|
cwd: root,
|
|
encoding: 'utf8',
|
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
})
|
|
}
|
|
|
|
export function parseBilingualRecord(recordPath, content) {
|
|
const entries = []
|
|
for (const [index, line] of content.split(/\r?\n/u).entries()) {
|
|
const trimmed = line.trim()
|
|
if (trimmed.length === 0 || trimmed.startsWith('#')) continue
|
|
const match = /^([^:]+):\s*([0-9a-f]{40})$/u.exec(trimmed)
|
|
if (match === null) {
|
|
throw new Error(`${recordPath}:${String(index + 1)} is not a filename and Git blob hash`)
|
|
}
|
|
const filename = match[1]?.trim() ?? ''
|
|
const hash = match[2] ?? ''
|
|
if (filename !== basename(filename) || isAbsolute(filename) || !DOCUMENT_PATTERN.test(filename)) {
|
|
throw new Error(`${recordPath}:${String(index + 1)} must name one Markdown file beside the record`)
|
|
}
|
|
if (!HASH_PATTERN.test(hash)) {
|
|
throw new Error(`${recordPath}:${String(index + 1)} has an invalid Git blob hash`)
|
|
}
|
|
entries.push({ filename, hash })
|
|
}
|
|
if (entries.length !== 2) {
|
|
throw new Error(`${recordPath} must contain exactly two bilingual document records`)
|
|
}
|
|
if (entries[0]?.filename === entries[1]?.filename) {
|
|
throw new Error(`${recordPath} must reference two distinct bilingual documents`)
|
|
}
|
|
return entries
|
|
}
|
|
|
|
export function verifyBilingualRecords({ root, recordPaths, readText, hashDocument }) {
|
|
let documentCount = 0
|
|
const violations = []
|
|
for (const recordPath of recordPaths) {
|
|
const normalizedRecord = normalizePath(recordPath)
|
|
const recordDirectory = dirname(resolve(root, normalizedRecord))
|
|
let entries
|
|
try {
|
|
entries = parseBilingualRecord(normalizedRecord, readText(resolve(root, normalizedRecord)))
|
|
} catch (cause) {
|
|
violations.push(cause instanceof Error ? cause.message : String(cause))
|
|
continue
|
|
}
|
|
for (const entry of entries) {
|
|
const documentPath = resolve(recordDirectory, entry.filename)
|
|
const relativeDocument = normalizePath(relative(root, documentPath))
|
|
if (relativeDocument.startsWith('../') || isAbsolute(relativeDocument)) {
|
|
violations.push(`${normalizedRecord} references a document outside the repository`)
|
|
continue
|
|
}
|
|
try {
|
|
readText(documentPath)
|
|
const actual = hashDocument(relativeDocument)
|
|
if (actual !== entry.hash) {
|
|
violations.push(`${normalizedRecord} is stale for ${relativeDocument}: expected ${actual}, recorded ${entry.hash}`)
|
|
}
|
|
} catch (cause) {
|
|
violations.push(`${normalizedRecord} cannot verify ${relativeDocument}: ${cause instanceof Error ? cause.message : String(cause)}`)
|
|
}
|
|
documentCount += 1
|
|
}
|
|
}
|
|
if (violations.length > 0) {
|
|
throw new Error(`bilingual documentation gate failed:\n- ${violations.join('\n- ')}`)
|
|
}
|
|
return { recordCount: recordPaths.length, documentCount }
|
|
}
|
|
|
|
export function verifyTrackedBilingualRecords(root) {
|
|
const recordPaths = runGit(root, ['ls-files', '-z', '--', '*.i18n.yaml'])
|
|
.split('\0')
|
|
.filter(Boolean)
|
|
.map(normalizePath)
|
|
return verifyBilingualRecords({
|
|
root,
|
|
recordPaths,
|
|
readText: path => readFileSync(path, 'utf8'),
|
|
hashDocument: relativePath => runGit(root, [
|
|
'hash-object',
|
|
`--path=${relativePath}`,
|
|
'--',
|
|
relativePath,
|
|
]).trim(),
|
|
})
|
|
}
|