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.
- package/.github/workflows/ci.yml +3 -0
- package/CHANGELOG.md +44 -0
- package/Dockerfile +3 -3
- package/README.md +8 -2
- package/app.js +138 -0
- package/nodemon.json +6 -1
- package/package.json +15 -11
- package/pdf_request_body.json +539 -42
- package/server.js +29 -0
- package/test/app.test.ts +249 -0
- package/test/smoke.test.js +36 -0
- package/tsconfig.json +2 -0
- package/types/schemas.ts +5 -2
- package/index.js +0 -95
package/.github/workflows/ci.yml
CHANGED
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.
|
|
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 ["
|
|
18
|
+
COPY ["server.js", "app.js", "pdf_request_body.json", "./"]
|
|
19
19
|
|
|
20
20
|
ENTRYPOINT ["tini", "-v", "--"]
|
|
21
|
-
CMD ["node", "
|
|
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
|
-
"
|
|
37
|
+
"pdfOptions": {},
|
|
38
|
+
"gotoOptions": {},
|
|
39
|
+
"browserContextOptions": {}
|
|
38
40
|
}
|
|
39
41
|
```
|
|
40
42
|
|
|
41
|
-
`
|
|
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
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": "
|
|
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": "
|
|
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
|
|
22
|
-
"start:dev": "nodemon --inspect
|
|
23
|
-
"dump-schemas": "typescript-json-schema types/schemas.ts PdfRequestBody --out pdf_request_body.json
|
|
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.
|
|
32
|
-
"playwright-core": "^1.
|
|
33
|
+
"fastify": "^4.12.0",
|
|
34
|
+
"playwright-core": "^1.30.0"
|
|
33
35
|
},
|
|
34
36
|
"devDependencies": {
|
|
35
37
|
"@changesets/cli": "^2.26.0",
|
|
36
|
-
"
|
|
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.
|
|
41
|
-
"tslib": "^2.
|
|
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
|
}
|