@ahmedrowaihi/pdf-forge-cli 1.1.0 → 1.2.0-canary.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.
Files changed (3) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/dist/index.js +157 -369
  3. package/package.json +6 -9
package/CHANGELOG.md CHANGED
@@ -1,5 +1,16 @@
1
1
  # @ahmedrowaihi/pdf-forge-cli
2
2
 
3
+ ## 1.2.0-canary.0
4
+
5
+ ### Minor Changes
6
+
7
+ - 5e324d8: All In Bun and Printer Pool
8
+
9
+ ### Patch Changes
10
+
11
+ - Updated dependencies [5e324d8]
12
+ - @ahmedrowaihi/pdf-forge-toolbox@1.2.0-canary.0
13
+
3
14
  ## 1.1.0
4
15
 
5
16
  ### Minor Changes
package/dist/index.js CHANGED
@@ -1,25 +1,19 @@
1
- #!/usr/bin/env node
2
- import { createRequire } from "node:module";
1
+ #!/usr/bin/env bun
3
2
  import { program } from "commander";
4
- import fs, { existsSync, promises, statSync, unlinkSync, writeFileSync } from "node:fs";
3
+ import fs, { existsSync, promises, unlinkSync, writeFileSync } from "node:fs";
5
4
  import path from "node:path";
5
+ import { createDependencyGraph, escapeStringForRegex, getTemplatesDirectoryMetadata, registerSpinnerAutostopping, styleText } from "@ahmedrowaihi/pdf-forge-toolbox";
6
6
  import logSymbols from "log-symbols";
7
7
  import { addDevDependency, installDependencies, runScript } from "nypm";
8
8
  import ora from "ora";
9
- import url from "node:url";
10
- import { createJiti } from "jiti";
11
9
  import prompts from "prompts";
12
10
  import { watch } from "chokidar";
13
11
  import debounce from "debounce";
14
12
  import { Server } from "socket.io";
15
- import { parse } from "@babel/parser";
16
- import traverseModule from "@babel/traverse";
17
- import { createMatchPath, loadConfig } from "tsconfig-paths";
18
13
  import http from "node:http";
19
- import * as nodeUtil from "node:util";
14
+ import url from "node:url";
20
15
  import { lookup } from "mime-types";
21
16
  import os from "node:os";
22
- import { build } from "esbuild";
23
17
  import { glob } from "glob";
24
18
  import normalize from "normalize-path";
25
19
  import { spawn } from "node:child_process";
@@ -27,7 +21,7 @@ import { spawn } from "node:child_process";
27
21
  //#region package.json
