@enspirit/printit 0.0.0-stage → 2.2.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/LICENCE.md ADDED
@@ -0,0 +1,22 @@
1
+ # The MIT Licence
2
+
3
+ Copyright (c) 2022 - Enspirit SRL
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,55 @@
1
- # Temporary Holding Version
1
+ # Printit - A dockerized web api to convert HTML & CSS to PDF
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Printit is a docker image and web api to easily convert HTML
4
+ and CSS to PDF documents. It does not implement a rendering
5
+ engine, but encapsulates existing ones under an api:
6
+
7
+ - weasyprint
8
+ - princexml
9
+ - wkhtmltopdf
10
+
11
+ ## Hacking
12
+
13
+ ### Prerequisites
14
+
15
+ * make
16
+ * node.js >= 16
17
+ * ruby >= 2.7
18
+ * docker
19
+
20
+ ### Install dependencies
21
+
22
+ Install dependencies first (npm for code, ruby for integration
23
+ tests) then run:
24
+
25
+ ```
26
+ make deps
27
+ ```
28
+
29
+ ### Run the unit tests and TDD
30
+
31
+ To run unit tests:
32
+
33
+ ```
34
+ make tests.unit
35
+ ```
36
+
37
+ To TDD:
38
+
39
+ ```
40
+ make tests.unit.watch
41
+ ```
42
+
43
+ ### Run integration tests
44
+
45
+ Start the api first (it reloads on every change):
46
+
47
+ ```
48
+ make up
49
+ ```
50
+
51
+ Then launch the webspicy tests:
52
+
53
+ ```
54
+ make tests.integration
55
+ ```
@@ -0,0 +1,66 @@
1
+ {
2
+ "name": "@enspirit/printit",
3
+ "version": "2.2.1",
4
+ "description": "Printit",
5
+ "main": "dist/src/index.js",
6
+ "types": "dist/src/index.d.ts",
7
+ "scripts": {
8
+ "build": "rm -rf dist && tsc",
9
+ "build:watch": "tsc -w",
10
+ "test": "mocha tests/helpers.ts 'tests/**/*.spec.ts'",
11
+ "test:watch": "mocha tests/helpers.ts 'tests/**/*.spec.ts' --watch --watch-files src/**/*.ts,tests/**/*.ts",
12
+ "lint": "eslint src tests",
13
+ "preversion": "npm run build",
14
+ "prepublishOnly": "npm run build",
15
+ "start": "node dist/src/index.js",
16
+ "dev": "tsc-watch --onSuccess 'node ./dist/src/index.js'"
17
+ },
18
+ "keywords": [],
19
+ "author": "@enspirit srl",
20
+ "license": "MIT",
21
+ "files": [
22
+ "dist/src",
23
+ "dist/package.json"
24
+ ],
25
+ "exports": {
26
+ ".": "./dist/src/index.js"
27
+ },
28
+ "dependencies": {
29
+ "express": "^5.3.0",
30
+ "html-to-docx": "^1.8.0",
31
+ "js-yaml": "^4.3.2",
32
+ "sanitize-html": "^2.18.1",
33
+ "winston": "^3.19.0"
34
+ },
35
+ "devDependencies": {
36
+ "@eslint/js": "^10.0.0",
37
+ "@stylistic/eslint-plugin": "^5.10.0",
38
+ "@types/chai": "^5.2.3",
39
+ "@types/chai-as-promised": "^8.0.2",
40
+ "@types/express": "^5.0.6",
41
+ "@types/mocha": "^10.0.10",
42
+ "@types/node": "^24.19.2",
43
+ "@types/sanitize-html": "^2.16.2",
44
+ "@types/sinon": "^22.0.1",
45
+ "@types/sinon-chai": "^4.0.1",
46
+ "@types/supertest": "^7.2.1",
47
+ "chai": "^6.3.0",
48
+ "chai-as-promised": "^8.0.2",
49
+ "eslint": "^10.13.0",
50
+ "eslint-plugin-chai-friendly": "^1.2.1",
51
+ "eslint-plugin-no-only-tests": "^3.4.0",
52
+ "globals": "^17.13.0",
53
+ "mocha": "^12.0.3",
54
+ "sinon": "^22.1.0",
55
+ "sinon-chai": "^4.0.1",
56
+ "supertest": "^7.3.1",
57
+ "tsc-watch": "^7.2.1",
58
+ "tsx": "^4.23.15",
59
+ "typescript": "~6.0.3",
60
+ "typescript-eslint": "^8.72.0"
61
+ },
62
+ "engines": {
63
+ "npm": ">=10.0.0",
64
+ "node": ">=24.0.0"
65
+ }
66
+ }
@@ -0,0 +1,3 @@
1
+ import express from 'express';
2
+ import { Config } from '../types';
3
+ export declare const createApp: (config: Config) => express.Express;
@@ -0,0 +1,36 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.createApp = void 0;
7
+ const express_1 = __importDefault(require("express"));
8
+ const logger_1 = __importDefault(require("../logger"));
9
+ const loggers_1 = require("./middlewares/loggers");
10
+ const print_1 = __importDefault(require("./routes/print"));
11
+ const createApp = (config) => {
12
+ config.logger ||= logger_1.default;
13
+ const app = (0, express_1.default)();
14
+ // Install logger
15
+ app.use((req, _res, next) => {
16
+ req.config = config;
17
+ req.logger = config.logger;
18
+ next();
19
+ });
20
+ // Log requests
21
+ app.use(loggers_1.RequestLogger);
22
+ // Health-check
23
+ app.get('/health-check', (_req, res, _next) => {
24
+ res.sendStatus(204);
25
+ throw new Error();
26
+ });
27
+ app.use(express_1.default.static('public'));
28
+ app.use(express_1.default.json({}));
29
+ app.use(express_1.default.urlencoded({ extended: true }));
30
+ app.post('/', print_1.default);
31
+ // Log errors
32
+ app.use(loggers_1.ErrorLogger);
33
+ return app;
34
+ };
35
+ exports.createApp = createApp;
36
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/api/index.ts"],"names":[],"mappings":";;;;;;AAAA,sDAA8B;AAC9B,uDAAsC;AAEtC,mDAAmE;AACnE,2DAAwC;AAEjC,MAAM,SAAS,GAAG,CAAC,MAAc,EAAmB,EAAE;IAC3D,MAAM,CAAC,MAAM,KAAK,gBAAa,CAAC;IAChC,MAAM,GAAG,GAAG,IAAA,iBAAO,GAAE,CAAC;IAEtB,iBAAiB;IACjB,GAAG,CAAC,GAAG,CAAC,CAAC,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE;QAC1B,GAAG,CAAC,MAAM,GAAG,MAAM,CAAC;QACpB,GAAG,CAAC,MAAM,GAAG,MAAM,CAAC,MAAM,CAAC;QAC3B,IAAI,EAAE,CAAC;IACT,CAAC,CAAC,CAAC;IAEH,eAAe;IACf,GAAG,CAAC,GAAG,CAAC,uBAAa,CAAC,CAAC;IAEvB,eAAe;IACf,GAAG,CAAC,GAAG,CAAC,eAAe,EAAE,CAAC,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,EAAE;QAC5C,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;QACpB,MAAM,IAAI,KAAK,EAAE,CAAC;IACpB,CAAC,CAAC,CAAC;IAEH,GAAG,CAAC,GAAG,CAAC,iBAAO,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC,CAAC;IAClC,GAAG,CAAC,GAAG,CAAC,iBAAO,CAAC,IAAI,CAAC,EAAG,CAAC,CAAC,CAAC;IAC3B,GAAG,CAAC,GAAG,CAAC,iBAAO,CAAC,UAAU,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IAEhD,GAAG,CAAC,IAAI,CAAC,GAAG,EAAE,eAAU,CAAC,CAAC;IAE1B,aAAa;IACb,GAAG,CAAC,GAAG,CAAC,qBAAW,CAAC,CAAC;IAErB,OAAO,GAAG,CAAC;AACb,CAAC,CAAC;AA9BW,QAAA,SAAS,aA8BpB"}
@@ -0,0 +1,3 @@
1
+ import { Request, Response, NextFunction } from 'express';
2
+ export declare const RequestLogger: (req: Request, res: Response, next: NextFunction) => void;
3
+ export declare const ErrorLogger: (err: Error, req: Request, res: Response, _next: NextFunction) => void;
@@ -0,0 +1,32 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ErrorLogger = exports.RequestLogger = void 0;
4
+ const errors_1 = require("../../errors");
5
+ // Log requests once they finish (or fail)
6
+ const RequestLogger = (req, res, next) => {
7
+ let finished = false;
8
+ const start = Date.now();
9
+ const elapsed = () => `${Date.now() - start}ms`;
10
+ res.on('finish', () => {
11
+ finished = true;
12
+ req.logger.info(`${req.method} ${req.originalUrl} ${res.statusCode} ${elapsed()}`);
13
+ });
14
+ res.on('close', () => {
15
+ if (!finished) {
16
+ req.logger.warn(`${req.method} ${req.originalUrl} - ${elapsed()} - connection closed abruptly`);
17
+ }
18
+ });
19
+ next();
20
+ };
21
+ exports.RequestLogger = RequestLogger;
22
+ const ErrorLogger = (err, req, res, _next) => {
23
+ req.logger.error({ error: err.constructor.name, message: err.message, request: { path: req.path, method: req.method } });
24
+ if (err instanceof errors_1.PrintitError) {
25
+ res.status(err.httpCode).send({ error: err.message });
26
+ }
27
+ else {
28
+ res.status(500).send({ error: err.message });
29
+ }
30
+ };
31
+ exports.ErrorLogger = ErrorLogger;
32
+ //# sourceMappingURL=loggers.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"loggers.js","sourceRoot":"","sources":["../../../../src/api/middlewares/loggers.ts"],"names":[],"mappings":";;;AACA,yCAA4C;AAE5C,0CAA0C;AACnC,MAAM,aAAa,GAAG,CAAC,GAAY,EAAE,GAAa,EAAE,IAAkB,EAAE,EAAE;IAC/E,IAAI,QAAQ,GAAG,KAAK,CAAC;IACrB,MAAM,KAAK,GAAG,IAAI,CAAC,GAAG,EAAE,CAAC;IACzB,MAAM,OAAO,GAAG,GAAG,EAAE,CAAC,GAAG,IAAI,CAAC,GAAG,EAAE,GAAG,KAAK,IAAI,CAAC;IAEhD,GAAG,CAAC,EAAE,CAAC,QAAQ,EAAE,GAAG,EAAE;QACpB,QAAQ,GAAG,IAAI,CAAC;QAChB,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,WAAW,IAAI,GAAG,CAAC,UAAU,IAAI,OAAO,EAAE,EAAE,CAAC,CAAC;IACrF,CAAC,CAAC,CAAC;IAEH,GAAG,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE;QACnB,IAAI,CAAC,QAAQ,EAAE,CAAC;YACd,GAAG,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,GAAG,CAAC,MAAM,IAAI,GAAG,CAAC,WAAW,MAAM,OAAO,EAAE,+BAA+B,CAAC,CAAC;QAClG,CAAC;IACH,CAAC,CAAC,CAAC;IAEH,IAAI,EAAE,CAAC;AACT,CAAC,CAAC;AAjBW,QAAA,aAAa,iBAiBxB;AAEK,MAAM,WAAW,GAAG,CAAC,GAAU,EAAE,GAAY,EAAE,GAAa,EAAE,KAAmB,EAAE,EAAE;IAC1F,GAAG,CAAC,MAAM,CAAC,KAAK,CAAC,EAAE,KAAK,EAAE,GAAG,CAAC,WAAW,CAAC,IAAI,EAAE,OAAO,EAAE,GAAG,CAAC,OAAO,EAAE,OAAO,EAAE,EAAE,IAAI,EAAE,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IACzH,IAAI,GAAG,YAAY,qBAAY,EAAE,CAAC;QAChC,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;IACxD,CAAC;SAAM,CAAC;QACN,GAAG,CAAC,MAAM,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,EAAE,KAAK,EAAE,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC;IAC/C,CAAC;AACH,CAAC,CAAC;AAPW,QAAA,WAAW,eAOtB"}
@@ -0,0 +1,3 @@
1
+ import { Request, Response, NextFunction } from 'express';
2
+ declare const _default: (req: Request, res: Response, next: NextFunction) => Promise<void | Response<any, Record<string, any>>>;
3
+ export default _default;
@@ -0,0 +1,31 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const sanitizer_1 = __importDefault(require("../../lib/sanitizer"));
7
+ exports.default = async (req, res, next) => {
8
+ try {
9
+ if (!req.body.html) {
10
+ return res.sendStatus(400);
11
+ }
12
+ const handler = req.config.handlers.find(h => h.accepts(req));
13
+ if (!handler) {
14
+ return res.sendStatus(415);
15
+ }
16
+ if (req.body.attachment) {
17
+ const filename = encodeURIComponent(req.body.attachment);
18
+ res.setHeader('content-disposition', `attachment; filename="${filename}"`);
19
+ }
20
+ const input = {
21
+ ...req.body,
22
+ html: sanitizer_1.default.sanitize(req.body.html),
23
+ };
24
+ const converter = handler.getConverter(req.config, req, res);
25
+ await converter(input);
26
+ }
27
+ catch (error) {
28
+ return next(error);
29
+ }
30
+ };
31
+ //# sourceMappingURL=print.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"print.js","sourceRoot":"","sources":["../../../../src/api/routes/print.ts"],"names":[],"mappings":";;;;;AAEA,oEAA4C;AAE5C,kBAAe,KAAK,EAAE,GAAY,EAAE,GAAa,EAAE,IAAkB,EAAE,EAAE;IACvE,IAAI,CAAC;QAEH,IAAI,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;YACnB,OAAO,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;QAC7B,CAAC;QAED,MAAM,OAAO,GAAG,GAAG,CAAC,MAAM,CAAC,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC;QAC9D,IAAI,CAAC,OAAO,EAAE,CAAC;YACb,OAAO,GAAG,CAAC,UAAU,CAAC,GAAG,CAAC,CAAC;QAC7B,CAAC;QAED,IAAI,GAAG,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC;YACxB,MAAM,QAAQ,GAAG,kBAAkB,CAAC,GAAG,CAAC,IAAI,CAAC,UAAU,CAAC,CAAC;YACzD,GAAG,CAAC,SAAS,CAAC,qBAAqB,EAAE,yBAAyB,QAAQ,GAAG,CAAC,CAAC;QAC7E,CAAC;QAED,MAAM,KAAK,GAAe;YACxB,GAAG,GAAG,CAAC,IAAI;YACX,IAAI,EAAE,mBAAS,CAAC,QAAQ,CAAC,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC;SACxC,CAAC;QACF,MAAM,SAAS,GAAG,OAAO,CAAC,YAAY,CAAC,GAAG,CAAC,MAAM,EAAE,GAAG,EAAE,GAAG,CAAC,CAAC;QAC7D,MAAM,SAAS,CAAC,KAAK,CAAC,CAAC;IACzB,CAAC;IAAC,OAAO,KAAU,EAAE,CAAC;QACpB,OAAO,IAAI,CAAC,KAAK,CAAC,CAAC;IACrB,CAAC;AACH,CAAC,CAAC"}
@@ -0,0 +1,4 @@
1
+ export declare class PrintitError extends Error {
2
+ httpCode: number;
3
+ constructor(message: string);
4
+ }
@@ -0,0 +1,11 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.PrintitError = void 0;
4
+ class PrintitError extends Error {
5
+ httpCode = 500;
6
+ constructor(message) {
7
+ super(message);
8
+ }
9
+ }
10
+ exports.PrintitError = PrintitError;
11
+ //# sourceMappingURL=errors.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/errors.ts"],"names":[],"mappings":";;;AAAA,MAAa,YAAa,SAAQ,KAAK;IACrC,QAAQ,GAAW,GAAG,CAAC;IACvB,YAAY,OAAe;QACzB,KAAK,CAAC,OAAO,CAAC,CAAC;IACjB,CAAC;CACF;AALD,oCAKC"}
@@ -0,0 +1,3 @@
1
+ declare const version: string;
2
+ export { version };
3
+ export * from './errors';
@@ -0,0 +1,71 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __exportStar = (this && this.__exportStar) || function(m, exports) {
36
+ for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
37
+ };
38
+ var __importDefault = (this && this.__importDefault) || function (mod) {
39
+ return (mod && mod.__esModule) ? mod : { "default": mod };
40
+ };
41
+ Object.defineProperty(exports, "__esModule", { value: true });
42
+ exports.version = void 0;
43
+ const pack = __importStar(require("../package.json"));
44
+ const logger_1 = __importDefault(require("./logger"));
45
+ const fs_1 = __importDefault(require("fs"));
46
+ const api_1 = require("./api");
47
+ const HtmlToDocx_1 = __importDefault(require("./lib/handlers/HtmlToDocx"));
48
+ const Weasyprint_1 = __importDefault(require("./lib/handlers/Weasyprint"));
49
+ const yaml = require('js-yaml');
50
+ const { version } = pack;
51
+ exports.version = version;
52
+ __exportStar(require("./errors"), exports);
53
+ try {
54
+ const data = yaml.load(fs_1.default.readFileSync('config/printit.yml', 'utf8'));
55
+ const config = {
56
+ ...data,
57
+ logger: logger_1.default,
58
+ handlers: [
59
+ Weasyprint_1.default,
60
+ HtmlToDocx_1.default,
61
+ ],
62
+ };
63
+ const app = (0, api_1.createApp)(config);
64
+ app.listen(3000, () => {
65
+ console.log('Printit is now listening on http://0.0.0.0:3000');
66
+ });
67
+ }
68
+ catch (e) {
69
+ console.log(e);
70
+ }
71
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,sDAAwC;AACxC,sDAA8B;AAC9B,4CAAoB;AACpB,+BAAkC;AAClC,2EAAmD;AACnD,2EAAmD;AAEnD,MAAM,IAAI,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC;AAEhC,MAAM,EAAE,OAAO,EAAE,GAAG,IAAI,CAAC;AAChB,0BAAO;AAEhB,2CAAyB;AAEzB,IAAI,CAAC;IACH,MAAM,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC,YAAE,CAAC,YAAY,CAAC,oBAAoB,EAAE,MAAM,CAAC,CAAC,CAAC;IACtE,MAAM,MAAM,GAAW;QACrB,GAAG,IAAI;QACP,MAAM,EAAE,gBAAM;QACd,QAAQ,EAAE;YACR,oBAAU;YACV,oBAAU;SACX;KACF,CAAC;IACF,MAAM,GAAG,GAAG,IAAA,eAAS,EAAC,MAAM,CAAC,CAAC;IAE9B,GAAG,CAAC,MAAM,CAAC,IAAI,EAAE,GAAG,EAAE;QACpB,OAAO,CAAC,GAAG,CAAC,iDAAiD,CAAC,CAAC;IACjE,CAAC,CAAC,CAAC;AACL,CAAC;AAAC,OAAO,CAAC,EAAE,CAAC;IACX,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC;AACjB,CAAC"}
@@ -0,0 +1,3 @@
1
+ import { Handler } from '../../types';
2
+ declare const HtmlToDocx: Handler;
3
+ export default HtmlToDocx;
@@ -0,0 +1,21 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const html_to_docx_1 = __importDefault(require("html-to-docx"));
7
+ const types_1 = require("../../types");
8
+ const HtmlToDocx = {
9
+ accepts(req) {
10
+ return !!req.accepts(types_1.ContentType.docx);
11
+ },
12
+ getConverter(config, req, res) {
13
+ return async (input) => {
14
+ res.setHeader('content-type', types_1.ContentType.docx);
15
+ const html = await (0, html_to_docx_1.default)(input.html, '', config.html2docx || {});
16
+ res.send(html);
17
+ };
18
+ },
19
+ };
20
+ exports.default = HtmlToDocx;
21
+ //# sourceMappingURL=HtmlToDocx.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"HtmlToDocx.js","sourceRoot":"","sources":["../../../../src/lib/handlers/HtmlToDocx.ts"],"names":[],"mappings":";;;;;AAAA,gEAAqC;AACrC,uCAA8D;AAE9D,MAAM,UAAU,GAAY;IAC1B,OAAO,CAAC,GAAG;QACT,OAAO,CAAC,CAAC,GAAG,CAAC,OAAO,CAAC,mBAAW,CAAC,IAAI,CAAC,CAAC;IACzC,CAAC;IACD,YAAY,CAAC,MAAM,EAAE,GAAG,EAAE,GAAG;QAC3B,OAAO,KAAK,EAAE,KAAK,EAAE,EAAE;YACrB,GAAG,CAAC,SAAS,CAAC,cAAc,EAAE,mBAAW,CAAC,IAAI,CAAC,CAAC;YAChD,MAAM,IAAI,GAAG,MAAM,IAAA,sBAAS,EAAC,KAAK,CAAC,IAAI,EAAE,EAAE,EAAE,MAAM,CAAC,SAAS,IAAI,EAAE,CAAC,CAAC;YAErE,GAAG,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;QACjB,CAAC,CAAC;IACJ,CAAC;CACF,CAAC;AAEF,kBAAe,UAAU,CAAC"}
@@ -0,0 +1,9 @@
1
+ import { Handler, WeasyprintConfig } from '../../types';
2
+ export declare class WeasyprintParams {
3
+ #private;
4
+ constructor(config?: WeasyprintConfig);
5
+ toArgs(): string[];
6
+ pairToArgs(pair: any): any;
7
+ }
8
+ declare const Weasyprint: Handler;
9
+ export default Weasyprint;
@@ -0,0 +1,60 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.WeasyprintParams = void 0;
4
+ const types_1 = require("../../types");
5
+ const child_process_1 = require("child_process");
6
+ class WeasyprintParams {
7
+ #config;
8
+ constructor(config) {
9
+ this.#config = config || {};
10
+ }
11
+ toArgs() {
12
+ return Object
13
+ .entries(this.#config)
14
+ .reduce((memo, pair) => {
15
+ return memo.concat(this.pairToArgs(pair));
16
+ }, [])
17
+ .concat(['-', '-']);
18
+ }
19
+ pairToArgs(pair) {
20
+ if (Array.isArray(pair[1])) {
21
+ return pair[1].reduce((memo, one) => memo.concat([`-${pair[0]}`, one]), []);
22
+ }
23
+ else {
24
+ return [`-${pair[0]}`, pair[1]];
25
+ }
26
+ }
27
+ }
28
+ exports.WeasyprintParams = WeasyprintParams;
29
+ const Weasyprint = {
30
+ accepts(req) {
31
+ return !!req.accepts(types_1.ContentType.pdf);
32
+ },
33
+ getConverter(config, req, res) {
34
+ const wpParams = new WeasyprintParams(config.weasyprint);
35
+ const command = 'weasyprint';
36
+ const args = wpParams.toArgs();
37
+ return function (input) {
38
+ return new Promise((resolve, reject) => {
39
+ res.setHeader('content-type', types_1.ContentType.pdf);
40
+ req.logger.info(`${command} ${args.join(' ')}`);
41
+ const w = (0, child_process_1.spawn)(command, args);
42
+ w.stdout.pipe(res);
43
+ w.stderr.on('data', (data) => {
44
+ req.logger.error(data.toString());
45
+ });
46
+ w.on('error', (err) => {
47
+ reject(err);
48
+ });
49
+ w.on('close', () => {
50
+ resolve();
51
+ });
52
+ w.stdin.write(input.html);
53
+ w.stdin.end();
54
+ res.status(200);
55
+ });
56
+ };
57
+ },
58
+ };
59
+ exports.default = Weasyprint;
60
+ //# sourceMappingURL=Weasyprint.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"Weasyprint.js","sourceRoot":"","sources":["../../../../src/lib/handlers/Weasyprint.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,4 @@
1
+ declare const _default: {
2
+ sanitize(html: string): string;
3
+ };
4
+ export default _default;
@@ -0,0 +1,48 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const sanitize_html_1 = __importDefault(require("sanitize-html"));
7
+ const OPTIONS = {
8
+ allowedTags: [
9
+ 'address', 'article', 'aside', 'footer', 'header', 'h1', 'h2', 'h3', 'h4',
10
+ 'h5', 'h6', 'hgroup', 'main', 'nav', 'section', 'blockquote', 'dd', 'div',
11
+ 'dl', 'dt', 'figcaption', 'figure', 'hr', 'li', 'main', 'ol', 'p', 'pre',
12
+ 'ul', 'a', 'abbr', 'b', 'bdi', 'bdo', 'br', 'cite', 'code', 'data', 'dfn',
13
+ 'em', 'i', 'kbd', 'mark', 'q', 'rb', 'rp', 'rt', 'rtc', 'ruby', 's', 'samp',
14
+ 'small', 'span', 'strong', 'sub', 'sup', 'time', 'u', 'var', 'wbr', 'caption',
15
+ 'col', 'colgroup', 'table', 'tbody', 'td', 'tfoot', 'th', 'thead', 'tr', 'img',
16
+ ],
17
+ // disallowedTagsMode: 'discard', // commented because of a TypeScript error, but default value used
18
+ allowedAttributes: {
19
+ a: ['href', 'name', 'target'],
20
+ // We don't currently allow img itself by default, but
21
+ // these attributes would make sense if we did.
22
+ img: ['src', 'srcset', 'alt', 'title', 'width', 'height', 'loading'],
23
+ // `id` is what makes a document's own `#section` links resolve: strip it
24
+ // and the converter has nothing to point an internal link at, so a table
25
+ // of contents comes out dead. It carries no script and no navigation of
26
+ // its own — DOM clobbering is a browser concern, and this HTML only ever
27
+ // reaches a document converter.
28
+ // `dir` is what decides which way a paragraph runs. A renderer that
29
+ // marks its output `dir="auto"` had the mark stripped here and Arabic or
30
+ // Hebrew came out laid out as if it were English. `bdo` is already an
31
+ // allowed tag and means nothing at all without it.
32
+ '*': ['class', 'id', 'dir'],
33
+ },
34
+ // Lots of these won't come up by default because we don't allow them
35
+ selfClosing: ['img', 'br', 'hr', 'area', 'base', 'basefont', 'input', 'link', 'meta'],
36
+ // URL schemes we permit
37
+ allowedSchemes: ['http', 'https', 'ftp', 'mailto', 'tel'],
38
+ allowedSchemesByTag: {},
39
+ allowedSchemesAppliedToAttributes: ['href', 'src', 'cite'],
40
+ allowProtocolRelative: true,
41
+ enforceHtmlBoundary: false,
42
+ };
43
+ exports.default = {
44
+ sanitize(html) {
45
+ return (0, sanitize_html_1.default)(html, OPTIONS);
46
+ },
47
+ };
48
+ //# sourceMappingURL=sanitizer.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"sanitizer.js","sourceRoot":"","sources":["../../../src/lib/sanitizer.ts"],"names":[],"mappings":";;;;;AAAA,kEAAyC;AAEzC,MAAM,OAAO,GAAG;IACd,WAAW,EAAE;QACX,SAAS,EAAE,SAAS,EAAE,OAAO,EAAE,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI;QACzE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,IAAI,EAAE,KAAK;QACzE,IAAI,EAAE,IAAI,EAAE,YAAY,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,GAAG,EAAE,KAAK;QACxE,IAAI,EAAE,GAAG,EAAE,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,KAAK;QACzE,IAAI,EAAE,GAAG,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,MAAM;QAC3E,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,KAAK,EAAE,KAAK,EAAE,SAAS;QAC7E,KAAK,EAAE,UAAU,EAAE,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK;KAC/E;IACD,oGAAoG;IACpG,iBAAiB,EAAE;QACjB,CAAC,EAAE,CAAC,MAAM,EAAE,MAAM,EAAE,QAAQ,CAAC;QAC7B,sDAAsD;QACtD,+CAA+C;QAC/C,GAAG,EAAE,CAAC,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,OAAO,EAAE,OAAO,EAAE,QAAQ,EAAE,SAAS,CAAC;QACpE,yEAAyE;QACzE,yEAAyE;QACzE,wEAAwE;QACxE,yEAAyE;QACzE,gCAAgC;QAChC,oEAAoE;QACpE,yEAAyE;QACzE,sEAAsE;QACtE,mDAAmD;QACnD,GAAG,EAAE,CAAC,OAAO,EAAE,IAAI,EAAE,KAAK,CAAC;KAC5B;IACD,qEAAqE;IACrE,WAAW,EAAE,CAAC,KAAK,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,EAAE,UAAU,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,CAAC;IACrF,wBAAwB;IACxB,cAAc,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,CAAC;IACzD,mBAAmB,EAAE,EAAE;IACvB,iCAAiC,EAAE,CAAC,MAAM,EAAE,KAAK,EAAE,MAAM,CAAC;IAC1D,qBAAqB,EAAE,IAAI;IAC3B,mBAAmB,EAAE,KAAK;CAC3B,CAAC;AAEF,kBAAe;IACb,QAAQ,CAAC,IAAY;QACnB,OAAO,IAAA,uBAAY,EAAC,IAAI,EAAE,OAAO,CAAC,CAAC;IACrC,CAAC;CACF,CAAC"}
@@ -0,0 +1,3 @@
1
+ import winston from 'winston';
2
+ declare const logger: winston.Logger;
3
+ export default logger;
@@ -0,0 +1,15 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ const winston_1 = __importDefault(require("winston"));
7
+ const logger = winston_1.default.createLogger({
8
+ level: 'info',
9
+ format: winston_1.default.format.combine(winston_1.default.format.timestamp(), winston_1.default.format.json()),
10
+ transports: [
11
+ new winston_1.default.transports.Console({}),
12
+ ],
13
+ });
14
+ exports.default = logger;
15
+ //# sourceMappingURL=logger.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"logger.js","sourceRoot":"","sources":["../../src/logger.ts"],"names":[],"mappings":";;;;;AAAA,sDAA8B;AAE9B,MAAM,MAAM,GAAG,iBAAO,CAAC,YAAY,CAAC;IAClC,KAAK,EAAE,MAAM;IACb,MAAM,EAAE,iBAAO,CAAC,MAAM,CAAC,OAAO,CAC5B,iBAAO,CAAC,MAAM,CAAC,SAAS,EAAE,EAC1B,iBAAO,CAAC,MAAM,CAAC,IAAI,EAAE,CACtB;IACD,UAAU,EAAE;QACV,IAAI,iBAAO,CAAC,UAAU,CAAC,OAAO,CAAC,EAC9B,CAAC;KACH;CACF,CAAC,CAAC;AAEH,kBAAe,MAAM,CAAC"}
@@ -0,0 +1,32 @@
1
+ import type { Request, Response } from 'express';
2
+ import { DocumentOptions as HTMLDocumentOptions } from 'html-to-docx';
3
+ import { Logger } from 'winston';
4
+ declare global {
5
+ namespace Express {
6
+ interface Request {
7
+ logger: Logger;
8
+ config: Config;
9
+ }
10
+ }
11
+ }
12
+ export type WeasyprintConfig = any;
13
+ export type Config = {
14
+ logger: Logger;
15
+ handlers: Array<Handler>;
16
+ handler?: string;
17
+ weasyprint?: WeasyprintConfig;
18
+ html2docx?: HTMLDocumentOptions;
19
+ };
20
+ export type PrintInput = {
21
+ html: string;
22
+ attachment?: string;
23
+ };
24
+ export type Converter = (input: PrintInput) => Promise<void>;
25
+ export type Handler = {
26
+ accepts(req: Request): Boolean;
27
+ getConverter(config: Config, req: Request, res: Response): Converter;
28
+ };
29
+ export declare enum ContentType {
30
+ docx = "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
31
+ pdf = "application/pdf"
32
+ }
@@ -0,0 +1,9 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.ContentType = void 0;
4
+ var ContentType;
5
+ (function (ContentType) {
6
+ ContentType["docx"] = "application/vnd.openxmlformats-officedocument.wordprocessingml.document";
7
+ ContentType["pdf"] = "application/pdf";
8
+ })(ContentType || (exports.ContentType = ContentType = {}));
9
+ //# sourceMappingURL=types.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/types.ts"],"names":[],"mappings":";;;AAmCA,IAAY,WAGX;AAHD,WAAY,WAAW;IACrB,+FAAgF,CAAA;IAChF,sCAAuB,CAAA;AACzB,CAAC,EAHW,WAAW,2BAAX,WAAW,QAGtB"}
package/package.json CHANGED
@@ -1,6 +1,66 @@
1
1
  {
2
2
  "name": "@enspirit/printit",
3
- "version": "0.0.0-stage",
4
- "stub": true,
5
- "description": "Temporary package placeholder for staged publishing"
6
- }
3
+ "version": "2.2.1",
4
+ "description": "Printit",
5
+ "main": "dist/src/index.js",
6
+ "types": "dist/src/index.d.ts",
7
+ "scripts": {
8
+ "build": "rm -rf dist && tsc",
9
+ "build:watch": "tsc -w",
10
+ "test": "mocha tests/helpers.ts 'tests/**/*.spec.ts'",
11
+ "test:watch": "mocha tests/helpers.ts 'tests/**/*.spec.ts' --watch --watch-files src/**/*.ts,tests/**/*.ts",
12
+ "lint": "eslint src tests",
13
+ "preversion": "npm run build",
14
+ "prepublishOnly": "npm run build",
15
+ "start": "node dist/src/index.js",
16
+ "dev": "tsc-watch --onSuccess 'node ./dist/src/index.js'"
17
+ },
18
+ "keywords": [],
19
+ "author": "@enspirit srl",
20
+ "license": "MIT",
21
+ "files": [
22
+ "dist/src",
23
+ "dist/package.json"
24
+ ],
25
+ "exports": {
26
+ ".": "./dist/src/index.js"
27
+ },
28
+ "dependencies": {
29
+ "express": "^5.3.0",
30
+ "html-to-docx": "^1.8.0",
31
+ "js-yaml": "^4.3.2",
32
+ "sanitize-html": "^2.18.1",
33
+ "winston": "^3.19.0"
34
+ },
35
+ "devDependencies": {
36
+ "@eslint/js": "^10.0.0",
37
+ "@stylistic/eslint-plugin": "^5.10.0",
38
+ "@types/chai": "^5.2.3",
39
+ "@types/chai-as-promised": "^8.0.2",
40
+ "@types/express": "^5.0.6",
41
+ "@types/mocha": "^10.0.10",
42
+ "@types/node": "^24.19.2",
43
+ "@types/sanitize-html": "^2.16.2",
44
+ "@types/sinon": "^22.0.1",
45
+ "@types/sinon-chai": "^4.0.1",
46
+ "@types/supertest": "^7.2.1",
47
+ "chai": "^6.3.0",
48
+ "chai-as-promised": "^8.0.2",
49
+ "eslint": "^10.13.0",
50
+ "eslint-plugin-chai-friendly": "^1.2.1",
51
+ "eslint-plugin-no-only-tests": "^3.4.0",
52
+ "globals": "^17.13.0",
53
+ "mocha": "^12.0.3",
54
+ "sinon": "^22.1.0",
55
+ "sinon-chai": "^4.0.1",
56
+ "supertest": "^7.3.1",
57
+ "tsc-watch": "^7.2.1",
58
+ "tsx": "^4.23.15",
59
+ "typescript": "~6.0.3",
60
+ "typescript-eslint": "^8.72.0"
61
+ },
62
+ "engines": {
63
+ "npm": ">=10.0.0",
64
+ "node": ">=24.0.0"
65
+ }
66
+ }