From 9d49245efa8d65a361b9488ef064267ccd88b5df Mon Sep 17 00:00:00 2001 From: Matt Date: Wed, 9 Jul 2025 22:38:46 -0400 Subject: [PATCH] refactor: replace Puppeteer with PDFKit for PDF generation - Updated package.json to remove Puppeteer and add PDFKit and its types. - Refactored generate-pdf.ts to utilize PDFKit for generating PDFs instead of Puppeteer. - Implemented functions to add headers, summaries, expense tables, and receipt images using PDFKit. - Removed HTML content generation and related functions, streamlining the PDF generation process. - Added error handling for receipt image fetching and improved logging. --- package-lock.json | 726 +++------------------------- package.json | 5 +- server/api/expenses/generate-pdf.ts | 580 +++++++++++++++------- 3 files changed, 459 insertions(+), 852 deletions(-) diff --git a/package-lock.json b/package-lock.json index 78f612f..b36546d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -22,7 +22,7 @@ "nodemailer": "^7.0.3", "nuxt": "^3.15.4", "nuxt-directus": "^5.7.0", - "puppeteer": "^24.12.1", + "pdfkit": "^0.17.1", "sharp": "^0.34.2", "v-phone-input": "^4.4.2", "vue": "latest", @@ -34,7 +34,8 @@ "@types/imap": "^0.8.42", "@types/mailparser": "^3.4.6", "@types/mime-types": "^3.0.1", - "@types/nodemailer": "^6.4.17" + "@types/nodemailer": "^6.4.17", + "@types/pdfkit": "^0.14.0" } }, "node_modules/@ampproject/remapping": { @@ -3751,27 +3752,6 @@ "integrity": "sha512-8LduaNlMZGwdZ6qWrKlfa+2M4gahzFkprZiAt2TF8uS0qQgBizKXpXURqvTJ4WtmupWxaLqjRb2UCTe72mu+Aw==", "license": "MIT" }, - "node_modules/@puppeteer/browsers": { - "version": "2.10.5", - "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.10.5.tgz", - "integrity": "sha512-eifa0o+i8dERnngJwKrfp3dEq7ia5XFyoqB17S4gK8GhsQE4/P8nxOfQSE0zQHxzzLo/cmF+7+ywEQ7wK7Fb+w==", - "license": "Apache-2.0", - "dependencies": { - "debug": "^4.4.1", - "extract-zip": "^2.0.1", - "progress": "^2.0.3", - "proxy-agent": "^6.5.0", - "semver": "^7.7.2", - "tar-fs": "^3.0.8", - "yargs": "^17.7.2" - }, - "bin": { - "browsers": "lib/cjs/main-cli.js" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/@rc-component/async-validator": { "version": "5.0.4", "resolved": "https://registry.npmjs.org/@rc-component/async-validator/-/async-validator-5.0.4.tgz", @@ -4488,12 +4468,6 @@ "tslib": "^2.8.0" } }, - "node_modules/@tootallnate/quickjs-emscripten": { - "version": "0.23.0", - "resolved": "https://registry.npmjs.org/@tootallnate/quickjs-emscripten/-/quickjs-emscripten-0.23.0.tgz", - "integrity": "sha512-C5Mc6rdnsaJDjO3UpGW/CQTHtCKaYlScZTly4JIu97Jxo/odCiH0ITnDXSJPTOrEKk/ycSZ0AOgTmkDtkOsvIA==", - "license": "MIT" - }, "node_modules/@trysound/sax": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/@trysound/sax/-/sax-0.2.0.tgz", @@ -4602,6 +4576,16 @@ "integrity": "sha512-LriObC2+KYZD3FzCrgWGv/qufdUy4eXrxcLgQMfYXgPbLIecKIsVBaQgUPmxSSLcjmYbDTQbMgr6qr6l/eb7Bg==", "license": "MIT" }, + "node_modules/@types/pdfkit": { + "version": "0.14.0", + "resolved": "https://registry.npmjs.org/@types/pdfkit/-/pdfkit-0.14.0.tgz", + "integrity": "sha512-X94hoZVr9dNfV23roeXRm57AWS+AOMak3gq2wZvn4TXiLvXE8+TrYaM5IkMyZbGRw49jEqI49rP/UVL3+C3Svg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/resolve": { "version": "1.20.2", "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", @@ -4614,16 +4598,6 @@ "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", "license": "MIT" }, - "node_modules/@types/yauzl": { - "version": "2.10.3", - "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.10.3.tgz", - "integrity": "sha512-oJoftv0LSuaDZE3Le4DbKX+KS9G36NzOeSap90UIK0yMA/NhKJhqlSGtNDORNRaIbQfzjXDrQa0ytJ6mNRGz/Q==", - "license": "MIT", - "optional": true, - "dependencies": { - "@types/node": "*" - } - }, "node_modules/@unhead/dom": { "version": "1.11.18", "resolved": "https://registry.npmjs.org/@unhead/dom/-/dom-1.11.18.tgz", @@ -5483,18 +5457,6 @@ "node": ">=16.14.0" } }, - "node_modules/ast-types": { - "version": "0.13.4", - "resolved": "https://registry.npmjs.org/ast-types/-/ast-types-0.13.4.tgz", - "integrity": "sha512-x1FCFnFifvYDDzTaLII71vG5uvDwgtmDTEVWAxrgeiR8VjMONcCXJx7E+USjDtHlwFmt9MysbqgF9b9Vjr6w+w==", - "license": "MIT", - "dependencies": { - "tslib": "^2.0.1" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/ast-walker-scope": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/ast-walker-scope/-/ast-walker-scope-0.6.2.tgz", @@ -5696,71 +5658,6 @@ "license": "Apache-2.0", "optional": true }, - "node_modules/bare-fs": { - "version": "4.1.6", - "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.1.6.tgz", - "integrity": "sha512-25RsLF33BqooOEFNdMcEhMpJy8EoR88zSMrnOQOaM3USnOK2VmaJ1uaQEwPA6AQjrv1lXChScosN6CzbwbO9OQ==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "bare-events": "^2.5.4", - "bare-path": "^3.0.0", - "bare-stream": "^2.6.4" - }, - "engines": { - "bare": ">=1.16.0" - }, - "peerDependencies": { - "bare-buffer": "*" - }, - "peerDependenciesMeta": { - "bare-buffer": { - "optional": true - } - } - }, - "node_modules/bare-os": { - "version": "3.6.1", - "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.6.1.tgz", - "integrity": "sha512-uaIjxokhFidJP+bmmvKSgiMzj2sV5GPHaZVAIktcxcpCyBFFWO+YlikVAdhmUo2vYFvFhOXIAlldqV29L8126g==", - "license": "Apache-2.0", - "optional": true, - "engines": { - "bare": ">=1.14.0" - } - }, - "node_modules/bare-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/bare-path/-/bare-path-3.0.0.tgz", - "integrity": "sha512-tyfW2cQcB5NN8Saijrhqn0Zh7AnFNsnczRcuWODH0eYAXBsJ5gVxAUuNr7tsHSC6IZ77cA0SitzT+s47kot8Mw==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "bare-os": "^3.0.1" - } - }, - "node_modules/bare-stream": { - "version": "2.6.5", - "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.6.5.tgz", - "integrity": "sha512-jSmxKJNJmHySi6hC42zlZnq00rga4jjxcgNZjY9N5WlOe/iOoGRtdwGsHzQv2RlH2KOYMwGUXhf2zXd32BA9RA==", - "license": "Apache-2.0", - "optional": true, - "dependencies": { - "streamx": "^2.21.0" - }, - "peerDependencies": { - "bare-buffer": "*", - "bare-events": "*" - }, - "peerDependenciesMeta": { - "bare-buffer": { - "optional": true - }, - "bare-events": { - "optional": true - } - } - }, "node_modules/base64-js": { "version": "1.5.1", "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", @@ -5781,15 +5678,6 @@ ], "license": "MIT" }, - "node_modules/basic-ftp": { - "version": "5.0.5", - "resolved": "https://registry.npmjs.org/basic-ftp/-/basic-ftp-5.0.5.tgz", - "integrity": "sha512-4Bcg1P8xhUuqcii/S0Z9wiHIrQVPMermM1any+MX5GeGD7faD3/msQUDGLol9wOcz4/jbg/WJnGqoJF6LiBdtg==", - "license": "MIT", - "engines": { - "node": ">=10.0.0" - } - }, "node_modules/binary-extensions": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", @@ -6087,15 +5975,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/callsites": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", - "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, "node_modules/caniuse-api": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/caniuse-api/-/caniuse-api-3.0.0.tgz", @@ -6186,19 +6065,6 @@ "node": ">=10" } }, - "node_modules/chromium-bidi": { - "version": "5.1.0", - "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-5.1.0.tgz", - "integrity": "sha512-9MSRhWRVoRPDG0TgzkHrshFSJJNZzfY5UFqUMuksg7zL1yoZIZ3jLB0YAgHclbiAxPI86pBnwDX1tbzoiV8aFw==", - "license": "Apache-2.0", - "dependencies": { - "mitt": "^3.0.1", - "zod": "^3.24.1" - }, - "peerDependencies": { - "devtools-protocol": "*" - } - }, "node_modules/citty": { "version": "0.1.6", "resolved": "https://registry.npmjs.org/citty/-/citty-0.1.6.tgz", @@ -6540,50 +6406,6 @@ "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", "license": "MIT" }, - "node_modules/cosmiconfig": { - "version": "9.0.0", - "resolved": "https://registry.npmjs.org/cosmiconfig/-/cosmiconfig-9.0.0.tgz", - "integrity": "sha512-itvL5h8RETACmOTFc4UfIyB2RfEHi71Ax6E/PivVxq9NseKbOWpeyHEOIbmAw1rs8Ak0VursQNww7lf7YtUwzg==", - "license": "MIT", - "dependencies": { - "env-paths": "^2.2.1", - "import-fresh": "^3.3.0", - "js-yaml": "^4.1.0", - "parse-json": "^5.2.0" - }, - "engines": { - "node": ">=14" - }, - "funding": { - "url": "https://github.com/sponsors/d-fischer" - }, - "peerDependencies": { - "typescript": ">=4.9.5" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/cosmiconfig/node_modules/parse-json": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", - "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==", - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.0.0", - "error-ex": "^1.3.1", - "json-parse-even-better-errors": "^2.3.0", - "lines-and-columns": "^1.1.6" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/countries-list": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/countries-list/-/countries-list-3.1.1.tgz", @@ -6682,6 +6504,12 @@ "uncrypto": "^0.1.3" } }, + "node_modules/crypto-js": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", + "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==", + "license": "MIT" + }, "node_modules/crypto-random-string": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/crypto-random-string/-/crypto-random-string-2.0.0.tgz", @@ -6882,15 +6710,6 @@ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", "license": "MIT" }, - "node_modules/data-uri-to-buffer": { - "version": "6.0.2", - "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-6.0.2.tgz", - "integrity": "sha512-7hvf7/GW8e86rW0ptuwS3OcBGDjIi6SZva7hCyWC0yYry2cOPmLIjXAUHI6DK2HsnwJd9ifmt57i8eV2n4YNpw==", - "license": "MIT", - "engines": { - "node": ">= 14" - } - }, "node_modules/data-view-buffer": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", @@ -7106,20 +6925,6 @@ "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==", "license": "MIT" }, - "node_modules/degenerator": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/degenerator/-/degenerator-5.0.1.tgz", - "integrity": "sha512-TllpMR/t0M5sqCXfj85i4XaAzxmS5tVA16dqvdkMwGmzI+dXLXnw3J+3Vdv7VKw+ThlTMboK6i9rnZ6Nntj5CQ==", - "license": "MIT", - "dependencies": { - "ast-types": "^0.13.4", - "escodegen": "^2.1.0", - "esprima": "^4.0.1" - }, - "engines": { - "node": ">= 14" - } - }, "node_modules/denque": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz", @@ -7172,12 +6977,6 @@ "integrity": "sha512-maua5KUiapvEwiEAe+XnlZ3Rh0GD+qI1J/nb9vrJc3muPXvcF/8gXYTWF76+5DAqHyDUtOIImEuo0YKE9mshVw==", "license": "MIT" }, - "node_modules/devtools-protocol": { - "version": "0.0.1464554", - "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1464554.tgz", - "integrity": "sha512-CAoP3lYfwAGQTaAXYvA6JZR0fjGUb7qec1qf4mToyoH2TZgUFeIqYcjh6f9jNuhHfuZiEdH+PONHYrLhRQX6aw==", - "license": "BSD-3-Clause" - }, "node_modules/dezalgo": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/dezalgo/-/dezalgo-1.0.4.tgz", @@ -7369,15 +7168,6 @@ "node": ">=8.10.0" } }, - "node_modules/end-of-stream": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", - "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", - "license": "MIT", - "dependencies": { - "once": "^1.4.0" - } - }, "node_modules/enhanced-resolve": { "version": "5.18.0", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.0.tgz", @@ -7403,30 +7193,6 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, - "node_modules/env-paths": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", - "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/error-ex": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz", - "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==", - "license": "MIT", - "dependencies": { - "is-arrayish": "^0.2.1" - } - }, - "node_modules/error-ex/node_modules/is-arrayish": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz", - "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==", - "license": "MIT" - }, "node_modules/error-stack-parser-es": { "version": "0.1.5", "resolved": "https://registry.npmjs.org/error-stack-parser-es/-/error-stack-parser-es-0.1.5.tgz", @@ -7642,59 +7408,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/escodegen": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-2.1.0.tgz", - "integrity": "sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==", - "license": "BSD-2-Clause", - "dependencies": { - "esprima": "^4.0.1", - "estraverse": "^5.2.0", - "esutils": "^2.0.2" - }, - "bin": { - "escodegen": "bin/escodegen.js", - "esgenerate": "bin/esgenerate.js" - }, - "engines": { - "node": ">=6.0" - }, - "optionalDependencies": { - "source-map": "~0.6.1" - } - }, - "node_modules/escodegen/node_modules/source-map": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", - "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", - "license": "BSD-3-Clause", - "optional": true, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/esprima": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", - "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", - "license": "BSD-2-Clause", - "bin": { - "esparse": "bin/esparse.js", - "esvalidate": "bin/esvalidate.js" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/estraverse": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", - "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", - "license": "BSD-2-Clause", - "engines": { - "node": ">=4.0" - } - }, "node_modules/estree-walker": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", @@ -7787,41 +7500,6 @@ "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", "license": "MIT" }, - "node_modules/extract-zip": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", - "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", - "license": "BSD-2-Clause", - "dependencies": { - "debug": "^4.1.1", - "get-stream": "^5.1.0", - "yauzl": "^2.10.0" - }, - "bin": { - "extract-zip": "cli.js" - }, - "engines": { - "node": ">= 10.17.0" - }, - "optionalDependencies": { - "@types/yauzl": "^2.9.1" - } - }, - "node_modules/extract-zip/node_modules/get-stream": { - "version": "5.2.0", - "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", - "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", - "license": "MIT", - "dependencies": { - "pump": "^3.0.0" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -7908,15 +7586,6 @@ "reusify": "^1.0.4" } }, - "node_modules/fd-slicer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", - "integrity": "sha512-cE1qsB/VwyQozZ+q1dGxR8LBYNZeofhEdUNGSMbQD3Gw2lAzX9Zb3uIU6Ebc/Fmyjo9AWWfnn0AUCHqtevs/8g==", - "license": "MIT", - "dependencies": { - "pend": "~1.2.0" - } - }, "node_modules/fdir": { "version": "6.4.3", "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.4.3.tgz", @@ -8403,20 +8072,6 @@ "url": "https://github.com/privatenumber/get-tsconfig?sponsor=1" } }, - "node_modules/get-uri": { - "version": "6.0.5", - "resolved": "https://registry.npmjs.org/get-uri/-/get-uri-6.0.5.tgz", - "integrity": "sha512-b1O07XYq8eRuVzBNgJLstU6FYc1tS6wnMtF1I1D9lE8LxZSOGZ7LhxN54yPP6mGw5f2CkXY2BQUL9Fx41qvcIg==", - "license": "MIT", - "dependencies": { - "basic-ftp": "^5.0.2", - "data-uri-to-buffer": "^6.0.2", - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, "node_modules/giget": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/giget/-/giget-1.2.4.tgz", @@ -8798,19 +8453,6 @@ "node": ">= 0.8" } }, - "node_modules/http-proxy-agent": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-7.0.2.tgz", - "integrity": "sha512-T1gkAiYYDWYx3V5Bmyu7HcfcvL7mUrTWiM6yOfa3PIphViJ/gFPbvidQ+veqSOHci/PxBcDabeUNCzpOODJZig==", - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.0", - "debug": "^4.3.4" - }, - "engines": { - "node": ">= 14" - } - }, "node_modules/http-shutdown": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/http-shutdown/-/http-shutdown-1.2.2.tgz", @@ -8938,31 +8580,6 @@ "integrity": "sha512-ev2QzSzWPYmy9GuqfIVildA4OdcGLeFZQrq5ys6RtiuF+RQQiZWr8TZNyAcuVXyQRYfEO+MsoB/1BuQVhOJuoQ==", "license": "MIT" }, - "node_modules/import-fresh": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", - "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", - "license": "MIT", - "dependencies": { - "parent-module": "^1.0.0", - "resolve-from": "^4.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/import-fresh/node_modules/resolve-from": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", - "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, "node_modules/importx": { "version": "0.4.4", "resolved": "https://registry.npmjs.org/importx/-/importx-0.4.4.tgz", @@ -9534,19 +9151,6 @@ "url": "https://opencollective.com/ioredis" } }, - "node_modules/ip-address": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz", - "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==", - "license": "MIT", - "dependencies": { - "jsbn": "1.1.0", - "sprintf-js": "^1.1.3" - }, - "engines": { - "node": ">= 12" - } - }, "node_modules/ipaddr.js": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-2.2.0.tgz", @@ -10183,6 +9787,12 @@ "jiti": "bin/jiti.js" } }, + "node_modules/jpeg-exif": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/jpeg-exif/-/jpeg-exif-1.1.4.tgz", + "integrity": "sha512-a+bKEcCjtuW5WTdgeXFzswSrdqi0jk4XlEtZlx5A94wCoBpFjfFTbo/Tra5SpNCl/YFZPvcV1dJc+TAYeg6ROQ==", + "license": "MIT" + }, "node_modules/js-cookie": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz", @@ -10220,12 +9830,6 @@ "js-yaml": "bin/js-yaml.js" } }, - "node_modules/jsbn": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz", - "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==", - "license": "MIT" - }, "node_modules/jsesc": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-3.1.0.tgz", @@ -10238,12 +9842,6 @@ "node": ">=6" } }, - "node_modules/json-parse-even-better-errors": { - "version": "2.3.1", - "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", - "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==", - "license": "MIT" - }, "node_modules/json-schema": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", @@ -10441,11 +10039,24 @@ "url": "https://github.com/sponsors/antonk52" } }, - "node_modules/lines-and-columns": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", - "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", - "license": "MIT" + "node_modules/linebreak": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/linebreak/-/linebreak-1.1.0.tgz", + "integrity": "sha512-MHp03UImeVhB7XZtjd0E4n6+3xr5Dq/9xI/5FptGk5FrbDR3zagPa2DS6U8ks/3HjbKWG9Q1M2ufOzxV2qLYSQ==", + "license": "MIT", + "dependencies": { + "base64-js": "0.0.8", + "unicode-trie": "^2.0.0" + } + }, + "node_modules/linebreak/node_modules/base64-js": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-0.0.8.tgz", + "integrity": "sha512-3XSA2cR/h/73EzlXXdU6YNycmYI7+kicTxks4eJg2g39biHR84slg2+des+p7iHYhbRg/udIS4TD53WabcOUkw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } }, "node_modules/linkify-it": { "version": "5.0.0", @@ -10925,15 +10536,6 @@ "integrity": "sha512-9ca1h0Xjvo9bEkE4UOxgAzLV0jHKe6LMaxo37ND2DAhhAtd0j8pR1Wxz+/goMrZO8AEZTWCmyaOsFI/W5AdpCQ==", "license": "MIT" }, - "node_modules/netmask": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/netmask/-/netmask-2.0.2.tgz", - "integrity": "sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==", - "license": "MIT", - "engines": { - "node": ">= 0.4.0" - } - }, "node_modules/nitropack": { "version": "2.10.4", "resolved": "https://registry.npmjs.org/nitropack/-/nitropack-2.10.4.tgz", @@ -11616,38 +11218,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/pac-proxy-agent": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/pac-proxy-agent/-/pac-proxy-agent-7.2.0.tgz", - "integrity": "sha512-TEB8ESquiLMc0lV8vcd5Ql/JAKAoyzHFXaStwjkzpOpC5Yv+pIzLfHvjTSdf3vpa2bMiUQrg9i6276yn8666aA==", - "license": "MIT", - "dependencies": { - "@tootallnate/quickjs-emscripten": "^0.23.0", - "agent-base": "^7.1.2", - "debug": "^4.3.4", - "get-uri": "^6.0.1", - "http-proxy-agent": "^7.0.0", - "https-proxy-agent": "^7.0.6", - "pac-resolver": "^7.0.1", - "socks-proxy-agent": "^8.0.5" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/pac-resolver": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/pac-resolver/-/pac-resolver-7.0.1.tgz", - "integrity": "sha512-5NPgf87AT2STgwa2ntRMr45jTKrYBGkVU36yT0ig/n/GMAa3oPqhZfIQ2kMEimReg0+t9kZViDVZ83qfVUlckg==", - "license": "MIT", - "dependencies": { - "degenerator": "^5.0.0", - "netmask": "^2.0.2" - }, - "engines": { - "node": ">= 14" - } - }, "node_modules/package-json-from-dist": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", @@ -11675,18 +11245,6 @@ "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==", "license": "(MIT AND Zlib)" }, - "node_modules/parent-module": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", - "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", - "license": "MIT", - "dependencies": { - "callsites": "^3.0.0" - }, - "engines": { - "node": ">=6" - } - }, "node_modules/parse-git-config": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/parse-git-config/-/parse-git-config-3.0.0.tgz", @@ -11831,6 +11389,19 @@ "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", "license": "MIT" }, + "node_modules/pdfkit": { + "version": "0.17.1", + "resolved": "https://registry.npmjs.org/pdfkit/-/pdfkit-0.17.1.tgz", + "integrity": "sha512-Kkf1I9no14O/uo593DYph5u3QwiMfby7JsBSErN1WqeyTgCBNJE3K4pXBn3TgkdKUIVu+buSl4bYUNC+8Up4xg==", + "license": "MIT", + "dependencies": { + "crypto-js": "^4.2.0", + "fontkit": "^2.0.4", + "jpeg-exif": "^1.1.4", + "linebreak": "^1.1.0", + "png-js": "^1.0.0" + } + }, "node_modules/peberminta": { "version": "0.9.0", "resolved": "https://registry.npmjs.org/peberminta/-/peberminta-0.9.0.tgz", @@ -11840,12 +11411,6 @@ "url": "https://ko-fi.com/killymxi" } }, - "node_modules/pend": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", - "integrity": "sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==", - "license": "MIT" - }, "node_modules/perfect-debounce": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/perfect-debounce/-/perfect-debounce-1.0.0.tgz", @@ -11897,6 +11462,11 @@ "node": ">=4" } }, + "node_modules/png-js": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/png-js/-/png-js-1.0.0.tgz", + "integrity": "sha512-k+YsbhpA9e+EFfKjTCH3VW6aoKlyNYI6NYdTfDL4CIvFnvsuO84ttonmZE7rc+v23SLTH8XX+5w/Ak9v0xGY4g==" + }, "node_modules/possible-typed-array-names": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", @@ -12461,15 +12031,6 @@ "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", "license": "MIT" }, - "node_modules/progress": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz", - "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==", - "license": "MIT", - "engines": { - "node": ">=0.4.0" - } - }, "node_modules/prompts": { "version": "2.4.2", "resolved": "https://registry.npmjs.org/prompts/-/prompts-2.4.2.tgz", @@ -12501,50 +12062,6 @@ "integrity": "sha512-/XJ368cyBJ7fzLMwLKv1e4vLxOju2MNAIokcr7meSaNcVbWz/CPcW22cP04mwxOErdA5mwjA8Q6w/cdAQxVn7Q==", "license": "MIT" }, - "node_modules/proxy-agent": { - "version": "6.5.0", - "resolved": "https://registry.npmjs.org/proxy-agent/-/proxy-agent-6.5.0.tgz", - "integrity": "sha512-TmatMXdr2KlRiA2CyDu8GqR8EjahTG3aY3nXjdzFyoZbmB8hrBsTyMezhULIXKnC0jpfjlmiZ3+EaCzoInSu/A==", - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.2", - "debug": "^4.3.4", - "http-proxy-agent": "^7.0.1", - "https-proxy-agent": "^7.0.6", - "lru-cache": "^7.14.1", - "pac-proxy-agent": "^7.1.0", - "proxy-from-env": "^1.1.0", - "socks-proxy-agent": "^8.0.5" - }, - "engines": { - "node": ">= 14" - } - }, - "node_modules/proxy-agent/node_modules/lru-cache": { - "version": "7.18.3", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz", - "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==", - "license": "ISC", - "engines": { - "node": ">=12" - } - }, - "node_modules/proxy-from-env": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", - "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", - "license": "MIT" - }, - "node_modules/pump": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", - "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", - "license": "MIT", - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -12563,44 +12080,6 @@ "node": ">=6" } }, - "node_modules/puppeteer": { - "version": "24.12.1", - "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-24.12.1.tgz", - "integrity": "sha512-+vvwl+Xo4z5uXLLHG+XW8uXnUXQ62oY6KU6bEFZJvHWLutbmv5dw9A/jcMQ0fqpQdLydHmK0Uy7/9Ilj8ufwSQ==", - "hasInstallScript": true, - "license": "Apache-2.0", - "dependencies": { - "@puppeteer/browsers": "2.10.5", - "chromium-bidi": "5.1.0", - "cosmiconfig": "^9.0.0", - "devtools-protocol": "0.0.1464554", - "puppeteer-core": "24.12.1", - "typed-query-selector": "^2.12.0" - }, - "bin": { - "puppeteer": "lib/cjs/puppeteer/node/cli.js" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/puppeteer-core": { - "version": "24.12.1", - "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-24.12.1.tgz", - "integrity": "sha512-8odp6d3ERKBa3BAVaYWXn95UxQv3sxvP1reD+xZamaX6ed8nCykhwlOiHSaHR9t/MtmIB+rJmNencI6Zy4Gxvg==", - "license": "Apache-2.0", - "dependencies": { - "@puppeteer/browsers": "2.10.5", - "chromium-bidi": "5.1.0", - "debug": "^4.4.1", - "devtools-protocol": "0.0.1464554", - "typed-query-selector": "^2.12.0", - "ws": "^8.18.3" - }, - "engines": { - "node": ">=18" - } - }, "node_modules/query-string": { "version": "7.1.3", "resolved": "https://registry.npmjs.org/query-string/-/query-string-7.1.3.tgz", @@ -14486,50 +13965,12 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/smart-buffer": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", - "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", - "license": "MIT", - "engines": { - "node": ">= 6.0.0", - "npm": ">= 3.0.0" - } - }, "node_modules/smob": { "version": "1.5.0", "resolved": "https://registry.npmjs.org/smob/-/smob-1.5.0.tgz", "integrity": "sha512-g6T+p7QO8npa+/hNx9ohv1E5pVCmWrVCUzUXJyLdMmftX6ER0oiWY/w9knEonLpnOp6b6FenKnMfR8gqwWdwig==", "license": "MIT" }, - "node_modules/socks": { - "version": "2.8.5", - "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.5.tgz", - "integrity": "sha512-iF+tNDQla22geJdTyJB1wM/qrX9DMRwWrciEPwWLPRWAUEM8sQiyxgckLxWT1f7+9VabJS0jTGGr4QgBuvi6Ww==", - "license": "MIT", - "dependencies": { - "ip-address": "^9.0.5", - "smart-buffer": "^4.2.0" - }, - "engines": { - "node": ">= 10.0.0", - "npm": ">= 3.0.0" - } - }, - "node_modules/socks-proxy-agent": { - "version": "8.0.5", - "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", - "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", - "license": "MIT", - "dependencies": { - "agent-base": "^7.1.2", - "debug": "^4.3.4", - "socks": "^2.8.3" - }, - "engines": { - "node": ">= 14" - } - }, "node_modules/source-map": { "version": "0.7.4", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz", @@ -14592,12 +14033,6 @@ "node": ">=6" } }, - "node_modules/sprintf-js": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz", - "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==", - "license": "BSD-3-Clause" - }, "node_modules/standard-as-callback": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/standard-as-callback/-/standard-as-callback-2.1.0.tgz", @@ -15014,20 +14449,6 @@ "node": ">=10" } }, - "node_modules/tar-fs": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.0.tgz", - "integrity": "sha512-5Mty5y/sOF1YWj1J6GiBodjlDc05CUR8PKXrsnFAiSG0xA+GHeWLovaZPYUDXkH/1iKRf2+M5+OrRgzC7O9b7w==", - "license": "MIT", - "dependencies": { - "pump": "^3.0.0", - "tar-stream": "^3.1.5" - }, - "optionalDependencies": { - "bare-fs": "^4.0.1", - "bare-path": "^3.0.0" - } - }, "node_modules/tar-stream": { "version": "3.1.7", "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-3.1.7.tgz", @@ -15786,12 +15207,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/typed-query-selector": { - "version": "2.12.0", - "resolved": "https://registry.npmjs.org/typed-query-selector/-/typed-query-selector-2.12.0.tgz", - "integrity": "sha512-SbklCd1F0EiZOyPiW192rrHZzZ5sBijB6xM+cpmrwDqObvdtunOHHIk9fCGsoK5JVIYXoyEp4iEdE3upFH3PAg==", - "license": "MIT" - }, "node_modules/typescript": { "version": "5.7.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", @@ -17724,25 +17139,6 @@ "node": ">=12" } }, - "node_modules/yauzl": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", - "integrity": "sha512-p4a9I6X6nu6IhoGmBqAcbJy1mlC4j27vEPZX9F4L4/vZT3Lyq1VkFHw/V/PUcB9Buo+DG3iHkT0x3Qya58zc3g==", - "license": "MIT", - "dependencies": { - "buffer-crc32": "~0.2.3", - "fd-slicer": "~1.1.0" - } - }, - "node_modules/yauzl/node_modules/buffer-crc32": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha512-VO9Ht/+p3SN7SKWqcrgEzjGbRSJYTx+Q1pTQC0wrWqHx0vpJraQ6GtHx8tvcg1rlK1byhU5gccxgOgj7B0TDkQ==", - "license": "MIT", - "engines": { - "node": "*" - } - }, "node_modules/zhead": { "version": "2.2.4", "resolved": "https://registry.npmjs.org/zhead/-/zhead-2.2.4.tgz", diff --git a/package.json b/package.json index 0b01152..9dd52b2 100644 --- a/package.json +++ b/package.json @@ -24,7 +24,7 @@ "nodemailer": "^7.0.3", "nuxt": "^3.15.4", "nuxt-directus": "^5.7.0", - "puppeteer": "^24.12.1", + "pdfkit": "^0.17.1", "sharp": "^0.34.2", "v-phone-input": "^4.4.2", "vue": "latest", @@ -36,6 +36,7 @@ "@types/imap": "^0.8.42", "@types/mailparser": "^3.4.6", "@types/mime-types": "^3.0.1", - "@types/nodemailer": "^6.4.17" + "@types/nodemailer": "^6.4.17", + "@types/pdfkit": "^0.14.0" } } diff --git a/server/api/expenses/generate-pdf.ts b/server/api/expenses/generate-pdf.ts index 6f5a07c..c9670af 100644 --- a/server/api/expenses/generate-pdf.ts +++ b/server/api/expenses/generate-pdf.ts @@ -3,7 +3,8 @@ import { getExpenseById } from '@/server/utils/nocodb'; import { processExpenseWithCurrency } from '@/server/utils/currency'; import { createError } from 'h3'; import { formatDate } from '@/utils/dateUtils'; -import puppeteer from 'puppeteer'; +import PDFDocument from 'pdfkit'; +import { getMinioClient } from '@/server/utils/minio'; interface PDFOptions { documentName: string; @@ -79,11 +80,8 @@ export default defineEventHandler(async (event) => { console.log('[expenses/generate-pdf] Successfully calculated totals:', totals); console.log('[expenses/generate-pdf] Options received:', options); - // Generate HTML content - const htmlContent = generateHTMLContent(expenses, options, totals); - - // Convert HTML to PDF using Puppeteer - const pdfBuffer = await generatePDFFromHTML(htmlContent, options); + // Generate PDF using PDFKit + const pdfBuffer = await generatePDFWithPDFKit(expenses, options, totals); // Return PDF as base64 for download const pdfBase64 = pdfBuffer.toString('base64'); @@ -99,11 +97,6 @@ export default defineEventHandler(async (event) => { }; } catch (error: any) { - // If it's our intentional error, re-throw it - if (error.statusCode === 501) { - throw error; - } - console.error('[expenses/generate-pdf] Error generating PDF:', error); throw createError({ statusCode: 500, @@ -112,7 +105,7 @@ export default defineEventHandler(async (event) => { } }); -function calculateTotals(expenses: Expense[], includeProcessingFee: boolean) { +function calculateTotals(expenses: Expense[], includeProcessingFee: boolean = false) { const originalTotal = expenses.reduce((sum, exp) => sum + (exp.PriceNumber || 0), 0); const usdTotal = expenses.reduce((sum, exp) => sum + (exp.PriceUSD || exp.PriceNumber || 0), 0); @@ -137,127 +130,297 @@ function getGroupingLabel(groupBy: string): string { } } -function generateHTMLContent(expenses: Expense[], options: PDFOptions, totals: any): string { - // Generate HTML content that can be converted to PDF - const html = ` - - - - - ${options.documentName} - - - -
-
${options.documentName}
- ${options.subheader ? `
${options.subheader}
` : ''} -
- - ${options.includeSummary ? ` -
-

Summary

-

Total Expenses: ${totals.count}

-

Subtotal: €${totals.originalTotal.toFixed(2)}

-

USD Equivalent: $${totals.usdTotal.toFixed(2)}

- ${options.includeProcessingFee ? `

Processing Fee (5%): €${totals.processingFee.toFixed(2)}

` : ''} -

Final Total: €${totals.finalTotal.toFixed(2)}

-

Grouping: ${getGroupingLabel(options.groupBy)}

-
- ` : ''} - - ${options.includeDetails ? generateExpenseTable(expenses, options) : ''} - -
- Generated on: ${new Date().toLocaleString()} -
- -`; - - return html; +function getPageDimensions(pageFormat: string) { + switch (pageFormat) { + case 'Letter': + return { width: 612, height: 792 }; // 8.5" x 11" + case 'Legal': + return { width: 612, height: 1008 }; // 8.5" x 14" + case 'A4': + default: + return { width: 595, height: 842 }; // A4 + } } -function generateExpenseTable(expenses: Expense[], options: PDFOptions): string { - let tableHTML = ` - - - - - - - - - - ${options.includeReceiptContents ? '' : ''} - - - - `; +async function generatePDFWithPDFKit(expenses: Expense[], options: PDFOptions, totals: any): Promise { + return new Promise(async (resolve, reject) => { + try { + console.log('[expenses/generate-pdf] Generating PDF with PDFKit...'); + + const pageDimensions = getPageDimensions(options.pageFormat); + const doc = new PDFDocument({ + size: [pageDimensions.width, pageDimensions.height], + margins: { top: 60, bottom: 60, left: 60, right: 60 } + }); + + const chunks: Buffer[] = []; + + doc.on('data', (chunk) => chunks.push(chunk)); + doc.on('end', () => { + const pdfBuffer = Buffer.concat(chunks); + console.log('[expenses/generate-pdf] PDF generated successfully, size:', pdfBuffer.length, 'bytes'); + resolve(pdfBuffer); + }); + doc.on('error', reject); + + // Add header + addHeader(doc, options); + + // Add summary if requested + if (options.includeSummary) { + addSummary(doc, totals, options); + } + + // Add expense details if requested + if (options.includeDetails) { + await addExpenseTable(doc, expenses, options); + } + + // Add receipt images if requested + if (options.includeReceipts) { + await addReceiptImages(doc, expenses); + } + + // Add footer + addFooter(doc); + + doc.end(); + + } catch (error: any) { + console.error('[expenses/generate-pdf] PDFKit error:', error); + reject(new Error(`PDF generation failed: ${error?.message || 'Unknown error'}`)); + } + }); +} +function addHeader(doc: PDFKit.PDFDocument, options: PDFOptions) { + doc.fontSize(24) + .font('Helvetica-Bold') + .text(options.documentName, { align: 'center' }); + + if (options.subheader) { + doc.fontSize(16) + .font('Helvetica') + .fillColor('#666666') + .text(options.subheader, { align: 'center' }); + } + + // Add line separator + const y = doc.y + 10; + doc.moveTo(60, y) + .lineTo(doc.page.width - 60, y) + .strokeColor('#333333') + .lineWidth(2) + .stroke(); + + doc.y = y + 20; + doc.fillColor('#000000'); // Reset color +} + +function addSummary(doc: PDFKit.PDFDocument, totals: any, options: PDFOptions) { + doc.fontSize(18) + .font('Helvetica-Bold') + .text('Summary', { continued: false }); + + doc.y += 10; + + // Summary box + const boxY = doc.y; + const boxHeight = options.includeProcessingFee ? 140 : 120; + + doc.rect(60, boxY, doc.page.width - 120, boxHeight) + .fillColor('#f5f5f5') + .fill() + .strokeColor('#dddddd') + .stroke(); + + doc.fillColor('#000000'); + + // Summary content + doc.y = boxY + 15; + doc.fontSize(12) + .font('Helvetica'); + + const leftX = 80; + const rightX = doc.page.width - 200; + + doc.text(`Total Expenses:`, leftX, doc.y, { continued: true }) + .font('Helvetica-Bold') + .text(` ${totals.count}`, { align: 'left' }); + + doc.font('Helvetica') + .text(`Subtotal:`, leftX, doc.y + 5, { continued: true }) + .font('Helvetica-Bold') + .text(` €${totals.originalTotal.toFixed(2)}`, { align: 'left' }); + + doc.font('Helvetica') + .text(`USD Equivalent:`, leftX, doc.y + 5, { continued: true }) + .font('Helvetica-Bold') + .text(` $${totals.usdTotal.toFixed(2)}`, { align: 'left' }); + + if (options.includeProcessingFee) { + doc.font('Helvetica') + .text(`Processing Fee (5%):`, leftX, doc.y + 5, { continued: true }) + .font('Helvetica-Bold') + .text(` €${totals.processingFee.toFixed(2)}`, { align: 'left' }); + } + + doc.font('Helvetica') + .text(`Final Total:`, leftX, doc.y + 5, { continued: true }) + .font('Helvetica-Bold') + .fontSize(14) + .text(` €${totals.finalTotal.toFixed(2)}`, { align: 'left' }); + + doc.fontSize(12) + .font('Helvetica') + .text(`Grouping:`, leftX, doc.y + 5, { continued: true }) + .font('Helvetica-Bold') + .text(` ${getGroupingLabel(options.groupBy)}`, { align: 'left' }); + + doc.y = boxY + boxHeight + 20; +} + +async function addExpenseTable(doc: PDFKit.PDFDocument, expenses: Expense[], options: PDFOptions) { + doc.fontSize(18) + .font('Helvetica-Bold') + .text('Expense Details', { continued: false }); + + doc.y += 15; + + const tableTop = doc.y; + const rowHeight = 25; + const fontSize = 9; + + // Column definitions + const columns = [ + { header: 'Date', width: 70, x: 60 }, + { header: 'Establishment', width: 120, x: 130 }, + { header: 'Category', width: 60, x: 250 }, + { header: 'Payer', width: 60, x: 310 }, + { header: 'Amount', width: 60, x: 370 }, + { header: 'Payment', width: 50, x: 430 } + ]; + + if (options.includeReceiptContents) { + columns.push({ header: 'Description', width: 85, x: 480 }); + } + + // Draw table header + doc.fontSize(fontSize + 1) + .font('Helvetica-Bold') + .fillColor('#000000'); + + // Header background + doc.rect(60, tableTop, doc.page.width - 120, rowHeight) + .fillColor('#f2f2f2') + .fill() + .strokeColor('#dddddd') + .stroke(); + + doc.fillColor('#000000'); + + columns.forEach(col => { + doc.text(col.header, col.x, tableTop + 8, { width: col.width, align: 'left' }); + }); + + let currentY = tableTop + rowHeight; + + // Group expenses if needed if (options.groupBy === 'none') { - // No grouping - just list all expenses - expenses.forEach(expense => { - tableHTML += generateExpenseRow(expense, options); - }); + currentY = await drawExpenseRows(doc, expenses, columns, currentY, rowHeight, fontSize, options); } else { - // Group expenses const groups = groupExpenses(expenses, options.groupBy); - Object.keys(groups).forEach(groupKey => { - const groupExpenses = groups[groupKey]; - const groupTotal = groupExpenses.reduce((sum, exp) => sum + (exp.PriceNumber || 0), 0); + for (const [groupKey, groupExpenses] of Object.entries(groups)) { + // Check if we need a new page + if (currentY > doc.page.height - 100) { + doc.addPage(); + currentY = 60; + } // Group header - tableHTML += ` - - - - `; + const groupTotal = groupExpenses.reduce((sum, exp) => sum + (exp.PriceNumber || 0), 0); + doc.fontSize(fontSize + 1) + .font('Helvetica-Bold') + .fillColor('#000000'); + + doc.rect(60, currentY, doc.page.width - 120, rowHeight) + .fillColor('#e7f3ff') + .fill() + .strokeColor('#dddddd') + .stroke(); + + doc.fillColor('#000000') + .text(`${groupKey} (${groupExpenses.length} expenses - €${groupTotal.toFixed(2)})`, + 65, currentY + 8, { width: doc.page.width - 130 }); + + currentY += rowHeight; // Group expenses - groupExpenses.forEach(expense => { - tableHTML += generateExpenseRow(expense, options); - }); - }); + currentY = await drawExpenseRows(doc, groupExpenses, columns, currentY, rowHeight, fontSize, options); + } } - - tableHTML += ` - -
DateEstablishmentCategoryPayerAmountPayment MethodDescription
${groupKey} (${groupExpenses.length} expenses - €${groupTotal.toFixed(2)})
- `; - - return tableHTML; } -function generateExpenseRow(expense: Expense, options: PDFOptions): string { - const date = expense.Time ? formatDate(expense.Time) : 'N/A'; - const description = expense.Contents || 'N/A'; +async function drawExpenseRows( + doc: PDFKit.PDFDocument, + expenses: Expense[], + columns: any[], + startY: number, + rowHeight: number, + fontSize: number, + options: PDFOptions +): Promise { + let currentY = startY; - return ` - - ${date} - ${expense['Establishment Name'] || 'N/A'} - ${expense.Category || 'N/A'} - ${expense.Payer || 'N/A'} - €${expense.PriceNumber ? expense.PriceNumber.toFixed(2) : '0.00'} - ${expense['Payment Method'] || 'N/A'} - ${options.includeReceiptContents ? `${description}` : ''} - - `; + doc.fontSize(fontSize) + .font('Helvetica'); + + expenses.forEach((expense, index) => { + // Check if we need a new page + if (currentY > doc.page.height - 100) { + doc.addPage(); + currentY = 60; + } + + // Alternate row colors + if (index % 2 === 0) { + doc.rect(60, currentY, doc.page.width - 120, rowHeight) + .fillColor('#f9f9f9') + .fill(); + } + + doc.fillColor('#000000'); + + // Draw row data + const date = expense.Time ? formatDate(expense.Time) : 'N/A'; + const establishment = expense['Establishment Name'] || 'N/A'; + const category = expense.Category || 'N/A'; + const payer = expense.Payer || 'N/A'; + const amount = `€${expense.PriceNumber ? expense.PriceNumber.toFixed(2) : '0.00'}`; + const payment = expense['Payment Method'] || 'N/A'; + + const rowData = [date, establishment, category, payer, amount, payment]; + + if (options.includeReceiptContents) { + const description = expense.Contents || 'N/A'; + rowData.push(description.length > 30 ? description.substring(0, 27) + '...' : description); + } + + rowData.forEach((data, colIndex) => { + if (colIndex < columns.length) { + doc.text(data, columns[colIndex].x, currentY + 8, { + width: columns[colIndex].width - 5, + align: 'left', + ellipsis: true + }); + } + }); + + currentY += rowHeight; + }); + + return currentY; } function groupExpenses(expenses: Expense[], groupBy: string): Record { @@ -287,77 +450,124 @@ function groupExpenses(expenses: Expense[], groupBy: string): Record { - let browser; +async function addReceiptImages(doc: PDFKit.PDFDocument, expenses: Expense[]) { + console.log('[expenses/generate-pdf] Adding receipt images...'); - try { - console.log('[expenses/generate-pdf] Launching Puppeteer browser...'); - - // Launch browser with optimized settings - browser = await puppeteer.launch({ - headless: true, - args: [ - '--no-sandbox', - '--disable-setuid-sandbox', - '--disable-dev-shm-usage', - '--disable-accelerated-2d-canvas', - '--no-first-run', - '--no-zygote', - '--disable-gpu' - ] - }); - - const page = await browser.newPage(); - - // Set content with proper encoding - await page.setContent(htmlContent, { - waitUntil: 'networkidle0', - timeout: 30000 - }); - - // Get page format dimensions - const format = getPageFormat(options.pageFormat); - - console.log('[expenses/generate-pdf] Generating PDF with format:', format); - - // Generate PDF with proper options - const pdfUint8Array = await page.pdf({ - format: format.format, - printBackground: true, - margin: { - top: '20mm', - right: '15mm', - bottom: '20mm', - left: '15mm' - }, - preferCSSPageSize: true - }); - - // Convert Uint8Array to Buffer - const pdfBuffer = Buffer.from(pdfUint8Array); - - console.log('[expenses/generate-pdf] PDF generated successfully, size:', pdfBuffer.length, 'bytes'); - - return pdfBuffer; - - } catch (error: any) { - console.error('[expenses/generate-pdf] Puppeteer error:', error); - throw new Error(`PDF generation failed: ${error?.message || 'Unknown error'}`); - } finally { - if (browser) { - await browser.close(); + const expensesWithReceipts = expenses.filter(expense => + expense.Receipt && Array.isArray(expense.Receipt) && expense.Receipt.length > 0 + ); + + if (expensesWithReceipts.length === 0) { + console.log('[expenses/generate-pdf] No receipts found to include'); + return; + } + + // Add new page for receipts + doc.addPage(); + + doc.fontSize(18) + .font('Helvetica-Bold') + .text('Receipt Images', { align: 'center' }); + + doc.y += 20; + + for (const expense of expensesWithReceipts) { + try { + // Add expense header + doc.fontSize(14) + .font('Helvetica-Bold') + .text(`Receipt for: ${expense['Establishment Name']} - €${expense.PriceNumber?.toFixed(2)}`, + { align: 'left' }); + + doc.fontSize(12) + .font('Helvetica') + .text(`Date: ${expense.Time ? formatDate(expense.Time) : 'N/A'}`, { align: 'left' }); + + doc.y += 10; + + // Process receipt images + if (expense.Receipt) { + for (const receipt of expense.Receipt) { + if (receipt.url || receipt.directus_files_id?.filename_download) { + try { + const imageBuffer = await fetchReceiptImage(receipt); + + if (imageBuffer) { + // Check if we need a new page + if (doc.y > doc.page.height - 400) { + doc.addPage(); + doc.y = 60; + } + + // Add image + const maxWidth = 400; + const maxHeight = 300; + + doc.image(imageBuffer, { + fit: [maxWidth, maxHeight], + align: 'center' + }); + + doc.y += 20; + } + } catch (imageError) { + console.error('[expenses/generate-pdf] Error adding receipt image:', imageError); + doc.fontSize(10) + .fillColor('#666666') + .text('Receipt image could not be loaded', { align: 'center' }); + doc.fillColor('#000000'); + doc.y += 10; + } + } + } + } + + doc.y += 20; + } catch (error) { + console.error('[expenses/generate-pdf] Error processing receipt for expense:', expense.Id, error); } } } -function getPageFormat(pageFormat: string): { format: any } { - switch (pageFormat) { - case 'Letter': - return { format: 'letter' }; - case 'Legal': - return { format: 'legal' }; - case 'A4': - default: - return { format: 'a4' }; +async function fetchReceiptImage(receipt: any): Promise { + try { + const client = getMinioClient(); + const bucketName = useRuntimeConfig().minio.bucketName; + + // Determine the file path + let filePath = receipt.url; + if (!filePath && receipt.directus_files_id?.filename_download) { + filePath = receipt.directus_files_id.filename_download; + } + + if (!filePath) { + console.log('[expenses/generate-pdf] No file path found for receipt'); + return null; + } + + console.log('[expenses/generate-pdf] Fetching receipt image:', filePath); + + // Get the object from MinIO + const dataStream = await client.getObject(bucketName, filePath); + + // Convert stream to buffer + const chunks: Buffer[] = []; + + return new Promise((resolve, reject) => { + dataStream.on('data', (chunk) => chunks.push(chunk)); + dataStream.on('end', () => resolve(Buffer.concat(chunks))); + dataStream.on('error', reject); + }); + + } catch (error) { + console.error('[expenses/generate-pdf] Error fetching receipt image:', error); + return null; } } + +function addFooter(doc: PDFKit.PDFDocument) { + doc.fontSize(10) + .fillColor('#666666') + .text(`Generated on: ${new Date().toLocaleString()}`, + 60, doc.page.height - 40, { align: 'right' }); +}