bits-to-dead-trees 1.5.1 → 2.0.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.
@@ -30,3 +30,6 @@ jobs:
30
30
 
31
31
  - name: Run typecheck
32
32
  run: npm run ts-check
33
+
34
+ - name: Run tests
35
+ run: npm test
package/CHANGELOG.md CHANGED
@@ -1,5 +1,49 @@
1
1
  # bits-to-dead-trees
2
2
 
3
+ ## 2.0.1
4
+
5
+ ### Patch Changes
6
+
7
+ - 57d032f: Don't close browser if it's currently creating a PDF. We've added smoke tests, so
8
+ this shouldn't happen again. Sorry!
9
+
10
+ ## 2.0.0
11
+
12
+ ### Major Changes
13
+
14
+ - b4ebed6: Allow more options for pdf, context and goto to be passed. Also working validations.
15
+
16
+ We allow way more options now to be given in the payload:
17
+
18
+ - `pdfOptions` are the options for the `pdf` call in Playwright: https://playwright.dev/docs/api/class-page#page-pdf.
19
+ - `gotoOptions` are the options for the `goto` method in Playwright: https://playwright.dev/docs/api/class-page#page-goto
20
+ - if you don't pass anything, we default to a timeout of 60000 and wait for `networkidle`
21
+ - `browserContextOptions` are the options for the browser context in Playwright: https://playwright.dev/docs/api/class-browser#browser-new-context
22
+ - if you don't pass anything, we default to set `ignoreHTTPSErrors` to `true`
23
+
24
+ We finally added tests too! ✌️
25
+
26
+ ## Breaking Changes
27
+
28
+ - We don't allow `options` in the payload anymore. Those are `pdfOptions` now. To adapt your code, rename `options` to `pdfOptions` and you should be good to go.
29
+ This change was needed to allow more options for other methods we're using (like `timeout` for `goto`).
30
+ - We have stricter validation now and don't allow unknown keys in the option objects now. Previously we've ignored them, but we've now opted to bail out with
31
+ an validation error so the user gets notified on typos and such.
32
+
33
+ - 484b197: Return an error if page could not be loaded
34
+
35
+ ## Breaking Changes
36
+
37
+ Previously we've ignored if the page could not be loaded and tried to create a PDF anyway. However,
38
+ this lead to PDFs with random error messages on them, instead of a proper error in logging or similar.
39
+
40
+ To provide an better experience, we've opted to change this, so errors are caught earlier.
41
+
42
+ ### Minor Changes
43
+
44
+ - c7e554e: Update to Playwright 1.30.0
45
+ - 8b2ed78: Merge passed options with default options
46
+
3
47
  ## 1.5.1
4
48
 
5
49
  ### Patch Changes
package/Dockerfile CHANGED
@@ -1,4 +1,4 @@
1
- FROM mcr.microsoft.com/playwright:v1.29.2-jammy
1
+ FROM mcr.microsoft.com/playwright:v1.30.0-jammy
2
2
 
3
3
  ENV NODE_ENV=production
4
4
  ENV SERVER_PORT=8000
@@ -15,7 +15,7 @@ COPY ["package.json", "package-lock.json*", "./"]
15
15
 
16
16
  RUN npm install --omit=dev --ignore-scripts
17
17
 
18
- COPY ["index.js", "pdf_request_body.json", "./"]
18
+ COPY ["server.js", "app.js", "pdf_request_body.json", "./"]
19
19
 
20
20
  ENTRYPOINT ["tini", "-v", "--"]
21
- CMD ["node", "index.js"]
21
+ CMD ["node", "server.js"]
package/README.md CHANGED
@@ -34,11 +34,17 @@ The server exposes the `/pdf` endpoint that listens to a POST request and expect
34
34
  ```json
35
35
  {
36
36
  "url": "https://your-target-url.com/site/you/want/a/pdf/of",
37
- "options": {}
37
+ "pdfOptions": {},
38
+ "gotoOptions": {},
39
+ "browserContextOptions": {}
38
40
  }
39
41
  ```
40
42
 
