Files
tilbudgivern/scripts/check-obsidian-vault.js
2026-08-17 09:12:03 +02:00

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);