Astro garden skeleton: Quartz-compatible URLs, Obsidian markdown, odometer homepage
- glob content loader over the vault's Digital_Garden with verbatim-path ids - remark plugin: wikilinks, image embeds (wiki + relative md), callouts - URL parity with live Quartz sitemap verified 102/102 (built set is a strict superset: +30 synthetic folder listings) - KaTeX, lazy mermaid, shiki dual themes, quoted-string draft handling - copy-assets resolves embeds vault-wide (fixes live-site 404s for Blog Scratch/assets images) into flat /assets/ - homepage odometer widget: fetches api.c0smere.net baseline, ticks at lifetime average rate, fail-closed hidden on error - deploy/: nginx try_files config + compose (port 18100) + containerized build script for cyrion Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,85 @@
|
||||
// Collect every image referenced by an Obsidian embed (![[...]]) in the
|
||||
// garden content and copy it to public/assets/ flat by basename — matching
|
||||
// the /assets/<name> URLs the remark plugin emits. Resolution order:
|
||||
// Digital_Garden first, then the wider vault (some embeds point at files
|
||||
// that live outside the published folder; the live Quartz site 404s those —
|
||||
// we fix them, but loudly, so stray references are visible).
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import {
|
||||
CONTENT_DIR,
|
||||
VAULT_ROOT,
|
||||
walkMarkdown,
|
||||
} from '../src/lib/quartz-compat.mjs';
|
||||
|
||||
const OUT_DIR = path.join(
|
||||
path.dirname(fileURLToPath(import.meta.url)),
|
||||
'..',
|
||||
'public',
|
||||
'assets',
|
||||
);
|
||||
const EMBED_RE = /!\[\[([^\]|]+?\.(?:png|jpe?g|gif|svg|webp))(?:\|[^\]]*)?\]\]/gi;
|
||||
const MD_IMG_RE = /!\[[^\]]*\]\(([^)\s]+?\.(?:png|jpe?g|gif|svg|webp))\)/gi;
|
||||
|
||||
function walkFiles(dir) {
|
||||
const out = [];
|
||||
for (const ent of fs.readdirSync(dir, { withFileTypes: true })) {
|
||||
if (ent.name.startsWith('.')) continue;
|
||||
const full = path.join(dir, ent.name);
|
||||
if (ent.isDirectory()) out.push(...walkFiles(full));
|
||||
else out.push(full);
|
||||
}
|
||||
return out;
|
||||
}
|
||||
|
||||
// referenced image names (basename, as Obsidian resolves vault-wide)
|
||||
const wanted = new Map(); // lowercase basename -> name as referenced
|
||||
for (const rel of walkMarkdown(CONTENT_DIR)) {
|
||||
const text = fs.readFileSync(path.join(CONTENT_DIR, rel), 'utf8');
|
||||
for (const m of text.matchAll(EMBED_RE)) {
|
||||
const name = m[1].split('/').pop().trim();
|
||||
wanted.set(name.toLowerCase(), name);
|
||||
}
|
||||
for (const m of text.matchAll(MD_IMG_RE)) {
|
||||
if (/^(?:[a-z]+:)?\/\//i.test(m[1])) continue;
|
||||
const name = decodeURIComponent(m[1]).split('/').pop().trim();
|
||||
wanted.set(name.toLowerCase(), name);
|
||||
}
|
||||
}
|
||||
|
||||
// index every file in the vault by lowercase basename
|
||||
const inDG = new Map();
|
||||
const inVault = new Map();
|
||||
for (const f of walkFiles(VAULT_ROOT)) {
|
||||
const key = path.basename(f).toLowerCase();
|
||||
if (f.startsWith(CONTENT_DIR + path.sep)) {
|
||||
if (!inDG.has(key)) inDG.set(key, f);
|
||||
} else if (!inVault.has(key)) {
|
||||
inVault.set(key, f);
|
||||
}
|
||||
}
|
||||
|
||||
fs.rmSync(OUT_DIR, { recursive: true, force: true });
|
||||
fs.mkdirSync(OUT_DIR, { recursive: true });
|
||||
|
||||
let copied = 0;
|
||||
let missing = 0;
|
||||
for (const [key, name] of wanted) {
|
||||
const src = inDG.get(key) ?? inVault.get(key);
|
||||
if (!src) {
|
||||
console.warn(`copy-assets: UNRESOLVED image embed: ${name}`);
|
||||
missing++;
|
||||
continue;
|
||||
}
|
||||
if (!inDG.has(key)) {
|
||||
console.warn(
|
||||
`copy-assets: ${name} lives outside Digital_Garden (${path.relative(VAULT_ROOT, src)})`,
|
||||
);
|
||||
}
|
||||
fs.copyFileSync(src, path.join(OUT_DIR, name));
|
||||
copied++;
|
||||
}
|
||||
|
||||
console.log(`copy-assets: ${copied} copied, ${missing} unresolved -> ${OUT_DIR}`);
|
||||
if (missing) process.exitCode = 0; // warn, don't fail the build
|
||||
Reference in New Issue
Block a user