41
- `options` are the options Playwright knows about PDF: https://playwright.dev/docs/api/class-page#page-pdf. We pass them to Playwright directly, so please refer to their docs.
43
+ - `pdfOptions` are the options for the `pdf` call in Playwright: https://playwright.dev/docs/api/class-page#page-pdf.
44
+ - `gotoOptions` are the options for the `goto` method in Playwright: https://playwright.dev/docs/api/class-page#page-goto
45
+ - if you don't pass anything, we default to a timeout of 60000 and wait for `networkidle`
46
+ - `browserContextOptions` are the options for the browser context in Playwright: https://playwright.dev/docs/api/class-browser#browser-new-context
47
+ - if you don't pass anything, we default to set `ignoreHTTPSErrors` to `true`
42
48
 
43
49
  The response is the PDF file.
44
50
 
package/app.js ADDED
@@ -0,0 +1,138 @@
1
+ // @ts-check
2
+
3
+ import { fastify } from "fastify";
4
+ import { chromium } from "playwright-core";
5
+ import { readFileSync } from "node:fs";
6
+
7
+ // we'd love to do this, but eslint doesn't like import assertions yet
8
+ // import PdfRequestBodySchema from "./pdf_request_body.json" assert { type: "json" };
9
+ const PdfRequestBodySchema = JSON.parse(
10
+ readFileSync("./pdf_request_body.json", { encoding: "utf-8" })
11
+ );
12
+
13
+ /**
14
+ * @typedef {Parameters<import("playwright-core").Page["pdf"]>[0]} PdfOptions
15
+ * @typedef {Parameters<import("playwright-core").Page["goto"]>[1]} GotoOptions
16
+ * @typedef {import("playwright-core").BrowserContextOptions} BrowserContextOptions
17
+ */
18
+
19
+ /**
20
+ * @typedef {Object} PdfRequestBody
21
+ * @property {string} url
22
+ * @property {PdfOptions} pdfOptions
23
+ * @property {BrowserContextOptions} browserContextOptions
24
+ * @property {GotoOptions} gotoOptions
25
+ */
26
+
27
+ export const defaultBrowserContextOptions = /** @type {const} */ ({
28
+ ignoreHTTPSErrors: true,
29
+ });
30
+ export const defaultGotoOptions = /** @type {const} */ ({
31
+ timeout: 60000,
32
+ waitUntil: "networkidle",
33
+ });
34
+
35
+ /**
36
+ *
37
+ * @param {string} url
38
+ * @param {PdfOptions} pdfOptions
39
+ * @param {BrowserContextOptions} browserContextOptions
40
+ * @param {GotoOptions} gotoOptions
41
+ * @returns Buffer
42
+ */
43
+ const createPDF = async (
44
+ url,
45
+ pdfOptions = {},
46
+ browserContextOptions = {},
47
+ gotoOptions = {}
48
+ ) => {
49
+ const browser = await chromium.launch();
50
+
51
+ try {
52
+ const context = await browser.newContext(browserContextOptions);
53
+ const page = await context.newPage();
54
+
55
+ const response = await page.goto(url, gotoOptions);
56
+ if (response?.ok()) {
57
+ return await page.pdf(pdfOptions);
58
+ }
59
+
60
+ return await Promise.reject({
61
+ error: true,
62
+ message: await response?.text(),
63
+ status: response?.status(),
64
+ statusText: response?.statusText(),
65
+ });
66
+ } finally {
67
+ await browser.close();
68
+ }
69
+ };
70
+
71
+ /**
72
+ *
73
+ * @param {import("fastify").FastifyServerOptions} opts
74
+ */
75
+ function build(opts = {}) {
76
+ const fastifyOptions = {
77
+ ...opts,
78
+ ajv: {
79
+ customOptions: {
80
+ // we want validation errors if someone passes an unknown option
81
+ removeAdditional: false,
82
+ // Don't adhere to ajvs strict types, as our schema is auto generated anyway
83
+ allowUnionTypes: true,
84
+ },
85
+ },
86
+ };
87
+ const app = fastify(fastifyOptions);
88
+
89
+ app.post(
90
+ "/pdf",
91
+ { schema: { body: PdfRequestBodySchema }, attachValidation: true },
92
+ /**
93
+ *
94
+ * @param {import('fastify').FastifyRequest<{Body: PdfRequestBody}>} request
95
+ * @param {import('fastify').FastifyReply} response
96
+ */
97
+ async (request, response) => {
98
+ if (request.validationError) {
99
+ response.code(400);
100
+
101
+ return {
102
+ error: true,
103
+ message: request.validationError.message,
104
+ };
105
+ }
106
+
107
+ const { url, pdfOptions, browserContextOptions, gotoOptions } =
108
+ request.body;
109
+
110
+ const pdfOpts = { ...pdfOptions };
111
+
112
+ const contextOpts = {
113
+ ...defaultBrowserContextOptions,
114
+ ...browserContextOptions,
115
+ };
116
+
117
+ const gotoOpts = {
118
+ ...defaultGotoOptions,
119
+ ...gotoOptions,
120
+ };
121
+
122
+ try {
123
+ return await createPDF(url, pdfOpts, contextOpts, gotoOpts);
124
+ } catch (error) {
125
+ // @ts-ignore
126
+ response.code(error.status ?? 500);
127
+
128
+ return error;
129
+ }
130
+ }
131
+ );
132
+
133
+ app.get("/health", async (_, response) => response.code(200).send());
134
+
135
+ return app;
136
+ }
137
+
138
+ export default build;
package/nodemon.json CHANGED
@@ -1,4 +1,9 @@
1
1
  {
2
- "watch": ["index.js", "schemas/pdf_request_body.json", "package-lock.json"],
2
+ "watch": [
3
+ "app.js",
4
+ "server.js",
5
+ "schemas/pdf_request_body.json",
6
+ "package-lock.json"
7
+ ],
3
8
  "ext": "js,json"
4
9
  }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "bits-to-dead-trees",
