@procedures/pdf 0.54.0 → 0.54.1
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/CHANGELOG.md +18 -0
- package/README.md +1 -1
- package/dist/index.d.ts +1 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +2 -2
- package/dist/index.js.map +1 -1
- package/dist/procedures/pdf/render.d.ts +1 -1
- package/dist/procedures/pdf/render.d.ts.map +1 -1
- package/dist/procedures/pdf/render.js +1 -1
- package/dist/procedures/pdf/render.js.map +1 -1
- package/package.json +6 -5
- package/src/index.ts +17 -2
- package/src/procedures/pdf/render.ts +1 -1
- package/dist/browser.d.ts +0 -34
- package/dist/browser.d.ts.map +0 -1
- package/dist/browser.js +0 -81
- package/dist/browser.js.map +0 -1
- package/dist/render.d.ts +0 -52
- package/dist/render.d.ts.map +0 -1
- package/dist/render.js +0 -125
- package/dist/render.js.map +0 -1
- package/src/browser.ts +0 -96
- package/src/render.ts +0 -154
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,23 @@
|
|
|
1
1
|
# @procedures/pdf
|
|
2
2
|
|
|
3
|
+
## 0.54.1
|
|
4
|
+
|
|
5
|
+
### Patch Changes
|
|
6
|
+
|
|
7
|
+
- a3efa7e: The new package `@procedures/browser` is the home of the renders of a page with a Chromium browser that is already installed. It has `findBrowser`, one headless run for each render, `renderPdf` and the new `renderScreenshot`.
|
|
8
|
+
|
|
9
|
+
- `renderScreenshot` loads an address, an HTML text or an HTML file in a window of a given size. It writes a PNG, JPEG or WebP picture of the window. The extension of the output sets the format.
|
|
10
|
+
- It accepts only an `http:`, `https:` or `file:` address, so a flag cannot get into the argv of the browser.
|
|
11
|
+
- The headless run is written one time. The two renders give only their flags and the check of their file.
|
|
12
|
+
|
|
13
|
+
`@procedures/pdf` gives the same names again from `@procedures/browser`, so no caller breaks. Its procedure `pdf.render` does not change.
|
|
14
|
+
|
|
15
|
+
PATCH: before 1.0.0, a new package and a new feature raise PATCH. The exports and the behavior of `@procedures/pdf` do not change.
|
|
16
|
+
- Updated dependencies [a3efa7e]
|
|
17
|
+
- Updated dependencies [13c3a8b]
|
|
18
|
+
- @procedures/browser@0.54.1
|
|
19
|
+
- @procedures/client@0.54.1
|
|
20
|
+
|
|
3
21
|
## 0.54.0
|
|
4
22
|
|
|
5
23
|
### Patch Changes
|
package/README.md
CHANGED
|
@@ -33,7 +33,7 @@ const { path, bytes } = await client.call(["pdf", "render"], {
|
|
|
33
33
|
|
|
34
34
|
## The functions
|
|
35
35
|
|
|
36
|
-
The package root also gives the functions without a registry
|
|
36
|
+
The package root also gives the functions without a registry. Their home is [`@procedures/browser`](../browser), and this package gives them again, so no caller breaks. New code imports them from `@procedures/browser`, which also takes screenshots.
|
|
37
37
|
|
|
38
38
|
- `renderPdf(options)` prints the page and gives `{ path, bytes, browser }`. The options are the input of `pdf.render`, and `signal`, `env`, `platform` and `runner`.
|
|
39
39
|
- `findBrowser(options)` gives the path of the browser program, or `undefined`.
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
export * from "./module.js";
|
|
2
2
|
export * from "./types.js";
|
|
3
|
-
export
|
|
4
|
-
export * from "./render.js";
|
|
3
|
+
export { BROWSER_ENV, BROWSER_NAMES, CHROME_PATH_ENV, DEFAULT_TIMEOUT_MS, WINDOWS_BROWSER_NAMES, browserCandidates, findBrowser, isPdfFile, pdfArgs, renderPdf, type CommandRunner, type FindBrowserOptions, type RenderPdfOptions, type RenderPdfResult, } from "@procedures/browser";
|
|
5
4
|
export { pdfRender } from "./procedures/pdf/index.js";
|
|
6
5
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC;AAE3B,OAAO,EACL,WAAW,EACX,aAAa,EACb,eAAe,EACf,kBAAkB,EAClB,qBAAqB,EACrB,iBAAiB,EACjB,WAAW,EACX,SAAS,EACT,OAAO,EACP,SAAS,EACT,KAAK,aAAa,EAClB,KAAK,kBAAkB,EACvB,KAAK,gBAAgB,EACrB,KAAK,eAAe,GACrB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
// The root registers nothing: import "<package>/register", or give the module to createRegistry()
|
|
2
2
|
export * from "./module.js";
|
|
3
3
|
export * from "./types.js";
|
|
4
|
-
|
|
5
|
-
export
|
|
4
|
+
// The search and the render moved to @procedures/browser. The names stay here, so no caller breaks.
|
|
5
|
+
export { BROWSER_ENV, BROWSER_NAMES, CHROME_PATH_ENV, DEFAULT_TIMEOUT_MS, WINDOWS_BROWSER_NAMES, browserCandidates, findBrowser, isPdfFile, pdfArgs, renderPdf, } from "@procedures/browser";
|
|
6
6
|
export { pdfRender } from "./procedures/pdf/index.js";
|
|
7
7
|
//# sourceMappingURL=index.js.map
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,kGAAkG;AAClG,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC;AAC3B,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,kGAAkG;AAClG,cAAc,aAAa,CAAC;AAC5B,cAAc,YAAY,CAAC;AAC3B,oGAAoG;AACpG,OAAO,EACL,WAAW,EACX,aAAa,EACb,eAAe,EACf,kBAAkB,EAClB,qBAAqB,EACrB,iBAAiB,EACjB,WAAW,EACX,SAAS,EACT,OAAO,EACP,SAAS,GAKV,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAAE,SAAS,EAAE,MAAM,2BAA2B,CAAC"}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* This procedure prints an HTML page to a PDF file with a headless Chromium browser that is
|
|
5
5
|
* already installed.
|
|
6
6
|
*/
|
|
7
|
-
import { type CommandRunner } from "
|
|
7
|
+
import { type CommandRunner } from "@procedures/browser";
|
|
8
8
|
import type { PdfRenderInput, PdfRenderOutput } from "../../types.js";
|
|
9
9
|
/**
|
|
10
10
|
* This function prints the HTML text or the HTML file of the input to a PDF file. It gives the
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../../src/procedures/pdf/render.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAa,KAAK,aAAa,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../../../src/procedures/pdf/render.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAa,KAAK,aAAa,EAAE,MAAM,qBAAqB,CAAC;AACpE,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AAEtE;;;GAGG;AACH,wBAAsB,SAAS,CAC7B,KAAK,EAAE,cAAc,EACrB,GAAG,CAAC,EAAE;IAAE,MAAM,CAAC,EAAE,WAAW,GAAG,SAAS,CAAA;CAAE,EAC1C,MAAM,CAAC,EAAE,aAAa,GACrB,OAAO,CAAC,eAAe,CAAC,CAY1B"}
|
|
@@ -4,7 +4,7 @@
|
|
|
4
4
|
* This procedure prints an HTML page to a PDF file with a headless Chromium browser that is
|
|
5
5
|
* already installed.
|
|
6
6
|
*/
|
|
7
|
-
import { renderPdf } from "
|
|
7
|
+
import { renderPdf } from "@procedures/browser";
|
|
8
8
|
/**
|
|
9
9
|
* This function prints the HTML text or the HTML file of the input to a PDF file. It gives the
|
|
10
10
|
* path and the size of the file.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"render.js","sourceRoot":"","sources":["../../../src/procedures/pdf/render.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,SAAS,EAAsB,MAAM,
|
|
1
|
+
{"version":3,"file":"render.js","sourceRoot":"","sources":["../../../src/procedures/pdf/render.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,EAAE,SAAS,EAAsB,MAAM,qBAAqB,CAAC;AAGpE;;;GAGG;AACH,MAAM,CAAC,KAAK,UAAU,SAAS,CAC7B,KAAqB,EACrB,GAA0C,EAC1C,MAAsB;IAEtB,MAAM,MAAM,GAAG,MAAM,SAAS,CAAC;QAC7B,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,MAAM,EAAE,KAAK,CAAC,MAAM;QACpB,UAAU,EAAE,KAAK,CAAC,UAAU;QAC5B,IAAI,EAAE,KAAK,CAAC,IAAI;QAChB,OAAO,EAAE,KAAK,CAAC,OAAO;QACtB,MAAM,EAAE,GAAG,EAAE,MAAM;QACnB,MAAM;KACP,CAAC,CAAC;IACH,OAAO,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,KAAK,EAAE,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,MAAM,CAAC,OAAO,EAAE,CAAC;AAC7E,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
{
|
|
2
2
|
"$schema": "https://json.schemastore.org/package",
|
|
3
3
|
"name": "@procedures/pdf",
|
|
4
|
-
"version": "0.54.
|
|
5
|
-
"description": "HTML to PDF with a headless Chromium browser that is already installed (Edge, Chrome or Chromium),
|
|
4
|
+
"version": "0.54.1",
|
|
5
|
+
"description": "HTML to PDF with a headless Chromium browser that is already installed (Edge, Chrome or Chromium), as the procedure pdf.render. The render is in @procedures/browser.",
|
|
6
6
|
"keywords": [
|
|
7
7
|
"pdf",
|
|
8
8
|
"html",
|
|
@@ -59,15 +59,16 @@
|
|
|
59
59
|
"provenance": true
|
|
60
60
|
},
|
|
61
61
|
"dependencies": {
|
|
62
|
-
"@procedures/
|
|
63
|
-
"@procedures/
|
|
62
|
+
"@procedures/browser": "0.54.1",
|
|
63
|
+
"@procedures/client": "0.54.1",
|
|
64
64
|
"zod": "^4.6.5"
|
|
65
65
|
},
|
|
66
66
|
"devDependencies": {
|
|
67
67
|
"typescript": "^7.0.2",
|
|
68
68
|
"@types/node": "^22.0.0",
|
|
69
69
|
"@mark1russell7/cue": "^2.1.1",
|
|
70
|
-
"vitest": "^5.0.3"
|
|
70
|
+
"vitest": "^5.0.3",
|
|
71
|
+
"@procedures/shell": "0.54.1"
|
|
71
72
|
},
|
|
72
73
|
"client": {
|
|
73
74
|
"procedures": "./dist/register.js"
|
package/src/index.ts
CHANGED
|
@@ -1,6 +1,21 @@
|
|
|
1
1
|
// The root registers nothing: import "<package>/register", or give the module to createRegistry()
|
|
2
2
|
export * from "./module.js";
|
|
3
3
|
export * from "./types.js";
|
|
4
|
-
|
|
5
|
-
export
|
|
4
|
+
// The search and the render moved to @procedures/browser. The names stay here, so no caller breaks.
|
|
5
|
+
export {
|
|
6
|
+
BROWSER_ENV,
|
|
7
|
+
BROWSER_NAMES,
|
|
8
|
+
CHROME_PATH_ENV,
|
|
9
|
+
DEFAULT_TIMEOUT_MS,
|
|
10
|
+
WINDOWS_BROWSER_NAMES,
|
|
11
|
+
browserCandidates,
|
|
12
|
+
findBrowser,
|
|
13
|
+
isPdfFile,
|
|
14
|
+
pdfArgs,
|
|
15
|
+
renderPdf,
|
|
16
|
+
type CommandRunner,
|
|
17
|
+
type FindBrowserOptions,
|
|
18
|
+
type RenderPdfOptions,
|
|
19
|
+
type RenderPdfResult,
|
|
20
|
+
} from "@procedures/browser";
|
|
6
21
|
export { pdfRender } from "./procedures/pdf/index.js";
|
package/dist/browser.d.ts
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Find a Chromium browser that is already installed: Microsoft Edge, Google Chrome or Chromium.
|
|
3
|
-
* The package does not download a browser.
|
|
4
|
-
*/
|
|
5
|
-
/** The environment variable that names the browser program. It comes before the search. */
|
|
6
|
-
export declare const BROWSER_ENV = "PDF_BROWSER";
|
|
7
|
-
/** The second environment variable for the browser program. Other tools use this name too. */
|
|
8
|
-
export declare const CHROME_PATH_ENV = "CHROME_PATH";
|
|
9
|
-
/** The program names that the search looks for on the `PATH`, in order. */
|
|
10
|
-
export declare const BROWSER_NAMES: readonly string[];
|
|
11
|
-
/** The program names on the `PATH` of Windows, in order. */
|
|
12
|
-
export declare const WINDOWS_BROWSER_NAMES: readonly string[];
|
|
13
|
-
/** The options of `findBrowser`. Each one has a default from the host. */
|
|
14
|
-
export interface FindBrowserOptions {
|
|
15
|
-
/** The browser program. When it is set, the search does not occur. */
|
|
16
|
-
readonly executable?: string | undefined;
|
|
17
|
-
/** The environment (default: `process.env`). */
|
|
18
|
-
readonly env?: Readonly<Record<string, string | undefined>> | undefined;
|
|
19
|
-
/** The operating system (default: `process.platform`). */
|
|
20
|
-
readonly platform?: NodeJS.Platform | undefined;
|
|
21
|
-
/** The function that tells if a file exists (default: `existsSync`). */
|
|
22
|
-
readonly exists?: ((path: string) => boolean) | undefined;
|
|
23
|
-
}
|
|
24
|
-
/**
|
|
25
|
-
* This function gives the paths that the search examines on an operating system, in order. The
|
|
26
|
-
* install folders come first, then the folders of the `PATH`.
|
|
27
|
-
*/
|
|
28
|
-
export declare function browserCandidates(platform: NodeJS.Platform, env: Readonly<Record<string, string | undefined>>): string[];
|
|
29
|
-
/**
|
|
30
|
-
* This function finds the browser program. The order is `executable`, then `PDF_BROWSER`, then
|
|
31
|
-
* `CHROME_PATH`, then the install folders and the `PATH`. It gives `undefined` when it finds none.
|
|
32
|
-
*/
|
|
33
|
-
export declare function findBrowser(options?: FindBrowserOptions): string | undefined;
|
|
34
|
-
//# sourceMappingURL=browser.d.ts.map
|
package/dist/browser.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"browser.d.ts","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAKH,2FAA2F;AAC3F,eAAO,MAAM,WAAW,gBAAgB,CAAC;AAEzC,8FAA8F;AAC9F,eAAO,MAAM,eAAe,gBAAgB,CAAC;AAE7C,2EAA2E;AAC3E,eAAO,MAAM,aAAa,EAAE,SAAS,MAAM,EAO1C,CAAC;AAEF,4DAA4D;AAC5D,eAAO,MAAM,qBAAqB,EAAE,SAAS,MAAM,EAAiC,CAAC;AAErF,0EAA0E;AAC1E,MAAM,WAAW,kBAAkB;IACjC,sEAAsE;IACtE,QAAQ,CAAC,UAAU,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACzC,gDAAgD;IAChD,QAAQ,CAAC,GAAG,CAAC,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,GAAG,SAAS,CAAC;IACxE,0DAA0D;IAC1D,QAAQ,CAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,QAAQ,GAAG,SAAS,CAAC;IAChD,wEAAwE;IACxE,QAAQ,CAAC,MAAM,CAAC,EAAE,CAAC,CAAC,IAAI,EAAE,MAAM,KAAK,OAAO,CAAC,GAAG,SAAS,CAAC;CAC3D;AASD;;;GAGG;AACH,wBAAgB,iBAAiB,CAAC,QAAQ,EAAE,MAAM,CAAC,QAAQ,EAAE,GAAG,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAC,GAAG,MAAM,EAAE,CA8BxH;AAED;;;GAGG;AACH,wBAAgB,WAAW,CAAC,OAAO,GAAE,kBAAuB,GAAG,MAAM,GAAG,SAAS,CAShF"}
|
package/dist/browser.js
DELETED
|
@@ -1,81 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Find a Chromium browser that is already installed: Microsoft Edge, Google Chrome or Chromium.
|
|
3
|
-
* The package does not download a browser.
|
|
4
|
-
*/
|
|
5
|
-
import { existsSync } from "node:fs";
|
|
6
|
-
import { posix, win32 } from "node:path";
|
|
7
|
-
/** The environment variable that names the browser program. It comes before the search. */
|
|
8
|
-
export const BROWSER_ENV = "PDF_BROWSER";
|
|
9
|
-
/** The second environment variable for the browser program. Other tools use this name too. */
|
|
10
|
-
export const CHROME_PATH_ENV = "CHROME_PATH";
|
|
11
|
-
/** The program names that the search looks for on the `PATH`, in order. */
|
|
12
|
-
export const BROWSER_NAMES = [
|
|
13
|
-
"google-chrome",
|
|
14
|
-
"google-chrome-stable",
|
|
15
|
-
"chromium",
|
|
16
|
-
"chromium-browser",
|
|
17
|
-
"microsoft-edge",
|
|
18
|
-
"microsoft-edge-stable",
|
|
19
|
-
];
|
|
20
|
-
/** The program names on the `PATH` of Windows, in order. */
|
|
21
|
-
export const WINDOWS_BROWSER_NAMES = ["msedge.exe", "chrome.exe"];
|
|
22
|
-
/** This function gives the folders of the `PATH`, in order. */
|
|
23
|
-
function pathFolders(env, windows) {
|
|
24
|
-
const value = windows ? (env["Path"] ?? env["PATH"]) : env["PATH"];
|
|
25
|
-
if (value === undefined)
|
|
26
|
-
return [];
|
|
27
|
-
return value.split(windows ? ";" : ":").filter((folder) => folder.length > 0);
|
|
28
|
-
}
|
|
29
|
-
/**
|
|
30
|
-
* This function gives the paths that the search examines on an operating system, in order. The
|
|
31
|
-
* install folders come first, then the folders of the `PATH`.
|
|
32
|
-
*/
|
|
33
|
-
export function browserCandidates(platform, env) {
|
|
34
|
-
if (platform === "win32") {
|
|
35
|
-
const programFiles = env["ProgramFiles"] ?? env["PROGRAMFILES"] ?? "C:\\Program Files";
|
|
36
|
-
const programFilesX86 = env["ProgramFiles(x86)"] ?? env["PROGRAMFILES(X86)"] ?? "C:\\Program Files (x86)";
|
|
37
|
-
const localAppData = env["LOCALAPPDATA"];
|
|
38
|
-
const installed = [
|
|
39
|
-
win32.join(programFilesX86, "Microsoft", "Edge", "Application", "msedge.exe"),
|
|
40
|
-
win32.join(programFiles, "Microsoft", "Edge", "Application", "msedge.exe"),
|
|
41
|
-
win32.join(programFiles, "Google", "Chrome", "Application", "chrome.exe"),
|
|
42
|
-
win32.join(programFilesX86, "Google", "Chrome", "Application", "chrome.exe"),
|
|
43
|
-
...(localAppData !== undefined
|
|
44
|
-
? [win32.join(localAppData, "Google", "Chrome", "Application", "chrome.exe"), win32.join(localAppData, "Chromium", "Application", "chrome.exe")]
|
|
45
|
-
: []),
|
|
46
|
-
];
|
|
47
|
-
const onPath = pathFolders(env, true).flatMap((folder) => WINDOWS_BROWSER_NAMES.map((name) => win32.join(folder, name)));
|
|
48
|
-
return [...installed, ...onPath];
|
|
49
|
-
}
|
|
50
|
-
const installed = [];
|
|
51
|
-
if (platform === "darwin") {
|
|
52
|
-
const bundles = [
|
|
53
|
-
"Google Chrome.app/Contents/MacOS/Google Chrome",
|
|
54
|
-
"Microsoft Edge.app/Contents/MacOS/Microsoft Edge",
|
|
55
|
-
"Chromium.app/Contents/MacOS/Chromium",
|
|
56
|
-
];
|
|
57
|
-
const home = env["HOME"];
|
|
58
|
-
installed.push(...bundles.map((bundle) => posix.join("/Applications", bundle)));
|
|
59
|
-
if (home !== undefined)
|
|
60
|
-
installed.push(...bundles.map((bundle) => posix.join(home, "Applications", bundle)));
|
|
61
|
-
}
|
|
62
|
-
const onPath = pathFolders(env, false).flatMap((folder) => BROWSER_NAMES.map((name) => posix.join(folder, name)));
|
|
63
|
-
return [...installed, ...onPath];
|
|
64
|
-
}
|
|
65
|
-
/**
|
|
66
|
-
* This function finds the browser program. The order is `executable`, then `PDF_BROWSER`, then
|
|
67
|
-
* `CHROME_PATH`, then the install folders and the `PATH`. It gives `undefined` when it finds none.
|
|
68
|
-
*/
|
|
69
|
-
export function findBrowser(options = {}) {
|
|
70
|
-
if (options.executable !== undefined && options.executable.length > 0)
|
|
71
|
-
return options.executable;
|
|
72
|
-
const env = options.env ?? process.env;
|
|
73
|
-
for (const name of [BROWSER_ENV, CHROME_PATH_ENV]) {
|
|
74
|
-
const value = env[name];
|
|
75
|
-
if (value !== undefined && value.length > 0)
|
|
76
|
-
return value;
|
|
77
|
-
}
|
|
78
|
-
const exists = options.exists ?? existsSync;
|
|
79
|
-
return browserCandidates(options.platform ?? process.platform, env).find((candidate) => exists(candidate));
|
|
80
|
-
}
|
|
81
|
-
//# sourceMappingURL=browser.js.map
|
package/dist/browser.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"browser.js","sourceRoot":"","sources":["../src/browser.ts"],"names":[],"mappings":"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"}
|
package/dist/render.d.ts
DELETED
|
@@ -1,52 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* HTML to PDF with a headless Chromium browser that is already installed.
|
|
3
|
-
*
|
|
4
|
-
* The browser starts with argv, not through a shell, through `runCommand` of
|
|
5
|
-
* `@procedures/shell`. A time limit and the signal of the call stop its process tree.
|
|
6
|
-
*/
|
|
7
|
-
import { type CommandOptions, type CommandResult } from "@procedures/shell/command";
|
|
8
|
-
import { type FindBrowserOptions } from "./browser.js";
|
|
9
|
-
/** The default time limit of one render, in milliseconds. */
|
|
10
|
-
export declare const DEFAULT_TIMEOUT_MS = 60000;
|
|
11
|
-
/** The function that runs a program. The default is `runCommand` of `@procedures/shell`. */
|
|
12
|
-
export type CommandRunner = (command: string, options: CommandOptions) => Promise<CommandResult>;
|
|
13
|
-
/** The options of `renderPdf`. Give `html` or `file`, not the two. */
|
|
14
|
-
export interface RenderPdfOptions extends FindBrowserOptions {
|
|
15
|
-
/** The HTML text. The function writes it to a temporary file. */
|
|
16
|
-
readonly html?: string | undefined;
|
|
17
|
-
/** The path of an HTML file. */
|
|
18
|
-
readonly file?: string | undefined;
|
|
19
|
-
/** The path of the PDF file. The default is a file in a new temporary folder. */
|
|
20
|
-
readonly output?: string | undefined;
|
|
21
|
-
/** More flags for the browser, after the default flags. */
|
|
22
|
-
readonly args?: readonly string[] | undefined;
|
|
23
|
-
/** The time limit, in milliseconds (default: 60000). */
|
|
24
|
-
readonly timeout?: number | undefined;
|
|
25
|
-
/** When the signal aborts, the browser stops. */
|
|
26
|
-
readonly signal?: AbortSignal | undefined;
|
|
27
|
-
/** The function that runs the browser (default: `runCommand`). The tests give their own. */
|
|
28
|
-
readonly runner?: CommandRunner | undefined;
|
|
29
|
-
}
|
|
30
|
-
/** The result of `renderPdf`. */
|
|
31
|
-
export interface RenderPdfResult {
|
|
32
|
-
/** The absolute path of the PDF file. */
|
|
33
|
-
readonly path: string;
|
|
34
|
-
/** The size of the PDF file, in bytes. */
|
|
35
|
-
readonly bytes: number;
|
|
36
|
-
/** The browser program that made the file. */
|
|
37
|
-
readonly browser: string;
|
|
38
|
-
}
|
|
39
|
-
/**
|
|
40
|
-
* This function gives the argv of the browser. The argv has the flags of a headless print, the
|
|
41
|
-
* profile folder, the extra flags and the address of the page.
|
|
42
|
-
*/
|
|
43
|
-
export declare function pdfArgs(page: string, output: string, profile: string, extra?: readonly string[]): string[];
|
|
44
|
-
/** This function tells if a file starts with the signature of a PDF file (`%PDF`). */
|
|
45
|
-
export declare function isPdfFile(path: string): boolean;
|
|
46
|
-
/**
|
|
47
|
-
* This function prints an HTML page to a PDF file with a headless browser. It gives the path and
|
|
48
|
-
* the size of the file. It throws when it finds no browser, at the time limit, and when the file
|
|
49
|
-
* is not a PDF file.
|
|
50
|
-
*/
|
|
51
|
-
export declare function renderPdf(options: RenderPdfOptions): Promise<RenderPdfResult>;
|
|
52
|
-
//# sourceMappingURL=render.d.ts.map
|
package/dist/render.d.ts.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"render.d.ts","sourceRoot":"","sources":["../src/render.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAMH,OAAO,EAAc,KAAK,cAAc,EAAE,KAAK,aAAa,EAAE,MAAM,2BAA2B,CAAC;AAChG,OAAO,EAA4B,KAAK,kBAAkB,EAAE,MAAM,cAAc,CAAC;AAEjF,6DAA6D;AAC7D,eAAO,MAAM,kBAAkB,QAAS,CAAC;AAEzC,4FAA4F;AAC5F,MAAM,MAAM,aAAa,GAAG,CAAC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,cAAc,KAAK,OAAO,CAAC,aAAa,CAAC,CAAC;AAEjG,sEAAsE;AACtE,MAAM,WAAW,gBAAiB,SAAQ,kBAAkB;IAC1D,iEAAiE;IACjE,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,gCAAgC;IAChC,QAAQ,CAAC,IAAI,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACnC,iFAAiF;IACjF,QAAQ,CAAC,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACrC,2DAA2D;IAC3D,QAAQ,CAAC,IAAI,CAAC,EAAE,SAAS,MAAM,EAAE,GAAG,SAAS,CAAC;IAC9C,wDAAwD;IACxD,QAAQ,CAAC,OAAO,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;IACtC,iDAAiD;IACjD,QAAQ,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,SAAS,CAAC;IAC1C,4FAA4F;IAC5F,QAAQ,CAAC,MAAM,CAAC,EAAE,aAAa,GAAG,SAAS,CAAC;CAC7C;AAED,iCAAiC;AACjC,MAAM,WAAW,eAAe;IAC9B,yCAAyC;IACzC,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,0CAA0C;IAC1C,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,8CAA8C;IAC9C,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;CAC1B;AAED;;;GAGG;AACH,wBAAgB,OAAO,CAAC,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,GAAE,SAAS,MAAM,EAAO,GAAG,MAAM,EAAE,CAW9G;AAED,sFAAsF;AACtF,wBAAgB,SAAS,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAS/C;AAcD;;;;GAIG;AACH,wBAAsB,SAAS,CAAC,OAAO,EAAE,gBAAgB,GAAG,OAAO,CAAC,eAAe,CAAC,CA2DnF"}
|
package/dist/render.js
DELETED
|
@@ -1,125 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* HTML to PDF with a headless Chromium browser that is already installed.
|
|
3
|
-
*
|
|
4
|
-
* The browser starts with argv, not through a shell, through `runCommand` of
|
|
5
|
-
* `@procedures/shell`. A time limit and the signal of the call stop its process tree.
|
|
6
|
-
*/
|
|
7
|
-
import { closeSync, copyFileSync, mkdirSync, mkdtempSync, openSync, readSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
8
|
-
import { tmpdir } from "node:os";
|
|
9
|
-
import { dirname, join, resolve } from "node:path";
|
|
10
|
-
import { pathToFileURL } from "node:url";
|
|
11
|
-
import { runCommand } from "@procedures/shell/command";
|
|
12
|
-
import { BROWSER_ENV, findBrowser } from "./browser.js";
|
|
13
|
-
/** The default time limit of one render, in milliseconds. */
|
|
14
|
-
export const DEFAULT_TIMEOUT_MS = 60_000;
|
|
15
|
-
/**
|
|
16
|
-
* This function gives the argv of the browser. The argv has the flags of a headless print, the
|
|
17
|
-
* profile folder, the extra flags and the address of the page.
|
|
18
|
-
*/
|
|
19
|
-
export function pdfArgs(page, output, profile, extra = []) {
|
|
20
|
-
return [
|
|
21
|
-
"--headless",
|
|
22
|
-
"--disable-gpu",
|
|
23
|
-
"--no-pdf-header-footer",
|
|
24
|
-
// A separate profile keeps the print apart from a browser that the user has open
|
|
25
|
-
`--user-data-dir=${profile}`,
|
|
26
|
-
`--print-to-pdf=${output}`,
|
|
27
|
-
...extra,
|
|
28
|
-
page,
|
|
29
|
-
];
|
|
30
|
-
}
|
|
31
|
-
/** This function tells if a file starts with the signature of a PDF file (`%PDF`). */
|
|
32
|
-
export function isPdfFile(path) {
|
|
33
|
-
const fd = openSync(path, "r");
|
|
34
|
-
try {
|
|
35
|
-
const head = Buffer.alloc(4);
|
|
36
|
-
const read = readSync(fd, head, 0, 4, 0);
|
|
37
|
-
return read === 4 && head.toString("latin1") === "%PDF";
|
|
38
|
-
}
|
|
39
|
-
finally {
|
|
40
|
-
closeSync(fd);
|
|
41
|
-
}
|
|
42
|
-
}
|
|
43
|
-
function fileSize(path) {
|
|
44
|
-
try {
|
|
45
|
-
return statSync(path).size;
|
|
46
|
-
}
|
|
47
|
-
catch {
|
|
48
|
-
return undefined;
|
|
49
|
-
}
|
|
50
|
-
}
|
|
51
|
-
function lastLines(text, count = 5) {
|
|
52
|
-
return text.trim().split(/\r?\n/).slice(-count).join("\n");
|
|
53
|
-
}
|
|
54
|
-
/**
|
|
55
|
-
* This function prints an HTML page to a PDF file with a headless browser. It gives the path and
|
|
56
|
-
* the size of the file. It throws when it finds no browser, at the time limit, and when the file
|
|
57
|
-
* is not a PDF file.
|
|
58
|
-
*/
|
|
59
|
-
export async function renderPdf(options) {
|
|
60
|
-
const hasHtml = options.html !== undefined;
|
|
61
|
-
const hasFile = options.file !== undefined;
|
|
62
|
-
if (hasHtml === hasFile)
|
|
63
|
-
throw new Error("renderPdf needs html or file, and not the two");
|
|
64
|
-
const browser = findBrowser(options);
|
|
65
|
-
if (browser === undefined) {
|
|
66
|
-
throw new Error(`No browser found. Install Microsoft Edge, Google Chrome or Chromium, or set ${BROWSER_ENV} to the browser program.`);
|
|
67
|
-
}
|
|
68
|
-
const work = mkdtempSync(join(tmpdir(), "procedures-pdf-"));
|
|
69
|
-
const profile = join(work, "profile");
|
|
70
|
-
const output = resolve(options.output ?? join(work, "page.pdf"));
|
|
71
|
-
// The browser prints into the temporary folder. The file replaces the output only after its
|
|
72
|
-
// check, so a failed render keeps an old output file (before, it was deleted: audit pass 2).
|
|
73
|
-
const printed = join(work, "page.pdf");
|
|
74
|
-
let made = false;
|
|
75
|
-
try {
|
|
76
|
-
let page;
|
|
77
|
-
if (options.html !== undefined) {
|
|
78
|
-
const htmlFile = join(work, "page.html");
|
|
79
|
-
writeFileSync(htmlFile, options.html, "utf8");
|
|
80
|
-
page = pathToFileURL(htmlFile).href;
|
|
81
|
-
}
|
|
82
|
-
else {
|
|
83
|
-
page = pathToFileURL(resolve(options.file)).href;
|
|
84
|
-
}
|
|
85
|
-
mkdirSync(dirname(output), { recursive: true });
|
|
86
|
-
const run = options.runner ?? runCommand;
|
|
87
|
-
const timeout = options.timeout ?? DEFAULT_TIMEOUT_MS;
|
|
88
|
-
const result = await run(browser, {
|
|
89
|
-
args: pdfArgs(page, printed, profile, options.args),
|
|
90
|
-
timeout,
|
|
91
|
-
signal: options.signal,
|
|
92
|
-
maxOutputBytes: 64 * 1024,
|
|
93
|
-
keep: "tail",
|
|
94
|
-
});
|
|
95
|
-
if (result.error !== undefined)
|
|
96
|
-
throw new Error(`The browser did not start: ${result.error}`);
|
|
97
|
-
if (result.timedOut === true)
|
|
98
|
-
throw new Error(`The browser did not finish in ${timeout} ms`);
|
|
99
|
-
if (result.aborted === true)
|
|
100
|
-
throw new Error("The render was cancelled");
|
|
101
|
-
const bytes = fileSize(printed);
|
|
102
|
-
if (bytes === undefined || bytes === 0) {
|
|
103
|
-
const detail = lastLines(result.stderr || result.stdout);
|
|
104
|
-
throw new Error(`The browser made no PDF file (exit code ${result.exitCode})${detail ? `: ${detail}` : ""}`);
|
|
105
|
-
}
|
|
106
|
-
if (!isPdfFile(printed))
|
|
107
|
-
throw new Error(`The file ${output} is not a PDF file`);
|
|
108
|
-
// The temporary folder goes in `finally`, so a copy is enough, also across two drives
|
|
109
|
-
if (printed !== output)
|
|
110
|
-
copyFileSync(printed, output);
|
|
111
|
-
made = true;
|
|
112
|
-
return { path: output, bytes, browser };
|
|
113
|
-
}
|
|
114
|
-
finally {
|
|
115
|
-
// The temporary page and profile go. A default output stays in its folder after a success.
|
|
116
|
-
if (made && options.output === undefined) {
|
|
117
|
-
rmSync(profile, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
|
|
118
|
-
rmSync(join(work, "page.html"), { force: true });
|
|
119
|
-
}
|
|
120
|
-
else {
|
|
121
|
-
rmSync(work, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
|
|
122
|
-
}
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
//# sourceMappingURL=render.js.map
|
package/dist/render.js.map
DELETED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"render.js","sourceRoot":"","sources":["../src/render.ts"],"names":[],"mappings":"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"}
|
package/src/browser.ts
DELETED
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Find a Chromium browser that is already installed: Microsoft Edge, Google Chrome or Chromium.
|
|
3
|
-
* The package does not download a browser.
|
|
4
|
-
*/
|
|
5
|
-
|
|
6
|
-
import { existsSync } from "node:fs";
|
|
7
|
-
import { posix, win32 } from "node:path";
|
|
8
|
-
|
|
9
|
-
/** The environment variable that names the browser program. It comes before the search. */
|
|
10
|
-
export const BROWSER_ENV = "PDF_BROWSER";
|
|
11
|
-
|
|
12
|
-
/** The second environment variable for the browser program. Other tools use this name too. */
|
|
13
|
-
export const CHROME_PATH_ENV = "CHROME_PATH";
|
|
14
|
-
|
|
15
|
-
/** The program names that the search looks for on the `PATH`, in order. */
|
|
16
|
-
export const BROWSER_NAMES: readonly string[] = [
|
|
17
|
-
"google-chrome",
|
|
18
|
-
"google-chrome-stable",
|
|
19
|
-
"chromium",
|
|
20
|
-
"chromium-browser",
|
|
21
|
-
"microsoft-edge",
|
|
22
|
-
"microsoft-edge-stable",
|
|
23
|
-
];
|
|
24
|
-
|
|
25
|
-
/** The program names on the `PATH` of Windows, in order. */
|
|
26
|
-
export const WINDOWS_BROWSER_NAMES: readonly string[] = ["msedge.exe", "chrome.exe"];
|
|
27
|
-
|
|
28
|
-
/** The options of `findBrowser`. Each one has a default from the host. */
|
|
29
|
-
export interface FindBrowserOptions {
|
|
30
|
-
/** The browser program. When it is set, the search does not occur. */
|
|
31
|
-
readonly executable?: string | undefined;
|
|
32
|
-
/** The environment (default: `process.env`). */
|
|
33
|
-
readonly env?: Readonly<Record<string, string | undefined>> | undefined;
|
|
34
|
-
/** The operating system (default: `process.platform`). */
|
|
35
|
-
readonly platform?: NodeJS.Platform | undefined;
|
|
36
|
-
/** The function that tells if a file exists (default: `existsSync`). */
|
|
37
|
-
readonly exists?: ((path: string) => boolean) | undefined;
|
|
38
|
-
}
|
|
39
|
-
|
|
40
|
-
/** This function gives the folders of the `PATH`, in order. */
|
|
41
|
-
function pathFolders(env: Readonly<Record<string, string | undefined>>, windows: boolean): string[] {
|
|
42
|
-
const value = windows ? (env["Path"] ?? env["PATH"]) : env["PATH"];
|
|
43
|
-
if (value === undefined) return [];
|
|
44
|
-
return value.split(windows ? ";" : ":").filter((folder) => folder.length > 0);
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
/**
|
|
48
|
-
* This function gives the paths that the search examines on an operating system, in order. The
|
|
49
|
-
* install folders come first, then the folders of the `PATH`.
|
|
50
|
-
*/
|
|
51
|
-
export function browserCandidates(platform: NodeJS.Platform, env: Readonly<Record<string, string | undefined>>): string[] {
|
|
52
|
-
if (platform === "win32") {
|
|
53
|
-
const programFiles = env["ProgramFiles"] ?? env["PROGRAMFILES"] ?? "C:\\Program Files";
|
|
54
|
-
const programFilesX86 = env["ProgramFiles(x86)"] ?? env["PROGRAMFILES(X86)"] ?? "C:\\Program Files (x86)";
|
|
55
|
-
const localAppData = env["LOCALAPPDATA"];
|
|
56
|
-
const installed = [
|
|
57
|
-
win32.join(programFilesX86, "Microsoft", "Edge", "Application", "msedge.exe"),
|
|
58
|
-
win32.join(programFiles, "Microsoft", "Edge", "Application", "msedge.exe"),
|
|
59
|
-
win32.join(programFiles, "Google", "Chrome", "Application", "chrome.exe"),
|
|
60
|
-
win32.join(programFilesX86, "Google", "Chrome", "Application", "chrome.exe"),
|
|
61
|
-
...(localAppData !== undefined
|
|
62
|
-
? [win32.join(localAppData, "Google", "Chrome", "Application", "chrome.exe"), win32.join(localAppData, "Chromium", "Application", "chrome.exe")]
|
|
63
|
-
: []),
|
|
64
|
-
];
|
|
65
|
-
const onPath = pathFolders(env, true).flatMap((folder) => WINDOWS_BROWSER_NAMES.map((name) => win32.join(folder, name)));
|
|
66
|
-
return [...installed, ...onPath];
|
|
67
|
-
}
|
|
68
|
-
const installed: string[] = [];
|
|
69
|
-
if (platform === "darwin") {
|
|
70
|
-
const bundles = [
|
|
71
|
-
"Google Chrome.app/Contents/MacOS/Google Chrome",
|
|
72
|
-
"Microsoft Edge.app/Contents/MacOS/Microsoft Edge",
|
|
73
|
-
"Chromium.app/Contents/MacOS/Chromium",
|
|
74
|
-
];
|
|
75
|
-
const home = env["HOME"];
|
|
76
|
-
installed.push(...bundles.map((bundle) => posix.join("/Applications", bundle)));
|
|
77
|
-
if (home !== undefined) installed.push(...bundles.map((bundle) => posix.join(home, "Applications", bundle)));
|
|
78
|
-
}
|
|
79
|
-
const onPath = pathFolders(env, false).flatMap((folder) => BROWSER_NAMES.map((name) => posix.join(folder, name)));
|
|
80
|
-
return [...installed, ...onPath];
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
/**
|
|
84
|
-
* This function finds the browser program. The order is `executable`, then `PDF_BROWSER`, then
|
|
85
|
-
* `CHROME_PATH`, then the install folders and the `PATH`. It gives `undefined` when it finds none.
|
|
86
|
-
*/
|
|
87
|
-
export function findBrowser(options: FindBrowserOptions = {}): string | undefined {
|
|
88
|
-
if (options.executable !== undefined && options.executable.length > 0) return options.executable;
|
|
89
|
-
const env = options.env ?? process.env;
|
|
90
|
-
for (const name of [BROWSER_ENV, CHROME_PATH_ENV]) {
|
|
91
|
-
const value = env[name];
|
|
92
|
-
if (value !== undefined && value.length > 0) return value;
|
|
93
|
-
}
|
|
94
|
-
const exists = options.exists ?? existsSync;
|
|
95
|
-
return browserCandidates(options.platform ?? process.platform, env).find((candidate) => exists(candidate));
|
|
96
|
-
}
|
package/src/render.ts
DELETED
|
@@ -1,154 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* HTML to PDF with a headless Chromium browser that is already installed.
|
|
3
|
-
*
|
|
4
|
-
* The browser starts with argv, not through a shell, through `runCommand` of
|
|
5
|
-
* `@procedures/shell`. A time limit and the signal of the call stop its process tree.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import { closeSync, copyFileSync, mkdirSync, mkdtempSync, openSync, readSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
9
|
-
import { tmpdir } from "node:os";
|
|
10
|
-
import { dirname, join, resolve } from "node:path";
|
|
11
|
-
import { pathToFileURL } from "node:url";
|
|
12
|
-
import { runCommand, type CommandOptions, type CommandResult } from "@procedures/shell/command";
|
|
13
|
-
import { BROWSER_ENV, findBrowser, type FindBrowserOptions } from "./browser.js";
|
|
14
|
-
|
|
15
|
-
/** The default time limit of one render, in milliseconds. */
|
|
16
|
-
export const DEFAULT_TIMEOUT_MS = 60_000;
|
|
17
|
-
|
|
18
|
-
/** The function that runs a program. The default is `runCommand` of `@procedures/shell`. */
|
|
19
|
-
export type CommandRunner = (command: string, options: CommandOptions) => Promise<CommandResult>;
|
|
20
|
-
|
|
21
|
-
/** The options of `renderPdf`. Give `html` or `file`, not the two. */
|
|
22
|
-
export interface RenderPdfOptions extends FindBrowserOptions {
|
|
23
|
-
/** The HTML text. The function writes it to a temporary file. */
|
|
24
|
-
readonly html?: string | undefined;
|
|
25
|
-
/** The path of an HTML file. */
|
|
26
|
-
readonly file?: string | undefined;
|
|
27
|
-
/** The path of the PDF file. The default is a file in a new temporary folder. */
|
|
28
|
-
readonly output?: string | undefined;
|
|
29
|
-
/** More flags for the browser, after the default flags. */
|
|
30
|
-
readonly args?: readonly string[] | undefined;
|
|
31
|
-
/** The time limit, in milliseconds (default: 60000). */
|
|
32
|
-
readonly timeout?: number | undefined;
|
|
33
|
-
/** When the signal aborts, the browser stops. */
|
|
34
|
-
readonly signal?: AbortSignal | undefined;
|
|
35
|
-
/** The function that runs the browser (default: `runCommand`). The tests give their own. */
|
|
36
|
-
readonly runner?: CommandRunner | undefined;
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
/** The result of `renderPdf`. */
|
|
40
|
-
export interface RenderPdfResult {
|
|
41
|
-
/** The absolute path of the PDF file. */
|
|
42
|
-
readonly path: string;
|
|
43
|
-
/** The size of the PDF file, in bytes. */
|
|
44
|
-
readonly bytes: number;
|
|
45
|
-
/** The browser program that made the file. */
|
|
46
|
-
readonly browser: string;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* This function gives the argv of the browser. The argv has the flags of a headless print, the
|
|
51
|
-
* profile folder, the extra flags and the address of the page.
|
|
52
|
-
*/
|
|
53
|
-
export function pdfArgs(page: string, output: string, profile: string, extra: readonly string[] = []): string[] {
|
|
54
|
-
return [
|
|
55
|
-
"--headless",
|
|
56
|
-
"--disable-gpu",
|
|
57
|
-
"--no-pdf-header-footer",
|
|
58
|
-
// A separate profile keeps the print apart from a browser that the user has open
|
|
59
|
-
`--user-data-dir=${profile}`,
|
|
60
|
-
`--print-to-pdf=${output}`,
|
|
61
|
-
...extra,
|
|
62
|
-
page,
|
|
63
|
-
];
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
/** This function tells if a file starts with the signature of a PDF file (`%PDF`). */
|
|
67
|
-
export function isPdfFile(path: string): boolean {
|
|
68
|
-
const fd = openSync(path, "r");
|
|
69
|
-
try {
|
|
70
|
-
const head = Buffer.alloc(4);
|
|
71
|
-
const read = readSync(fd, head, 0, 4, 0);
|
|
72
|
-
return read === 4 && head.toString("latin1") === "%PDF";
|
|
73
|
-
} finally {
|
|
74
|
-
closeSync(fd);
|
|
75
|
-
}
|
|
76
|
-
}
|
|
77
|
-
|
|
78
|
-
function fileSize(path: string): number | undefined {
|
|
79
|
-
try {
|
|
80
|
-
return statSync(path).size;
|
|
81
|
-
} catch {
|
|
82
|
-
return undefined;
|
|
83
|
-
}
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
function lastLines(text: string, count = 5): string {
|
|
87
|
-
return text.trim().split(/\r?\n/).slice(-count).join("\n");
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
/**
|
|
91
|
-
* This function prints an HTML page to a PDF file with a headless browser. It gives the path and
|
|
92
|
-
* the size of the file. It throws when it finds no browser, at the time limit, and when the file
|
|
93
|
-
* is not a PDF file.
|
|
94
|
-
*/
|
|
95
|
-
export async function renderPdf(options: RenderPdfOptions): Promise<RenderPdfResult> {
|
|
96
|
-
const hasHtml = options.html !== undefined;
|
|
97
|
-
const hasFile = options.file !== undefined;
|
|
98
|
-
if (hasHtml === hasFile) throw new Error("renderPdf needs html or file, and not the two");
|
|
99
|
-
const browser = findBrowser(options);
|
|
100
|
-
if (browser === undefined) {
|
|
101
|
-
throw new Error(`No browser found. Install Microsoft Edge, Google Chrome or Chromium, or set ${BROWSER_ENV} to the browser program.`);
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
const work = mkdtempSync(join(tmpdir(), "procedures-pdf-"));
|
|
105
|
-
const profile = join(work, "profile");
|
|
106
|
-
const output = resolve(options.output ?? join(work, "page.pdf"));
|
|
107
|
-
// The browser prints into the temporary folder. The file replaces the output only after its
|
|
108
|
-
// check, so a failed render keeps an old output file (before, it was deleted: audit pass 2).
|
|
109
|
-
const printed = join(work, "page.pdf");
|
|
110
|
-
let made = false;
|
|
111
|
-
try {
|
|
112
|
-
let page: string;
|
|
113
|
-
if (options.html !== undefined) {
|
|
114
|
-
const htmlFile = join(work, "page.html");
|
|
115
|
-
writeFileSync(htmlFile, options.html, "utf8");
|
|
116
|
-
page = pathToFileURL(htmlFile).href;
|
|
117
|
-
} else {
|
|
118
|
-
page = pathToFileURL(resolve(options.file!)).href;
|
|
119
|
-
}
|
|
120
|
-
mkdirSync(dirname(output), { recursive: true });
|
|
121
|
-
|
|
122
|
-
const run = options.runner ?? runCommand;
|
|
123
|
-
const timeout = options.timeout ?? DEFAULT_TIMEOUT_MS;
|
|
124
|
-
const result = await run(browser, {
|
|
125
|
-
args: pdfArgs(page, printed, profile, options.args),
|
|
126
|
-
timeout,
|
|
127
|
-
signal: options.signal,
|
|
128
|
-
maxOutputBytes: 64 * 1024,
|
|
129
|
-
keep: "tail",
|
|
130
|
-
});
|
|
131
|
-
if (result.error !== undefined) throw new Error(`The browser did not start: ${result.error}`);
|
|
132
|
-
if (result.timedOut === true) throw new Error(`The browser did not finish in ${timeout} ms`);
|
|
133
|
-
if (result.aborted === true) throw new Error("The render was cancelled");
|
|
134
|
-
|
|
135
|
-
const bytes = fileSize(printed);
|
|
136
|
-
if (bytes === undefined || bytes === 0) {
|
|
137
|
-
const detail = lastLines(result.stderr || result.stdout);
|
|
138
|
-
throw new Error(`The browser made no PDF file (exit code ${result.exitCode})${detail ? `: ${detail}` : ""}`);
|
|
139
|
-
}
|
|
140
|
-
if (!isPdfFile(printed)) throw new Error(`The file ${output} is not a PDF file`);
|
|
141
|
-
// The temporary folder goes in `finally`, so a copy is enough, also across two drives
|
|
142
|
-
if (printed !== output) copyFileSync(printed, output);
|
|
143
|
-
made = true;
|
|
144
|
-
return { path: output, bytes, browser };
|
|
145
|
-
} finally {
|
|
146
|
-
// The temporary page and profile go. A default output stays in its folder after a success.
|
|
147
|
-
if (made && options.output === undefined) {
|
|
148
|
-
rmSync(profile, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
|
|
149
|
-
rmSync(join(work, "page.html"), { force: true });
|
|
150
|
-
} else {
|
|
151
|
-
rmSync(work, { recursive: true, force: true, maxRetries: 3, retryDelay: 100 });
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
}
|