129 lines
3.9 KiB
JavaScript
Executable File
129 lines
3.9 KiB
JavaScript
Executable File
#!/usr/bin/env node
|
|
|
|
const fs = require('fs');
|
|
const path = require('path');
|
|
|
|
const repoRoot = path.resolve(__dirname, '..');
|
|
const vaultRoot = path.join(repoRoot, 'obsidian-vault');
|
|
const manifestPath = path.join(vaultRoot, '00 Start', 'Kildemanifest.tsv');
|
|
const archivePrefixes = ['_archive/', 'archive/', 'docs/archived/', 'tmp/', '_temp/'];
|
|
const configurationPrefixes = ['.claude/', '.codex/', '.github/'];
|
|
|
|
const errors = [];
|
|
const warnings = [];
|
|
const destinations = new Set();
|
|
|
|
const toRepoPath = value => path.join(repoRoot, value);
|
|
const normalizeTarget = target => {
|
|
let value = target.trim().replace(/^<|>$/g, '');
|
|
value = value.split(/\s+["']/)[0];
|
|
value = value.split('#')[0];
|
|
try {
|
|
return decodeURIComponent(value);
|
|
} catch (_error) {
|
|
return value;
|
|
}
|
|
};
|
|
|
|
const isIgnoredLink = target => (
|
|
!target ||
|
|
target.startsWith('#') ||
|
|
target.startsWith('/') ||
|
|
/^[a-z][a-z0-9+.-]*:/i.test(target) ||
|
|
/[{}$]/.test(target)
|
|
);
|
|
|
|
if (!fs.existsSync(manifestPath)) {
|
|
console.error(`Manifest mangler: ${manifestPath}`);
|
|
process.exit(1);
|
|
}
|
|
|
|
const rows = fs.readFileSync(manifestPath, 'utf8')
|
|
.trimEnd()
|
|
.split('\n')
|
|
.slice(1)
|
|
.map(line => line.split('\t'));
|
|
|
|
for (const [source, vaultPath] of rows) {
|
|
if (!source || !vaultPath) {
|
|
errors.push(`Ugyldig manifestpost: ${source || '<tom>'}`);
|
|
continue;
|
|
}
|
|
|
|
if (destinations.has(vaultPath)) {
|
|
errors.push(`Dobbelt vault-destination: ${vaultPath}`);
|
|
}
|
|
destinations.add(vaultPath);
|
|
|
|
const sourcePath = toRepoPath(source);
|
|
const copyPath = path.join(vaultRoot, vaultPath);
|
|
if (!fs.existsSync(sourcePath)) {
|
|
errors.push(`Kilde mangler: ${source}`);
|
|
continue;
|
|
}
|
|
if (!fs.existsSync(copyPath)) {
|
|
errors.push(`Vault-kopi mangler: ${vaultPath}`);
|
|
continue;
|
|
}
|
|
if (!fs.readFileSync(sourcePath).equals(fs.readFileSync(copyPath))) {
|
|
errors.push(`Vault-kopi afviger fra kilde: ${source}`);
|
|
}
|
|
|
|
const archived = archivePrefixes.some(prefix => source.startsWith(prefix));
|
|
const configuration = configurationPrefixes.some(prefix => source.startsWith(prefix));
|
|
const content = fs.readFileSync(sourcePath, 'utf8');
|
|
|
|
if (!archived && !configuration && content.trim() === '') {
|
|
errors.push(`Aktivt dokument er tomt: ${source}`);
|
|
}
|
|
if (!archived && !configuration && !/^#\s+\S/m.test(content)) {
|
|
warnings.push(`Aktivt dokument mangler H1: ${source}`);
|
|
}
|
|
|
|
if (archived || configuration) continue;
|
|
|
|
const linkPattern = /!?\[[^\]]*\]\(([^)]+)\)/g;
|
|
for (const match of content.matchAll(linkPattern)) {
|
|
const rawTarget = match[1].trim();
|
|
if (isIgnoredLink(rawTarget)) continue;
|
|
const target = normalizeTarget(rawTarget);
|
|
if (!target) continue;
|
|
const candidates = [
|
|
path.resolve(path.dirname(sourcePath), target),
|
|
path.resolve(repoRoot, target)
|
|
];
|
|
if (!candidates.some(candidate => fs.existsSync(candidate))) {
|
|
errors.push(`Brudt link i ${source}: ${rawTarget}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
for (const indexName of ['Aktiv dokumentation.md', 'Dokumentindeks.md']) {
|
|
const indexPath = path.join(vaultRoot, '00 Start', indexName);
|
|
if (!fs.existsSync(indexPath)) {
|
|
errors.push(`Vault-indeks mangler: 00 Start/${indexName}`);
|
|
continue;
|
|
}
|
|
|
|
const content = fs.readFileSync(indexPath, 'utf8');
|
|
const wikiPattern = /\[\[([^\]|#]+)(?:#[^\]|]+)?(?:\|[^\]]+)?\]\]/g;
|
|
for (const match of content.matchAll(wikiPattern)) {
|
|
const target = match[1].trim();
|
|
const candidates = [
|
|
path.join(vaultRoot, `${target}.md`),
|
|
path.resolve(path.dirname(indexPath), `${target}.md`)
|
|
];
|
|
if (!candidates.some(candidate => fs.existsSync(candidate))) {
|
|
errors.push(`Brudt Obsidian-link i ${indexName}: ${target}`);
|
|
}
|
|
}
|
|
}
|
|
|
|
console.log(`Kontrollerede ${rows.length} manifestposter.`);
|
|
console.log(`Fejl: ${errors.length}. Advarsler: ${warnings.length}.`);
|
|
|
|
for (const warning of warnings) console.log(`ADVARSEL: ${warning}`);
|
|
for (const error of errors) console.error(`FEJL: ${error}`);
|
|
|
|
process.exit(errors.length > 0 ? 1 : 0);
|