28
22
  var package_default = {
29
23
  name: "@ahmedrowaihi/pdf-forge-cli",
30
- version: "1.1.0",
24
+ version: "1.2.0-canary.0",
31
25
  description: "A live preview of your PDF templates right in your browser.",
32
26
  bin: { "pdf-dev": "./dist/index.js" },
33
27
  type: "module",
@@ -43,17 +37,14 @@ var package_default = {
43
37
  "directory": "packages/react-pdf"
44
38
  },
45
39
  keywords: ["react", "pdf"],
46
- engines: { "node": ">=20.0.0" },
40
+ engines: { "bun": ">=1.0.0" },
47
41
  dependencies: {
48
- "@babel/parser": "^7.27.0",
49
- "@babel/traverse": "^7.27.0",
42
+ "@ahmedrowaihi/pdf-forge-toolbox": "workspace:*",
50
43
  "chokidar": "^4.0.3",
51
44
  "commander": "^13.0.0",
52
45
  "conf": "^15.0.2",
53
46
  "debounce": "^2.0.0",
54
- "esbuild": "^0.25.0",
55
47
  "glob": "^11.0.0",
56
- "jiti": "2.4.2",
57
48
  "log-symbols": "^7.0.0",
58
49
  "mime-types": "^3.0.0",
59
50
  "normalize-path": "^3.0.0",
@@ -66,10 +57,10 @@ var package_default = {
66
57
  devDependencies: {
67
58
  "@ahmedrowaihi/pdf-forge-components": "workspace:*",
68
59
  "@ahmedrowaihi/pdf-forge-core": "workspace:*",
69
- "@types/babel__core": "7.20.5",
70
- "@types/babel__traverse": "7.20.7",
60
+ "@types/bun": "^1.3.5",
71
61
  "@types/mime-types": "2.1.4",
72
62
  "@types/prompts": "2.4.9",
63
+ "@types/normalize-path": "3.0.2",
73
64
  "next": "16.0.10",
74
65
  "react": "19.0.0",
75
66
  "react-dom": "19.0.0",
@@ -112,104 +103,33 @@ const findWorkspacePreviewServer = () => {
112
103
  }
113
104
  return null;
114
105
  };
106
+ /**
107
+ * Finds the location of the preview server
108
+ * 1. Checks if the preview server is installed in the workspace
109
+ * 2. If not, checks if the preview server is installed in the cwd
110
+ * 3. If not, prompts the user to install it
111
+ * 4. If the user installs it, it will return the location of the preview server
112
+ * 5. If the user does not install it, it will exit the process
113
+ * @returns The location of the preview server
114
+ */
115
115
  const getPreviewServerLocation = async () => {
116
- const usersProject = createJiti(process.cwd());
117
116
  let previewServerLocation;
118
117
  const workspacePath = findWorkspacePreviewServer();
119
118
  if (workspacePath) previewServerLocation = workspacePath;
120
119
  else try {
121
- previewServerLocation = path.dirname(url.fileURLToPath(usersProject.esmResolve("@ahmedrowaihi/pdf-forge-preview")));
120
+ previewServerLocation = path.dirname(Bun.resolveSync("@ahmedrowaihi/pdf-forge-preview", process.cwd()));
122
121
  } catch {
123
122
  await ensurePreviewServerInstalled("To run the preview server, the package \"@ahmedrowaihi/pdf-forge-preview\" must be installed. Would you like to install it?");
124
123
  }
125
124
  if (!workspacePath) try {
126
- const { version: version$1 } = await usersProject.import("@ahmedrowaihi/pdf-forge-preview");
127
- if (version$1 !== package_default.version) await ensurePreviewServerInstalled(`To run the preview server, the version of "@ahmedrowaihi/pdf-forge-preview" must match the version of "@ahmedrowaihi/pdf-forge-cli" (${package_default.version}). Would you like to install it?`);
125
+ const packagePath = Bun.resolveSync("@ahmedrowaihi/pdf-forge-preview/package.json", process.cwd());
126
+ if (JSON.parse(await Bun.file(packagePath).text()).version !== package_default.version) await ensurePreviewServerInstalled(`To run the preview server, the version of "@ahmedrowaihi/pdf-forge-preview" must match the version of "@ahmedrowaihi/pdf-forge-cli" (${package_default.version}). Would you like to install it?`);
128
127
  } catch {
129
128
  await ensurePreviewServerInstalled("To run the preview server, the package \"@ahmedrowaihi/pdf-forge-preview\" must be installed. Would you like to install it?");
130
129
  }
131
130
  return previewServerLocation;
132
131
  };
133
132
 
134
- //#endregion
135
- //#region src/utils/get-templates-directory-metadata.ts
136
- const isFileATemplate = async (fullPath) => {
137
- let fileHandle;
138
- try {
139
- fileHandle = await fs.promises.open(fullPath, "r");
140
- } catch (exception) {
141
- console.warn(exception);
142
- return false;
143
- }
144
- if ((await fileHandle.stat()).isDirectory()) {
145
- await fileHandle.close();
146
- return false;
147
- }
148
- const { ext } = path.parse(fullPath);
149
- if (![
150
- ".js",
151
- ".tsx",
152
- ".jsx"
153
- ].includes(ext)) {
154
- await fileHandle.close();
155
- return false;
156
- }
157
- const fileContents = await fileHandle.readFile("utf8");
158
- await fileHandle.close();
159
- const hasES6DefaultExport = /\bexport\s+default\b/gm.test(fileContents);
160
- const hasCommonJSExport = /\bmodule\.exports\s*=/gm.test(fileContents);
161
- const hasNamedExport = /\bexport\s+\{[^}]*\bdefault\b[^}]*\}/gm.test(fileContents);
162
- return hasES6DefaultExport || hasCommonJSExport || hasNamedExport;
163
- };
164
- const mergeDirectoriesWithSubDirectories = (templatesDirectoryMetadata) => {
165
- let currentResultingMergedDirectory = templatesDirectoryMetadata;
166
- while (currentResultingMergedDirectory.templateFilenames.length === 0 && currentResultingMergedDirectory.subDirectories.length === 1) {
167
- const onlySubDirectory = currentResultingMergedDirectory.subDirectories[0];
168
- currentResultingMergedDirectory = {
169
- ...onlySubDirectory,
170
- directoryName: path.join(currentResultingMergedDirectory.directoryName, onlySubDirectory.directoryName)
171
- };
172
- }
173
- return currentResultingMergedDirectory;
174
- };
175
- const getTemplatesDirectoryMetadata = async (absolutePathToTemplatesDirectory, keepFileExtensions = false, isSubDirectory = false, baseDirectoryPath = absolutePathToTemplatesDirectory) => {
176
- if (!fs.existsSync(absolutePathToTemplatesDirectory)) return;
177
- const dirents = await fs.promises.readdir(absolutePathToTemplatesDirectory, { withFileTypes: true });
178
- const isTemplatePredicates = await Promise.all(dirents.map((dirent) => isFileATemplate(path.join(absolutePathToTemplatesDirectory, dirent.name))));
179
- const templateFilenames = dirents.filter((_, i) => isTemplatePredicates[i]).map((dirent) => keepFileExtensions ? dirent.name : dirent.name.replace(path.extname(dirent.name), ""));
180
- const subDirectories = await Promise.all(dirents.filter((dirent) => dirent.isDirectory() && !dirent.name.startsWith("_") && dirent.name !== "static").map((dirent) => {
181
- return getTemplatesDirectoryMetadata(path.join(absolutePathToTemplatesDirectory, dirent.name), keepFileExtensions, true, baseDirectoryPath);
182
- }));
183
- const templatesMetadata = {
184
- absolutePath: absolutePathToTemplatesDirectory,
185
- relativePath: path.relative(baseDirectoryPath, absolutePathToTemplatesDirectory),
186
- directoryName: absolutePathToTemplatesDirectory.split(path.sep).pop(),
187
- templateFilenames,
188
- subDirectories
189
- };
190
- return isSubDirectory ? mergeDirectoriesWithSubDirectories(templatesMetadata) : templatesMetadata;
191
- };
192
-
193
- //#endregion
194
- //#region src/utils/register-spinner-autostopping.ts
195
- const spinners = /* @__PURE__ */ new Set();
196
- process.on("SIGINT", () => {
197
- spinners.forEach((spinner) => {
198
- if (spinner.isSpinning) spinner.stop();
199
- });
200
- });
201
- process.on("exit", (code) => {
202
- if (code !== 0) spinners.forEach((spinner) => {
203
- if (spinner.isSpinning) spinner.stopAndPersist({
204
- symbol: logSymbols.error,
205
- text: "Process exited with error"
206
- });
207
- });
208
- });
209
- const registerSpinnerAutostopping = (spinner) => {
210
- spinners.add(spinner);
211
- };
212
-
213
133
  //#endregion
214
134
  //#region src/commands/build.ts
215
135
  const setNextEnvironmentVariablesForBuild = async (templatesDirRelativePath, builtPreviewAppPath) => {
@@ -228,7 +148,7 @@ const nextConfig = {
228
148
  USER_PROJECT_LOCATION: userProjectLocation
229
149
  },
230
150
  outputFileTracingRoot: previewServerLocation,
231
- serverExternalPackages: ['esbuild'],
151
+ serverExternalPackages: ["playwright", "playwright-core", "sharp"],
232
152
  typescript: {
233
153
  ignoreBuildErrors: true
234
154
  },
@@ -263,6 +183,37 @@ export function generateStaticParams() {
263
183
  );
264
184
  }`, "utf8");
265
185
  };
186
+ const findWorkspaceRoot = (startPath) => {
187
+ let currentPath = startPath;
188
+ while (currentPath !== path.dirname(currentPath)) {
189
+ const pnpmWorkspace = path.join(currentPath, "pnpm-workspace.yaml");
190
+ if (fs.existsSync(pnpmWorkspace)) return currentPath;
191
+ currentPath = path.dirname(currentPath);
192
+ }
193
+ return null;
194
+ };
195
+ const findWorkspacePackage = (packageName, workspaceRoot) => {
196
+ const searchDirs = [path.join(workspaceRoot, "packages"), path.join(workspaceRoot, "apps")];
197
+ for (const baseDir of searchDirs) {
198
+ if (!fs.existsSync(baseDir)) continue;
199
+ const packageDirs = fs.readdirSync(baseDir, { withFileTypes: true });
200
+ for (const dirent of packageDirs) if (dirent.isDirectory()) {
201
+ const packagePath = path.join(baseDir, dirent.name);
202
+ const packageJsonPath = path.join(packagePath, "package.json");
203
+ if (fs.existsSync(packageJsonPath)) try {
204
+ const pkg = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
205
+ if (pkg.name === packageName) {
206
+ if (pkg.main?.includes("dist")) {
207
+ const distPath = path.join(packagePath, "dist");
208
+ if (!fs.existsSync(distPath)) console.warn(`Warning: Package ${packageName} has not been built (dist/ missing). It may need to be built first.`);
209
+ }
210
+ return packagePath;
211
+ }
212
+ } catch {}
213
+ }
214
+ }
215
+ return null;
216
+ };
266
217
  const updatePackageJson = async (builtPreviewAppPath) => {
267
218
  const packageJsonPath = path.resolve(builtPreviewAppPath, "./package.json");
268
219
  const packageJson = JSON.parse(await fs.promises.readFile(packageJsonPath, "utf8"));
@@ -270,13 +221,38 @@ const updatePackageJson = async (builtPreviewAppPath) => {
270
221
  packageJson.scripts.start = "next start";
271
222
  delete packageJson.scripts.postbuild;
272
223
  packageJson.name = "preview-server";
273
- for (const [dependency, version$1] of Object.entries(packageJson.devDependencies)) packageJson.devDependencies[dependency] = version$1.replace("workspace:", "");
224
+ const workspaceRoot = findWorkspaceRoot(process.cwd());
225
+ if (packageJson.dependencies && workspaceRoot) {
226
+ for (const [dependency, version$1] of Object.entries(packageJson.dependencies)) if (typeof version$1 === "string" && version$1.startsWith("workspace:")) {
227
+ const packagePath = findWorkspacePackage(dependency, workspaceRoot);
228
+ if (packagePath) {
229
+ const packageJsonPath$1 = path.join(packagePath, "package.json");
230
+ try {
231
+ if (JSON.parse(fs.readFileSync(packageJsonPath$1, "utf8")).main?.includes("dist")) {
232
+ const distPath = path.join(packagePath, "dist");
233
+ if (!fs.existsSync(distPath)) throw new Error(`Package ${dependency} has not been built. Please run 'pnpm build:packages' first.`);
234
+ }
235
+ } catch (error) {
236
+ if (error instanceof Error && error.message.includes("built")) throw error;
237
+ }
238
+ const normalizedPath = path.resolve(packagePath).replace(/\\/g, "/");
239
+ packageJson.dependencies[dependency] = `file:${normalizedPath}`;
240
+ } else {
241
+ console.warn(`Warning: Could not find workspace package ${dependency}, removing from dependencies.`);
242
+ delete packageJson.dependencies[dependency];
243
+ }
244
+ }
245
+ } else if (packageJson.dependencies && !workspaceRoot) {
246
+ console.warn("Warning: Could not find workspace root. Workspace dependencies will be removed.");
247
+ for (const [dependency, version$1] of Object.entries(packageJson.dependencies)) if (typeof version$1 === "string" && version$1.startsWith("workspace:")) delete packageJson.dependencies[dependency];
248
+ }
249
+ for (const [dependency, version$1] of Object.entries(packageJson.devDependencies || {})) packageJson.devDependencies[dependency] = version$1.replace("workspace:", "");
274
250
  delete packageJson.devDependencies["@ahmedrowaihi/pdf-forge-components"];
275
251
  delete packageJson.devDependencies["@ahmedrowaihi/pdf-forge-core"];
276
252
  delete packageJson.scripts.prepare;
277
253
  await fs.promises.writeFile(packageJsonPath, JSON.stringify(packageJson), "utf8");
278
254
  };
279
- const build$1 = async ({ dir: templatesDirRelativePath, packageManager }) => {
255
+ const build = async ({ dir: templatesDirRelativePath, packageManager }) => {
280
256
  try {
281
257
  const previewServerLocation = await getPreviewServerLocation();
282
258
  const spinner = ora({
@@ -331,217 +307,6 @@ const build$1 = async ({ dir: templatesDirRelativePath, packageManager }) => {
331
307
  }
332
308
  };
333
309
 
334
- //#endregion
335
- //#region src/utils/preview/hot-reloading/get-imported-modules.ts
336
- const traverse = typeof traverseModule === "function" ? traverseModule : traverseModule.default;
337
- const getImportedModules = (contents) => {
338
- const importedPaths = [];
339
- traverse(parse(contents, {
340
- sourceType: "unambiguous",
341
- strictMode: false,
342
- errorRecovery: true,
343
- plugins: [
344
- "jsx",
345
- "typescript",
346
- "decorators"
347
- ]
348
- }), {
349
- ImportDeclaration({ node }) {
350
- importedPaths.push(node.source.value);
351
- },
352
- ExportAllDeclaration({ node }) {
353
- importedPaths.push(node.source.value);
354
- },
355
- ExportNamedDeclaration({ node }) {
356
- if (node.source) importedPaths.push(node.source.value);
357
- },
358
- TSExternalModuleReference({ node }) {
359
- importedPaths.push(node.expression.value);
360
- },
361
- CallExpression({ node }) {
362
- if ("name" in node.callee && node.callee.name === "require") {
363
- if (node.arguments.length === 1) {
364
- const importPathNode = node.arguments[0];
365
- if (importPathNode.type === "StringLiteral") importedPaths.push(importPathNode.value);
366
- }
367
- }
368
- }
369
- });
370
- return importedPaths;
371
- };
372
-
373
- //#endregion
374
- //#region src/utils/preview/hot-reloading/resolve-path-aliases.ts
375
- const resolvePathAliases = (importPaths, projectPath) => {
376
- const configLoadResult = loadConfig(projectPath);
377
- if (configLoadResult.resultType === "success") {
378
- const matchPath = createMatchPath(configLoadResult.absoluteBaseUrl, configLoadResult.paths);
379
- return importPaths.map((importedPath) => {
380
- const unaliasedPath = matchPath(importedPath, void 0, void 0, [
381
- ".tsx",
382
- ".ts",
383
- ".js",
384
- ".jsx",
385
- ".cjs",
386
- ".mjs"
387
- ]);
388
- if (unaliasedPath) return `./${path.relative(projectPath, unaliasedPath)}`;
389
- return importedPath;
390
- });
391
- }
392
- return importPaths;
393
- };
394
-
395
- //#endregion
396
- //#region src/utils/preview/hot-reloading/create-dependency-graph.ts
397
- const readAllFilesInsideDirectory = async (directory) => {
398
- let allFilePaths = [];
399
- const topLevelDirents = await promises.readdir(directory, { withFileTypes: true });
400
- for (const dirent of topLevelDirents) {
401
- const pathToDirent = path.join(directory, dirent.name);
402
- if (dirent.isDirectory()) allFilePaths = allFilePaths.concat(await readAllFilesInsideDirectory(pathToDirent));
403
- else allFilePaths.push(pathToDirent);
404
- }
405
- return allFilePaths;
406
- };
407
- const javascriptExtensions = [
408
- ".js",
409
- ".ts",
410
- ".jsx",
411
- ".tsx",
412
- ".mjs",
413
- ".cjs"
414
- ];
415
- const isJavascriptModule = (filePath) => {
416
- const extensionName = path.extname(filePath);
417
- return javascriptExtensions.includes(extensionName);
418
- };
419
- const checkFileExtensionsUntilItExists = (pathWithoutExtension) => {
420
- if (existsSync(`${pathWithoutExtension}.ts`)) return `${pathWithoutExtension}.ts`;
421
- if (existsSync(`${pathWithoutExtension}.tsx`)) return `${pathWithoutExtension}.tsx`;
422
- if (existsSync(`${pathWithoutExtension}.js`)) return `${pathWithoutExtension}.js`;
423
- if (existsSync(`${pathWithoutExtension}.jsx`)) return `${pathWithoutExtension}.jsx`;
424
- if (existsSync(`${pathWithoutExtension}.mjs`)) return `${pathWithoutExtension}.mjs`;
425
- if (existsSync(`${pathWithoutExtension}.cjs`)) return `${pathWithoutExtension}.cjs`;
426
- };
427
- /**
428
- * Creates a stateful dependency graph that is structured in a way that you can get
429
- * the dependents of a module from its path.
430
- *
431
- * Stateful in the sense that it provides a `getter` and an "`updater`". The updater
432
- * will receive changes to the files, that can be perceived through some file watching mechanism,
433
- * so that it doesn't need to recompute the entire dependency graph but only the parts changed.
434
- */
435
- const createDependencyGraph = async (directory) => {
436
- const modulePaths = (await readAllFilesInsideDirectory(directory)).filter(isJavascriptModule);
437
- const graph = Object.fromEntries(modulePaths.map((path$1) => [path$1, {
438
- path: path$1,
439
- dependencyPaths: [],
440
- dependentPaths: [],
441
- moduleDependencies: []
442
- }]));
443
- const getDependencyPaths = async (filePath) => {
444
- const contents = await promises.readFile(filePath, "utf8");
445
- const importedPathsRelativeToDirectory = (isJavascriptModule(filePath) ? resolvePathAliases(getImportedModules(contents), path.dirname(filePath)) : []).map((dependencyPath) => {
446
- if (!dependencyPath.startsWith(".") || path.isAbsolute(dependencyPath)) return dependencyPath;
447
- let pathToDependencyFromDirectory = path.resolve(path.dirname(filePath), dependencyPath);
448
- let isDirectory = false;
449
- try {
450
- isDirectory = statSync(pathToDependencyFromDirectory).isDirectory();
451
- } catch {}
452
- if (isDirectory) {
453
- const pathWithExtension = checkFileExtensionsUntilItExists(`${pathToDependencyFromDirectory}/index`);
454
- if (pathWithExtension) pathToDependencyFromDirectory = pathWithExtension;
455
- else console.warn(`Could not find index file for directory at ${pathToDependencyFromDirectory}. This is probably going to cause issues with both hot reloading and your code.`);
456
- }
457
- const extension = path.extname(pathToDependencyFromDirectory);
458
- const pathWithEnsuredExtension = (() => {
459
- if (extension.length > 0 && existsSync(pathToDependencyFromDirectory)) return pathToDependencyFromDirectory;
460
- if (javascriptExtensions.includes(extension)) return checkFileExtensionsUntilItExists(pathToDependencyFromDirectory.replace(extension, ""));
461
- return checkFileExtensionsUntilItExists(pathToDependencyFromDirectory);
462
- })();
463
- if (pathWithEnsuredExtension) pathToDependencyFromDirectory = pathWithEnsuredExtension;
464
- else console.warn(`Could not find file at ${pathToDependencyFromDirectory}`);
465
- return pathToDependencyFromDirectory;
466
- });
467
- const moduleDependencies = importedPathsRelativeToDirectory.filter((dependencyPath) => !dependencyPath.startsWith(".") && !path.isAbsolute(dependencyPath));
468
- return {
469
- dependencyPaths: importedPathsRelativeToDirectory.filter((dependencyPath) => dependencyPath.startsWith(".") || path.isAbsolute(dependencyPath)),
470
- moduleDependencies
471
- };
472
- };
473
- const updateModuleDependenciesInGraph = async (moduleFilePath) => {
474
- if (graph[moduleFilePath] === void 0) graph[moduleFilePath] = {
475
- path: moduleFilePath,
476
- dependencyPaths: [],
477
- dependentPaths: [],
478
- moduleDependencies: []
479
- };
480
- const { moduleDependencies, dependencyPaths: newDependencyPaths } = await getDependencyPaths(moduleFilePath);
481
- graph[moduleFilePath].moduleDependencies = moduleDependencies;
482
- for (const dependencyPath of graph[moduleFilePath].dependencyPaths) {
483
- if (newDependencyPaths.includes(dependencyPath)) continue;
484
- const dependencyModule = graph[dependencyPath];
485
- if (dependencyModule !== void 0) dependencyModule.dependentPaths = dependencyModule.dependentPaths.filter((dependentPath) => dependentPath !== moduleFilePath);
486
- }
487
- graph[moduleFilePath].dependencyPaths = newDependencyPaths;
488
- for (const dependencyPath of newDependencyPaths) {
489
- if (graph[dependencyPath] === void 0) await updateModuleDependenciesInGraph(dependencyPath);
490
- const dependencyModule = graph[dependencyPath];
491
- if (dependencyModule === void 0) throw new Error(`Loading the dependency path ${dependencyPath} did not initialize it at all. This is a bug in React PDF.`);
492
- if (!dependencyModule.dependentPaths.includes(moduleFilePath)) dependencyModule.dependentPaths.push(moduleFilePath);
493
- }
494
- };
495
- for (const filePath of modulePaths) await updateModuleDependenciesInGraph(filePath);
496
- const removeModuleFromGraph = (filePath) => {
497
- const module = graph[filePath];
498
- if (module) {
499
- for (const dependencyPath of module.dependencyPaths) if (graph[dependencyPath]) graph[dependencyPath].dependentPaths = graph[dependencyPath].dependentPaths.filter((dependentPath) => dependentPath !== filePath);
500
- delete graph[filePath];
501
- }
502
- };
503
- return [
504
- graph,
505
- async (event, pathToModified) => {
506
- switch (event) {
507
- case "change":
508
- if (isJavascriptModule(pathToModified)) await updateModuleDependenciesInGraph(pathToModified);
509
- break;
510
- case "add":
511
- if (isJavascriptModule(pathToModified)) await updateModuleDependenciesInGraph(pathToModified);
512
- break;
513
- case "addDir": {
514
- const modulesInsideAddedDirectory = (await readAllFilesInsideDirectory(pathToModified)).filter(isJavascriptModule);
515
- for (const filePath of modulesInsideAddedDirectory) await updateModuleDependenciesInGraph(filePath);
516
- break;
517
- }
518
- case "unlink":
519
- if (isJavascriptModule(pathToModified)) removeModuleFromGraph(pathToModified);
520
- break;
521
- case "unlinkDir": {
522
- const modulesInsideDeletedDirectory = (await readAllFilesInsideDirectory(pathToModified)).filter(isJavascriptModule);
523
- for (const filePath of modulesInsideDeletedDirectory) removeModuleFromGraph(filePath);
524
- break;
525
- }
526
- }
527
- },
528
- { resolveDependentsOf: function resolveDependentsOf(pathToModule) {
529
- const dependentPaths = /* @__PURE__ */ new Set();
530
- const stack = [pathToModule];
531
- while (stack.length > 0) {
532
- const moduleEntry = graph[stack.pop()];
533
- if (!moduleEntry) continue;
534
- for (const dependentPath of moduleEntry.dependentPaths) {
535
- if (dependentPaths.has(dependentPath) || dependentPath === pathToModule) continue;
536
- dependentPaths.add(dependentPath);
537
- stack.push(dependentPath);
538
- }
539
- }
540
- return [...dependentPaths.values()];
541
- } }
542
- ];
543
- };
544
-
545
310
  //#endregion
546
311
  //#region src/utils/preview/hot-reloading/setup-hot-reloading.ts
547
312
  const setupHotreloading = async (devServer$1, templatesDirRelativePath) => {
@@ -594,10 +359,6 @@ const setupHotreloading = async (devServer$1, templatesDirRelativePath) => {
594
359
  return watcher;
595
360
  };
596
361
 
597
- //#endregion
598
- //#region src/utils/style-text.ts
599
- const styleText = nodeUtil.styleText ? nodeUtil.styleText : (_, text) => text;
600
-
601
362
  //#endregion
602
363
  //#region src/utils/preview/get-env-variables-for-preview-app.ts
603
364
  const getEnvVariablesForPreviewApp = (relativePathToTemplatesDirectory, previewServerLocation, cwd) => {
@@ -700,13 +461,11 @@ const safeAsyncServerListen = (server, port) => {
700
461
  });
701
462
  };
702
463
  const startDevServer = async (templatesDirRelativePath, staticBaseDirRelativePath, port) => {
703
- const [majorNodeVersion] = process.versions.node.split(".");
704
- if (majorNodeVersion && Number.parseInt(majorNodeVersion, 10) < 20) {
705
- console.error(` ${logSymbols.error} Node ${majorNodeVersion} is not supported. Please upgrade to Node 20 or higher.`);
464
+ if (typeof Bun === "undefined") {
465
+ console.error(` ${logSymbols.error} Bun runtime is required. Please run with: bun run pdf-dev dev`);
706
466
  process.exit(1);
707
467
  }
708
468
  const previewServerLocation = await getPreviewServerLocation();
709
- const previewServer = createJiti(previewServerLocation);
710
469
  devServer = http.createServer((req, res) => {
711
470
  if (!req.url) {
712
471
  res.end(404);
@@ -759,7 +518,8 @@ const startDevServer = async (templatesDirRelativePath, staticBaseDirRelativePat
759
518
  ...process.env,
760
519
  ...getEnvVariablesForPreviewApp(path.normalize(templatesDirRelativePath), previewServerLocation, process.cwd())
761
520
  };
762
- const app = (await previewServer.import("next", { default: true }))({
521
+ const nextModule = await import(Bun.resolveSync("next", previewServerLocation));
522
+ const app = (nextModule.default ?? nextModule)({
763
523
  dev: false,
764
524
  conf: { images: { unoptimized: true } },
765
525
  hostname: "localhost",
@@ -867,15 +627,9 @@ const dev = async ({ dir: templatesDirRelativePath, port }) => {
867
627
  };
868
628
 
869
629
  //#endregion
870
- //#region src/utils/esbuild/escape-string-for-regex.ts
871
- function escapeStringForRegex(string) {
872
- return string.replace(/[|\\{}()[\]^$+*?.]/g, "\\$&").replace(/-/g, "\\x2d");
873
- }
874
-
875
- //#endregion
876
- //#region src/utils/esbuild/renderring-utilities-exporter.ts
630
+ //#region src/utils/bun/rendering-utilities-exporter.ts
877
631
  /**
878
- * Made to export the `render` function out of the user's PDF template
632
+ * Bun plugin to export the `render` function out of the user's PDF template
879
633
  * so that React version mismatches don't happen.
880
634
  *
881
635
  * This also exports the `createElement` from the user's React version as well
@@ -886,28 +640,35 @@ function escapeStringForRegex(string) {
886
640
  */
887
641
  const renderingUtilitiesExporter = (pdfTemplates) => ({
888
642
  name: "rendering-utilities-exporter",
889
- setup: (b) => {
890
- b.onLoad({ filter: new RegExp(pdfTemplates.map((templatePath) => escapeStringForRegex(templatePath)).join("|")) }, async ({ path: pathToFile }) => {
643
+ setup(builder) {
644
+ const templateRegex = new RegExp(pdfTemplates.map((templatePath) => escapeStringForRegex(templatePath)).join("|"));
645
+ builder.onLoad({ filter: templateRegex }, async (args) => {
646
+ const code = await promises.readFile(args.path, "utf8");
647
+ const ext = path.extname(args.path).slice(1);
891
648
  return {
892
- contents: `${await promises.readFile(pathToFile, "utf8")};
893
- export { render } from 'react-pdf-module-that-will-export-render'
894
- export { createElement as reactPDFCreateReactElement } from 'react';
895
- `,
896
- loader: path.extname(pathToFile).slice(1)
649
+ contents: `${code}
650
+ export { render } from 'react-pdf-module-that-will-export-render';
651
+ export { createElement as reactPDFCreateReactElement } from 'react';
652
+ `,
653
+ loader: ext === "tsx" || ext === "jsx" ? "tsx" : ext
897
654
  };
898
655
  });
899
- b.onResolve({ filter: /^react-pdf-module-that-will-export-render$/ }, async (args) => {
900
- const options = {
901
- kind: "import-statement",
902
- importer: args.importer,
903
- resolveDir: args.resolveDir,
904
- namespace: args.namespace
905
- };
906
- let result = await b.resolve("@ahmedrowaihi/pdf-forge-core", options);
907
- if (result.errors.length === 0) return result;
908
- result = await b.resolve("@ahmedrowaihi/pdf-forge-components", options);
909
- if (result.errors.length > 0 && result.errors[0]) result.errors[0].text = "Failed trying to import `render` from either `@ahmedrowaihi/pdf-forge-core` or `@ahmedrowaihi/pdf-forge-components` to be able to render your PDF template.\n Maybe you don't have either of them installed?";
910
- return result;
656
+ builder.onResolve({ filter: /^react-pdf-module-that-will-export-render$/ }, async (args) => {
657
+ try {
658
+ return {
659
+ path: await Bun.resolve("@ahmedrowaihi/pdf-forge-core", args.importer),
660
+ namespace: "file"
661
+ };
662
+ } catch {
663
+ try {
664
+ return {
665
+ path: await Bun.resolve("@ahmedrowaihi/pdf-forge-components", args.importer),
666
+ namespace: "file"
667
+ };
668
+ } catch (_error) {
669
+ throw new Error("Failed trying to import `render` from either `@ahmedrowaihi/pdf-forge-core` or `@ahmedrowaihi/pdf-forge-components` to be able to render your PDF template.\n Maybe you don't have either of them installed?");
670
+ }
671
+ }
911
672
  });
912
673
  }
913
674
  });
@@ -920,7 +681,6 @@ const getTemplatesFromDirectory = (templateDirectory) => {
920
681
  for (const directory of templateDirectory.subDirectories) templatePaths.push(...getTemplatesFromDirectory(directory));
921
682
  return templatePaths;
922
683
  };
923
- const require = createRequire(url.fileURLToPath(import.meta.url));
924
684
  const exportTemplates = async (pathToWhereTemplateMarkupShouldBeDumped, templatesDirectoryPath, options) => {
925
685
  if (fs.existsSync(pathToWhereTemplateMarkupShouldBeDumped)) fs.rmSync(pathToWhereTemplateMarkupShouldBeDumped, { recursive: true });
926
686
  let spinner;
@@ -937,27 +697,54 @@ const exportTemplates = async (pathToWhereTemplateMarkupShouldBeDumped, template
937
697
  return;
938
698
  }
939
699
  const allTemplates = getTemplatesFromDirectory(templatesDirectoryMetadata);
700
+ const templateBaseDir = path.resolve(process.cwd(), templatesDirectoryPath);
701
+ const assetTransformPlugin = {
702
+ name: "asset-to-import-transform",
703
+ setup(builder) {
704
+ builder.onLoad({ filter: /\.(tsx?|jsx?)$/ }, async (args) => {
705
+ const filePath = args.path;
706
+ if (!filePath.startsWith(templateBaseDir)) return;
707
+ const code = await Bun.file(filePath).text();
708
+ const { transformAssetsToImports } = await import("@ahmedrowaihi/pdf-forge-toolbox");
709
+ const { code: transformedCode } = await transformAssetsToImports(code, filePath);
710
+ if (transformedCode !== code) return {
711
+ contents: transformedCode,
712
+ loader: "tsx"
713
+ };
714
+ });
715
+ }
716
+ };
940
717
  try {
941
- await build({
942
- bundle: true,
943
- entryPoints: allTemplates,
944
- format: "cjs",
945
- jsx: "automatic",
946
- loader: { ".js": "jsx" },
947
- logLevel: "silent",
948
- outExtension: { ".js": ".cjs" },
718
+ const buildResult = await Bun.build({
719
+ entrypoints: allTemplates,
949
720
  outdir: pathToWhereTemplateMarkupShouldBeDumped,
950
- platform: "node",
951
- plugins: [renderingUtilitiesExporter(allTemplates)],
952
- write: true
721
+ target: "node",
722
+ format: "cjs",
723
+ plugins: [assetTransformPlugin, renderingUtilitiesExporter(allTemplates)],
724
+ jsx: {
725
+ runtime: "automatic",
726
+ importSource: "react"
727
+ },
728
+ minify: false,
729
+ sourcemap: "external",
730
+ splitting: false
953
731
  });
732
+ if (!buildResult.success) {
733
+ const errorMessages = buildResult.logs.map((log) => log.message).join("\n");
734
+ throw new Error(`Bun build failed: ${errorMessages}`);
735
+ }
736
+ const builtFiles = buildResult.outputs.filter((output) => output.path.endsWith(".js"));
737
+ for (const file of builtFiles) {
738
+ const newPath = file.path.replace(/\.js$/, ".cjs");
739
+ await promises.rename(file.path, newPath);
740
+ }
954
741
  } catch (exception) {
955
742
  if (spinner) spinner.stopAndPersist({
956
743
  symbol: logSymbols.error,
957
744
  text: "Failed to build PDF templates"
958
745
  });
959
- const buildFailure = exception;
960
- console.error(`\n${buildFailure.message}`);
746
+ const error = exception;
747
+ console.error(`\n${error.message}`);
961
748
  process.exit(1);
962
749
  }
963
750
  if (spinner) spinner.succeed();
@@ -967,9 +754,10 @@ const exportTemplates = async (pathToWhereTemplateMarkupShouldBeDumped, template
967
754
  spinner.text = `rendering ${template.split("/").pop()}`;
968
755
  spinner.render();
969
756
  }
970
- delete require.cache[template];
971
- const templateModule = require(template);
972
- const rendered = await templateModule.render(templateModule.reactPDFCreateReactElement(templateModule.default, {}), options);
757
+ const templateModule = await new Function("path", "return import(path)")(`file://${template}`);
758
+ const TemplateComponent = templateModule.default;
759
+ const previewProps = TemplateComponent && "PreviewProps" in TemplateComponent ? TemplateComponent.PreviewProps : {};
760
+ const rendered = await templateModule.render(templateModule.reactPDFCreateReactElement(TemplateComponent, previewProps), options);
973
761
  writeFileSync(template.replace(".cjs", options.plainText ? ".txt" : ".html"), rendered);
974
762
  unlinkSync(template);
975
763
  } catch (exception) {
@@ -1047,7 +835,7 @@ const start = async () => {
1047
835
  //#region src/index.ts
1048
836
  program.name("react-pdf").description("A live preview of your PDF templates right in your browser").version(package_default.version);
1049
837
  program.command("dev").description("Starts the preview PDF development app").option("-d, --dir <path>", "Directory with your PDF templates", "./templates").option("-p --port <port>", "Port to run dev server on", "3000").action(dev);
1050
- program.command("build").description("Copies the preview app for onto .react-pdf and builds it").option("-d, --dir <path>", "Directory with your PDF templates", "./templates").option("-p --packageManager <name>", "Package name to use on installation on `.react-pdf`", "npm").action(build$1);
838
+ program.command("build").description("Copies the preview app for onto .react-pdf and builds it").option("-d, --dir <path>", "Directory with your PDF templates", "./templates").option("-p --packageManager <name>", "Package name to use on installation on `.react-pdf`", "npm").action(build);
1051
839
  program.command("start").description("Runs the built preview app that is inside of \".react-pdf\"").action(start);
1052
840
  program.command("export").description("Build the templates to the `out` directory").option("--outDir <path>", "Output directory", "out").option("-p, --pretty", "Pretty print the output", false).option("-t, --plainText", "Set output format as plain text", false).option("-d, --dir <path>", "Directory with your PDF templates", "./templates").option("-s, --silent", "To, or not to show a spinner with process information", false).action(({ outDir, pretty, plainText, silent, dir: srcDir }) => exportTemplates(outDir, srcDir, {
1053
841
  silent,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@ahmedrowaihi/pdf-forge-cli",
3
- "version": "1.1.0",
3
+ "version": "1.2.0-canary.0",
4
4
  "description": "A live preview of your PDF templates right in your browser.",
5
5
  "bin": {
6
6
  "pdf-dev": "./dist/index.js"
@@ -17,18 +17,14 @@
17
17
  "pdf"
18
18
  ],
19
19
  "engines": {
20
- "node": ">=20.0.0"
20
+ "bun": ">=1.0.0"
21
21
  },
22
22
  "dependencies": {
23
- "@babel/parser": "^7.27.0",
24
- "@babel/traverse": "^7.27.0",
25
23
  "chokidar": "^4.0.3",
26
24
  "commander": "^13.0.0",
27
25
  "conf": "^15.0.2",
28
26
  "debounce": "^2.0.0",
29
- "esbuild": "^0.25.0",
30
27
  "glob": "^11.0.0",
31
- "jiti": "2.4.2",
32
28
  "log-symbols": "^7.0.0",
33
29
  "mime-types": "^3.0.0",
34
30
  "normalize-path": "^3.0.0",
@@ -36,13 +32,14 @@
36
32
  "ora": "^8.0.0",
37
33
  "prompts": "2.4.2",
38
34
  "socket.io": "^4.8.1",
39
- "tsconfig-paths": "4.2.0"
35
+ "tsconfig-paths": "4.2.0",
36
+ "@ahmedrowaihi/pdf-forge-toolbox": "1.2.0-canary.0"
40
37
  },
41
38
  "devDependencies": {
42
- "@types/babel__core": "7.20.5",
43
- "@types/babel__traverse": "7.20.7",
39
+ "@types/bun": "^1.3.5",
44
40
  "@types/mime-types": "2.1.4",
45
41
  "@types/prompts": "2.4.9",
42
+ "@types/normalize-path": "3.0.2",
46
43
  "next": "16.0.10",
47
44
  "react": "19.0.0",
48
45
  "react-dom": "19.0.0",