3
3
  "author": "Robin Mehner <robin@coding-robin.de>",
4
- "version": "1.5.1",
4
+ "version": "2.0.1",
5
5
  "description": "Turn HTML into PDF files",
6
6
  "repository": "github:rmehner/bits-to-dead-trees",
7
7
  "license": "Apache-2.0",
@@ -13,32 +13,36 @@
13
13
  "engines": {
14
14
  "node": ">= 16"
15
15
  },
16
- "main": "index.js",
16
+ "main": "server.js",
17
+ "type": "module",
17
18
  "scripts": {
18
19
  "check-format": "prettier --check **/*.{js,ts,json,md}",
19
20
  "lint": "eslint *.js",
20
21
  "format": "prettier --write **/*.{js,ts,json,md}",
21
- "start": "node index.js",
22
- "start:dev": "nodemon --inspect index.js",
23
- "dump-schemas": "typescript-json-schema types/schemas.ts PdfRequestBody --out pdf_request_body.json && npm run format",
22
+ "start": "node server.js",
23
+ "start:dev": "nodemon --inspect server.js",
24
+ "dump-schemas": "typescript-json-schema --noExtraProps --required --strictNullChecks types/schemas.ts PdfRequestBody --out pdf_request_body.json",
24
25
  "update-schemas": "npm run dump-schemas && npm run format",
25
26
  "postinstall": "npx playwright install chromium",
26
27
  "add-changeset": "changeset",
27
28
  "release": "changeset publish",
29
+ "test": "vitest --reporter verbose",
28
30
  "ts-check": "npx tsc --noEmit"
29
31
  },
30
32
  "dependencies": {
31
- "fastify": "^4.11.0",
32
- "playwright-core": "^1.29.2"
33
+ "fastify": "^4.12.0",
34
+ "playwright-core": "^1.30.0"
33
35
  },
34
36
  "devDependencies": {
35
37
  "@changesets/cli": "^2.26.0",
36
- "eslint": "^8.31.0",
38
+ "@types/lodash": "^4.14.191",
39
+ "eslint": "^8.33.0",
37
40
  "eslint-config-prettier": "^8.6.0",
38
41
  "nodemon": "^2.0.20",
39
42
  "pino-pretty": "^9.1.1",
40
- "prettier": "^2.8.2",
41
- "tslib": "^2.4.1",
42
- "typescript-json-schema": "^0.55.0"
43
+ "prettier": "^2.8.3",
44
+ "tslib": "^2.5.0",
45
+ "typescript-json-schema": "^0.55.0",
46
+ "vitest": "^0.28.3"
43
47
  }
44
48
  }