Add deployment scripts for blog, links, notes, photos, and projects; implement history squashing and vision processing

- Created `publish-blog.sh`, `publish-links.sh`, `publish-notes.sh`, `publish-photos.sh`, and `publish-projects.sh` for deploying respective content to the VPS.
- Implemented `squash-history.sh` to replace the entire git history with a single commit.
- Added `vision.ts` and `vision.spec.ts` for processing images with AI, including metadata extraction and merging.
- Enhanced error handling and logging in vision processing scripts.
This commit is contained in:
Adrian Altner 2026-04-21 23:17:21 +02:00
parent a123886ee4
commit 0f43bb18cc
24 changed files with 2430 additions and 41 deletions

View file

@ -0,0 +1,74 @@
/**
* Fetches favicons for all linked domains and saves them locally to public/favicons/.
* Run before astro build so favicons are served statically instead of via Google S2.
* Idempotent: skips domains that already have a cached favicon.
*/
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
import { join } from "node:path";
import { fileURLToPath } from "node:url";
const __dirname = fileURLToPath(new URL(".", import.meta.url));
const root = join(__dirname, "..");
const linksPath = join(root, "src/content/links/links.json");
const outDir = join(root, "public/favicons");
function getDomain(url) {
try {
return new URL(url).hostname.replace(/^www\./, "");
} catch {
return null;
}
}
function faviconPath(domain) {
return join(outDir, `${domain}.png`);
}
async function fetchFavicon(domain) {
const url = `https://www.google.com/s2/favicons?domain=${domain}&sz=32`;
const res = await fetch(url);
if (!res.ok) throw new Error(`HTTP ${res.status}`);
const buf = await res.arrayBuffer();
return Buffer.from(buf);
}
async function main() {
const links = JSON.parse(readFileSync(linksPath, "utf-8"));
const domains = [
...new Set(links.map((l) => getDomain(l.url)).filter(Boolean)),
];
if (!existsSync(outDir)) mkdirSync(outDir, { recursive: true });
let fetched = 0;
let skipped = 0;
await Promise.all(
domains.map(async (domain) => {
const dest = faviconPath(domain);
if (existsSync(dest)) {
skipped++;
return;
}
try {
const data = await fetchFavicon(domain);
writeFileSync(dest, data);
fetched++;
console.log(`${domain}`);
} catch (err) {
console.warn(`${domain}: ${err.message}`);
}
}),
);
console.log(
`Favicons: ${fetched} fetched, ${skipped} cached, ${domains.length} total`,
);
}
main().catch((err) => {
console.error(err);
process.exit(1);
});