pdfn 0.1.0 → 0.2.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/README.md CHANGED
@@ -56,7 +56,8 @@ npx pdfn serve --max-concurrent 10 # Concurrency limit
56
56
  Add starter templates to your project.
57
57
 
58
58
  ```bash
59
- npx pdfn add invoice # Add invoice template
59
+ npx pdfn add invoice # Add invoice template (inline styles)
60
+ npx pdfn add invoice --tailwind # Add with Tailwind classes
60
61
  npx pdfn add letter # Add business letter
61
62
  npx pdfn add contract # Add contract template
62
63
  npx pdfn add ticket # Add event ticket
@@ -65,6 +66,13 @@ npx pdfn add --list # Show all templates
65
66
  npx pdfn add invoice --output ./src/templates
66
67
  ```
67
68
 
69
+ | Option | Default | Description |
70
+ |--------|---------|-------------|
71
+ | `--inline` | ✓ | Use inline styles (default, no extra dependencies) |
72
+ | `--tailwind` | | Use Tailwind CSS classes (requires `@pdfn/tailwind`) |
73
+ | `--output` | `./pdf-templates` | Output directory |
74
+ | `--force` | | Overwrite existing files |
75
+
68
76
  | Template | Description | Page Size |
69
77
  |----------|-------------|-----------|
70
78
  | `invoice` | Professional invoice with itemized billing | A4 |
package/dist/cli.js CHANGED
@@ -5,8 +5,8 @@ import { Command as Command4 } from "commander";
5
5
 
6
6
  // src/commands/dev.ts
7
7
  import { Command } from "commander";
8
- import { existsSync, readdirSync, readFileSync } from "fs";
9
- import { join, resolve } from "path";
8
+ import { existsSync as existsSync2, readdirSync, readFileSync } from "fs";
9
+ import { join, resolve as resolve2 } from "path";
10
10
  import { createServer as createViteServer } from "vite";
11
11
  import { WebSocketServer, WebSocket } from "ws";
12
12
  import chokidar from "chokidar";
