pdf-smith 0.1.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.
@@ -0,0 +1,246 @@
1
+ "use strict";
2
+ var __create = Object.create;
3
+ var __defProp = Object.defineProperty;
4
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
5
+ var __getOwnPropNames = Object.getOwnPropertyNames;
6
+ var __getProtoOf = Object.getPrototypeOf;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __esm = (fn, res) => function __init() {
9
+ return fn && (res = (0, fn[__getOwnPropNames(fn)[0]])(fn = 0)), res;
10
+ };
11
+ var __export = (target, all) => {
12
+ for (var name in all)
13
+ __defProp(target, name, { get: all[name], enumerable: true });
14
+ };
15
+ var __copyProps = (to, from, except, desc) => {
16
+ if (from && typeof from === "object" || typeof from === "function") {
17
+ for (let key of __getOwnPropNames(from))
18
+ if (!__hasOwnProp.call(to, key) && key !== except)
19
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
20
+ }
21
+ return to;
22
+ };
23
+ var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
24
+ // If the importer is in node compatibility mode or this is not an ESM
25
+ // file that has been converted to a CommonJS file using a Babel-
26
+ // compatible transform (i.e. "__esModule" has not been set), then set
27
+ // "default" to the CommonJS "module.exports" for node compatibility.
28
+ isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
29
+ mod
30
+ ));
31
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
32
+
33
+ // src/export.ts
34
+ async function exportPDF(options) {
35
+ const { root, output, pageNumbers, pages } = options;
36
+ const outputPath = import_node_path.default.resolve(root, output ?? "./output.pdf");
37
+ import_node_fs.default.mkdirSync(import_node_path.default.dirname(outputPath), { recursive: true });
38
+ let playwright;
39
+ try {
40
+ playwright = await import("playwright");
41
+ } catch {
42
+ throw new Error(
43
+ "Playwright is required for PDF export. Install it with: npm install -D playwright && npx playwright install chromium"
44
+ );
45
+ }
46
+ const { startPreview: startPreview2 } = await Promise.resolve().then(() => (init_server(), server_exports));
47
+ const server = await startPreview2({ root, port: 0, pages, open: false });
48
+ const browser = await playwright.chromium.launch({ headless: true });
49
+ try {
50
+ const page = await browser.newPage();
51
+ await page.goto(server.url, { waitUntil: "networkidle" });
52
+ await page.waitForSelector("[data-pdf-smith-page]", { timeout: 3e4 });
53
+ const margin = { top: "0", bottom: "0", left: "0", right: "0" };
54
+ const pdfOptions = {
55
+ path: outputPath,
56
+ preferCSSPageSize: true,
57
+ printBackground: true,
58
+ margin
59
+ };
60
+ if (pageNumbers?.enabled) {
61
+ pdfOptions.displayHeaderFooter = true;
62
+ pdfOptions.headerTemplate = "<span></span>";
63
+ pdfOptions.footerTemplate = pageNumbers.template ?? '<div style="font-size:10px;text-align:center;width:100%;"><span class="pageNumber"></span> / <span class="totalPages"></span></div>';
64
+ margin.bottom = "40px";
65
+ }
66
+ await page.pdf(pdfOptions);
67
+ } finally {
68
+ await browser.close().catch(() => {
69
+ });
70
+ await server.close().catch(() => {
71
+ });
72
+ }
73
+ return { outputPath };
74
+ }
75
+ var import_node_fs, import_node_path;
76
+ var init_export = __esm({
77
+ "src/export.ts"() {
78
+ "use strict";
79
+ import_node_fs = __toESM(require("fs"), 1);
80
+ import_node_path = __toESM(require("path"), 1);
81
+ }
82
+ });
83
+
84
+ // src/preview/preview-plugin.ts
85
+ var preview_plugin_exports = {};
86
+ __export(preview_plugin_exports, {
87
+ pdfSmithPreviewPlugin: () => pdfSmithPreviewPlugin
88
+ });
89
+ function pdfSmithPreviewPlugin({ pkgSrcDir, pagesGlob }) {
90
+ return {
91
+ name: "pdf-smith-preview",
92
+ configureServer(server) {
93
+ return () => {
94
+ server.middlewares.use((req, res, next) => {
95
+ if (req.url !== "/" && req.url !== "/index.html") {
96
+ next();
97
+ return;
98
+ }
99
+ const html = `<!DOCTYPE html>
100
+ <html lang="en">
101
+ <head>
102
+ <script type="module">
103
+ import RefreshRuntime from "/@react-refresh"
104
+ RefreshRuntime.injectIntoGlobalHook(window)
105
+ window.$RefreshReg$ = () => {}
106
+ window.$RefreshSig$ = () => (type) => type
107
+ window.__vite_plugin_react_preamble_installed__ = true
108
+ </script>
109
+ <script type="module" src="/@vite/client"></script>
110
+ <meta charset="UTF-8" />
111
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
112
+ <title>pdf-smith Preview</title>
113
+ <style>
114
+ @media print {
115
+ [data-pdf-smith-nav] { display: none !important; }
116
+ [data-pdf-smith-container] {
117
+ margin-left: 0 !important;
118
+ padding: 0 !important;
119
+ background: none !important;
120
+ }
121
+ [data-pdf-smith-container] > [data-pdf-smith-document] > div {
122
+ margin-bottom: 0 !important;
123
+ }
124
+ [data-pdf-smith-container] > [data-pdf-smith-document] > div > div:first-child {
125
+ display: none !important;
126
+ }
127
+ }
128
+ </style>
129
+ </head>
130
+ <body>
131
+ <div id="root"></div>
132
+ <script type="module" src="/@fs/${pkgSrcDir}/preview/entry.tsx"></script>
133
+ </body>
134
+ </html>`;
135
+ res.setHeader("Content-Type", "text/html");
136
+ res.statusCode = 200;
137
+ res.end(html);
138
+ });
139
+ };
140
+ },
141
+ resolveId(id) {
142
+ if (id === VIRTUAL_PAGES_ID) {
143
+ return RESOLVED_VIRTUAL_PAGES_ID;
144
+ }
145
+ },
146
+ load(id) {
147
+ if (id === RESOLVED_VIRTUAL_PAGES_ID) {
148
+ return `
149
+ const modules = import.meta.glob('${pagesGlob}', { eager: true });
150
+
151
+ export function getPages() {
152
+ const pages = {};
153
+ for (const [path, mod] of Object.entries(modules)) {
154
+ const name = path
155
+ .replace(/^\\/pages\\//, '')
156
+ .replace(/\\.[^.]+$/, '');
157
+ pages[name] = mod.default;
158
+ }
159
+ return pages;
160
+ }
161
+ `;
162
+ }
163
+ }
164
+ };
165
+ }
166
+ var VIRTUAL_PAGES_ID, RESOLVED_VIRTUAL_PAGES_ID;
167
+ var init_preview_plugin = __esm({
168
+ "src/preview/preview-plugin.ts"() {
169
+ "use strict";
170
+ VIRTUAL_PAGES_ID = "virtual:pdf-smith-pages";
171
+ RESOLVED_VIRTUAL_PAGES_ID = `\0${VIRTUAL_PAGES_ID}`;
172
+ }
173
+ });
174
+
175
+ // src/server.ts
176
+ var server_exports = {};
177
+ __export(server_exports, {
178
+ exportPDF: () => exportPDF,
179
+ startPreview: () => startPreview
180
+ });
181
+ module.exports = __toCommonJS(server_exports);
182
+ async function startPreview(options) {
183
+ const { root, port = 3e3, pages = "/pages/**/*.tsx", open = false } = options;
184
+ const { createServer } = await import("vite");
185
+ const react = (await import("@vitejs/plugin-react")).default;
186
+ const tailwindcss = (await import("@tailwindcss/vite")).default;
187
+ const { pdfSmithPreviewPlugin: pdfSmithPreviewPlugin2 } = await Promise.resolve().then(() => (init_preview_plugin(), preview_plugin_exports));
188
+ const currentDir = import_node_path2.default.dirname((0, import_node_url.fileURLToPath)(import_meta.url));
189
+ const pkgRoot = import_node_path2.default.resolve(currentDir, "..");
190
+ const pkgSrcDir = import_node_path2.default.resolve(pkgRoot, "src");
191
+ const server = await createServer({
192
+ configFile: false,
193
+ appType: "custom",
194
+ root,
195
+ server: {
196
+ port,
197
+ strictPort: port !== 0,
198
+ open,
199
+ fs: {
200
+ allow: [root, pkgSrcDir, pkgRoot]
201
+ }
202
+ },
203
+ resolve: {
204
+ alias: {
205
+ "pdf-smith": import_node_path2.default.resolve(pkgSrcDir, "index.ts")
206
+ },
207
+ dedupe: ["react", "react-dom"]
208
+ },
209
+ plugins: [react(), tailwindcss(), pdfSmithPreviewPlugin2({ pkgSrcDir, pagesGlob: pages })],
210
+ optimizeDeps: {
211
+ include: ["react", "react-dom", "react-dom/client", "react/jsx-runtime"]
212
+ }
213
+ });
214
+ await server.listen();
215
+ if (port !== 0) {
216
+ server.printUrls();
217
+ }
218
+ const address = server.httpServer?.address();
219
+ const resolvedPort = address?.port ?? port;
220
+ const url = `http://localhost:${resolvedPort}`;
221
+ return {
222
+ close: async () => {
223
+ const httpServer = server.httpServer;
224
+ httpServer?.closeAllConnections?.();
225
+ await server.close();
226
+ },
227
+ port: resolvedPort,
228
+ url
229
+ };
230
+ }
231
+ var import_node_path2, import_node_url, import_meta;
232
+ var init_server = __esm({
233
+ "src/server.ts"() {
234
+ import_node_path2 = __toESM(require("path"), 1);
235
+ import_node_url = require("url");
236
+ init_export();
237
+ import_meta = {};
238
+ }
239
+ });
240
+ init_server();
241
+ // Annotate the CommonJS export names for ESM import in node:
242
+ 0 && (module.exports = {
243
+ exportPDF,
244
+ startPreview
245
+ });
246
+ //# sourceMappingURL=server.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/export.ts","../src/preview/preview-plugin.ts","../src/server.ts"],"sourcesContent":["import fs from 'node:fs';\nimport path from 'node:path';\nimport type { ExportOptions, ExportResult } from './export-types';\n\nexport async function exportPDF(options: ExportOptions): Promise<ExportResult> {\n const { root, output, pageNumbers, pages } = options;\n const outputPath = path.resolve(root, output ?? './output.pdf');\n\n fs.mkdirSync(path.dirname(outputPath), { recursive: true });\n\n let playwright: typeof import('playwright');\n try {\n playwright = await import('playwright');\n } catch {\n throw new Error(\n 'Playwright is required for PDF export. Install it with: npm install -D playwright && npx playwright install chromium',\n );\n }\n\n const { startPreview } = await import('./server');\n const server = await startPreview({ root, port: 0, pages, open: false });\n\n const browser = await playwright.chromium.launch({ headless: true });\n try {\n const page = await browser.newPage();\n await page.goto(server.url, { 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\n const pdfOptions: Parameters<typeof page.pdf>[0] = {\n path: outputPath,\n preferCSSPageSize: true,\n printBackground: true,\n margin,\n };\n\n if (pageNumbers?.enabled) {\n pdfOptions.displayHeaderFooter = true;\n pdfOptions.headerTemplate = '<span></span>';\n pdfOptions.footerTemplate =\n 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 await page.pdf(pdfOptions);\n } finally {\n await browser.close().catch(() => {});\n await server.close().catch(() => {});\n }\n\n return { outputPath };\n}\n","import type { Plugin } from 'vite';\n\nconst VIRTUAL_PAGES_ID = 'virtual:pdf-smith-pages';\nconst RESOLVED_VIRTUAL_PAGES_ID = `\\0${VIRTUAL_PAGES_ID}`;\n\ninterface PreviewPluginOptions {\n pkgSrcDir: string;\n pagesGlob: string;\n}\n\nexport function pdfSmithPreviewPlugin({ pkgSrcDir, pagesGlob }: PreviewPluginOptions): Plugin {\n return {\n name: 'pdf-smith-preview',\n\n configureServer(server) {\n return () => {\n server.middlewares.use((req, res, next) => {\n if (req.url !== '/' && req.url !== '/index.html') {\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 <style>\n @media print {\n [data-pdf-smith-nav] { display: none !important; }\n [data-pdf-smith-container] {\n margin-left: 0 !important;\n padding: 0 !important;\n background: none !important;\n }\n [data-pdf-smith-container] > [data-pdf-smith-document] > div {\n margin-bottom: 0 !important;\n }\n [data-pdf-smith-container] > [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_PAGES_ID) {\n return RESOLVED_VIRTUAL_PAGES_ID;\n }\n },\n\n load(id) {\n if (id === RESOLVED_VIRTUAL_PAGES_ID) {\n return `\nconst modules = import.meta.glob('${pagesGlob}', { eager: true });\n\nexport function getPages() {\n const pages = {};\n for (const [path, mod] of Object.entries(modules)) {\n const name = path\n .replace(/^\\\\/pages\\\\//, '')\n .replace(/\\\\.[^.]+$/, '');\n pages[name] = mod.default;\n }\n return pages;\n}\n`;\n }\n },\n };\n}\n","import type { AddressInfo } from 'node:net';\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport type { PreviewOptions, PreviewServer } from './preview/types';\n\nexport { exportPDF } from './export';\nexport type { ExportOptions, ExportResult } from './export-types';\nexport type { PreviewOptions, PreviewServer } from './preview/types';\n\nexport async function startPreview(options: PreviewOptions): Promise<PreviewServer> {\n const { root, port = 3000, pages = '/pages/**/*.tsx', open = false } = options;\n\n const { createServer } = await import('vite');\n const react = (await import('@vitejs/plugin-react')).default;\n const tailwindcss = (await import('@tailwindcss/vite')).default;\n const { pdfSmithPreviewPlugin } = await import('./preview/preview-plugin');\n\n const currentDir = path.dirname(fileURLToPath(import.meta.url));\n const pkgRoot = path.resolve(currentDir, '..');\n const pkgSrcDir = path.resolve(pkgRoot, 'src');\n\n const server = await createServer({\n configFile: false,\n appType: 'custom',\n root,\n server: {\n port,\n strictPort: port !== 0,\n open,\n fs: {\n allow: [root, pkgSrcDir, pkgRoot],\n },\n },\n resolve: {\n alias: {\n 'pdf-smith': path.resolve(pkgSrcDir, 'index.ts'),\n },\n dedupe: ['react', 'react-dom'],\n },\n plugins: [react(), tailwindcss(), pdfSmithPreviewPlugin({ pkgSrcDir, pagesGlob: pages })],\n optimizeDeps: {\n include: ['react', 'react-dom', 'react-dom/client', 'react/jsx-runtime'],\n },\n });\n\n await server.listen();\n\n if (port !== 0) {\n server.printUrls();\n }\n\n const address = server.httpServer?.address() as AddressInfo | null;\n const resolvedPort = address?.port ?? port;\n const url = `http://localhost:${resolvedPort}`;\n\n return {\n close: async () => {\n const httpServer = server.httpServer as { closeAllConnections?: () => void } | null;\n httpServer?.closeAllConnections?.();\n await server.close();\n },\n port: resolvedPort,\n url,\n };\n}\n"],"mappings":"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","names":["path","fs","startPreview","pdfSmithPreviewPlugin","path","import_node_path"]}
@@ -0,0 +1,33 @@
1
+ interface PreviewOptions {
2
+ /** User's project root (absolute path) */
3
+ root: string;
4
+ /** Dev server port (default: 3000) */
5
+ port?: number;
6
+ pages?: string;
7
+ /** Auto-open browser (default: false) */
8
+ open?: boolean;
9
+ }
10
+ interface PreviewServer {
11
+ close(): Promise<void>;
12
+ port: number;
13
+ url: string;
14
+ }
15
+
16
+ interface ExportOptions {
17
+ root: string;
18
+ output?: string;
19
+ pageNumbers?: {
20
+ enabled: boolean;
21
+ template?: string;
22
+ };
23
+ pages?: string;
24
+ }
25
+ interface ExportResult {
26
+ outputPath: string;
27
+ }
28
+
29
+ declare function exportPDF(options: ExportOptions): Promise<ExportResult>;
30
+
31
+ declare function startPreview(options: PreviewOptions): Promise<PreviewServer>;
32
+
33
+ export { type ExportOptions, type ExportResult, type PreviewOptions, type PreviewServer, exportPDF, startPreview };
@@ -0,0 +1,33 @@
1
+ interface PreviewOptions {
2
+ /** User's project root (absolute path) */
3
+ root: string;
4
+ /** Dev server port (default: 3000) */
5
+ port?: number;
6
+ pages?: string;
7
+ /** Auto-open browser (default: false) */
8
+ open?: boolean;
9
+ }
10
+ interface PreviewServer {
11
+ close(): Promise<void>;
12
+ port: number;
13
+ url: string;
14
+ }
15
+
16
+ interface ExportOptions {
17
+ root: string;
18
+ output?: string;
19
+ pageNumbers?: {
20
+ enabled: boolean;
21
+ template?: string;
22
+ };
23
+ pages?: string;
24
+ }
25
+ interface ExportResult {
26
+ outputPath: string;
27
+ }
28
+
29
+ declare function exportPDF(options: ExportOptions): Promise<ExportResult>;
30
+
31
+ declare function startPreview(options: PreviewOptions): Promise<PreviewServer>;
32
+
33
+ export { type ExportOptions, type ExportResult, type PreviewOptions, type PreviewServer, exportPDF, startPreview };
package/dist/server.js ADDED
@@ -0,0 +1,104 @@
1
+ // src/server.ts
2
+ import path2 from "path";
3
+ import { fileURLToPath } from "url";
4
+
5
+ // src/export.ts
6
+ import fs from "fs";
7
+ import path from "path";
8
+ async function exportPDF(options) {
9
+ const { root, output, pageNumbers, pages } = options;
10
+ const outputPath = path.resolve(root, output ?? "./output.pdf");
11
+ fs.mkdirSync(path.dirname(outputPath), { recursive: true });
12
+ let playwright;
13
+ try {
14
+ playwright = await import("playwright");
15
+ } catch {
16
+ throw new Error(
17
+ "Playwright is required for PDF export. Install it with: npm install -D playwright && npx playwright install chromium"
18
+ );
19
+ }
20
+ const { startPreview: startPreview2 } = await import("./server.js");
21
+ const server = await startPreview2({ root, port: 0, pages, open: false });
22
+ const browser = await playwright.chromium.launch({ headless: true });
23
+ try {
24
+ const page = await browser.newPage();
25
+ await page.goto(server.url, { waitUntil: "networkidle" });
26
+ await page.waitForSelector("[data-pdf-smith-page]", { timeout: 3e4 });
27
+ const margin = { top: "0", bottom: "0", left: "0", right: "0" };
28
+ const pdfOptions = {
29
+ path: outputPath,
30
+ preferCSSPageSize: true,
31
+ printBackground: true,
32
+ margin
33
+ };
34
+ if (pageNumbers?.enabled) {
35
+ pdfOptions.displayHeaderFooter = true;
36
+ pdfOptions.headerTemplate = "<span></span>";
37
+ pdfOptions.footerTemplate = pageNumbers.template ?? '<div style="font-size:10px;text-align:center;width:100%;"><span class="pageNumber"></span> / <span class="totalPages"></span></div>';
38
+ margin.bottom = "40px";
39
+ }
40
+ await page.pdf(pdfOptions);
41
+ } finally {
42
+ await browser.close().catch(() => {
43
+ });
44
+ await server.close().catch(() => {
45
+ });
46
+ }
47
+ return { outputPath };
48
+ }
49
+
50
+ // src/server.ts
51
+ async function startPreview(options) {
52
+ const { root, port = 3e3, pages = "/pages/**/*.tsx", open = false } = options;
53
+ const { createServer } = await import("vite");
54
+ const react = (await import("@vitejs/plugin-react")).default;
55
+ const tailwindcss = (await import("@tailwindcss/vite")).default;
56
+ const { pdfSmithPreviewPlugin } = await import("./preview-plugin-QWJEXDW2.js");
57
+ const currentDir = path2.dirname(fileURLToPath(import.meta.url));
58
+ const pkgRoot = path2.resolve(currentDir, "..");
59
+ const pkgSrcDir = path2.resolve(pkgRoot, "src");
60
+ const server = await createServer({
61
+ configFile: false,
62
+ appType: "custom",
63
+ root,
64
+ server: {
65
+ port,
66
+ strictPort: port !== 0,
67
+ open,
68
+ fs: {
69
+ allow: [root, pkgSrcDir, pkgRoot]
70
+ }
71
+ },
72
+ resolve: {
73
+ alias: {
74
+ "pdf-smith": path2.resolve(pkgSrcDir, "index.ts")
75
+ },
76
+ dedupe: ["react", "react-dom"]
77
+ },
78
+ plugins: [react(), tailwindcss(), pdfSmithPreviewPlugin({ pkgSrcDir, pagesGlob: pages })],
79
+ optimizeDeps: {
80
+ include: ["react", "react-dom", "react-dom/client", "react/jsx-runtime"]
81
+ }
82
+ });
83
+ await server.listen();
84
+ if (port !== 0) {
85
+ server.printUrls();
86
+ }
87
+ const address = server.httpServer?.address();
88
+ const resolvedPort = address?.port ?? port;
89
+ const url = `http://localhost:${resolvedPort}`;
90
+ return {
91
+ close: async () => {
92
+ const httpServer = server.httpServer;
93
+ httpServer?.closeAllConnections?.();
94
+ await server.close();
95
+ },
96
+ port: resolvedPort,
97
+ url
98
+ };
99
+ }
100
+ export {
101
+ exportPDF,
102
+ startPreview
103
+ };
104
+ //# sourceMappingURL=server.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/server.ts","../src/export.ts"],"sourcesContent":["import type { AddressInfo } from 'node:net';\nimport path from 'node:path';\nimport { fileURLToPath } from 'node:url';\nimport type { PreviewOptions, PreviewServer } from './preview/types';\n\nexport { exportPDF } from './export';\nexport type { ExportOptions, ExportResult } from './export-types';\nexport type { PreviewOptions, PreviewServer } from './preview/types';\n\nexport async function startPreview(options: PreviewOptions): Promise<PreviewServer> {\n const { root, port = 3000, pages = '/pages/**/*.tsx', open = false } = options;\n\n const { createServer } = await import('vite');\n const react = (await import('@vitejs/plugin-react')).default;\n const tailwindcss = (await import('@tailwindcss/vite')).default;\n const { pdfSmithPreviewPlugin } = await import('./preview/preview-plugin');\n\n const currentDir = path.dirname(fileURLToPath(import.meta.url));\n const pkgRoot = path.resolve(currentDir, '..');\n const pkgSrcDir = path.resolve(pkgRoot, 'src');\n\n const server = await createServer({\n configFile: false,\n appType: 'custom',\n root,\n server: {\n port,\n strictPort: port !== 0,\n open,\n fs: {\n allow: [root, pkgSrcDir, pkgRoot],\n },\n },\n resolve: {\n alias: {\n 'pdf-smith': path.resolve(pkgSrcDir, 'index.ts'),\n },\n dedupe: ['react', 'react-dom'],\n },\n plugins: [react(), tailwindcss(), pdfSmithPreviewPlugin({ pkgSrcDir, pagesGlob: pages })],\n optimizeDeps: {\n include: ['react', 'react-dom', 'react-dom/client', 'react/jsx-runtime'],\n },\n });\n\n await server.listen();\n\n if (port !== 0) {\n server.printUrls();\n }\n\n const address = server.httpServer?.address() as AddressInfo | null;\n const resolvedPort = address?.port ?? port;\n const url = `http://localhost:${resolvedPort}`;\n\n return {\n close: async () => {\n const httpServer = server.httpServer as { closeAllConnections?: () => void } | null;\n httpServer?.closeAllConnections?.();\n await server.close();\n },\n port: resolvedPort,\n url,\n };\n}\n","import fs from 'node:fs';\nimport path from 'node:path';\nimport type { ExportOptions, ExportResult } from './export-types';\n\nexport async function exportPDF(options: ExportOptions): Promise<ExportResult> {\n const { root, output, pageNumbers, pages } = options;\n const outputPath = path.resolve(root, output ?? './output.pdf');\n\n fs.mkdirSync(path.dirname(outputPath), { recursive: true });\n\n let playwright: typeof import('playwright');\n try {\n playwright = await import('playwright');\n } catch {\n throw new Error(\n 'Playwright is required for PDF export. Install it with: npm install -D playwright && npx playwright install chromium',\n );\n }\n\n const { startPreview } = await import('./server');\n const server = await startPreview({ root, port: 0, pages, open: false });\n\n const browser = await playwright.chromium.launch({ headless: true });\n try {\n const page = await browser.newPage();\n await page.goto(server.url, { 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\n const pdfOptions: Parameters<typeof page.pdf>[0] = {\n path: outputPath,\n preferCSSPageSize: true,\n printBackground: true,\n margin,\n };\n\n if (pageNumbers?.enabled) {\n pdfOptions.displayHeaderFooter = true;\n pdfOptions.headerTemplate = '<span></span>';\n pdfOptions.footerTemplate =\n 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 await page.pdf(pdfOptions);\n } finally {\n await browser.close().catch(() => {});\n await server.close().catch(() => {});\n }\n\n return { outputPath };\n}\n"],"mappings":"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","names":["path","startPreview","path"]}
package/package.json ADDED
@@ -0,0 +1,91 @@
1
+ {
2
+ "name": "pdf-smith",
3
+ "version": "0.1.0",
4
+ "description": "Build beautiful PDFs with React components and Tailwind CSS",
5
+ "author": "Kareem Elbahrawy",
6
+ "license": "MIT",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/kareemaly/pdf-smith.git",
10
+ "directory": "packages/pdf-smith"
11
+ },
12
+ "homepage": "https://github.com/kareemaly/pdf-smith",
13
+ "keywords": [
14
+ "pdf",
15
+ "react",
16
+ "tailwind",
17
+ "vite",
18
+ "playwright",
19
+ "pdf-generation",
20
+ "react-pdf"
21
+ ],
22
+ "type": "module",
23
+ "main": "./dist/index.cjs",
24
+ "module": "./dist/index.js",
25
+ "types": "./dist/index.d.ts",
26
+ "exports": {
27
+ ".": {
28
+ "import": {
29
+ "types": "./dist/index.d.ts",
30
+ "default": "./dist/index.js"
31
+ },
32
+ "require": {
33
+ "types": "./dist/index.d.cts",
34
+ "default": "./dist/index.cjs"
35
+ }
36
+ },
37
+ "./server": {
38
+ "import": {
39
+ "types": "./dist/server.d.ts",
40
+ "default": "./dist/server.js"
41
+ },
42
+ "require": {
43
+ "types": "./dist/server.d.cts",
44
+ "default": "./dist/server.cjs"
45
+ }
46
+ }
47
+ },
48
+ "files": [
49
+ "dist",
50
+ "src/preview/",
51
+ "src/index.ts",
52
+ "src/page.tsx",
53
+ "src/document.tsx",
54
+ "src/types.ts",
55
+ "src/constants.ts",
56
+ "src/styles.ts"
57
+ ],
58
+ "dependencies": {
59
+ "vite": "^7.0.0",
60
+ "@vitejs/plugin-react": "^5.0.0",
61
+ "@tailwindcss/vite": "^4.0.0"
62
+ },
63
+ "devDependencies": {
64
+ "@testing-library/react": "^16.3.2",
65
+ "@types/react": "^19.0.0",
66
+ "@types/react-dom": "^19.2.3",
67
+ "react": "^19.0.0",
68
+ "react-dom": "^19.2.4",
69
+ "tsup": "^8.5.1"
70
+ },
71
+ "peerDependencies": {
72
+ "react": ">=18",
73
+ "react-dom": ">=18",
74
+ "playwright": ">=1.40.0"
75
+ },
76
+ "peerDependenciesMeta": {
77
+ "react": {
78
+ "optional": true
79
+ },
80
+ "react-dom": {
81
+ "optional": true
82
+ },
83
+ "playwright": {
84
+ "optional": true
85
+ }
86
+ },
87
+ "scripts": {
88
+ "build": "tsup",
89
+ "typecheck": "tsc --noEmit"
90
+ }
91
+ }
@@ -0,0 +1,11 @@
1
+ import type { PageDimensions, PageSizePreset } from './types';
2
+
3
+ export const PAGE_SIZES: Record<PageSizePreset, PageDimensions> = {
4
+ A4: { width: 210, height: 297 },
5
+ Letter: { width: 215.9, height: 279.4 },
6
+ A3: { width: 297, height: 420 },
7
+ };
8
+
9
+ export const DEFAULT_PADDING = 0;
10
+
11
+ export const DEFAULT_PAGE_SIZE: PageSizePreset = 'A4';
@@ -0,0 +1,15 @@
1
+ import { PAGE_SIZES } from './constants';
2
+ import { getDocumentStyleSheet } from './styles';
3
+ import type { DocumentProps } from './types';
4
+
5
+ export function Document({ children, className }: DocumentProps): React.ReactElement {
6
+ const styleSheet = getDocumentStyleSheet(PAGE_SIZES.A4);
7
+
8
+ return (
9
+ <div data-pdf-smith-document="" className={className}>
10
+ {/* biome-ignore lint/security/noDangerouslySetInnerHtml: trusted internal CSS */}
11
+ <style dangerouslySetInnerHTML={{ __html: styleSheet }} />
12
+ {children}
13
+ </div>
14
+ );
15
+ }
package/src/index.ts ADDED
@@ -0,0 +1,11 @@
1
+ export { DEFAULT_PADDING, DEFAULT_PAGE_SIZE, PAGE_SIZES } from './constants';
2
+ export { Document } from './document';
3
+ export { Page } from './page';
4
+ export type {
5
+ DocumentProps,
6
+ PageDimensions,
7
+ PagePadding,
8
+ PageProps,
9
+ PageSize,
10
+ PageSizePreset,
11
+ } from './types';
package/src/page.tsx ADDED
@@ -0,0 +1,35 @@
1
+ import { DEFAULT_PADDING, DEFAULT_PAGE_SIZE, PAGE_SIZES } from './constants';
2
+ import { getPageStyle, resolvePadding } from './styles';
3
+ import type { PageDimensions, PageProps, PageSize } from './types';
4
+
5
+ function resolveSize(size: PageSize): PageDimensions {
6
+ if (typeof size === 'string') {
7
+ return PAGE_SIZES[size];
8
+ }
9
+ return size;
10
+ }
11
+
12
+ export function Page({
13
+ children,
14
+ className,
15
+ size = DEFAULT_PAGE_SIZE,
16
+ padding = DEFAULT_PADDING,
17
+ id,
18
+ }: PageProps): React.ReactElement {
19
+ const dimensions = resolveSize(size);
20
+ const resolvedPadding = resolvePadding(padding);
21
+ const style = getPageStyle(dimensions, resolvedPadding);
22
+
23
+ return (
24
+ <div
25
+ data-pdf-smith-page=""
26
+ data-page-width={dimensions.width}
27
+ data-page-height={dimensions.height}
28
+ className={className}
29
+ id={id}
30
+ style={style}
31
+ >
32
+ {children}
33
+ </div>
34
+ );
35
+ }
@@ -0,0 +1,52 @@
1
+ import { getPages } from 'virtual:pdf-smith-pages';
2
+ import { useState } from 'react';
3
+ import { Document } from '../document';
4
+ import { Page } from '../page';
5
+ import { Navigation } from './navigation';
6
+
7
+ const containerStyle: React.CSSProperties = {
8
+ marginLeft: '240px',
9
+ minHeight: '100vh',
10
+ background: '#e8e8e8',
11
+ padding: '32px',
12
+ boxSizing: 'border-box',
13
+ };
14
+
15
+ const pageWrapperStyle: React.CSSProperties = {
16
+ marginBottom: '24px',
17
+ };
18
+
19
+ const pageLabelStyle: React.CSSProperties = {
20
+ fontFamily: 'system-ui, -apple-system, sans-serif',
21
+ fontSize: '12px',
22
+ color: '#666',
23
+ marginBottom: '8px',
24
+ marginLeft: 'auto',
25
+ marginRight: 'auto',
26
+ width: 'fit-content',
27
+ };
28
+
29
+ export function PreviewApp() {
30
+ const pages = getPages();
31
+ const [activePage, setActivePage] = useState<string | null>(null);
32
+
33
+ const visiblePages = activePage ? { [activePage]: pages[activePage] } : pages;
34
+
35
+ return (
36
+ <>
37
+ <Navigation pages={pages} activePage={activePage} onSelectPage={setActivePage} />
38
+ <div data-pdf-smith-container="" style={containerStyle}>
39
+ <Document>
40
+ {Object.entries(visiblePages).map(([name, PageComponent]) => (
41
+ <div key={name} style={pageWrapperStyle}>
42
+ <div style={pageLabelStyle}>{name}</div>
43
+ <Page>
44
+ <PageComponent />
45
+ </Page>
46
+ </div>
47
+ ))}
48
+ </Document>
49
+ </div>
50
+ </>
51
+ );
52
+ }