pdf-smith 0.1.0 → 0.3.0

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 ADDED
@@ -0,0 +1,72 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/add.ts
4
+ import fs from "fs";
5
+ import path from "path";
6
+ var SLUG_REGEX = /^[a-z0-9]+(-[a-z0-9]+)*$/;
7
+ function toTitle(slug) {
8
+ return slug.split("-").map((w) => w[0].toUpperCase() + w.slice(1)).join(" ");
9
+ }
10
+ function addDocument({ slug, root }) {
11
+ if (!SLUG_REGEX.test(slug)) {
12
+ throw new Error(
13
+ `Invalid slug "${slug}". Use lowercase letters, numbers, and hyphens (e.g. "my-report").`
14
+ );
15
+ }
16
+ const docDir = path.join(root, "pdfs", slug);
17
+ if (fs.existsSync(docDir)) {
18
+ throw new Error(`Document "${slug}" already exists at ${docDir}`);
19
+ }
20
+ const pagesDir = path.join(docDir, "pages");
21
+ fs.mkdirSync(pagesDir, { recursive: true });
22
+ const title = toTitle(slug);
23
+ const page1Content = `import '../../../styles.css';
24
+
25
+ export default function Page1() {
26
+ return (
27
+ <div className="h-full flex flex-col items-center justify-center p-12 font-sans">
28
+ <h1 className="text-4xl font-bold tracking-tight text-gray-900">${title}</h1>
29
+ <p className="mt-4 text-gray-500">Start editing this page to build your document.</p>
30
+ </div>
31
+ );
32
+ }
33
+ `;
34
+ const configContent = `import { defineConfig } from 'pdf-smith';
35
+
36
+ export default defineConfig({
37
+ pageNumbers: { enabled: false },
38
+ });
39
+ `;
40
+ const page1Path = path.join(pagesDir, "Page1.tsx");
41
+ const configPath = path.join(docDir, "config.ts");
42
+ fs.writeFileSync(page1Path, page1Content);
43
+ fs.writeFileSync(configPath, configContent);
44
+ return {
45
+ paths: [
46
+ path.relative(root, pagesDir),
47
+ path.relative(root, page1Path),
48
+ path.relative(root, configPath)
49
+ ]
50
+ };
51
+ }
52
+
53
+ // src/cli.ts
54
+ var args = process.argv.slice(2);
55
+ var command = args[0];
56
+ if (command === "add") {
57
+ const slug = args[1];
58
+ if (!slug || slug === "--help") {
59
+ console.log("Usage: pdf-smith add <slug>");
60
+ process.exit(slug ? 0 : 1);
61
+ }
62
+ try {
63
+ addDocument({ slug, root: process.cwd() });
64
+ console.log(`Created document '${slug}'. Start editing at pdfs/${slug}/pages/Page1.tsx`);
65
+ } catch (error) {
66
+ console.error(error.message);
67
+ process.exit(1);
68
+ }
69
+ } else {
70
+ console.log("Usage: pdf-smith add <slug>");
71
+ process.exit(command === "--help" || command === "-h" ? 0 : 1);
72
+ }
package/dist/index.cjs CHANGED
@@ -24,10 +24,16 @@ __export(index_exports, {
24
24
  DEFAULT_PAGE_SIZE: () => DEFAULT_PAGE_SIZE,
25
25
  Document: () => Document,
26
26
  PAGE_SIZES: () => PAGE_SIZES,
27
- Page: () => Page
27
+ Page: () => Page,
28
+ defineConfig: () => defineConfig
28
29
  });
29
30
  module.exports = __toCommonJS(index_exports);
30
31
 
32
+ // src/config.ts
33
+ function defineConfig(config) {
34
+ return config;
35
+ }
36
+
31
37
  // src/constants.ts
