@olwiba/dx 0.0.30 → 0.0.31
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/dist/cli.js +1 -1
- package/dist/docs-check.js +1 -1
- package/dist/docs-check.js.map +1 -1
- package/dist/generate-previews.js +47 -1
- package/dist/generate-previews.js.map +1 -1
- package/package.json +2 -2
package/dist/cli.js
CHANGED
|
@@ -2586,7 +2586,7 @@ function formatDocsReport(result) {
|
|
|
2586
2586
|
lines.push(
|
|
2587
2587
|
` ${finding.file}`,
|
|
2588
2588
|
" Shows a preview but has no <APIReference>. Add one, or opt out with",
|
|
2589
|
-
" {/* api-reference: none
|
|
2589
|
+
" {/* api-reference: none (why) */} if there is no public API here."
|
|
2590
2590
|
);
|
|
2591
2591
|
} else if (finding.kind === "broken-props") {
|
|
2592
2592
|
lines.push(
|
package/dist/docs-check.js
CHANGED
|
@@ -95,7 +95,7 @@ function formatDocsReport(result) {
|
|
|
95
95
|
lines.push(
|
|
96
96
|
` ${finding.file}`,
|
|
97
97
|
" Shows a preview but has no <APIReference>. Add one, or opt out with",
|
|
98
|
-
" {/* api-reference: none
|
|
98
|
+
" {/* api-reference: none (why) */} if there is no public API here."
|
|
99
99
|
);
|
|
100
100
|
} else if (finding.kind === "broken-props") {
|
|
101
101
|
lines.push(
|
package/dist/docs-check.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/docs-check.ts"],"names":[],"mappings":"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","file":"docs-check.js","sourcesContent":["/**\n * Checks that every documentation page showing a component also documents it.\n *\n * A docs page that renders a preview is making a promise: this is a component\n * you can use. The properties table is the other half of that promise, and it\n * is the half that goes missing, because a page reads as finished long before\n * anyone writes the props down. Fourteen marketing pages and an `ErrorPage`\n * page that said in prose \"takes no props\" — it has seven — shipped that way\n * and nobody noticed until someone opened one on a phone.\n *\n * So the rule is mechanical: a preview implies an `<APIReference>`. Pages that\n * genuinely have no public surface to document opt out in the file, in a\n * comment that has to carry a reason.\n */\n\n/** Marks a page as having nothing to document, and says why. */\nconst OPT_OUT = /api-reference:\\s*none\\b/;\n\n/** What counts as showing a component off. */\nconst PREVIEW = /<(Sandbox|ComponentPreview)\\b/;\n\nconst API_REFERENCE = /<APIReference\\b/;\n\n/**\n * Drops fenced and inline code before looking for markers.\n *\n * A page that quotes `<Sandbox>` in a table, or shows what the opt-out comment\n * looks like inside a fence, is describing the markers rather than using them —\n * nothing is rendered and nothing is claimed. Matching the raw source made the\n * checker's own documentation page its first false positive, which is the sort\n * of rule that gets switched off rather than fixed.\n *\n * Fences go first so an unpaired backtick inside one cannot swallow the rest of\n * the file.\n */\nfunction withoutCode(source: string): string {\n return source.replace(/^[ \\t]*(`{3,}|~{3,})[\\s\\S]*?^[ \\t]*\\1[ \\t]*$/gm, '').replace(/`[^`\\n]*`/g, '');\n}\n\nexport type DocsFinding =\n | { kind: 'missing'; file: string }\n /** Opted out without saying why, which is how an opt-out becomes a habit. */\n | { kind: 'unexplained-opt-out'; file: string }\n /** Opted out and then documented it anyway — the comment is stale. */\n | { kind: 'redundant-opt-out'; file: string }\n /** The props array is not valid JavaScript, so the page cannot render. */\n | { kind: 'broken-props'; file: string; reason: string };\n\nexport interface DocsCheckResult {\n findings: DocsFinding[];\n /** Pages with a preview and a reference. Counted, not listed. */\n okCount: number;\n /** Pages with a preview and an explained opt-out. */\n exemptCount: number;\n /** Pages with no preview, which this says nothing about. */\n skippedCount: number;\n}\n\nexport interface DocsPage {\n /** Path as it should be reported — repository-relative, forward slashes. */\n file: string;\n source: string;\n}\n\n/**\n * An opt-out needs a reason after the marker, on the same line or the ones\n * following it inside the same comment. `api-reference: none` on its own is\n * rejected: the point of the escape hatch is that using it is a decision\n * somebody wrote down.\n */\nfunction optOutIsExplained(source: string): boolean {\n const match = source.match(/api-reference:\\s*none\\b([\\s\\S]{0,400})/);\n if (!match) return false;\n\n const tail = match[1] ?? '';\n // Stop at the end of the comment the marker sits in, so prose further down\n // the page cannot pass for a justification.\n const end = tail.indexOf('*/');\n const within = end === -1 ? tail : tail.slice(0, end);\n\n // Strip the punctuation an author uses to introduce the reason, plus the\n // comment gutter, and see whether any words are left.\n const words = within\n .replace(/^[\\s—\\-–:,.]+/, '')\n .replace(/^\\s*\\*/gm, '')\n .trim();\n\n return words.split(/\\s+/).filter(Boolean).length >= 3;\n}\n\n/**\n * Every balanced `{...}` following a `props=` attribute.\n *\n * Matched by counting braces rather than by regex: the prop tables contain\n * object literals, so the first `}` is nowhere near the end of the attribute.\n */\nfunction extractPropsExpressions(source: string): string[] {\n const found: string[] = [];\n const marker = 'props=';\n let index = source.indexOf(marker);\n\n while (index !== -1) {\n let cursor = index + marker.length;\n if (source[cursor] !== '{') {\n index = source.indexOf(marker, cursor);\n continue;\n }\n\n let depth = 0;\n const start = cursor;\n for (; cursor < source.length; cursor += 1) {\n if (source[cursor] === '{') depth += 1;\n else if (source[cursor] === '}') {\n depth -= 1;\n if (depth === 0) break;\n }\n }\n\n found.push(source.slice(start + 1, cursor));\n index = source.indexOf(marker, cursor);\n }\n\n return found;\n}\n\n/**\n * Confirms the prop table is JavaScript that runs.\n *\n * MDX does not evaluate a page's expressions until something renders it, so a\n * mis-escaped quote inside a description is invisible to every build step and\n * only shows up as a blank page. Evaluating the array here is the cheapest way\n * to find that out — the source being run is the repository's own.\n */\nfunction propsFailure(source: string): string | null {\n for (const expression of extractPropsExpressions(source)) {\n let value: unknown;\n try {\n value = new Function(`return (${expression});`)();\n } catch (error) {\n return error instanceof Error ? error.message : String(error);\n }\n\n if (!Array.isArray(value)) return 'props is not an array';\n\n for (const entry of value as Array<Record<string, unknown>>) {\n if (typeof entry?.name !== 'string' || typeof entry?.type !== 'string') {\n return `a prop entry is missing name or type: ${JSON.stringify(entry)}`;\n }\n }\n }\n\n return null;\n}\n\nexport function checkDocs(pages: DocsPage[]): DocsCheckResult {\n const findings: DocsFinding[] = [];\n let okCount = 0;\n let exemptCount = 0;\n let skippedCount = 0;\n\n for (const page of pages) {\n // Markers are looked for in what the page renders, not in what it quotes.\n const rendered = withoutCode(page.source);\n const hasPreview = PREVIEW.test(rendered);\n const hasReference = API_REFERENCE.test(rendered);\n const optedOut = OPT_OUT.test(rendered);\n\n // Checked before anything else: a page whose table cannot run has a worse\n // problem than a page that has no table. Read from the raw source: a props\n // table is an expression MDX evaluates, and stripping inline code would\n // change it.\n const broken = hasReference ? propsFailure(page.source) : null;\n if (broken) {\n findings.push({ kind: 'broken-props', file: page.file, reason: broken });\n continue;\n }\n\n if (optedOut && hasReference) {\n findings.push({ kind: 'redundant-opt-out', file: page.file });\n continue;\n }\n\n if (!hasPreview) {\n // Index pages, guides, concept pages. Nothing is being shown off, so\n // there is nothing this can reasonably demand.\n skippedCount += 1;\n continue;\n }\n\n if (optedOut) {\n if (optOutIsExplained(rendered)) exemptCount += 1;\n else findings.push({ kind: 'unexplained-opt-out', file: page.file });\n continue;\n }\n\n if (hasReference) okCount += 1;\n else findings.push({ kind: 'missing', file: page.file });\n }\n\n return { findings, okCount, exemptCount, skippedCount };\n}\n\nexport function formatDocsReport(result: DocsCheckResult): string {\n const lines: string[] = [];\n\n for (const finding of result.findings) {\n if (finding.kind === 'missing') {\n lines.push(\n ` ${finding.file}`,\n ' Shows a preview but has no <APIReference>. Add one, or opt out with',\n ' {/* api-reference: none — why */} if there is no public API here.',\n );\n } else if (finding.kind === 'broken-props') {\n lines.push(\n ` ${finding.file}`,\n ` Its <APIReference> props are not valid JavaScript, so the page will not render.`,\n ` ${finding.reason}`,\n );\n } else if (finding.kind === 'unexplained-opt-out') {\n lines.push(\n ` ${finding.file}`,\n ' Opts out of the API reference without a reason. Write one after the marker.',\n );\n } else {\n lines.push(\n ` ${finding.file}`,\n ' Opts out of the API reference and then has one. Remove the stale marker.',\n );\n }\n }\n\n const tally =\n `${result.okCount} documented, ${result.exemptCount} exempt, ` +\n `${result.skippedCount} without a preview`;\n\n if (result.findings.length === 0) {\n return `All documentation pages that show a component document it (${tally}).`;\n }\n\n return (\n `${result.findings.length} documentation page(s) need attention:\\n` +\n `${lines.join('\\n')}\\n\\n${tally}`\n );\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/docs-check.ts"],"names":[],"mappings":"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","file":"docs-check.js","sourcesContent":["/**\n * Checks that every documentation page showing a component also documents it.\n *\n * A docs page that renders a preview is making a promise: this is a component\n * you can use. The properties table is the other half of that promise, and it\n * is the half that goes missing, because a page reads as finished long before\n * anyone writes the props down. Fourteen marketing pages and an `ErrorPage`\n * page that said in prose \"takes no props\" — it has seven — shipped that way\n * and nobody noticed until someone opened one on a phone.\n *\n * So the rule is mechanical: a preview implies an `<APIReference>`. Pages that\n * genuinely have no public surface to document opt out in the file, in a\n * comment that has to carry a reason.\n */\n\n/** Marks a page as having nothing to document, and says why. */\nconst OPT_OUT = /api-reference:\\s*none\\b/;\n\n/** What counts as showing a component off. */\nconst PREVIEW = /<(Sandbox|ComponentPreview)\\b/;\n\nconst API_REFERENCE = /<APIReference\\b/;\n\n/**\n * Drops fenced and inline code before looking for markers.\n *\n * A page that quotes `<Sandbox>` in a table, or shows what the opt-out comment\n * looks like inside a fence, is describing the markers rather than using them —\n * nothing is rendered and nothing is claimed. Matching the raw source made the\n * checker's own documentation page its first false positive, which is the sort\n * of rule that gets switched off rather than fixed.\n *\n * Fences go first so an unpaired backtick inside one cannot swallow the rest of\n * the file.\n */\nfunction withoutCode(source: string): string {\n return source.replace(/^[ \\t]*(`{3,}|~{3,})[\\s\\S]*?^[ \\t]*\\1[ \\t]*$/gm, '').replace(/`[^`\\n]*`/g, '');\n}\n\nexport type DocsFinding =\n | { kind: 'missing'; file: string }\n /** Opted out without saying why, which is how an opt-out becomes a habit. */\n | { kind: 'unexplained-opt-out'; file: string }\n /** Opted out and then documented it anyway — the comment is stale. */\n | { kind: 'redundant-opt-out'; file: string }\n /** The props array is not valid JavaScript, so the page cannot render. */\n | { kind: 'broken-props'; file: string; reason: string };\n\nexport interface DocsCheckResult {\n findings: DocsFinding[];\n /** Pages with a preview and a reference. Counted, not listed. */\n okCount: number;\n /** Pages with a preview and an explained opt-out. */\n exemptCount: number;\n /** Pages with no preview, which this says nothing about. */\n skippedCount: number;\n}\n\nexport interface DocsPage {\n /** Path as it should be reported — repository-relative, forward slashes. */\n file: string;\n source: string;\n}\n\n/**\n * An opt-out needs a reason after the marker, on the same line or the ones\n * following it inside the same comment. `api-reference: none` on its own is\n * rejected: the point of the escape hatch is that using it is a decision\n * somebody wrote down.\n */\nfunction optOutIsExplained(source: string): boolean {\n const match = source.match(/api-reference:\\s*none\\b([\\s\\S]{0,400})/);\n if (!match) return false;\n\n const tail = match[1] ?? '';\n // Stop at the end of the comment the marker sits in, so prose further down\n // the page cannot pass for a justification.\n const end = tail.indexOf('*/');\n const within = end === -1 ? tail : tail.slice(0, end);\n\n // Strip the punctuation an author uses to introduce the reason, plus the\n // comment gutter, and see whether any words are left.\n const words = within\n .replace(/^[\\s—\\-–:,.]+/, '')\n .replace(/^\\s*\\*/gm, '')\n .trim();\n\n return words.split(/\\s+/).filter(Boolean).length >= 3;\n}\n\n/**\n * Every balanced `{...}` following a `props=` attribute.\n *\n * Matched by counting braces rather than by regex: the prop tables contain\n * object literals, so the first `}` is nowhere near the end of the attribute.\n */\nfunction extractPropsExpressions(source: string): string[] {\n const found: string[] = [];\n const marker = 'props=';\n let index = source.indexOf(marker);\n\n while (index !== -1) {\n let cursor = index + marker.length;\n if (source[cursor] !== '{') {\n index = source.indexOf(marker, cursor);\n continue;\n }\n\n let depth = 0;\n const start = cursor;\n for (; cursor < source.length; cursor += 1) {\n if (source[cursor] === '{') depth += 1;\n else if (source[cursor] === '}') {\n depth -= 1;\n if (depth === 0) break;\n }\n }\n\n found.push(source.slice(start + 1, cursor));\n index = source.indexOf(marker, cursor);\n }\n\n return found;\n}\n\n/**\n * Confirms the prop table is JavaScript that runs.\n *\n * MDX does not evaluate a page's expressions until something renders it, so a\n * mis-escaped quote inside a description is invisible to every build step and\n * only shows up as a blank page. Evaluating the array here is the cheapest way\n * to find that out — the source being run is the repository's own.\n */\nfunction propsFailure(source: string): string | null {\n for (const expression of extractPropsExpressions(source)) {\n let value: unknown;\n try {\n value = new Function(`return (${expression});`)();\n } catch (error) {\n return error instanceof Error ? error.message : String(error);\n }\n\n if (!Array.isArray(value)) return 'props is not an array';\n\n for (const entry of value as Array<Record<string, unknown>>) {\n if (typeof entry?.name !== 'string' || typeof entry?.type !== 'string') {\n return `a prop entry is missing name or type: ${JSON.stringify(entry)}`;\n }\n }\n }\n\n return null;\n}\n\nexport function checkDocs(pages: DocsPage[]): DocsCheckResult {\n const findings: DocsFinding[] = [];\n let okCount = 0;\n let exemptCount = 0;\n let skippedCount = 0;\n\n for (const page of pages) {\n // Markers are looked for in what the page renders, not in what it quotes.\n const rendered = withoutCode(page.source);\n const hasPreview = PREVIEW.test(rendered);\n const hasReference = API_REFERENCE.test(rendered);\n const optedOut = OPT_OUT.test(rendered);\n\n // Checked before anything else: a page whose table cannot run has a worse\n // problem than a page that has no table. Read from the raw source: a props\n // table is an expression MDX evaluates, and stripping inline code would\n // change it.\n const broken = hasReference ? propsFailure(page.source) : null;\n if (broken) {\n findings.push({ kind: 'broken-props', file: page.file, reason: broken });\n continue;\n }\n\n if (optedOut && hasReference) {\n findings.push({ kind: 'redundant-opt-out', file: page.file });\n continue;\n }\n\n if (!hasPreview) {\n // Index pages, guides, concept pages. Nothing is being shown off, so\n // there is nothing this can reasonably demand.\n skippedCount += 1;\n continue;\n }\n\n if (optedOut) {\n if (optOutIsExplained(rendered)) exemptCount += 1;\n else findings.push({ kind: 'unexplained-opt-out', file: page.file });\n continue;\n }\n\n if (hasReference) okCount += 1;\n else findings.push({ kind: 'missing', file: page.file });\n }\n\n return { findings, okCount, exemptCount, skippedCount };\n}\n\nexport function formatDocsReport(result: DocsCheckResult): string {\n const lines: string[] = [];\n\n for (const finding of result.findings) {\n if (finding.kind === 'missing') {\n lines.push(\n ` ${finding.file}`,\n ' Shows a preview but has no <APIReference>. Add one, or opt out with',\n ' {/* api-reference: none (why) */} if there is no public API here.',\n );\n } else if (finding.kind === 'broken-props') {\n lines.push(\n ` ${finding.file}`,\n ` Its <APIReference> props are not valid JavaScript, so the page will not render.`,\n ` ${finding.reason}`,\n );\n } else if (finding.kind === 'unexplained-opt-out') {\n lines.push(\n ` ${finding.file}`,\n ' Opts out of the API reference without a reason. Write one after the marker.',\n );\n } else {\n lines.push(\n ` ${finding.file}`,\n ' Opts out of the API reference and then has one. Remove the stale marker.',\n );\n }\n }\n\n const tally =\n `${result.okCount} documented, ${result.exemptCount} exempt, ` +\n `${result.skippedCount} without a preview`;\n\n if (result.findings.length === 0) {\n return `All documentation pages that show a component document it (${tally}).`;\n }\n\n return (\n `${result.findings.length} documentation page(s) need attention:\\n` +\n `${lines.join('\\n')}\\n\\n${tally}`\n );\n}\n"]}
|
|
@@ -34,6 +34,51 @@ function findChromePath() {
|
|
|
34
34
|
"No Chrome/Edge executable found. Pass executablePath in config or install Chrome."
|
|
35
35
|
);
|
|
36
36
|
}
|
|
37
|
+
function shrinkFrameToContent(selector) {
|
|
38
|
+
const d = globalThis.document;
|
|
39
|
+
const wrapper = d?.querySelector(selector);
|
|
40
|
+
if (!wrapper) return;
|
|
41
|
+
const iframe = wrapper.querySelector("iframe");
|
|
42
|
+
if (!iframe) return;
|
|
43
|
+
let doc = null;
|
|
44
|
+
try {
|
|
45
|
+
doc = iframe.contentDocument;
|
|
46
|
+
} catch {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
if (!doc) return;
|
|
50
|
+
const root = doc.getElementById("sandbox-root") ?? doc.body;
|
|
51
|
+
if (!root) return;
|
|
52
|
+
const chain = [doc.documentElement, doc.body, root];
|
|
53
|
+
const saved = chain.map((node) => node?.getAttribute("style") ?? null);
|
|
54
|
+
const restore = () => {
|
|
55
|
+
chain.forEach((node, index) => {
|
|
56
|
+
if (!node) return;
|
|
57
|
+
if (saved[index] === null) node.removeAttribute("style");
|
|
58
|
+
else node.setAttribute("style", saved[index]);
|
|
59
|
+
});
|
|
60
|
+
};
|
|
61
|
+
for (const node of chain) {
|
|
62
|
+
if (!node) continue;
|
|
63
|
+
node.style.height = "";
|
|
64
|
+
node.style.minHeight = "";
|
|
65
|
+
}
|
|
66
|
+
let contentHeight = 0;
|
|
67
|
+
for (const child of Array.from(root.children)) {
|
|
68
|
+
contentHeight = Math.max(contentHeight, child.getBoundingClientRect().bottom);
|
|
69
|
+
}
|
|
70
|
+
contentHeight = Math.ceil(contentHeight);
|
|
71
|
+
const frameHeight = iframe.clientHeight;
|
|
72
|
+
const PLAUSIBLE = 120;
|
|
73
|
+
const WORTH_IT = 24;
|
|
74
|
+
if (contentHeight < PLAUSIBLE || contentHeight > frameHeight - WORTH_IT) {
|
|
75
|
+
restore();
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
iframe.style.height = `${contentHeight}px`;
|
|
79
|
+
wrapper.style.height = `${contentHeight}px`;
|
|
80
|
+
wrapper.style.minHeight = "0";
|
|
81
|
+
}
|
|
37
82
|
async function generatePreviews(config) {
|
|
38
83
|
const {
|
|
39
84
|
baseUrl,
|
|
@@ -139,7 +184,8 @@ async function generatePreviews(config) {
|
|
|
139
184
|
await page.evaluate(
|
|
140
185
|
`document.querySelector(${JSON.stringify(captureSelector)}).style.minHeight = '0'`
|
|
141
186
|
);
|
|
142
|
-
await
|
|
187
|
+
await page.evaluate(shrinkFrameToContent, captureSelector);
|
|
188
|
+
await new Promise((r) => setTimeout(r, 150));
|
|
143
189
|
const box = await el.boundingBox();
|
|
144
190
|
if (!box) throw new Error(`Could not get bounding box for: ${captureSelector}`);
|
|
145
191
|
if (padding > 0) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/generate-previews.ts"],"names":[],"mappings":";;;;;AAwCO,SAAS,wBAAA,CACd,SAAA,EACA,aAAA,EACA,KAAA,EACA,YAAA,EACwC;AACxC,EAAA,IACE,CAAC,aAAA,IACD,aAAA,KAAkB,GAAA,IAClB,aAAA,KAAkB,QAClB,IAAA,CAAK,UAAA,CAAW,aAAa,CAAA,IAC7B,cAAc,QAAA,CAAS,GAAG,KAC1B,aAAA,CAAc,QAAA,CAAS,IAAI,CAAA,EAC3B;AACA,IAAA,MAAM,IAAI,KAAA,CAAM,CAAA,gCAAA,EAAmC,aAAa,CAAA,CAAE,CAAA;AAAA,EACpE;AAEA,EAAA,MAAM,QAAA,GAAW,eACb,CAAA,EAAG,aAAa,IAAI,KAAK,CAAA,IAAA,CAAA,GACzB,GAAG,aAAa,CAAA,IAAA,CAAA;AACpB,EAAA,MAAM,iBAAA,GAAoB,IAAA,CAAK,OAAA,CAAQ,SAAS,CAAA;AAChD,EAAA,MAAM,QAAA,GAAW,IAAA,CAAK,OAAA,CAAQ,iBAAA,EAAmB,QAAQ,CAAA;AACzD,EAAA,MAAM,YAAA,GAAe,IAAA,CAAK,QAAA,CAAS,iBAAA,EAAmB,QAAQ,CAAA;AAE9D,EAAA,IACE,YAAA,KAAiB,IAAA,IACjB,YAAA,CAAa,UAAA,CAAW,CAAA,EAAA,EAAK,IAAA,CAAK,GAAG,CAAA,CAAE,CAAA,IACvC,IAAA,CAAK,UAAA,CAAW,YAAY,CAAA,EAC5B;AACA,IAAA,MAAM,IAAI,KAAA,CAAM,CAAA,gCAAA,EAAmC,aAAa,CAAA,CAAE,CAAA;AAAA,EACpE;AAEA,EAAA,OAAO,EAAE,UAAU,QAAA,EAAS;AAC9B;AAEA,SAAS,cAAA,GAAyB;AAChC,EAAA,MAAM,UAAA,GACJ,OAAA,CAAQ,QAAA,KAAa,OAAA,GACjB;AAAA,IACE,4DAAA;AAAA,IACA,kEAAA;AAAA,IACA,CAAA,EAAG,OAAA,CAAQ,GAAA,CAAI,YAAY,CAAA,yCAAA,CAAA;AAAA,IAC3B,mEAAA;AAAA,IACA,CAAA,EAAG,OAAA,CAAQ,GAAA,CAAI,YAAY,CAAA,0CAAA;AAAA,GAC7B,GACA,OAAA,CAAQ,QAAA,KAAa,QAAA,GACnB;AAAA,IACE,8DAAA;AAAA,IACA;AAAA,GACF,GACA,CAAC,wBAAA,EAA0B,2BAAA,EAA6B,mBAAmB,CAAA;AAEnF,EAAA,KAAA,MAAW,KAAK,UAAA,EAAY;AAC1B,IAAA,IAAI,CAAA,IAAK,EAAA,CAAG,UAAA,CAAW,CAAC,GAAG,OAAO,CAAA;AAAA,EACpC;AACA,EAAA,MAAM,IAAI,KAAA;AAAA,IACR;AAAA,GACF;AACF;AAEA,eAAsB,iBAAiB,MAAA,EAA+C;AACpF,EAAA,MAAM;AAAA,IACJ,OAAA;AAAA,IACA,SAAA;AAAA,IACA,UAAA;AAAA,IACA,QAAA,GAAW,wCAAA;AAAA,IACX,MAAA,GAAS,CAAC,OAAA,EAAS,MAAM,CAAA;AAAA,IACzB,YAAA;AAAA,IACA,cAAA;AAAA,IACA,OAAA,GAAU,CAAA;AAAA,IACV,QAAA;AAAA,IACA;AAAA,GACF,GAAI,MAAA;AAEJ,EAAA,MAAM,EAAA,GAAK,UAAU,KAAA,IAAS,IAAA;AAC9B,EAAA,MAAM,EAAA,GAAK,UAAU,MAAA,IAAU,GAAA;AAE/B,EAAA,KAAA,MAAW,aAAa,UAAA,EAAY;AAClC,IAAA,KAAA,MAAW,SAAS,MAAA,EAAQ;AAC1B,MAAA,wBAAA,CAAyB,WAAW,SAAA,CAAU,IAAA,EAAM,KAAA,EAAO,MAAA,CAAO,SAAS,CAAC,CAAA;AAAA,IAC9E;AAAA,EACF;AAEA,EAAA,IAAI;AACF,IAAA,MAAM,GAAA,GAAM,MAAM,KAAA,CAAM,OAAO,CAAA;AAC/B,IAAA,IAAI,CAAC,GAAA,CAAI,EAAA,EAAI,MAAM,IAAI,KAAA,CAAM,CAAA,KAAA,EAAQ,GAAA,CAAI,MAAM,CAAA,CAAA,EAAI,GAAA,CAAI,UAAU,CAAA,CAAA,CAAG,MAAM,CAAA;AAAA,EAC5E,SAAS,KAAA,EAAO;AACd,IAAA,MAAM,SAAS,KAAA,YAAiB,KAAA,GAAQ,CAAA,EAAA,EAAK,KAAA,CAAM,OAAO,CAAA,CAAA,GAAK,EAAA;AAC/D,IAAA,MAAM,IAAI,KAAA,CAAM,CAAA,qCAAA,EAAwC,OAAO,CAAA,EAAG,MAAM,CAAA,CAAE,CAAA;AAAA,EAC5E;AAEA,EAAA,MAAM,UAAA,GAAa,kBAAkB,cAAA,EAAe;AACpD,EAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,eAAA,EAAkB,UAAU,CAAA,CAAE,CAAA;AAE1C,EAAA,EAAA,CAAG,SAAA,CAAU,SAAA,EAAW,EAAE,SAAA,EAAW,MAAM,CAAA;AAE3C,EAAA,MAAM,OAAA,GAAU,MAAM,SAAA,CAAU,MAAA,CAAO;AAAA,IACrC,cAAA,EAAgB,UAAA;AAAA,IAChB,QAAA,EAAU,IAAA;AAAA,IACV,IAAA,EAAM,CAAC,cAAA,EAAgB,0BAA0B;AAAA,GAClD,CAAA;AAED,EAAA,MAAM,WAA4B,EAAC;AACnC,EAAA,MAAM,kBAA2B,EAAC;AAElC,EAAA,IAAI;AACF,IAAA,KAAA,MAAW,aAAa,UAAA,EAAY;AAClC,MAAA,MAAM,OAAA,GAAU,SAAA,CAAU,OAAA,IAAW,CAAA,iBAAA,EAAoB,UAAU,IAAI,CAAA,CAAA;AACvE,MAAA,MAAM,GAAA,GAAM,CAAA,EAAG,OAAO,CAAA,EAAG,OAAO,CAAA,CAAA;AAChC,MAAA,MAAM,eAAA,GACJ,SAAA,CAAU,QAAA,KAAa,KAAA,CAAA,GAAY,UAAU,QAAA,GAAW,QAAA;AAE1D,MAAA,KAAA,MAAW,SAAS,MAAA,EAAQ;AAC1B,QAAA,MAAM,IAAA,GAAO,MAAM,OAAA,CAAQ,OAAA,EAAQ;AACnC,QAAA,IAAI;AAEF,UAAA,MAAM,KAAK,oBAAA,CAAqB;AAAA,YAC9B,EAAE,IAAA,EAAM,sBAAA,EAAwB,KAAA,EAAO,KAAA;AAAM,WAC9C,CAAA;AAGD,UAAA,MAAM,IAAA,CAAK,qBAAA;AAAA,YACT,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,eAAA,EAaK,IAAA,CAAK,SAAA,CAAU,KAAK,CAAC,CAAA,CAAA;AAAA,WAC5B;AAEA,UAAA,MAAM,KAAK,WAAA,CAAY,EAAE,OAAO,EAAA,EAAI,MAAA,EAAQ,IAAI,CAAA;AAEhD,UAAA,IAAI,OAAA,IAAW,OAAA,CAAQ,MAAA,GAAS,CAAA,EAAG;AACjC,YAAA,MAAM,EAAE,QAAA,EAAS,GAAI,IAAI,IAAI,OAAO,CAAA;AACpC,YAAA,MAAM,IAAA,CAAK,SAAA;AAAA,cACT,GAAG,OAAA,CAAQ,GAAA,CAAI,CAAC,MAAA,MAAY;AAAA,gBAC1B,IAAA,EAAM,GAAA;AAAA,gBACN,MAAA,EAAQ,QAAA;AAAA,gBACR,GAAG;AAAA,eACL,CAAE;AAAA,aACJ;AAAA,UACF;AAEA,UAAA,MAAM,KAAK,IAAA,CAAK,GAAA,EAAK,EAAE,SAAA,EAAW,gBAAgB,CAAA;AAElD,UAAA,MAAM,IAAA,CAAK,QAAA;AAAA,YACT,CAAA;AAAA;AAAA;AAAA;AAAA;AAAA,eAAA,EAKK,IAAA,CAAK,SAAA,CAAU,KAAK,CAAC,CAAA,CAAA;AAAA,WAC5B;AAEA,UAAA,MAAM,IAAA,CAAK,eAAA;AAAA,YACT,CAAA,uFAAA,CAAA;AAAA,YACA,EAAE,SAAS,GAAA,EAAK;AAAA,YAChB;AAAA,WACF;AACA,UAAA,MAAM,IAAI,OAAA,CAAQ,CAAC,MAAM,UAAA,CAAW,CAAA,EAAG,GAAG,CAAC,CAAA;AAE3C,UAAA,MAAM,EAAE,QAAA,EAAU,QAAA,EAAS,GAAI,wBAAA;AAAA,YAC7B,SAAA;AAAA,YACA,SAAA,CAAU,IAAA;AAAA,YACV,KAAA;AAAA,YACA,OAAO,MAAA,GAAS;AAAA,WAClB;AAEA,UAAA,IAAI,oBAAoB,IAAA,EAAM;AAC5B,YAAA,MAAM,KAAK,UAAA,CAAW,EAAE,IAAA,EAAM,QAAA,EAAU,MAAM,EAAE,CAAA,EAAG,CAAA,EAAG,CAAA,EAAG,GAAG,KAAA,EAAO,EAAA,EAAI,MAAA,EAAQ,EAAA,IAAM,CAAA;AACrF,YAAA,QAAA,CAAS,IAAA,CAAK,EAAE,IAAA,EAAM,SAAA,CAAU,IAAA,EAAM,KAAA,EAAO,IAAA,EAAM,QAAA,EAAU,KAAA,EAAO,EAAA,EAAI,MAAA,EAAQ,EAAA,EAAI,CAAA;AAAA,UACtF,CAAA,MAAO;AACL,YAAA,MAAM,EAAA,GAAK,MAAM,IAAA,CAAK,eAAA,CAAgB,iBAAiB,EAAE,OAAA,EAAS,KAAQ,CAAA;AAC1E,YAAA,IAAI,CAAC,EAAA,EAAI,MAAM,IAAI,KAAA,CAAM,CAAA,oBAAA,EAAuB,eAAe,CAAA,CAAE,CAAA;AAEjE,YAAA,MAAM,IAAA,CAAK,QAAA;AAAA,cACT,CAAA,uBAAA,EAA0B,IAAA,CAAK,SAAA,CAAU,eAAe,CAAC,CAAA,uBAAA;AAAA,aAC3D;AACA,YAAA,MAAM,IAAI,OAAA,CAAQ,CAAC,MAAM,UAAA,CAAW,CAAA,EAAG,GAAG,CAAC,CAAA;AAE3C,YAAA,MAAM,GAAA,GAAM,MAAM,EAAA,CAAG,WAAA,EAAY;AACjC,YAAA,IAAI,CAAC,GAAA,EAAK,MAAM,IAAI,KAAA,CAAM,CAAA,gCAAA,EAAmC,eAAe,CAAA,CAAE,CAAA;AAE9E,YAAA,IAAI,UAAU,CAAA,EAAG;AACf,cAAA,MAAM,IAAA,GAAO;AAAA,gBACX,GAAG,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,GAAA,CAAI,IAAI,OAAO,CAAA;AAAA,gBAC9B,GAAG,IAAA,CAAK,GAAA,CAAI,CAAA,EAAG,GAAA,CAAI,IAAI,OAAO,CAAA;AAAA,gBAC9B,KAAA,EAAO,GAAA,CAAI,KAAA,GAAQ,OAAA,GAAU,CAAA;AAAA,gBAC7B,MAAA,EAAQ,GAAA,CAAI,MAAA,GAAS,OAAA,GAAU;AAAA,eACjC;AACA,cAAA,MAAM,KAAK,UAAA,CAAW,EAAE,IAAA,EAAM,QAAA,EAAU,MAAM,CAAA;AAC9C,cAAA,QAAA,CAAS,IAAA,CAAK;AAAA,gBACZ,MAAM,SAAA,CAAU,IAAA;AAAA,gBAChB,KAAA;AAAA,gBACA,IAAA,EAAM,QAAA;AAAA,gBACN,KAAA,EAAO,IAAA,CAAK,KAAA,CAAM,IAAA,CAAK,KAAK,CAAA;AAAA,gBAC5B,MAAA,EAAQ,IAAA,CAAK,KAAA,CAAM,IAAA,CAAK,MAAM;AAAA,eAC/B,CAAA;AAAA,YACH,CAAA,MAAO;AACL,cAAA,MAAM,EAAA,CAAG,UAAA,CAAW,EAAE,IAAA,EAAM,UAAU,CAAA;AACtC,cAAA,QAAA,CAAS,IAAA,CAAK;AAAA,gBACZ,MAAM,SAAA,CAAU,IAAA;AAAA,gBAChB,KAAA;AAAA,gBACA,IAAA,EAAM,QAAA;AAAA,gBACN,KAAA,EAAO,IAAA,CAAK,KAAA,CAAM,GAAA,CAAI,KAAK,CAAA;AAAA,gBAC3B,MAAA,EAAQ,IAAA,CAAK,KAAA,CAAM,GAAA,CAAI,MAAM;AAAA,eAC9B,CAAA;AAAA,YACH;AAAA,UACF;AAEA,UAAA,OAAA,CAAQ,GAAA,CAAI,CAAA,OAAA,EAAK,QAAQ,CAAA,CAAE,CAAA;AAAA,QAC7B,SAAS,GAAA,EAAK;AACZ,UAAA,OAAA,CAAQ,MAAM,CAAA,OAAA,EAAK,SAAA,CAAU,IAAI,CAAA,EAAA,EAAK,KAAK,MAAM,GAAG,CAAA;AACpD,UAAA,MAAM,SAAS,GAAA,YAAe,KAAA,GAAQ,GAAA,CAAI,OAAA,GAAU,OAAO,GAAG,CAAA;AAC9D,UAAA,eAAA,CAAgB,IAAA;AAAA,YACd,IAAI,MAAM,CAAA,kBAAA,EAAqB,SAAA,CAAU,IAAI,CAAA,EAAA,EAAK,KAAK,CAAA,GAAA,EAAM,MAAM,CAAA,CAAE;AAAA,WACvE;AAAA,QACF,CAAA,SAAE;AACA,UAAA,MAAM,KAAK,KAAA,EAAM;AAAA,QACnB;AAAA,MACF;AAAA,IACF;AAAA,EACF,CAAA,SAAE;AACA,IAAA,MAAM,QAAQ,KAAA,EAAM;AAAA,EACtB;AAEA,EAAA,IAAI,eAAA,CAAgB,SAAS,CAAA,EAAG;AAC9B,IAAA,MAAM,IAAI,cAAA;AAAA,MACR,eAAA;AAAA,MACA,CAAA,mBAAA,EAAsB,gBAAgB,MAAM,CAAA,mBAAA;AAAA,KAC9C;AAAA,EACF;AAEA,EAAA,MAAM,oBAAA,GAAuB,YAAA,IAAgB,IAAA,CAAK,IAAA,CAAK,WAAW,eAAe,CAAA;AACjF,EAAA,EAAA,CAAG,SAAA,CAAU,KAAK,OAAA,CAAQ,oBAAoB,GAAG,EAAE,SAAA,EAAW,MAAM,CAAA;AACpE,EAAA,EAAA,CAAG,cAAc,oBAAA,EAAsB,IAAA,CAAK,UAAU,QAAA,EAAU,IAAA,EAAM,CAAC,CAAC,CAAA;AAExE,EAAA,OAAA,CAAQ,GAAA,CAAI;AAAA,UAAA,EAAe,QAAA,CAAS,MAAM,CAAA,iBAAA,EAAe,SAAS,CAAA,CAAE,CAAA;AACtE","file":"generate-previews.js","sourcesContent":["import puppeteer from 'puppeteer-core';\nimport fs from 'node:fs';\nimport path from 'node:path';\n\nexport interface PreviewEntry {\n name: string;\n urlPath?: string;\n /** Override config.selector for this entry (e.g. null for full-page captures). */\n selector?: string | null;\n}\n\nexport interface GeneratePreviewsConfig {\n baseUrl: string;\n outputDir: string;\n components: PreviewEntry[];\n selector?: string | null;\n themes?: ('light' | 'dark')[];\n manifestPath?: string;\n executablePath?: string;\n padding?: number;\n viewport?: { width?: number; height?: number };\n /**\n * Cookies to set before navigating, for pages that only render when signed in.\n *\n * Without these, capturing an authenticated page silently photographs its\n * signed-out state — a sign-in form or an empty shell — and the result looks\n * like a working screenshot. Use a fixture account; never a real user's\n * session, since whatever it can see ends up in an image on disk.\n */\n cookies?: Array<{ name: string; value: string; domain?: string; path?: string }>;\n}\n\nexport interface ManifestEntry {\n name: string;\n theme: 'light' | 'dark';\n file: string;\n width: number;\n height: number;\n}\n\nexport function resolvePreviewOutputPath(\n outputDir: string,\n componentName: string,\n theme: 'light' | 'dark',\n includeTheme: boolean,\n): { filename: string; filepath: string } {\n if (\n !componentName ||\n componentName === '.' ||\n componentName === '..' ||\n path.isAbsolute(componentName) ||\n componentName.includes('/') ||\n componentName.includes('\\\\')\n ) {\n throw new Error(`Invalid preview component name: ${componentName}`);\n }\n\n const filename = includeTheme\n ? `${componentName}-${theme}.png`\n : `${componentName}.png`;\n const resolvedOutputDir = path.resolve(outputDir);\n const filepath = path.resolve(resolvedOutputDir, filename);\n const relativePath = path.relative(resolvedOutputDir, filepath);\n\n if (\n relativePath === '..' ||\n relativePath.startsWith(`..${path.sep}`) ||\n path.isAbsolute(relativePath)\n ) {\n throw new Error(`Invalid preview component name: ${componentName}`);\n }\n\n return { filename, filepath };\n}\n\nfunction findChromePath(): string {\n const candidates =\n process.platform === 'win32'\n ? [\n 'C:\\\\Program Files\\\\Google\\\\Chrome\\\\Application\\\\chrome.exe',\n 'C:\\\\Program Files (x86)\\\\Google\\\\Chrome\\\\Application\\\\chrome.exe',\n `${process.env.LOCALAPPDATA}\\\\Google\\\\Chrome\\\\Application\\\\chrome.exe`,\n 'C:\\\\Program Files (x86)\\\\Microsoft\\\\Edge\\\\Application\\\\msedge.exe',\n `${process.env.ProgramFiles}\\\\Microsoft\\\\Edge\\\\Application\\\\msedge.exe`,\n ]\n : process.platform === 'darwin'\n ? [\n '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',\n '/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge',\n ]\n : ['/usr/bin/google-chrome', '/usr/bin/chromium-browser', '/usr/bin/chromium'];\n\n for (const p of candidates) {\n if (p && fs.existsSync(p)) return p;\n }\n throw new Error(\n 'No Chrome/Edge executable found. Pass executablePath in config or install Chrome.',\n );\n}\n\nexport async function generatePreviews(config: GeneratePreviewsConfig): Promise<void> {\n const {\n baseUrl,\n outputDir,\n components,\n selector = '[data-slot=\"component-preview-canvas\"]',\n themes = ['light', 'dark'],\n manifestPath,\n executablePath,\n padding = 0,\n viewport,\n cookies,\n } = config;\n\n const vw = viewport?.width ?? 1280;\n const vh = viewport?.height ?? 800;\n\n for (const component of components) {\n for (const theme of themes) {\n resolvePreviewOutputPath(outputDir, component.name, theme, themes.length > 1);\n }\n }\n\n try {\n const res = await fetch(baseUrl);\n if (!res.ok) throw new Error(`HTTP ${res.status} ${res.statusText}`.trim());\n } catch (error) {\n const detail = error instanceof Error ? `: ${error.message}` : '';\n throw new Error(`Preview dev server is unreachable at ${baseUrl}${detail}`);\n }\n\n const chromePath = executablePath ?? findChromePath();\n console.log(`Using browser: ${chromePath}`);\n\n fs.mkdirSync(outputDir, { recursive: true });\n\n const browser = await puppeteer.launch({\n executablePath: chromePath,\n headless: true,\n args: ['--no-sandbox', '--disable-setuid-sandbox'],\n });\n\n const manifest: ManifestEntry[] = [];\n const captureFailures: Error[] = [];\n\n try {\n for (const component of components) {\n const urlPath = component.urlPath ?? `/docs/components/${component.name}`;\n const url = `${baseUrl}${urlPath}`;\n const captureSelector =\n component.selector !== undefined ? component.selector : selector;\n\n for (const theme of themes) {\n const page = await browser.newPage();\n try {\n // next-themes (Fumadocs RootProvider) defaults to system — match OS unless we override early.\n await page.emulateMediaFeatures([\n { name: 'prefers-color-scheme', value: theme },\n ]);\n\n // Run before page scripts (next-themes / ModeSwitcher) so light captures stay light.\n await page.evaluateOnNewDocument(\n `((scheme) => {\n const isDark = scheme === 'dark';\n const apply = () => {\n const root = document.documentElement;\n root.classList.toggle('dark', isDark);\n root.style.colorScheme = scheme;\n };\n try { localStorage.setItem('theme', scheme); } catch {}\n apply();\n new MutationObserver(apply).observe(document.documentElement, {\n attributes: true,\n attributeFilter: ['class'],\n });\n })(${JSON.stringify(theme)})`,\n );\n\n await page.setViewport({ width: vw, height: vh });\n\n if (cookies && cookies.length > 0) {\n const { hostname } = new URL(baseUrl);\n await page.setCookie(\n ...cookies.map((cookie) => ({\n path: '/',\n domain: hostname,\n ...cookie,\n })),\n );\n }\n\n await page.goto(url, { waitUntil: 'networkidle0' });\n\n await page.evaluate(\n `((scheme) => {\n const isDark = scheme === 'dark';\n document.documentElement.classList.toggle('dark', isDark);\n document.documentElement.style.colorScheme = scheme;\n try { localStorage.setItem('theme', scheme); } catch {}\n })(${JSON.stringify(theme)})`,\n );\n\n await page.waitForFunction(\n `(scheme) => document.documentElement.classList.contains('dark') === (scheme === 'dark')`,\n { timeout: 5000 },\n theme,\n );\n await new Promise((r) => setTimeout(r, 300));\n\n const { filename, filepath } = resolvePreviewOutputPath(\n outputDir,\n component.name,\n theme,\n themes.length > 1,\n );\n\n if (captureSelector === null) {\n await page.screenshot({ path: filepath, clip: { x: 0, y: 0, width: vw, height: vh } });\n manifest.push({ name: component.name, theme, file: filename, width: vw, height: vh });\n } else {\n const el = await page.waitForSelector(captureSelector, { timeout: 10_000 });\n if (!el) throw new Error(`Selector not found: ${captureSelector}`);\n\n await page.evaluate(\n `document.querySelector(${JSON.stringify(captureSelector)}).style.minHeight = '0'`,\n );\n await new Promise((r) => setTimeout(r, 100));\n\n const box = await el.boundingBox();\n if (!box) throw new Error(`Could not get bounding box for: ${captureSelector}`);\n\n if (padding > 0) {\n const clip = {\n x: Math.max(0, box.x - padding),\n y: Math.max(0, box.y - padding),\n width: box.width + padding * 2,\n height: box.height + padding * 2,\n };\n await page.screenshot({ path: filepath, clip });\n manifest.push({\n name: component.name,\n theme,\n file: filename,\n width: Math.round(clip.width),\n height: Math.round(clip.height),\n });\n } else {\n await el.screenshot({ path: filepath });\n manifest.push({\n name: component.name,\n theme,\n file: filename,\n width: Math.round(box.width),\n height: Math.round(box.height),\n });\n }\n }\n\n console.log(`✓ ${filename}`);\n } catch (err) {\n console.error(`✗ ${component.name} (${theme}):`, err);\n const detail = err instanceof Error ? err.message : String(err);\n captureFailures.push(\n new Error(`Failed to capture ${component.name} (${theme}): ${detail}`),\n );\n } finally {\n await page.close();\n }\n }\n }\n } finally {\n await browser.close();\n }\n\n if (captureFailures.length > 0) {\n throw new AggregateError(\n captureFailures,\n `Failed to generate ${captureFailures.length} preview capture(s)`,\n );\n }\n\n const resolvedManifestPath = manifestPath ?? path.join(outputDir, 'manifest.json');\n fs.mkdirSync(path.dirname(resolvedManifestPath), { recursive: true });\n fs.writeFileSync(resolvedManifestPath, JSON.stringify(manifest, null, 2));\n\n console.log(`\\nGenerated ${manifest.length} previews → ${outputDir}`);\n}\n"]}
|
|
1
|
+
{"version":3,"sources":["../src/generate-previews.ts"],"names":[],"mappings":"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","file":"generate-previews.js","sourcesContent":["import puppeteer from 'puppeteer-core';\nimport fs from 'node:fs';\nimport path from 'node:path';\n\nexport interface PreviewEntry {\n name: string;\n urlPath?: string;\n /** Override config.selector for this entry (e.g. null for full-page captures). */\n selector?: string | null;\n}\n\nexport interface GeneratePreviewsConfig {\n baseUrl: string;\n outputDir: string;\n components: PreviewEntry[];\n selector?: string | null;\n themes?: ('light' | 'dark')[];\n manifestPath?: string;\n executablePath?: string;\n padding?: number;\n viewport?: { width?: number; height?: number };\n /**\n * Cookies to set before navigating, for pages that only render when signed in.\n *\n * Without these, capturing an authenticated page silently photographs its\n * signed-out state — a sign-in form or an empty shell — and the result looks\n * like a working screenshot. Use a fixture account; never a real user's\n * session, since whatever it can see ends up in an image on disk.\n */\n cookies?: Array<{ name: string; value: string; domain?: string; path?: string }>;\n}\n\nexport interface ManifestEntry {\n name: string;\n theme: 'light' | 'dark';\n file: string;\n width: number;\n height: number;\n}\n\nexport function resolvePreviewOutputPath(\n outputDir: string,\n componentName: string,\n theme: 'light' | 'dark',\n includeTheme: boolean,\n): { filename: string; filepath: string } {\n if (\n !componentName ||\n componentName === '.' ||\n componentName === '..' ||\n path.isAbsolute(componentName) ||\n componentName.includes('/') ||\n componentName.includes('\\\\')\n ) {\n throw new Error(`Invalid preview component name: ${componentName}`);\n }\n\n const filename = includeTheme\n ? `${componentName}-${theme}.png`\n : `${componentName}.png`;\n const resolvedOutputDir = path.resolve(outputDir);\n const filepath = path.resolve(resolvedOutputDir, filename);\n const relativePath = path.relative(resolvedOutputDir, filepath);\n\n if (\n relativePath === '..' ||\n relativePath.startsWith(`..${path.sep}`) ||\n path.isAbsolute(relativePath)\n ) {\n throw new Error(`Invalid preview component name: ${componentName}`);\n }\n\n return { filename, filepath };\n}\n\nfunction findChromePath(): string {\n const candidates =\n process.platform === 'win32'\n ? [\n 'C:\\\\Program Files\\\\Google\\\\Chrome\\\\Application\\\\chrome.exe',\n 'C:\\\\Program Files (x86)\\\\Google\\\\Chrome\\\\Application\\\\chrome.exe',\n `${process.env.LOCALAPPDATA}\\\\Google\\\\Chrome\\\\Application\\\\chrome.exe`,\n 'C:\\\\Program Files (x86)\\\\Microsoft\\\\Edge\\\\Application\\\\msedge.exe',\n `${process.env.ProgramFiles}\\\\Microsoft\\\\Edge\\\\Application\\\\msedge.exe`,\n ]\n : process.platform === 'darwin'\n ? [\n '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome',\n '/Applications/Microsoft Edge.app/Contents/MacOS/Microsoft Edge',\n ]\n : ['/usr/bin/google-chrome', '/usr/bin/chromium-browser', '/usr/bin/chromium'];\n\n for (const p of candidates) {\n if (p && fs.existsSync(p)) return p;\n }\n throw new Error(\n 'No Chrome/Edge executable found. Pass executablePath in config or install Chrome.',\n );\n}\n\n/**\n * Collapses a preview frame down to the height of what it is actually showing.\n *\n * Docs sandboxes render into an iframe, and a page can ask for a tall canvas so\n * an application shell has somewhere to be. That is right for the page and\n * wrong for a screenshot: a chat bubble photographed inside a 640px frame is\n * mostly empty pixels, and every page asking for that canvas produced an image\n * of exactly the same height regardless of its contents.\n *\n * The iframe is written with `srcDoc`, so it is same-origin and its document\n * can be measured from here. Runs in the page, stringified by Puppeteer.\n *\n * Deliberately conservative. It only shrinks, never grows, and it leaves the\n * frame alone unless the content is both measurable and meaningfully shorter.\n * A shell whose children size themselves against the frame would otherwise\n * collapse to nothing the moment the frame stopped being tall.\n */\n// Typed loosely on purpose: this package targets Node and carries no DOM lib,\n// because everything else in it runs in a terminal. The body below is\n// stringified by Puppeteer and executed in the page, where these globals exist.\nfunction shrinkFrameToContent(selector: string): void {\n const d: any = (globalThis as any).document;\n const wrapper: any = d?.querySelector(selector);\n if (!wrapper) return;\n\n const iframe = wrapper.querySelector('iframe');\n if (!iframe) return;\n\n let doc: any = null;\n try {\n doc = iframe.contentDocument;\n } catch {\n // Cross-origin. Nothing to measure, so leave the frame as the page built it.\n return;\n }\n if (!doc) return;\n\n const root = doc.getElementById('sandbox-root') ?? doc.body;\n if (!root) return;\n\n // A fixed-canvas sandbox pins html, body and the mount node to `height:100%`,\n // which stretches whatever is inside them to the full frame. Measuring\n // against that chain always reports the frame's own height, so release it\n // first. The sandbox does the same thing when it sizes itself to content.\n const chain = [doc.documentElement, doc.body, root];\n const saved = chain.map((node: any) => node?.getAttribute('style') ?? null);\n const restore = () => {\n chain.forEach((node: any, index: number) => {\n if (!node) return;\n if (saved[index] === null) node.removeAttribute('style');\n else node.setAttribute('style', saved[index]);\n });\n };\n\n for (const node of chain as any[]) {\n if (!node) continue;\n node.style.height = '';\n node.style.minHeight = '';\n }\n\n // The union of the children's boxes rather than the root's own height: the\n // root is a flex column and may still be taller than what it contains.\n let contentHeight = 0;\n for (const child of Array.from(root.children) as any[]) {\n contentHeight = Math.max(contentHeight, child.getBoundingClientRect().bottom);\n }\n contentHeight = Math.ceil(contentHeight);\n\n const frameHeight = iframe.clientHeight;\n // Below this, assume the demo sized itself against the frame and has just\n // collapsed now that the frame stopped being tall. An application shell does\n // exactly that, and shrinking it would photograph a sliver.\n const PLAUSIBLE = 120;\n const WORTH_IT = 24;\n\n if (contentHeight < PLAUSIBLE || contentHeight > frameHeight - WORTH_IT) {\n restore();\n return;\n }\n\n // The released chain stays released: the capture should show the content\n // laid out naturally, not re-stretched to a frame it no longer fills.\n //\n // Both elements, and the wrapper is the one that matters. It carries the\n // fixed height inline and the iframe inside it is `h-full`, so shrinking\n // only the frame changes nothing that a screenshot would see: the capture is\n // measured from the wrapper's box. Its `min-height` floor has to go with it.\n iframe.style.height = `${contentHeight}px`;\n wrapper.style.height = `${contentHeight}px`;\n wrapper.style.minHeight = '0';\n}\n\nexport async function generatePreviews(config: GeneratePreviewsConfig): Promise<void> {\n const {\n baseUrl,\n outputDir,\n components,\n selector = '[data-slot=\"component-preview-canvas\"]',\n themes = ['light', 'dark'],\n manifestPath,\n executablePath,\n padding = 0,\n viewport,\n cookies,\n } = config;\n\n const vw = viewport?.width ?? 1280;\n const vh = viewport?.height ?? 800;\n\n for (const component of components) {\n for (const theme of themes) {\n resolvePreviewOutputPath(outputDir, component.name, theme, themes.length > 1);\n }\n }\n\n try {\n const res = await fetch(baseUrl);\n if (!res.ok) throw new Error(`HTTP ${res.status} ${res.statusText}`.trim());\n } catch (error) {\n const detail = error instanceof Error ? `: ${error.message}` : '';\n throw new Error(`Preview dev server is unreachable at ${baseUrl}${detail}`);\n }\n\n const chromePath = executablePath ?? findChromePath();\n console.log(`Using browser: ${chromePath}`);\n\n fs.mkdirSync(outputDir, { recursive: true });\n\n const browser = await puppeteer.launch({\n executablePath: chromePath,\n headless: true,\n args: ['--no-sandbox', '--disable-setuid-sandbox'],\n });\n\n const manifest: ManifestEntry[] = [];\n const captureFailures: Error[] = [];\n\n try {\n for (const component of components) {\n const urlPath = component.urlPath ?? `/docs/components/${component.name}`;\n const url = `${baseUrl}${urlPath}`;\n const captureSelector =\n component.selector !== undefined ? component.selector : selector;\n\n for (const theme of themes) {\n const page = await browser.newPage();\n try {\n // next-themes (Fumadocs RootProvider) defaults to system — match OS unless we override early.\n await page.emulateMediaFeatures([\n { name: 'prefers-color-scheme', value: theme },\n ]);\n\n // Run before page scripts (next-themes / ModeSwitcher) so light captures stay light.\n await page.evaluateOnNewDocument(\n `((scheme) => {\n const isDark = scheme === 'dark';\n const apply = () => {\n const root = document.documentElement;\n root.classList.toggle('dark', isDark);\n root.style.colorScheme = scheme;\n };\n try { localStorage.setItem('theme', scheme); } catch {}\n apply();\n new MutationObserver(apply).observe(document.documentElement, {\n attributes: true,\n attributeFilter: ['class'],\n });\n })(${JSON.stringify(theme)})`,\n );\n\n await page.setViewport({ width: vw, height: vh });\n\n if (cookies && cookies.length > 0) {\n const { hostname } = new URL(baseUrl);\n await page.setCookie(\n ...cookies.map((cookie) => ({\n path: '/',\n domain: hostname,\n ...cookie,\n })),\n );\n }\n\n await page.goto(url, { waitUntil: 'networkidle0' });\n\n await page.evaluate(\n `((scheme) => {\n const isDark = scheme === 'dark';\n document.documentElement.classList.toggle('dark', isDark);\n document.documentElement.style.colorScheme = scheme;\n try { localStorage.setItem('theme', scheme); } catch {}\n })(${JSON.stringify(theme)})`,\n );\n\n await page.waitForFunction(\n `(scheme) => document.documentElement.classList.contains('dark') === (scheme === 'dark')`,\n { timeout: 5000 },\n theme,\n );\n await new Promise((r) => setTimeout(r, 300));\n\n const { filename, filepath } = resolvePreviewOutputPath(\n outputDir,\n component.name,\n theme,\n themes.length > 1,\n );\n\n if (captureSelector === null) {\n await page.screenshot({ path: filepath, clip: { x: 0, y: 0, width: vw, height: vh } });\n manifest.push({ name: component.name, theme, file: filename, width: vw, height: vh });\n } else {\n const el = await page.waitForSelector(captureSelector, { timeout: 10_000 });\n if (!el) throw new Error(`Selector not found: ${captureSelector}`);\n\n await page.evaluate(\n `document.querySelector(${JSON.stringify(captureSelector)}).style.minHeight = '0'`,\n );\n await page.evaluate(shrinkFrameToContent, captureSelector);\n await new Promise((r) => setTimeout(r, 150));\n\n const box = await el.boundingBox();\n if (!box) throw new Error(`Could not get bounding box for: ${captureSelector}`);\n\n if (padding > 0) {\n const clip = {\n x: Math.max(0, box.x - padding),\n y: Math.max(0, box.y - padding),\n width: box.width + padding * 2,\n height: box.height + padding * 2,\n };\n await page.screenshot({ path: filepath, clip });\n manifest.push({\n name: component.name,\n theme,\n file: filename,\n width: Math.round(clip.width),\n height: Math.round(clip.height),\n });\n } else {\n await el.screenshot({ path: filepath });\n manifest.push({\n name: component.name,\n theme,\n file: filename,\n width: Math.round(box.width),\n height: Math.round(box.height),\n });\n }\n }\n\n console.log(`✓ ${filename}`);\n } catch (err) {\n console.error(`✗ ${component.name} (${theme}):`, err);\n const detail = err instanceof Error ? err.message : String(err);\n captureFailures.push(\n new Error(`Failed to capture ${component.name} (${theme}): ${detail}`),\n );\n } finally {\n await page.close();\n }\n }\n }\n } finally {\n await browser.close();\n }\n\n if (captureFailures.length > 0) {\n throw new AggregateError(\n captureFailures,\n `Failed to generate ${captureFailures.length} preview capture(s)`,\n );\n }\n\n const resolvedManifestPath = manifestPath ?? path.join(outputDir, 'manifest.json');\n fs.mkdirSync(path.dirname(resolvedManifestPath), { recursive: true });\n fs.writeFileSync(resolvedManifestPath, JSON.stringify(manifest, null, 2));\n\n console.log(`\\nGenerated ${manifest.length} previews → ${outputDir}`);\n}\n"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@olwiba/dx",
|
|
3
|
-
"version": "0.0.
|
|
3
|
+
"version": "0.0.31",
|
|
4
4
|
"private": false,
|
|
5
5
|
"type": "module",
|
|
6
6
|
"sideEffects": false,
|
|
@@ -63,7 +63,7 @@
|
|
|
63
63
|
"showcase:build": "bun scripts/build-showcase.ts"
|
|
64
64
|
},
|
|
65
65
|
"devDependencies": {
|
|
66
|
-
"@olwiba/cn": "0.1.
|
|
66
|
+
"@olwiba/cn": "0.1.43",
|
|
67
67
|
"@olwiba/docs": "0.1.47",
|
|
68
68
|
"@tailwindcss/vite": "^4.1.18",
|
|
69
69
|
"@tanstack/react-router": "1.154.8",
|