@@ -411,9 +411,32 @@ function createBaseServer(options = {}) {
411
411
 
412
412
  // src/commands/dev.ts
413
413
  import { injectDebugSupport } from "@pdfn/react/debug";
414
+ import { pdfnTailwind } from "@pdfn/vite";
414
415
  import chalk from "chalk";
416
+
417
+ // src/utils/env.ts
418
+ import { existsSync } from "fs";
419
+ import { resolve } from "path";
420
+ import { config } from "dotenv";
421
+ function loadEnv(mode) {
422
+ const cwd = process.cwd();
423
+ const envFiles = [
424
+ ".env",
425
+ ".env.local",
426
+ `.env.${mode}`,
427
+ `.env.${mode}.local`
428
+ ];
429
+ for (const file of envFiles) {
430
+ const filePath = resolve(cwd, file);
431
+ if (existsSync(filePath)) {
432
+ config({ path: filePath, override: true, quiet: true });
433
+ }
434
+ }
435
+ }
436
+
437
+ // src/commands/dev.ts
415
438
  async function scanTemplates(templatesDir) {
416
- if (!existsSync(templatesDir)) {
439
+ if (!existsSync2(templatesDir)) {
417
440
  return [];
418
441
  }
419
442
  let configData = {};
@@ -423,11 +446,11 @@ async function scanTemplates(templatesDir) {
423
446
  join(templatesDir, "templates.json")
424
447
  ];
425
448
  for (const configPath of configPaths) {
426
- if (existsSync(configPath)) {
449
+ if (existsSync2(configPath)) {
427
450
  try {
428
- const config = JSON.parse(readFileSync(configPath, "utf-8"));
429
- if (config.templates && Array.isArray(config.templates)) {
430
- for (const t of config.templates) {
451
+ const config2 = JSON.parse(readFileSync(configPath, "utf-8"));
452
+ if (config2.templates && Array.isArray(config2.templates)) {
453
+ for (const t of config2.templates) {
431
454
  if (t.id && t.sampleData) {
432
455
  configData[t.id] = t.sampleData;
433
456
  }
@@ -1440,8 +1463,8 @@ function createPreviewHTML(templates, activeTemplate) {
1440
1463
  </html>`;
1441
1464
  }
1442
1465
  async function startDevServer(options) {
1443
- const { port, templatesDir, open } = options;
1444
- const absoluteTemplatesDir = resolve(process.cwd(), templatesDir);
1466
+ const { port, templatesDir, open, mode } = options;
1467
+ const absoluteTemplatesDir = resolve2(process.cwd(), templatesDir);
1445
1468
  console.log(chalk.bold("\n pdfn dev\n"));
1446
1469
  console.log(chalk.dim(" Initializing..."));
1447
1470
  let templates = await scanTemplates(absoluteTemplatesDir);
@@ -1473,6 +1496,12 @@ async function startDevServer(options) {
1473
1496
  clients.add(ws);
1474
1497
  ws.on("close", () => clients.delete(ws));
1475
1498
  });
1499
+ wss.on("error", (err) => {
1500
+ if (err.code === "EADDRINUSE") {
1501
+ return;
1502
+ }
1503
+ console.error(chalk.red(" \u2717 WebSocket error:"), err.message);
1504
+ });
1476
1505
  function broadcast(message) {
1477
1506
  const data = JSON.stringify(message);
1478
1507
  clients.forEach((client) => {
@@ -1496,7 +1525,12 @@ async function startDevServer(options) {
1496
1525
  };
1497
1526
  const vite = await createViteServer({
1498
1527
  root: process.cwd(),
1499
- server: { middlewareMode: true },
1528
+ mode,
1529
+ server: {
1530
+ middlewareMode: true,
1531
+ hmr: { server }
1532
+ // Use our HTTP server for Vite's WebSocket
1533
+ },
1500
1534
  appType: "custom",
1501
1535
  customLogger: viteLogger,
1502
1536
  optimizeDeps: {
@@ -1510,6 +1544,12 @@ async function startDevServer(options) {
1510
1544
  noExternal: ["@pdfn/react", "@pdfn/tailwind", "server-only"]
1511
1545
  },
1512
1546
  plugins: [
1547
+ // Pre-compile Tailwind CSS for edge compatibility
1548
+ pdfnTailwind({
1549
+ templates: [
1550
+ join(templatesDir, "**/*.tsx")
1551
+ ]
1552
+ }),
1513
1553
  {
1514
1554
  name: "mock-server-only",
1515
1555
  enforce: "pre",
@@ -1549,6 +1589,17 @@ async function startDevServer(options) {
1549
1589
  const fileName = filePath.split("/").pop() || filePath;
1550
1590
  if (isCodeFile(filePath)) {
1551
1591
  console.log(chalk.blue(" \u21BB"), fileName, chalk.dim("changed"));
1592
+ const mod = vite.moduleGraph.getModuleById(filePath);
1593
+ if (mod) {
1594
+ vite.moduleGraph.invalidateModule(mod);
1595
+ }
1596
+ const virtualMod = vite.moduleGraph.getModuleById("\0virtual:pdfn-tailwind-css");
1597
+ if (virtualMod) {
1598
+ vite.moduleGraph.invalidateModule(virtualMod);
1599
+ for (const importer of virtualMod.importers) {
1600
+ vite.moduleGraph.invalidateModule(importer);
1601
+ }
1602
+ }
1552
1603
  }
1553
1604
  templates = await scanTemplates(absoluteTemplatesDir);
1554
1605
  broadcast({ type: "reload" });
@@ -1738,8 +1789,20 @@ async function startDevServer(options) {
1738
1789
  res.status(500).json({ error: `Error generating PDF: ${error}` });
1739
1790
  }
1740
1791
  });
1741
- await new Promise((resolve2) => {
1742
- server.listen(port, () => resolve2());
1792
+ await new Promise((resolve3, reject) => {
1793
+ server.on("error", (err) => {
1794
+ if (err.code === "EADDRINUSE") {
1795
+ console.error(chalk.red(`
1796
+ \u2717 Port ${port} is already in use.
1797
+ `));
1798
+ console.error(chalk.dim(` Either stop the existing server or use a different port:`));
1799
+ console.error(chalk.dim(` npx pdfn dev --port ${port + 1}
1800
+ `));
1801
+ process.exit(1);
1802
+ }
1803
+ reject(err);
1804
+ });
1805
+ server.listen(port, () => resolve3());
1743
1806
  });
1744
1807
  await browserManager.getBrowser();
1745
1808
  console.log(chalk.dim(` Templates: ${displayPath} (${templateCount})`));
@@ -1762,12 +1825,14 @@ async function startDevServer(options) {
1762
1825
  process.on("SIGTERM", shutdown);
1763
1826
  process.on("SIGINT", shutdown);
1764
1827
  }
1765
- var devCommand = new Command("dev").description("Start development server with live preview").option("--port <number>", "Server port (env: PDFN_PORT)", process.env.PDFN_PORT ?? "3456").option("--templates <path>", "Templates directory", "./pdf-templates").option("--open", "Open browser automatically").action(async (options) => {
1828
+ var devCommand = new Command("dev").description("Start development server with live preview").option("--port <number>", "Server port (env: PDFN_PORT)", process.env.PDFN_PORT ?? "3456").option("--templates <path>", "Templates directory", "./pdf-templates").option("--open", "Open browser automatically").option("--mode <mode>", "Environment mode (loads .env.[mode])", "development").action(async (options) => {
1829
+ loadEnv(options.mode);
1766
1830
  const port = parseInt(options.port, 10);
1767
1831
  await startDevServer({
1768
1832
  port,
1769
1833
  templatesDir: options.templates,
1770
- open: options.open ?? false
1834
+ open: options.open ?? false,
1835
+ mode: options.mode
1771
1836
  });
1772
1837
  });
1773
1838
 
@@ -1978,10 +2043,18 @@ For development with live preview, use:
1978
2043
  logger.browser("launching");
1979
2044
  await browserManager.getBrowser();
1980
2045
  logger.browser("ready");
1981
- return new Promise((resolve2) => {
2046
+ return new Promise((resolve3, reject) => {
1982
2047
  server = app.listen(port, () => {
1983
2048
  logger.banner(port, maxConcurrent, timeout);
1984
- resolve2();
2049
+ resolve3();
2050
+ });
2051
+ server.on("error", (err) => {
2052
+ if (err.code === "EADDRINUSE") {
2053
+ logger.error(`Port ${port} is already in use`);
2054
+ logger.info(`Either stop the existing server or use a different port`);
2055
+ process.exit(1);
2056
+ }
2057
+ reject(err);
1985
2058
  });
1986
2059
  });
1987
2060
  },
@@ -1989,8 +2062,8 @@ For development with live preview, use:
1989
2062
  await browserManager.close();
1990
2063
  logger.browser("closed");
1991
2064
  if (server) {
1992
- await new Promise((resolve2) => {
1993
- server.close(() => resolve2());
2065
+ await new Promise((resolve3) => {
2066
+ server.close(() => resolve3());
1994
2067
  });
1995
2068
  server = null;
1996
2069
  }
@@ -2000,7 +2073,8 @@ For development with live preview, use:
2000
2073
  }
2001
2074
 
2002
2075
  // src/commands/serve.ts
2003
- var serveCommand = new Command2("serve").description("Start production server (headless, no UI)").option("--port <number>", "Server port (env: PDFN_PORT)", "3456").option("--max-concurrent <number>", "Max concurrent pages (env: PDFN_MAX_CONCURRENT)", "5").option("--timeout <ms>", "Request timeout in ms (env: PDFN_TIMEOUT)", "30000").action(async (options) => {
2076
+ var serveCommand = new Command2("serve").description("Start production server (headless, no UI)").option("--port <number>", "Server port (env: PDFN_PORT)", "3456").option("--max-concurrent <number>", "Max concurrent pages (env: PDFN_MAX_CONCURRENT)", "5").option("--timeout <ms>", "Request timeout in ms (env: PDFN_TIMEOUT)", "30000").option("--mode <mode>", "Environment mode (loads .env.[mode])", "production").action(async (options) => {
2077
+ loadEnv(options.mode);
2004
2078
  const port = parseInt(options.port, 10);
2005
2079
  const maxConcurrent = parseInt(options.maxConcurrent, 10);
2006
2080
  const timeout = parseInt(options.timeout, 10);
@@ -2017,7 +2091,7 @@ var serveCommand = new Command2("serve").description("Start production server (h
2017
2091
 
2018
2092
  // src/commands/add.ts
2019
2093
  import { Command as Command3 } from "commander";
2020
- import { existsSync as existsSync2, mkdirSync, copyFileSync } from "fs";
2094
+ import { existsSync as existsSync3, mkdirSync, copyFileSync } from "fs";
2021
2095
  import { join as join2, dirname } from "path";
2022
2096
  import { fileURLToPath } from "url";
2023
2097
  import chalk2 from "chalk";
@@ -2049,17 +2123,30 @@ var TEMPLATES = {
2049
2123
  pageSize: "Tabloid"
2050
2124
  }
2051
2125
  };
2052
- function getTemplatesDir() {
2053
- return join2(__dirname, "..", "templates");
2126
+ function getTemplatesDir(style) {
2127
+ return join2(__dirname, "..", "templates", style);
2128
+ }
2129
+ function isTailwindInstalled(cwd) {
2130
+ try {
2131
+ const tailwindPath = join2(cwd, "node_modules", "@pdfn", "tailwind");
2132
+ return existsSync3(tailwindPath);
2133
+ } catch {
2134
+ return false;
2135
+ }
2054
2136
  }
2055
- var addCommand = new Command3("add").description("Add a starter template to your project").argument("[template]", "Template name (e.g., invoice, letter, contract)").option("--list", "List available templates").option("--output <path>", "Output directory", "./pdf-templates").option("--force", "Overwrite existing files").action(async (template, options) => {
2137
+ var addCommand = new Command3("add").description("Add a starter template to your project").argument("[template]", "Template name (e.g., invoice, letter, contract)").option("--list", "List available templates").option("--tailwind", "Use Tailwind CSS styling (requires @pdfn/tailwind)").option("--inline", "Use inline styles (default)").option("--output <path>", "Output directory", "./pdf-templates").option("--force", "Overwrite existing files").action(async (template, options) => {
2138
+ const cwd = process.cwd();
2056
2139
  if (options.list || !template) {
2057
2140
  console.log(chalk2.bold("\nAvailable templates:\n"));
2058
2141
  for (const [id, info] of Object.entries(TEMPLATES)) {
2059
2142
  console.log(` ${chalk2.cyan(id.padEnd(12))} ${info.description} ${chalk2.dim(`(${info.pageSize})`)}`);
2060
2143
  }
2061
- console.log(chalk2.dim("\nUsage: pdfn add <template>"));
2062
- console.log(chalk2.dim("Example: pdfn add invoice\n"));
2144
+ console.log(chalk2.dim("\nUsage: pdfn add <template> [--tailwind]"));
2145
+ console.log(chalk2.dim("Example: pdfn add invoice"));
2146
+ console.log(chalk2.dim("Example: pdfn add invoice --tailwind\n"));
2147
+ console.log(chalk2.bold("Options:"));
2148
+ console.log(chalk2.dim(" --inline Use inline styles (default)"));
2149
+ console.log(chalk2.dim(" --tailwind Use Tailwind CSS (requires @pdfn/tailwind)\n"));
2063
2150
  return;
2064
2151
  }
2065
2152
  if (!TEMPLATES[template]) {
@@ -2068,21 +2155,32 @@ Error: Unknown template "${template}"`));
2068
2155
  console.log(chalk2.dim("Run 'pdfn add --list' to see available templates\n"));
2069
2156
  process.exit(1);
2070
2157
  }
2071
- const templatesDir = getTemplatesDir();
2158
+ const style = options.tailwind ? "tailwind" : "inline";
2159
+ if (style === "tailwind" && !isTailwindInstalled(cwd)) {
2160
+ console.error(chalk2.yellow(`
2161
+ \u26A0 @pdfn/tailwind is not installed.`));
2162
+ console.log(chalk2.dim("Install it first to use Tailwind templates:\n"));
2163
+ console.log(chalk2.cyan(" npm install @pdfn/tailwind\n"));
2164
+ console.log(chalk2.dim("Or use inline styles (default):\n"));
2165
+ console.log(chalk2.cyan(` pdfn add ${template}
2166
+ `));
2167
+ process.exit(1);
2168
+ }
2169
+ const templatesDir = getTemplatesDir(style);
2072
2170
  const sourceFile = join2(templatesDir, `${template}.tsx`);
2073
2171
  const outputDir = options.output;
2074
2172
  const outputFile = join2(outputDir, `${template}.tsx`);
2075
- if (!existsSync2(sourceFile)) {
2173
+ if (!existsSync3(sourceFile)) {
2076
2174
  console.error(chalk2.red(`
2077
2175
  Error: Template file not found: ${sourceFile}`));
2078
2176
  console.log(chalk2.dim("This may be a package installation issue.\n"));
2079
2177
  process.exit(1);
2080
2178
  }
2081
- if (!existsSync2(outputDir)) {
2179
+ if (!existsSync3(outputDir)) {
2082
2180
  mkdirSync(outputDir, { recursive: true });
2083
2181
  console.log(chalk2.dim(`Created ${outputDir}/`));
2084
2182
  }
2085
- if (existsSync2(outputFile) && !options.force) {
2183
+ if (existsSync3(outputFile) && !options.force) {
2086
2184
  console.error(chalk2.yellow(`
2087
2185
  File already exists: ${outputFile}`));
2088
2186
  console.log(chalk2.dim("Use --force to overwrite\n"));
@@ -2091,14 +2189,18 @@ File already exists: ${outputFile}`));
2091
2189
  try {
2092
2190
  copyFileSync(sourceFile, outputFile);
2093
2191
  const info = TEMPLATES[template];
2192
+ const styleLabel = style === "tailwind" ? chalk2.cyan(" (Tailwind)") : chalk2.dim(" (inline styles)");
2094
2193
  console.log(chalk2.green(`
2095
- \u2713 Added ${info.name} template`));
2194
+ \u2713 Added ${info.name} template`) + styleLabel);
2096
2195
  console.log(chalk2.dim(` ${outputFile}
2097
2196
  `));
2098
2197
  console.log(chalk2.bold("Next steps:"));
2099
2198
  console.log(chalk2.dim(` 1. Edit ${outputFile} to customize`));
2100
2199
  console.log(chalk2.dim(` 2. Run 'npx pdfn dev' to preview
2101
2200
  `));
2201
+ if (style === "tailwind") {
2202
+ console.log(chalk2.dim("Note: Tailwind templates require @pdfn/tailwind to be installed.\n"));
2203
+ }
2102
2204
  } catch (error) {
2103
2205
  console.error(chalk2.red(`
2104
2206
  Error copying template: ${error}`));