32
38
  var PAGE_SIZES = {
33
39
  A4: { width: 210, height: 297 },
@@ -141,6 +147,7 @@ function Page({
141
147
  DEFAULT_PAGE_SIZE,
142
148
  Document,
143
149
  PAGE_SIZES,
144
- Page
150
+ Page,
151
+ defineConfig
145
152
  });
146
153
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/constants.ts","../src/styles.ts","../src/document.tsx","../src/page.tsx"],"sourcesContent":["export { DEFAULT_PADDING, DEFAULT_PAGE_SIZE, PAGE_SIZES } from './constants';\nexport { Document } from './document';\nexport { Page } from './page';\nexport type {\n DocumentProps,\n PageDimensions,\n PagePadding,\n PageProps,\n PageSize,\n PageSizePreset,\n} from './types';\n","import type { PageDimensions, PageSizePreset } from './types';\n\nexport const PAGE_SIZES: Record<PageSizePreset, PageDimensions> = {\n A4: { width: 210, height: 297 },\n Letter: { width: 215.9, height: 279.4 },\n A3: { width: 297, height: 420 },\n};\n\nexport const DEFAULT_PADDING = 0;\n\nexport const DEFAULT_PAGE_SIZE: PageSizePreset = 'A4';\n","import type { PageDimensions, PagePadding } from './types';\n\ninterface ResolvedPadding {\n top: number;\n right: number;\n bottom: number;\n left: number;\n}\n\nexport function resolvePadding(padding: PagePadding): ResolvedPadding {\n if (typeof padding === 'number') {\n return { top: padding, right: padding, bottom: padding, left: padding };\n }\n return {\n top: padding.top ?? 0,\n right: padding.right ?? 0,\n bottom: padding.bottom ?? 0,\n left: padding.left ?? 0,\n };\n}\n\nexport function getPageStyle(\n dimensions: PageDimensions,\n padding: ResolvedPadding,\n): React.CSSProperties {\n return {\n width: `${dimensions.width}mm`,\n height: `${dimensions.height}mm`,\n paddingTop: `${padding.top}mm`,\n paddingRight: `${padding.right}mm`,\n paddingBottom: `${padding.bottom}mm`,\n paddingLeft: `${padding.left}mm`,\n display: 'block',\n overflow: 'hidden',\n background: 'white',\n boxSizing: 'border-box',\n };\n}\n\nexport function getDocumentStyleSheet(dimensions: PageDimensions): string {\n return `\n@page {\n size: ${dimensions.width}mm ${dimensions.height}mm;\n margin: 0;\n}\n\n* {\n print-color-adjust: exact;\n -webkit-print-color-adjust: exact;\n}\n\n@media screen {\n [data-pdf-smith-page] {\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24);\n margin-left: auto;\n margin-right: auto;\n }\n}\n\n@media print {\n [data-pdf-smith-page] {\n box-shadow: none;\n break-after: page;\n overflow: visible;\n height: auto;\n }\n}\n`.trim();\n}\n","import { PAGE_SIZES } from './constants';\nimport { getDocumentStyleSheet } from './styles';\nimport type { DocumentProps } from './types';\n\nexport function Document({ children, className }: DocumentProps): React.ReactElement {\n const styleSheet = getDocumentStyleSheet(PAGE_SIZES.A4);\n\n return (\n <div data-pdf-smith-document=\"\" className={className}>\n {/* biome-ignore lint/security/noDangerouslySetInnerHtml: trusted internal CSS */}\n <style dangerouslySetInnerHTML={{ __html: styleSheet }} />\n {children}\n </div>\n );\n}\n","import { DEFAULT_PADDING, DEFAULT_PAGE_SIZE, PAGE_SIZES } from './constants';\nimport { getPageStyle, resolvePadding } from './styles';\nimport type { PageDimensions, PageProps, PageSize } from './types';\n\nfunction resolveSize(size: PageSize): PageDimensions {\n if (typeof size === 'string') {\n return PAGE_SIZES[size];\n }\n return size;\n}\n\nexport function Page({\n children,\n className,\n size = DEFAULT_PAGE_SIZE,\n padding = DEFAULT_PADDING,\n id,\n}: PageProps): React.ReactElement {\n const dimensions = resolveSize(size);\n const resolvedPadding = resolvePadding(padding);\n const style = getPageStyle(dimensions, resolvedPadding);\n\n return (\n <div\n data-pdf-smith-page=\"\"\n data-page-width={dimensions.width}\n data-page-height={dimensions.height}\n className={className}\n id={id}\n style={style}\n >\n {children}\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACEO,IAAM,aAAqD;AAAA,EAChE,IAAI,EAAE,OAAO,KAAK,QAAQ,IAAI;AAAA,EAC9B,QAAQ,EAAE,OAAO,OAAO,QAAQ,MAAM;AAAA,EACtC,IAAI,EAAE,OAAO,KAAK,QAAQ,IAAI;AAChC;AAEO,IAAM,kBAAkB;AAExB,IAAM,oBAAoC;;;ACD1C,SAAS,eAAe,SAAuC;AACpE,MAAI,OAAO,YAAY,UAAU;AAC/B,WAAO,EAAE,KAAK,SAAS,OAAO,SAAS,QAAQ,SAAS,MAAM,QAAQ;AAAA,EACxE;AACA,SAAO;AAAA,IACL,KAAK,QAAQ,OAAO;AAAA,IACpB,OAAO,QAAQ,SAAS;AAAA,IACxB,QAAQ,QAAQ,UAAU;AAAA,IAC1B,MAAM,QAAQ,QAAQ;AAAA,EACxB;AACF;AAEO,SAAS,aACd,YACA,SACqB;AACrB,SAAO;AAAA,IACL,OAAO,GAAG,WAAW,KAAK;AAAA,IAC1B,QAAQ,GAAG,WAAW,MAAM;AAAA,IAC5B,YAAY,GAAG,QAAQ,GAAG;AAAA,IAC1B,cAAc,GAAG,QAAQ,KAAK;AAAA,IAC9B,eAAe,GAAG,QAAQ,MAAM;AAAA,IAChC,aAAa,GAAG,QAAQ,IAAI;AAAA,IAC5B,SAAS;AAAA,IACT,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,WAAW;AAAA,EACb;AACF;AAEO,SAAS,sBAAsB,YAAoC;AACxE,SAAO;AAAA;AAAA,UAEC,WAAW,KAAK,MAAM,WAAW,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAyB/C,KAAK;AACP;;;AC5DI;AAJG,SAAS,SAAS,EAAE,UAAU,UAAU,GAAsC;AACnF,QAAM,aAAa,sBAAsB,WAAW,EAAE;AAEtD,SACE,6CAAC,SAAI,2BAAwB,IAAG,WAE9B;AAAA,gDAAC,WAAM,yBAAyB,EAAE,QAAQ,WAAW,GAAG;AAAA,IACvD;AAAA,KACH;AAEJ;;;ACSI,IAAAA,sBAAA;AAnBJ,SAAS,YAAY,MAAgC;AACnD,MAAI,OAAO,SAAS,UAAU;AAC5B,WAAO,WAAW,IAAI;AAAA,EACxB;AACA,SAAO;AACT;AAEO,SAAS,KAAK;AAAA,EACnB;AAAA,EACA;AAAA,EACA,OAAO;AAAA,EACP,UAAU;AAAA,EACV;AACF,GAAkC;AAChC,QAAM,aAAa,YAAY,IAAI;AACnC,QAAM,kBAAkB,eAAe,OAAO;AAC9C,QAAM,QAAQ,aAAa,YAAY,eAAe;AAEtD,SACE;AAAA,IAAC;AAAA;AAAA,MACC,uBAAoB;AAAA,MACpB,mBAAiB,WAAW;AAAA,MAC5B,oBAAkB,WAAW;AAAA,MAC7B;AAAA,MACA;AAAA,MACA;AAAA,MAEC;AAAA;AAAA,EACH;AAEJ;","names":["import_jsx_runtime"]}
1
+ {"version":3,"sources":["../src/index.ts","../src/config.ts","../src/constants.ts","../src/styles.ts","../src/document.tsx","../src/page.tsx"],"sourcesContent":["export type { DocumentConfig } from './config';\nexport { defineConfig } from './config';\nexport { DEFAULT_PADDING, DEFAULT_PAGE_SIZE, PAGE_SIZES } from './constants';\nexport { Document } from './document';\nexport { Page } from './page';\nexport type {\n DocumentProps,\n PageDimensions,\n PagePadding,\n PageProps,\n PageSize,\n PageSizePreset,\n} from './types';\n","export interface DocumentConfig {\n pageNumbers?: { enabled: boolean; template?: string };\n output?: string;\n}\n\nexport function defineConfig(config: DocumentConfig): DocumentConfig {\n return config;\n}\n","import type { PageDimensions, PageSizePreset } from './types';\n\nexport const PAGE_SIZES: Record<PageSizePreset, PageDimensions> = {\n A4: { width: 210, height: 297 },\n Letter: { width: 215.9, height: 279.4 },\n A3: { width: 297, height: 420 },\n};\n\nexport const DEFAULT_PADDING = 0;\n\nexport const DEFAULT_PAGE_SIZE: PageSizePreset = 'A4';\n","import type { PageDimensions, PagePadding } from './types';\n\ninterface ResolvedPadding {\n top: number;\n right: number;\n bottom: number;\n left: number;\n}\n\nexport function resolvePadding(padding: PagePadding): ResolvedPadding {\n if (typeof padding === 'number') {\n return { top: padding, right: padding, bottom: padding, left: padding };\n }\n return {\n top: padding.top ?? 0,\n right: padding.right ?? 0,\n bottom: padding.bottom ?? 0,\n left: padding.left ?? 0,\n };\n}\n\nexport function getPageStyle(\n dimensions: PageDimensions,\n padding: ResolvedPadding,\n): React.CSSProperties {\n return {\n width: `${dimensions.width}mm`,\n height: `${dimensions.height}mm`,\n paddingTop: `${padding.top}mm`,\n paddingRight: `${padding.right}mm`,\n paddingBottom: `${padding.bottom}mm`,\n paddingLeft: `${padding.left}mm`,\n display: 'block',\n overflow: 'hidden',\n background: 'white',\n boxSizing: 'border-box',\n };\n}\n\nexport function getDocumentStyleSheet(dimensions: PageDimensions): string {\n return `\n@page {\n size: ${dimensions.width}mm ${dimensions.height}mm;\n margin: 0;\n}\n\n* {\n print-color-adjust: exact;\n -webkit-print-color-adjust: exact;\n}\n\n@media screen {\n [data-pdf-smith-page] {\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24);\n margin-left: auto;\n margin-right: auto;\n }\n}\n\n@media print {\n [data-pdf-smith-page] {\n box-shadow: none;\n break-after: page;\n overflow: visible;\n height: auto;\n }\n}\n`.trim();\n}\n","import { PAGE_SIZES } from './constants';\nimport { getDocumentStyleSheet } from './styles';\nimport type { DocumentProps } from './types';\n\nexport function Document({ children, className }: DocumentProps): React.ReactElement {\n const styleSheet = getDocumentStyleSheet(PAGE_SIZES.A4);\n\n return (\n <div data-pdf-smith-document=\"\" className={className}>\n {/* biome-ignore lint/security/noDangerouslySetInnerHtml: trusted internal CSS */}\n <style dangerouslySetInnerHTML={{ __html: styleSheet }} />\n {children}\n </div>\n );\n}\n","import { DEFAULT_PADDING, DEFAULT_PAGE_SIZE, PAGE_SIZES } from './constants';\nimport { getPageStyle, resolvePadding } from './styles';\nimport type { PageDimensions, PageProps, PageSize } from './types';\n\nfunction resolveSize(size: PageSize): PageDimensions {\n if (typeof size === 'string') {\n return PAGE_SIZES[size];\n }\n return size;\n}\n\nexport function Page({\n children,\n className,\n size = DEFAULT_PAGE_SIZE,\n padding = DEFAULT_PADDING,\n id,\n}: PageProps): React.ReactElement {\n const dimensions = resolveSize(size);\n const resolvedPadding = resolvePadding(padding);\n const style = getPageStyle(dimensions, resolvedPadding);\n\n return (\n <div\n data-pdf-smith-page=\"\"\n data-page-width={dimensions.width}\n data-page-height={dimensions.height}\n className={className}\n id={id}\n style={style}\n >\n {children}\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACKO,SAAS,aAAa,QAAwC;AACnE,SAAO;AACT;;;ACLO,IAAM,aAAqD;AAAA,EAChE,IAAI,EAAE,OAAO,KAAK,QAAQ,IAAI;AAAA,EAC9B,QAAQ,EAAE,OAAO,OAAO,QAAQ,MAAM;AAAA,EACtC,IAAI,EAAE,OAAO,KAAK,QAAQ,IAAI;AAChC;AAEO,IAAM,kBAAkB;AAExB,IAAM,oBAAoC;;;ACD1C,SAAS,eAAe,SAAuC;AACpE,MAAI,OAAO,YAAY,UAAU;AAC/B,WAAO,EAAE,KAAK,SAAS,OAAO,SAAS,QAAQ,SAAS,MAAM,QAAQ;AAAA,EACxE;AACA,SAAO;AAAA,IACL,KAAK,QAAQ,OAAO;AAAA,IACpB,OAAO,QAAQ,SAAS;AAAA,IACxB,QAAQ,QAAQ,UAAU;AAAA,IAC1B,MAAM,QAAQ,QAAQ;AAAA,EACxB;AACF;AAEO,SAAS,aACd,YACA,SACqB;AACrB,SAAO;AAAA,IACL,OAAO,GAAG,WAAW,KAAK;AAAA,IAC1B,QAAQ,GAAG,WAAW,MAAM;AAAA,IAC5B,YAAY,GAAG,QAAQ,GAAG;AAAA,IAC1B,cAAc,GAAG,QAAQ,KAAK;AAAA,IAC9B,eAAe,GAAG,QAAQ,MAAM;AAAA,IAChC,aAAa,GAAG,QAAQ,IAAI;AAAA,IAC5B,SAAS;AAAA,IACT,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,WAAW;AAAA,EACb;AACF;AAEO,SAAS,sBAAsB,YAAoC;AACxE,SAAO;AAAA;AAAA,UAEC,WAAW,KAAK,MAAM,WAAW,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAyB/C,KAAK;AACP;;;AC5DI;AAJG,SAAS,SAAS,EAAE,UAAU,UAAU,GAAsC;AACnF,QAAM,aAAa,sBAAsB,WAAW,EAAE;AAEtD,SACE,6CAAC,SAAI,2BAAwB,IAAG,WAE9B;AAAA,gDAAC,WAAM,yBAAyB,EAAE,QAAQ,WAAW,GAAG;AAAA,IACvD;AAAA,KACH;AAEJ;;;ACSI,IAAAA,sBAAA;AAnBJ,SAAS,YAAY,MAAgC;AACnD,MAAI,OAAO,SAAS,UAAU;AAC5B,WAAO,WAAW,IAAI;AAAA,EACxB;AACA,SAAO;AACT;AAEO,SAAS,KAAK;AAAA,EACnB;AAAA,EACA;AAAA,EACA,OAAO;AAAA,EACP,UAAU;AAAA,EACV;AACF,GAAkC;AAChC,QAAM,aAAa,YAAY,IAAI;AACnC,QAAM,kBAAkB,eAAe,OAAO;AAC9C,QAAM,QAAQ,aAAa,YAAY,eAAe;AAEtD,SACE;AAAA,IAAC;AAAA;AAAA,MACC,uBAAoB;AAAA,MACpB,mBAAiB,WAAW;AAAA,MAC5B,oBAAkB,WAAW;AAAA,MAC7B;AAAA,MACA;AAAA,MACA;AAAA,MAEC;AAAA;AAAA,EACH;AAEJ;","names":["import_jsx_runtime"]}
package/dist/index.d.cts CHANGED
@@ -1,3 +1,12 @@
1
+ interface DocumentConfig {
2
+ pageNumbers?: {
3
+ enabled: boolean;
4
+ template?: string;
5
+ };
6
+ output?: string;
7
+ }
8
+ declare function defineConfig(config: DocumentConfig): DocumentConfig;
9
+
1
10
  type PageSizePreset = 'A4' | 'Letter' | 'A3';
2
11
  interface PageDimensions {
3
12
  width: number;
@@ -30,4 +39,4 @@ declare function Document({ children, className }: DocumentProps): React.ReactEl
30
39
 
31
40
  declare function Page({ children, className, size, padding, id, }: PageProps): React.ReactElement;
32
41
 
33
- export { DEFAULT_PADDING, DEFAULT_PAGE_SIZE, Document, type DocumentProps, PAGE_SIZES, Page, type PageDimensions, type PagePadding, type PageProps, type PageSize, type PageSizePreset };
42
+ export { DEFAULT_PADDING, DEFAULT_PAGE_SIZE, Document, type DocumentConfig, type DocumentProps, PAGE_SIZES, Page, type PageDimensions, type PagePadding, type PageProps, type PageSize, type PageSizePreset, defineConfig };
package/dist/index.d.ts CHANGED
@@ -1,3 +1,12 @@
1
+ interface DocumentConfig {
2
+ pageNumbers?: {
3
+ enabled: boolean;
4
+ template?: string;
5
+ };
6
+ output?: string;
7
+ }
8
+ declare function defineConfig(config: DocumentConfig): DocumentConfig;
9
+
1
10
  type PageSizePreset = 'A4' | 'Letter' | 'A3';
2
11
  interface PageDimensions {
3
12
  width: number;
@@ -30,4 +39,4 @@ declare function Document({ children, className }: DocumentProps): React.ReactEl
30
39
 
31
40
  declare function Page({ children, className, size, padding, id, }: PageProps): React.ReactElement;
32
41
 
33
- export { DEFAULT_PADDING, DEFAULT_PAGE_SIZE, Document, type DocumentProps, PAGE_SIZES, Page, type PageDimensions, type PagePadding, type PageProps, type PageSize, type PageSizePreset };
42
+ export { DEFAULT_PADDING, DEFAULT_PAGE_SIZE, Document, type DocumentConfig, type DocumentProps, PAGE_SIZES, Page, type PageDimensions, type PagePadding, type PageProps, type PageSize, type PageSizePreset, defineConfig };
package/dist/index.js CHANGED
@@ -1,3 +1,8 @@
1
+ // src/config.ts
2
+ function defineConfig(config) {
3
+ return config;
4
+ }
5
+
1
6
  // src/constants.ts
2
7
  var PAGE_SIZES = {
3
8
  A4: { width: 210, height: 297 },
@@ -110,6 +115,7 @@ export {
110
115
  DEFAULT_PAGE_SIZE,
111
116
  Document,
112
117
  PAGE_SIZES,
113
- Page
118
+ Page,
119
+ defineConfig
114
120
  };
115
121
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/constants.ts","../src/styles.ts","../src/document.tsx","../src/page.tsx"],"sourcesContent":["import type { PageDimensions, PageSizePreset } from './types';\n\nexport const PAGE_SIZES: Record<PageSizePreset, PageDimensions> = {\n A4: { width: 210, height: 297 },\n Letter: { width: 215.9, height: 279.4 },\n A3: { width: 297, height: 420 },\n};\n\nexport const DEFAULT_PADDING = 0;\n\nexport const DEFAULT_PAGE_SIZE: PageSizePreset = 'A4';\n","import type { PageDimensions, PagePadding } from './types';\n\ninterface ResolvedPadding {\n top: number;\n right: number;\n bottom: number;\n left: number;\n}\n\nexport function resolvePadding(padding: PagePadding): ResolvedPadding {\n if (typeof padding === 'number') {\n return { top: padding, right: padding, bottom: padding, left: padding };\n }\n return {\n top: padding.top ?? 0,\n right: padding.right ?? 0,\n bottom: padding.bottom ?? 0,\n left: padding.left ?? 0,\n };\n}\n\nexport function getPageStyle(\n dimensions: PageDimensions,\n padding: ResolvedPadding,\n): React.CSSProperties {\n return {\n width: `${dimensions.width}mm`,\n height: `${dimensions.height}mm`,\n paddingTop: `${padding.top}mm`,\n paddingRight: `${padding.right}mm`,\n paddingBottom: `${padding.bottom}mm`,\n paddingLeft: `${padding.left}mm`,\n display: 'block',\n overflow: 'hidden',\n background: 'white',\n boxSizing: 'border-box',\n };\n}\n\nexport function getDocumentStyleSheet(dimensions: PageDimensions): string {\n return `\n@page {\n size: ${dimensions.width}mm ${dimensions.height}mm;\n margin: 0;\n}\n\n* {\n print-color-adjust: exact;\n -webkit-print-color-adjust: exact;\n}\n\n@media screen {\n [data-pdf-smith-page] {\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24);\n margin-left: auto;\n margin-right: auto;\n }\n}\n\n@media print {\n [data-pdf-smith-page] {\n box-shadow: none;\n break-after: page;\n overflow: visible;\n height: auto;\n }\n}\n`.trim();\n}\n","import { PAGE_SIZES } from './constants';\nimport { getDocumentStyleSheet } from './styles';\nimport type { DocumentProps } from './types';\n\nexport function Document({ children, className }: DocumentProps): React.ReactElement {\n const styleSheet = getDocumentStyleSheet(PAGE_SIZES.A4);\n\n return (\n <div data-pdf-smith-document=\"\" className={className}>\n {/* biome-ignore lint/security/noDangerouslySetInnerHtml: trusted internal CSS */}\n <style dangerouslySetInnerHTML={{ __html: styleSheet }} />\n {children}\n </div>\n );\n}\n","import { DEFAULT_PADDING, DEFAULT_PAGE_SIZE, PAGE_SIZES } from './constants';\nimport { getPageStyle, resolvePadding } from './styles';\nimport type { PageDimensions, PageProps, PageSize } from './types';\n\nfunction resolveSize(size: PageSize): PageDimensions {\n if (typeof size === 'string') {\n return PAGE_SIZES[size];\n }\n return size;\n}\n\nexport function Page({\n children,\n className,\n size = DEFAULT_PAGE_SIZE,\n padding = DEFAULT_PADDING,\n id,\n}: PageProps): React.ReactElement {\n const dimensions = resolveSize(size);\n const resolvedPadding = resolvePadding(padding);\n const style = getPageStyle(dimensions, resolvedPadding);\n\n return (\n <div\n data-pdf-smith-page=\"\"\n data-page-width={dimensions.width}\n data-page-height={dimensions.height}\n className={className}\n id={id}\n style={style}\n >\n {children}\n </div>\n );\n}\n"],"mappings":";AAEO,IAAM,aAAqD;AAAA,EAChE,IAAI,EAAE,OAAO,KAAK,QAAQ,IAAI;AAAA,EAC9B,QAAQ,EAAE,OAAO,OAAO,QAAQ,MAAM;AAAA,EACtC,IAAI,EAAE,OAAO,KAAK,QAAQ,IAAI;AAChC;AAEO,IAAM,kBAAkB;AAExB,IAAM,oBAAoC;;;ACD1C,SAAS,eAAe,SAAuC;AACpE,MAAI,OAAO,YAAY,UAAU;AAC/B,WAAO,EAAE,KAAK,SAAS,OAAO,SAAS,QAAQ,SAAS,MAAM,QAAQ;AAAA,EACxE;AACA,SAAO;AAAA,IACL,KAAK,QAAQ,OAAO;AAAA,IACpB,OAAO,QAAQ,SAAS;AAAA,IACxB,QAAQ,QAAQ,UAAU;AAAA,IAC1B,MAAM,QAAQ,QAAQ;AAAA,EACxB;AACF;AAEO,SAAS,aACd,YACA,SACqB;AACrB,SAAO;AAAA,IACL,OAAO,GAAG,WAAW,KAAK;AAAA,IAC1B,QAAQ,GAAG,WAAW,MAAM;AAAA,IAC5B,YAAY,GAAG,QAAQ,GAAG;AAAA,IAC1B,cAAc,GAAG,QAAQ,KAAK;AAAA,IAC9B,eAAe,GAAG,QAAQ,MAAM;AAAA,IAChC,aAAa,GAAG,QAAQ,IAAI;AAAA,IAC5B,SAAS;AAAA,IACT,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,WAAW;AAAA,EACb;AACF;AAEO,SAAS,sBAAsB,YAAoC;AACxE,SAAO;AAAA;AAAA,UAEC,WAAW,KAAK,MAAM,WAAW,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAyB/C,KAAK;AACP;;;AC5DI,SAEE,KAFF;AAJG,SAAS,SAAS,EAAE,UAAU,UAAU,GAAsC;AACnF,QAAM,aAAa,sBAAsB,WAAW,EAAE;AAEtD,SACE,qBAAC,SAAI,2BAAwB,IAAG,WAE9B;AAAA,wBAAC,WAAM,yBAAyB,EAAE,QAAQ,WAAW,GAAG;AAAA,IACvD;AAAA,KACH;AAEJ;;;ACSI,gBAAAA,YAAA;AAnBJ,SAAS,YAAY,MAAgC;AACnD,MAAI,OAAO,SAAS,UAAU;AAC5B,WAAO,WAAW,IAAI;AAAA,EACxB;AACA,SAAO;AACT;AAEO,SAAS,KAAK;AAAA,EACnB;AAAA,EACA;AAAA,EACA,OAAO;AAAA,EACP,UAAU;AAAA,EACV;AACF,GAAkC;AAChC,QAAM,aAAa,YAAY,IAAI;AACnC,QAAM,kBAAkB,eAAe,OAAO;AAC9C,QAAM,QAAQ,aAAa,YAAY,eAAe;AAEtD,SACE,gBAAAA;AAAA,IAAC;AAAA;AAAA,MACC,uBAAoB;AAAA,MACpB,mBAAiB,WAAW;AAAA,MAC5B,oBAAkB,WAAW;AAAA,MAC7B;AAAA,MACA;AAAA,MACA;AAAA,MAEC;AAAA;AAAA,EACH;AAEJ;","names":["jsx"]}
1
+ {"version":3,"sources":["../src/config.ts","../src/constants.ts","../src/styles.ts","../src/document.tsx","../src/page.tsx"],"sourcesContent":["export interface DocumentConfig {\n pageNumbers?: { enabled: boolean; template?: string };\n output?: string;\n}\n\nexport function defineConfig(config: DocumentConfig): DocumentConfig {\n return config;\n}\n","import type { PageDimensions, PageSizePreset } from './types';\n\nexport const PAGE_SIZES: Record<PageSizePreset, PageDimensions> = {\n A4: { width: 210, height: 297 },\n Letter: { width: 215.9, height: 279.4 },\n A3: { width: 297, height: 420 },\n};\n\nexport const DEFAULT_PADDING = 0;\n\nexport const DEFAULT_PAGE_SIZE: PageSizePreset = 'A4';\n","import type { PageDimensions, PagePadding } from './types';\n\ninterface ResolvedPadding {\n top: number;\n right: number;\n bottom: number;\n left: number;\n}\n\nexport function resolvePadding(padding: PagePadding): ResolvedPadding {\n if (typeof padding === 'number') {\n return { top: padding, right: padding, bottom: padding, left: padding };\n }\n return {\n top: padding.top ?? 0,\n right: padding.right ?? 0,\n bottom: padding.bottom ?? 0,\n left: padding.left ?? 0,\n };\n}\n\nexport function getPageStyle(\n dimensions: PageDimensions,\n padding: ResolvedPadding,\n): React.CSSProperties {\n return {\n width: `${dimensions.width}mm`,\n height: `${dimensions.height}mm`,\n paddingTop: `${padding.top}mm`,\n paddingRight: `${padding.right}mm`,\n paddingBottom: `${padding.bottom}mm`,\n paddingLeft: `${padding.left}mm`,\n display: 'block',\n overflow: 'hidden',\n background: 'white',\n boxSizing: 'border-box',\n };\n}\n\nexport function getDocumentStyleSheet(dimensions: PageDimensions): string {\n return `\n@page {\n size: ${dimensions.width}mm ${dimensions.height}mm;\n margin: 0;\n}\n\n* {\n print-color-adjust: exact;\n -webkit-print-color-adjust: exact;\n}\n\n@media screen {\n [data-pdf-smith-page] {\n box-shadow: 0 1px 3px rgba(0, 0, 0, 0.12), 0 1px 2px rgba(0, 0, 0, 0.24);\n margin-left: auto;\n margin-right: auto;\n }\n}\n\n@media print {\n [data-pdf-smith-page] {\n box-shadow: none;\n break-after: page;\n overflow: visible;\n height: auto;\n }\n}\n`.trim();\n}\n","import { PAGE_SIZES } from './constants';\nimport { getDocumentStyleSheet } from './styles';\nimport type { DocumentProps } from './types';\n\nexport function Document({ children, className }: DocumentProps): React.ReactElement {\n const styleSheet = getDocumentStyleSheet(PAGE_SIZES.A4);\n\n return (\n <div data-pdf-smith-document=\"\" className={className}>\n {/* biome-ignore lint/security/noDangerouslySetInnerHtml: trusted internal CSS */}\n <style dangerouslySetInnerHTML={{ __html: styleSheet }} />\n {children}\n </div>\n );\n}\n","import { DEFAULT_PADDING, DEFAULT_PAGE_SIZE, PAGE_SIZES } from './constants';\nimport { getPageStyle, resolvePadding } from './styles';\nimport type { PageDimensions, PageProps, PageSize } from './types';\n\nfunction resolveSize(size: PageSize): PageDimensions {\n if (typeof size === 'string') {\n return PAGE_SIZES[size];\n }\n return size;\n}\n\nexport function Page({\n children,\n className,\n size = DEFAULT_PAGE_SIZE,\n padding = DEFAULT_PADDING,\n id,\n}: PageProps): React.ReactElement {\n const dimensions = resolveSize(size);\n const resolvedPadding = resolvePadding(padding);\n const style = getPageStyle(dimensions, resolvedPadding);\n\n return (\n <div\n data-pdf-smith-page=\"\"\n data-page-width={dimensions.width}\n data-page-height={dimensions.height}\n className={className}\n id={id}\n style={style}\n >\n {children}\n </div>\n );\n}\n"],"mappings":";AAKO,SAAS,aAAa,QAAwC;AACnE,SAAO;AACT;;;ACLO,IAAM,aAAqD;AAAA,EAChE,IAAI,EAAE,OAAO,KAAK,QAAQ,IAAI;AAAA,EAC9B,QAAQ,EAAE,OAAO,OAAO,QAAQ,MAAM;AAAA,EACtC,IAAI,EAAE,OAAO,KAAK,QAAQ,IAAI;AAChC;AAEO,IAAM,kBAAkB;AAExB,IAAM,oBAAoC;;;ACD1C,SAAS,eAAe,SAAuC;AACpE,MAAI,OAAO,YAAY,UAAU;AAC/B,WAAO,EAAE,KAAK,SAAS,OAAO,SAAS,QAAQ,SAAS,MAAM,QAAQ;AAAA,EACxE;AACA,SAAO;AAAA,IACL,KAAK,QAAQ,OAAO;AAAA,IACpB,OAAO,QAAQ,SAAS;AAAA,IACxB,QAAQ,QAAQ,UAAU;AAAA,IAC1B,MAAM,QAAQ,QAAQ;AAAA,EACxB;AACF;AAEO,SAAS,aACd,YACA,SACqB;AACrB,SAAO;AAAA,IACL,OAAO,GAAG,WAAW,KAAK;AAAA,IAC1B,QAAQ,GAAG,WAAW,MAAM;AAAA,IAC5B,YAAY,GAAG,QAAQ,GAAG;AAAA,IAC1B,cAAc,GAAG,QAAQ,KAAK;AAAA,IAC9B,eAAe,GAAG,QAAQ,MAAM;AAAA,IAChC,aAAa,GAAG,QAAQ,IAAI;AAAA,IAC5B,SAAS;AAAA,IACT,UAAU;AAAA,IACV,YAAY;AAAA,IACZ,WAAW;AAAA,EACb;AACF;AAEO,SAAS,sBAAsB,YAAoC;AACxE,SAAO;AAAA;AAAA,UAEC,WAAW,KAAK,MAAM,WAAW,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAyB/C,KAAK;AACP;;;AC5DI,SAEE,KAFF;AAJG,SAAS,SAAS,EAAE,UAAU,UAAU,GAAsC;AACnF,QAAM,aAAa,sBAAsB,WAAW,EAAE;AAEtD,SACE,qBAAC,SAAI,2BAAwB,IAAG,WAE9B;AAAA,wBAAC,WAAM,yBAAyB,EAAE,QAAQ,WAAW,GAAG;AAAA,IACvD;AAAA,KACH;AAEJ;;;ACSI,gBAAAA,YAAA;AAnBJ,SAAS,YAAY,MAAgC;AACnD,MAAI,OAAO,SAAS,UAAU;AAC5B,WAAO,WAAW,IAAI;AAAA,EACxB;AACA,SAAO;AACT;AAEO,SAAS,KAAK;AAAA,EACnB;AAAA,EACA;AAAA,EACA,OAAO;AAAA,EACP,UAAU;AAAA,EACV;AACF,GAAkC;AAChC,QAAM,aAAa,YAAY,IAAI;AACnC,QAAM,kBAAkB,eAAe,OAAO;AAC9C,QAAM,QAAQ,aAAa,YAAY,eAAe;AAEtD,SACE,gBAAAA;AAAA,IAAC;AAAA;AAAA,MACC,uBAAoB;AAAA,MACpB,mBAAiB,WAAW;AAAA,MAC5B,oBAAkB,WAAW;AAAA,MAC7B;AAAA,MACA;AAAA,MACA;AAAA,MAEC;AAAA;AAAA,EACH;AAEJ;","names":["jsx"]}
@@ -0,0 +1,199 @@
1
+ // src/preview/preview-plugin.ts
2
+ import fs from "fs";
3
+ import path from "path";
4
+ var VIRTUAL_DOCUMENTS_ID = "virtual:pdf-smith-documents";
5
+ var RESOLVED_VIRTUAL_DOCUMENTS_ID = `\0${VIRTUAL_DOCUMENTS_ID}`;
6
+ async function handleExportRequest(server, slug, res) {
7
+ const root = server.config.root;
8
+ const pagesDir = path.join(root, "pdfs", slug, "pages");
9
+ if (!fs.existsSync(pagesDir)) {
10
+ res.statusCode = 404;
11
+ res.setHeader("Content-Type", "application/json");
12
+ res.end(JSON.stringify({ error: `Document "${slug}" not found` }));
13
+ return;
14
+ }
15
+ let config;
16
+ const configPath = path.join(root, "pdfs", slug, "config.ts");
17
+ if (fs.existsSync(configPath)) {
18
+ try {
19
+ const configModule = await server.ssrLoadModule(configPath);
20
+ config = configModule.default;
21
+ } catch {
22
+ }
23
+ }
24
+ let playwright;
25
+ try {
26
+ playwright = await import("playwright");
27
+ } catch {
28
+ res.statusCode = 500;
29
+ res.setHeader("Content-Type", "application/json");
30
+ res.end(
31
+ JSON.stringify({
32
+ error: "Playwright is required for PDF export. Install it with: npm install -D playwright && npx playwright install chromium"
33
+ })
34
+ );
35
+ return;
36
+ }
37
+ let browser;
38
+ try {
39
+ const address = server.httpServer?.address();
40
+ const port = address?.port;
41
+ if (!port) throw new Error("Could not determine server port");
42
+ browser = await playwright.chromium.launch({ headless: true });
43
+ const page = await browser.newPage();
44
+ await page.goto(`http://localhost:${port}/${slug}`, { waitUntil: "networkidle" });
45
+ await page.waitForSelector("[data-pdf-smith-page]", { timeout: 3e4 });
46
+ const margin = { top: "0", bottom: "0", left: "0", right: "0" };
47
+ const pdfOptions = {
48
+ preferCSSPageSize: true,
49
+ printBackground: true,
50
+ margin
51
+ };
52
+ if (config?.pageNumbers?.enabled) {
53
+ pdfOptions.displayHeaderFooter = true;
54
+ pdfOptions.headerTemplate = "<span></span>";
55
+ pdfOptions.footerTemplate = config.pageNumbers.template ?? '<div style="font-size:10px;text-align:center;width:100%;"><span class="pageNumber"></span> / <span class="totalPages"></span></div>';
56
+ margin.bottom = "40px";
57
+ }
58
+ const buffer = await page.pdf(pdfOptions);
59
+ await page.close();
60
+ const filename = config?.output ?? `${slug}.pdf`;
61
+ res.statusCode = 200;
62
+ res.setHeader("Content-Type", "application/pdf");
63
+ res.setHeader("Content-Disposition", `attachment; filename="${filename}"`);
64
+ res.end(buffer);
65
+ } catch (err) {
66
+ if (!res.headersSent) {
67
+ res.statusCode = 500;
68
+ res.setHeader("Content-Type", "application/json");
69
+ res.end(JSON.stringify({ error: err instanceof Error ? err.message : "PDF export failed" }));
70
+ }
71
+ } finally {
72
+ await browser?.close().catch(() => {
73
+ });
74
+ }
75
+ }
76
+ function pdfSmithPreviewPlugin({ pkgSrcDir }) {
77
+ return {
78
+ name: "pdf-smith-preview",
79
+ configureServer(server) {
80
+ server.middlewares.use((req, res, next) => {
81
+ if (req.method !== "POST") return next();
82
+ const match = req.url?.match(/^\/api\/export\/([^/]+)$/);
83
+ if (!match) return next();
84
+ void handleExportRequest(server, match[1], res);
85
+ });
86
+ return () => {
87
+ server.middlewares.use((req, res, next) => {
88
+ const url = req.url ?? "";
89
+ if (url.includes(".") || url.startsWith("/@") || url.startsWith("/node_modules/")) {
90
+ next();
91
+ return;
92
+ }
93
+ const html = `<!DOCTYPE html>
94
+ <html lang="en">
95
+ <head>
96
+ <script type="module">
97
+ import RefreshRuntime from "/@react-refresh"
98
+ RefreshRuntime.injectIntoGlobalHook(window)
99
+ window.$RefreshReg$ = () => {}
100
+ window.$RefreshSig$ = () => (type) => type
101
+ window.__vite_plugin_react_preamble_installed__ = true
102
+ </script>
103
+ <script type="module" src="/@vite/client"></script>
104
+ <meta charset="UTF-8" />
105
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
106
+ <title>pdf-smith Preview</title>
107
+ <link rel="preconnect" href="https://fonts.googleapis.com">
108
+ <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
109
+ <link href="https://fonts.googleapis.com/css2?family=Fraunces:ital,opsz,wght,SOFT,WONK@0,9..144,100..900,0..100,0..1&family=Inter:opsz,wght@14..32,100..900&family=JetBrains+Mono:wght@400;500;600&display=swap" rel="stylesheet">
110
+ <style>
111
+ *, *::before, *::after { box-sizing: border-box; }
112
+ body {
113
+ margin: 0;
114
+ font-family: 'Inter', system-ui, sans-serif;
115
+ -webkit-font-smoothing: antialiased;
116
+ }
117
+ .pdf-smith-search::placeholder { color: #78716C; }
118
+ @media print {
119
+ [data-pdf-smith-nav] { display: none !important; }
120
+ [data-pdf-smith-toolbar] { display: none !important; }
121
+ [data-pdf-smith-minimap] { display: none !important; }
122
+ [data-pdf-smith-container] {
123
+ margin-left: 0 !important;
124
+ padding: 0 !important;
125
+ padding-top: 0 !important;
126
+ background: none !important;
127
+ }
128
+ [data-pdf-smith-container] > div {
129
+ padding: 0 !important;
130
+ gap: 0 !important;
131
+ }
132
+ [data-pdf-smith-document] > div {
133
+ margin-bottom: 0 !important;
134
+ }
135
+ [data-pdf-smith-document] > div > div:first-child {
136
+ display: none !important;
137
+ }
138
+ }
139
+ </style>
140
+ </head>
141
+ <body>
142
+ <div id="root"></div>
143
+ <script type="module" src="/@fs/${pkgSrcDir}/preview/entry.tsx"></script>
144
+ </body>
145
+ </html>`;
146
+ res.setHeader("Content-Type", "text/html");
147
+ res.statusCode = 200;
148
+ res.end(html);
149
+ });
150
+ };
151
+ },
152
+ resolveId(id) {
153
+ if (id === VIRTUAL_DOCUMENTS_ID) {
154
+ return RESOLVED_VIRTUAL_DOCUMENTS_ID;
155
+ }
156
+ },
157
+ load(id) {
158
+ if (id === RESOLVED_VIRTUAL_DOCUMENTS_ID) {
159
+ return `
160
+ const pageModules = import.meta.glob('/pdfs/*/pages/**/*.tsx', { eager: true });
161
+ const configModules = import.meta.glob('/pdfs/*/config.ts', { eager: true });
162
+
163
+ export function getDocuments() {
164
+ const documents = {};
165
+
166
+ for (const [path, mod] of Object.entries(pageModules)) {
167
+ const match = path.match(/^\\/pdfs\\/([^/]+)\\/pages\\/(.+)\\.[^.]+$/);
168
+ if (!match) continue;
169
+ const [, slug, pageName] = match;
170
+ if (!documents[slug]) {
171
+ documents[slug] = { slug, pages: {}, config: undefined };
172
+ }
173
+ documents[slug].pages[pageName] = mod.default;
174
+ }
175
+
176
+ for (const [path, mod] of Object.entries(configModules)) {
177
+ const match = path.match(/^\\/pdfs\\/([^/]+)\\/config\\.ts$/);
178
+ if (!match) continue;
179
+ const slug = match[1];
180
+ if (documents[slug]) {
181
+ documents[slug].config = mod.default;
182
+ }
183
+ }
184
+
185
+ return documents;
186
+ }
187
+
188
+ export function getDocumentSlugs() {
189
+ return Object.keys(getDocuments());
190
+ }
191
+ `;
192
+ }
193
+ }
194
+ };
195
+ }
196
+ export {
197
+ pdfSmithPreviewPlugin
198
+ };
199
+ //# sourceMappingURL=preview-plugin-26BUIGCE.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/preview/preview-plugin.ts"],"sourcesContent":["import fs from 'node:fs';\nimport type { AddressInfo } from 'node:net';\nimport path from 'node:path';\nimport type { Plugin } from 'vite';\nimport type { DocumentConfig } from '../config';\n\nconst VIRTUAL_DOCUMENTS_ID = 'virtual:pdf-smith-documents';\nconst RESOLVED_VIRTUAL_DOCUMENTS_ID = `\\0${VIRTUAL_DOCUMENTS_ID}`;\n\ninterface PreviewPluginOptions {\n pkgSrcDir: string;\n}\n\nasync function handleExportRequest(\n server: import('vite').ViteDevServer,\n slug: string,\n res: import('node:http').ServerResponse,\n) {\n const root = server.config.root;\n const pagesDir = path.join(root, 'pdfs', slug, 'pages');\n\n if (!fs.existsSync(pagesDir)) {\n res.statusCode = 404;\n res.setHeader('Content-Type', 'application/json');\n res.end(JSON.stringify({ error: `Document \"${slug}\" not found` }));\n return;\n }\n\n let config: DocumentConfig | undefined;\n const configPath = path.join(root, 'pdfs', slug, 'config.ts');\n if (fs.existsSync(configPath)) {\n try {\n const configModule = await server.ssrLoadModule(configPath);\n config = configModule.default;\n } catch {\n // Config loading failed, proceed without it\n }\n }\n\n let playwright: typeof import('playwright');\n try {\n playwright = await import('playwright');\n } catch {\n res.statusCode = 500;\n res.setHeader('Content-Type', 'application/json');\n res.end(\n JSON.stringify({\n error:\n 'Playwright is required for PDF export. Install it with: npm install -D playwright && npx playwright install chromium',\n }),\n );\n return;\n }\n\n let browser: import('playwright').Browser | undefined;\n try {\n const address = server.httpServer?.address() as AddressInfo | null;\n const port = address?.port;\n if (!port) throw new Error('Could not determine server port');\n\n browser = await playwright.chromium.launch({ headless: true });\n const page = await browser.newPage();\n await page.goto(`http://localhost:${port}/${slug}`, { waitUntil: 'networkidle' });\n await page.waitForSelector('[data-pdf-smith-page]', { timeout: 30_000 });\n\n const margin = { top: '0', bottom: '0', left: '0', right: '0' };\n const pdfOptions: Parameters<typeof page.pdf>[0] = {\n preferCSSPageSize: true,\n printBackground: true,\n margin,\n };\n\n if (config?.pageNumbers?.enabled) {\n pdfOptions.displayHeaderFooter = true;\n pdfOptions.headerTemplate = '<span></span>';\n pdfOptions.footerTemplate =\n config.pageNumbers.template ??\n '<div style=\"font-size:10px;text-align:center;width:100%;\"><span class=\"pageNumber\"></span> / <span class=\"totalPages\"></span></div>';\n margin.bottom = '40px';\n }\n\n const buffer = await page.pdf(pdfOptions);\n await page.close();\n\n const filename = config?.output ?? `${slug}.pdf`;\n res.statusCode = 200;\n res.setHeader('Content-Type', 'application/pdf');\n res.setHeader('Content-Disposition', `attachment; filename=\"${filename}\"`);\n res.end(buffer);\n } catch (err) {\n if (!res.headersSent) {\n res.statusCode = 500;\n res.setHeader('Content-Type', 'application/json');\n res.end(JSON.stringify({ error: err instanceof Error ? err.message : 'PDF export failed' }));\n }\n } finally {\n await browser?.close().catch(() => {});\n }\n}\n\nexport function pdfSmithPreviewPlugin({ pkgSrcDir }: PreviewPluginOptions): Plugin {\n return {\n name: 'pdf-smith-preview',\n\n configureServer(server) {\n // Pre-middleware: Export API runs before Vite internals.\n // Must be a sync function — async middleware returns a Promise that\n // Connect ignores, so the pipeline can advance to the catch-all\n // before the async work finishes.\n server.middlewares.use((req, res, next) => {\n if (req.method !== 'POST') return next();\n const match = req.url?.match(/^\\/api\\/export\\/([^/]+)$/);\n if (!match) return next();\n void handleExportRequest(server, match[1], res);\n });\n\n // Post-middleware: catch-all HTML handler (after Vite internals)\n return () => {\n server.middlewares.use((req, res, next) => {\n const url = req.url ?? '';\n\n // Skip file requests, Vite internals, and node_modules\n if (url.includes('.') || url.startsWith('/@') || url.startsWith('/node_modules/')) {\n next();\n return;\n }\n\n const html = `<!DOCTYPE html>\n<html lang=\"en\">\n<head>\n <script type=\"module\">\nimport RefreshRuntime from \"/@react-refresh\"\nRefreshRuntime.injectIntoGlobalHook(window)\nwindow.$RefreshReg$ = () => {}\nwindow.$RefreshSig$ = () => (type) => type\nwindow.__vite_plugin_react_preamble_installed__ = true\n</script>\n <script type=\"module\" src=\"/@vite/client\"></script>\n <meta charset=\"UTF-8\" />\n <meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0\" />\n <title>pdf-smith Preview</title>\n <link rel=\"preconnect\" href=\"https://fonts.googleapis.com\">\n <link rel=\"preconnect\" href=\"https://fonts.gstatic.com\" crossorigin>\n <link href=\"https://fonts.googleapis.com/css2?family=Fraunces:ital,opsz,wght,SOFT,WONK@0,9..144,100..900,0..100,0..1&family=Inter:opsz,wght@14..32,100..900&family=JetBrains+Mono:wght@400;500;600&display=swap\" rel=\"stylesheet\">\n <style>\n *, *::before, *::after { box-sizing: border-box; }\n body {\n margin: 0;\n font-family: 'Inter', system-ui, sans-serif;\n -webkit-font-smoothing: antialiased;\n }\n .pdf-smith-search::placeholder { color: #78716C; }\n @media print {\n [data-pdf-smith-nav] { display: none !important; }\n [data-pdf-smith-toolbar] { display: none !important; }\n [data-pdf-smith-minimap] { display: none !important; }\n [data-pdf-smith-container] {\n margin-left: 0 !important;\n padding: 0 !important;\n padding-top: 0 !important;\n background: none !important;\n }\n [data-pdf-smith-container] > div {\n padding: 0 !important;\n gap: 0 !important;\n }\n [data-pdf-smith-document] > div {\n margin-bottom: 0 !important;\n }\n [data-pdf-smith-document] > div > div:first-child {\n display: none !important;\n }\n }\n </style>\n</head>\n<body>\n <div id=\"root\"></div>\n <script type=\"module\" src=\"/@fs/${pkgSrcDir}/preview/entry.tsx\"></script>\n</body>\n</html>`;\n\n res.setHeader('Content-Type', 'text/html');\n res.statusCode = 200;\n res.end(html);\n });\n };\n },\n\n resolveId(id) {\n if (id === VIRTUAL_DOCUMENTS_ID) {\n return RESOLVED_VIRTUAL_DOCUMENTS_ID;\n }\n },\n\n load(id) {\n if (id === RESOLVED_VIRTUAL_DOCUMENTS_ID) {\n return `\nconst pageModules = import.meta.glob('/pdfs/*/pages/**/*.tsx', { eager: true });\nconst configModules = import.meta.glob('/pdfs/*/config.ts', { eager: true });\n\nexport function getDocuments() {\n const documents = {};\n\n for (const [path, mod] of Object.entries(pageModules)) {\n const match = path.match(/^\\\\/pdfs\\\\/([^/]+)\\\\/pages\\\\/(.+)\\\\.[^.]+$/);\n if (!match) continue;\n const [, slug, pageName] = match;\n if (!documents[slug]) {\n documents[slug] = { slug, pages: {}, config: undefined };\n }\n documents[slug].pages[pageName] = mod.default;\n }\n\n for (const [path, mod] of Object.entries(configModules)) {\n const match = path.match(/^\\\\/pdfs\\\\/([^/]+)\\\\/config\\\\.ts$/);\n if (!match) continue;\n const slug = match[1];\n if (documents[slug]) {\n documents[slug].config = mod.default;\n }\n }\n\n return documents;\n}\n\nexport function getDocumentSlugs() {\n return Object.keys(getDocuments());\n}\n`;\n }\n },\n };\n}\n"],"mappings":"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","names":[]}