mnfst-render 0.5.33 → 0.5.35
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/manifest.render.mjs +61 -1
- package/package.json +1 -1
package/manifest.render.mjs
CHANGED
|
@@ -1800,6 +1800,56 @@ function computeGlobalAssetSignature(rootDir) {
|
|
|
1800
1800
|
return sha(parts.join('|'));
|
|
1801
1801
|
}
|
|
1802
1802
|
|
|
1803
|
+
/**
|
|
1804
|
+
* Wait for the page to visually settle before OG fingerprint/screenshot.
|
|
1805
|
+
* manifest:render-ready covers tracked data sources, but async work can land
|
|
1806
|
+
* after it — x-markdown article fetches, runtime-compiled utility CSS, and
|
|
1807
|
+
* webfonts — and screenshotting early captures a blank article region. Two
|
|
1808
|
+
* conditions gate the snapshot: every [x-markdown] region has rendered
|
|
1809
|
+
* content (or the cap expires — some regions are legitimately empty in
|
|
1810
|
+
* prerender, e.g. demos), and a cheap DOM signature (text length + injected
|
|
1811
|
+
* style bytes + font status + empty-markdown count) holds steady. Then let
|
|
1812
|
+
* fonts finish and the compositor paint.
|
|
1813
|
+
*/
|
|
1814
|
+
async function waitForVisualSettle(page, { settleMs = 300, mdWaitMs = 5000, capMs = 8000 } = {}) {
|
|
1815
|
+
try {
|
|
1816
|
+
await page.evaluate(async ({ settleMs, mdWaitMs, capMs }) => {
|
|
1817
|
+
const state = () => {
|
|
1818
|
+
let styleLen = 0;
|
|
1819
|
+
for (const s of document.querySelectorAll('style')) styleLen += (s.textContent || '').length;
|
|
1820
|
+
let mdEmpty = 0;
|
|
1821
|
+
for (const e of document.querySelectorAll('[x-markdown]')) {
|
|
1822
|
+
if (!e.innerHTML.trim()) mdEmpty++;
|
|
1823
|
+
}
|
|
1824
|
+
const sig = `${mdEmpty}:${document.body?.innerText.length || 0}:${styleLen}:${document.fonts ? document.fonts.status : ''}`;
|
|
1825
|
+
return { sig, mdEmpty };
|
|
1826
|
+
};
|
|
1827
|
+
const start = Date.now();
|
|
1828
|
+
let last = state();
|
|
1829
|
+
let stableSince = Date.now();
|
|
1830
|
+
while (Date.now() - start < capMs) {
|
|
1831
|
+
await new Promise((r) => setTimeout(r, 100));
|
|
1832
|
+
const cur = state();
|
|
1833
|
+
const mdPending = cur.mdEmpty > 0 && Date.now() - start < mdWaitMs;
|
|
1834
|
+
if (cur.sig !== last.sig || mdPending) {
|
|
1835
|
+
last = cur;
|
|
1836
|
+
stableSince = Date.now();
|
|
1837
|
+
} else if (Date.now() - stableSince >= settleMs) {
|
|
1838
|
+
break;
|
|
1839
|
+
}
|
|
1840
|
+
}
|
|
1841
|
+
if (document.fonts?.ready) { try { await document.fonts.ready; } catch { /* best-effort */ } }
|
|
1842
|
+
// Let the compositor paint the settled state — but raced against a
|
|
1843
|
+
// timeout: a frame-idle headless page may never fire rAF, and an
|
|
1844
|
+
// unresolved evaluate() wedges the whole render worker.
|
|
1845
|
+
await new Promise((r) => {
|
|
1846
|
+
const t = setTimeout(r, 250);
|
|
1847
|
+
requestAnimationFrame(() => requestAnimationFrame(() => { clearTimeout(t); r(); }));
|
|
1848
|
+
});
|
|
1849
|
+
}, { settleMs, mdWaitMs, capMs });
|
|
1850
|
+
} catch { /* settle is best-effort — never blocks the snapshot */ }
|
|
1851
|
+
}
|
|
1852
|
+
|
|
1803
1853
|
/**
|
|
1804
1854
|
* Snapshot the page at 1200×630 and write to <output>/og/<slug>.png. Cache
|
|
1805
1855
|
* sidecar lives in <root>/.mnfst-cache/og/ — outside the output dir, which is
|
|
@@ -1811,6 +1861,10 @@ function computeGlobalAssetSignature(rootDir) {
|
|
|
1811
1861
|
* - html.className (theme variant: light/dark/etc.)
|
|
1812
1862
|
*/
|
|
1813
1863
|
async function takeOgSnapshot(page, outputDir, pathSeg, globalAssetSignature, cacheDir) {
|
|
1864
|
+
// Settle BEFORE the cache fingerprint: the hash must describe the same
|
|
1865
|
+
// steady-state DOM the screenshot captures, or late-arriving content makes
|
|
1866
|
+
// the cache key unstable across runs.
|
|
1867
|
+
await waitForVisualSettle(page);
|
|
1814
1868
|
// The 404 page must NOT share the homepage's 'index' slug: both pages render
|
|
1815
1869
|
// concurrently and would otherwise race on the same og/index.png + cache
|
|
1816
1870
|
// files. The 404 (route hidden, near-empty body) screenshots blank, and that
|
|
@@ -2836,9 +2890,12 @@ function startStaticServer(rootDir) {
|
|
|
2836
2890
|
}
|
|
2837
2891
|
|
|
2838
2892
|
// --- Copy project into output so website is self-contained (e.g. for Appwrite). ---
|
|
2893
|
+
// Internal/tooling files stay out of the published output: agent instructions,
|
|
2894
|
+
// the schema copy, and OS metadata anywhere in the tree.
|
|
2839
2895
|
const COPY_EXCLUDE = new Set([
|
|
2840
2896
|
'node_modules', '.git', 'package.json', 'package-lock.json',
|
|
2841
2897
|
'index.html', 'prerender.mjs', 'prerender.js', '_redirects',
|
|
2898
|
+
'CLAUDE.md', 'AGENTS.md', 'manifest.schema.json',
|
|
2842
2899
|
]);
|
|
2843
2900
|
|
|
2844
2901
|
function copyProjectIntoDist(rootResolved, outputResolved) {
|
|
@@ -2850,7 +2907,10 @@ function copyProjectIntoDist(rootResolved, outputResolved) {
|
|
|
2850
2907
|
if (COPY_EXCLUDE.has(name) || name.startsWith('.')) continue;
|
|
2851
2908
|
const src = join(rootResolved, name);
|
|
2852
2909
|
const dest = join(outputResolved, name);
|
|
2853
|
-
cpSync(src, dest, {
|
|
2910
|
+
cpSync(src, dest, {
|
|
2911
|
+
recursive: true,
|
|
2912
|
+
filter: (p) => { const b = basename(p); return b !== '.DS_Store' && b !== 'Thumbs.db'; },
|
|
2913
|
+
});
|
|
2854
2914
|
}
|
|
2855
2915
|
COPY_EXCLUDE.delete(outputDirName);
|
|
2856
2916
|
}
|