Notebooks from the marimo server can now appear in the garden. A notebook
opts in with an HTML comment in one of its markdown cells — invisible when
rendered, greppable in the .py source:
<!-- garden:publish
title: Dream of Spotification
order: 30
-->
Default-deny on purpose: garden.c0smere.net is public and the export bakes
each notebook's executed output into the page, not just its code.
export-notebooks.py also carries a NEVER_PUBLISH list (genome_*, coursework)
so a marker pasted into one of those refuses loudly instead of publishing.
Pipeline: export-notebooks.py runs each marked notebook in a one-shot
marimo container (same image/env/GPU as the live server, so it hits the real
databases) into notebooks-export/ + index.json; copy-notebooks.mjs stages
those to public/nb/; Astro reads index.json to build the pages and the
sidebar section.
Isolation choices worth keeping:
- Own timer and own lock, separate from the 5-min garden build — executing a
notebook takes minutes and must never hold up a build tick.
- Cached on notebook content; both index.json and .cache.json go through
write_if_changed, since auto-build.sh hashes mtimes under notebooks-export/
and an unconditional rewrite would force a full rebuild every 30 minutes.
- Per-notebook timeout; a failure keeps the previous export and continues.
- Runs against a throwaway copy of the notebook dir, so notebooks that write
scratch files don't dirty the notebooks repo.
- Notebooks are NOT injected into the garden collection — they aren't vault
notes, and doing so would move noteCount and the sitemap.
build.format:'file' makes the listing a file (notebooks.html) beside a
directory of detail pages. Verified against the running nginx: /notebooks and
/notebooks/<slug> resolve through the generic try_files but /notebooks/ does
not, so nginx.conf gets an explicit location for the trailing-slash form.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
46 lines
1.7 KiB
JavaScript
46 lines
1.7 KiB
JavaScript
// Stage the marimo notebook exports produced by deploy/export-notebooks.py
|
|
// into public/, where Astro will pick them up as static files.
|
|
//
|
|
// The raw marimo HTML lands at public/nb/<slug>.html (served /nb/<slug>.html)
|
|
// and is what the notebook pages iframe. It deliberately does NOT live under
|
|
// /notebooks/ — those URLs belong to the Astro pages that wrap it in the
|
|
// garden chrome, and with build.format:'file' the two would collide on a
|
|
// trailing slash.
|
|
//
|
|
// No exports (export script never ran, or nothing is marked) is a normal
|
|
// state, not an error: the notebooks section simply doesn't render.
|
|
import fs from 'node:fs';
|
|
import path from 'node:path';
|
|
import { fileURLToPath } from 'node:url';
|
|
|
|
const REPO = path.join(path.dirname(fileURLToPath(import.meta.url)), '..');
|
|
const SRC_DIR = path.join(REPO, 'notebooks-export');
|
|
const OUT_DIR = path.join(REPO, 'public', 'nb');
|
|
|
|
fs.rmSync(OUT_DIR, { recursive: true, force: true });
|
|
|
|
let index = [];
|
|
try {
|
|
index = JSON.parse(fs.readFileSync(path.join(SRC_DIR, 'index.json'), 'utf8'));
|
|
} catch {
|
|
console.log('copy-notebooks: no notebooks-export/index.json — nothing to stage');
|
|
process.exit(0);
|
|
}
|
|
|
|
fs.mkdirSync(OUT_DIR, { recursive: true });
|
|
|
|
let copied = 0;
|
|
for (const nb of index) {
|
|
const src = path.join(SRC_DIR, `${nb.slug}.html`);
|
|
if (!fs.existsSync(src)) {
|
|
// export-notebooks.py only indexes notebooks it exported, so this means
|
|
// the two got out of sync — warn rather than ship a link to a 404
|
|
console.warn(`copy-notebooks: MISSING export for indexed notebook ${nb.slug}`);
|
|
continue;
|
|
}
|
|
fs.copyFileSync(src, path.join(OUT_DIR, `${nb.slug}.html`));
|
|
copied++;
|
|
}
|
|
|
|
console.log(`copy-notebooks: ${copied}/${index.length} staged -> ${OUT_DIR}`);
|