From 0ebf5fe3de3a2a35710dc83f1bb427d8eec26e18 Mon Sep 17 00:00:00 2001 From: geulah Date: Mon, 6 Oct 2025 01:10:02 +0100 Subject: [PATCH] feat(cv-export): add PDF export functionality with async job support Implement PDF export feature with both synchronous and asynchronous modes. Includes: - New cv-export-server service using Puppeteer - Shared printable HTML builder module - ExportControls React component with job status tracking - Classic template for PDF output - API endpoints for job management The system supports cancelable async jobs with polling and error handling. Both client and server share the same HTML rendering logic via the shared-printable module. --- cv-engine/package-lock.json | 280 +++ cv-engine/package.json | 1 + cv-engine/src/api/export.ts | 66 + cv-engine/src/components/ExportControls.tsx | 149 ++ cv-engine/src/components/PreviewPanel.tsx | 4 + cv-engine/src/templates/ClassicTemplate.tsx | 133 ++ cv-engine/src/types/shared-printable.d.ts | 4 + cv-engine/src/utils/printable.ts | 160 +- cv-engine/tsconfig.app.json | 5 +- cv-engine/vite.config.ts | 5 + cv-export-server/.gitignore | 24 + cv-export-server/package-lock.json | 2123 +++++++++++++++++++ cv-export-server/package.json | 21 + cv-export-server/server.js | 281 +++ headers.txt | 11 + job.json | Bin 0 -> 13298 bytes out.json | 1 + out.pdf | Bin 0 -> 13298 bytes shared-printable/buildPrintableHtml.d.ts | 2 + shared-printable/buildPrintableHtml.js | 152 ++ 20 files changed, 3269 insertions(+), 153 deletions(-) create mode 100644 cv-engine/src/api/export.ts create mode 100644 cv-engine/src/components/ExportControls.tsx create mode 100644 cv-engine/src/templates/ClassicTemplate.tsx create mode 100644 cv-engine/src/types/shared-printable.d.ts create mode 100644 cv-export-server/.gitignore create mode 100644 cv-export-server/package-lock.json create mode 100644 cv-export-server/package.json create mode 100644 cv-export-server/server.js create mode 100644 headers.txt create mode 100644 job.json create mode 100644 out.json create mode 100644 out.pdf create mode 100644 shared-printable/buildPrintableHtml.d.ts create mode 100644 shared-printable/buildPrintableHtml.js diff --git a/cv-engine/package-lock.json b/cv-engine/package-lock.json index a4f58bc..8ab8ca4 100644 --- a/cv-engine/package-lock.json +++ b/cv-engine/package-lock.json @@ -13,6 +13,7 @@ "@tiptap/react": "^3.6.5", "@tiptap/starter-kit": "^3.6.5", "autoprefixer": "^10.4.21", + "axios": "^1.12.2", "dompurify": "^3.2.7", "postcss": "^8.5.6", "react": "^19.1.1", @@ -2658,6 +2659,12 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "license": "Python-2.0" }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, "node_modules/autoprefixer": { "version": "10.4.21", "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.21.tgz", @@ -2695,6 +2702,17 @@ "postcss": "^8.1.0" } }, + "node_modules/axios": { + "version": "1.12.2", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.12.2.tgz", + "integrity": "sha512-vMJzPewAlRyOgxV2dU0Cuz2O8zzzx9VYtbJOaBgXFeLc4IV/Eg50n4LowmehOOR61S8ZMpc2K5Sa7g6A4jfkUw==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.6", + "form-data": "^4.0.4", + "proxy-from-env": "^1.1.0" + } + }, "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", @@ -2768,6 +2786,19 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/callsites": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", @@ -2845,6 +2876,18 @@ "dev": true, "license": "MIT" }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", @@ -2911,6 +2954,15 @@ "dev": true, "license": "MIT" }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, "node_modules/detect-libc": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", @@ -2930,6 +2982,20 @@ "@types/trusted-types": "^2.0.7" } }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/electron-to-chromium": { "version": "1.5.230", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.230.tgz", @@ -2962,6 +3028,51 @@ "url": "https://github.com/fb55/entities?sponsor=1" } }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/esbuild": { "version": "0.25.10", "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.10.tgz", @@ -3327,6 +3438,42 @@ "dev": true, "license": "ISC" }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.4.tgz", + "integrity": "sha512-KrGhL9Q4zjj0kiUt5OO4Mr/A/jlI2jDYs5eHBpYHPcBEVSiipAvn2Ko2HnPe20rmcuuvMHNdZFp+4IlGTMF0Ow==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/fraction.js": { "version": "4.3.7", "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.3.7.tgz", @@ -3355,6 +3502,15 @@ "node": "^8.16.0 || ^10.6.0 || >=11.0.0" } }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -3365,6 +3521,43 @@ "node": ">=6.9.0" } }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/glob-parent": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", @@ -3391,6 +3584,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -3415,6 +3620,45 @@ "node": ">=8" } }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -3907,6 +4151,15 @@ "markdown-it": "bin/markdown-it.mjs" } }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, "node_modules/mdurl": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/mdurl/-/mdurl-2.0.0.tgz", @@ -3937,6 +4190,27 @@ "node": ">=8.6" } }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -4367,6 +4641,12 @@ "prosemirror-transform": "^1.1.0" } }, + "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/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", diff --git a/cv-engine/package.json b/cv-engine/package.json index 833ae1f..6b4edc1 100644 --- a/cv-engine/package.json +++ b/cv-engine/package.json @@ -15,6 +15,7 @@ "@tiptap/react": "^3.6.5", "@tiptap/starter-kit": "^3.6.5", "autoprefixer": "^10.4.21", + "axios": "^1.12.2", "dompurify": "^3.2.7", "postcss": "^8.5.6", "react": "^19.1.1", diff --git a/cv-engine/src/api/export.ts b/cv-engine/src/api/export.ts new file mode 100644 index 0000000..4d0bda8 --- /dev/null +++ b/cv-engine/src/api/export.ts @@ -0,0 +1,66 @@ +import axios from 'axios'; +import type { CV } from '../schema/cvSchema'; + +export interface ExportResult { filename: string; } +export interface JobResponse { jobId: string; } +export interface JobStatus { status: 'queued' | 'done' | 'error' | 'canceled'; filename?: string; error?: string; } + +// Synchronous export (no job) +export async function exportPdf(cv: CV, endpoint = 'http://localhost:4000/export/pdf', templateId?: string): Promise { + const response = await axios.post(endpoint, { ...cv, templateId }, { responseType: 'blob' }); + const contentDisposition = response.headers['content-disposition'] || ''; + const match = /filename="?([^";]+)"?/i.exec(contentDisposition); + const filename = match ? match[1] : `${cv.personal.firstName || 'Candidate'}_${cv.personal.lastName || 'CV'}.pdf`; + + const blob = new Blob([response.data], { type: 'application/pdf' }); + const url = window.URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = filename; + document.body.appendChild(a); + a.click(); + a.remove(); + window.URL.revokeObjectURL(url); + + return { filename }; +} + +// Async export: request job, poll status with backoff, then download +export async function requestExportJob(cv: CV, endpoint = 'http://localhost:4000/export/pdf', templateId?: string): Promise { + const response = await axios.post(`${endpoint}?async=1`, { ...cv, templateId }); + return response.data.jobId; +} + +export async function pollJobStatus(jobId: string, baseUrl = 'http://localhost:4000', initialDelayMs = 500, maxDelayMs = 5000, maxAttempts = 10): Promise { + let attempt = 0; + let delay = initialDelayMs; + while (attempt < maxAttempts) { + const { data } = await axios.get(`${baseUrl}/export/status/${jobId}`); + if (data.status === 'done' || data.status === 'error' || data.status === 'canceled') return data; + await new Promise(r => setTimeout(r, delay)); + delay = Math.min(Math.round(delay * 1.6), maxDelayMs); + attempt += 1; + } + return { status: 'error', error: 'Timeout waiting for export' }; +} + +export async function downloadJob(jobId: string, baseUrl = 'http://localhost:4000'): Promise { + const response = await axios.get(`${baseUrl}/export/download/${jobId}`, { responseType: 'blob' }); + const contentDisposition = response.headers['content-disposition'] || ''; + const match = /filename="?([^";]+)"?/i.exec(contentDisposition); + const filename = match ? match[1] : `CV.pdf`; + const blob = new Blob([response.data], { type: 'application/pdf' }); + const url = window.URL.createObjectURL(blob); + const a = document.createElement('a'); + a.href = url; + a.download = filename; + document.body.appendChild(a); + a.click(); + a.remove(); + window.URL.revokeObjectURL(url); + return { filename }; +} + +export async function cancelJob(jobId: string, baseUrl = 'http://localhost:4000'): Promise { + await axios.post(`${baseUrl}/export/cancel/${jobId}`); +} \ No newline at end of file diff --git a/cv-engine/src/components/ExportControls.tsx b/cv-engine/src/components/ExportControls.tsx new file mode 100644 index 0000000..b5a01ef --- /dev/null +++ b/cv-engine/src/components/ExportControls.tsx @@ -0,0 +1,149 @@ +import React, { useRef, useState } from 'react'; +import { useCvStore } from '../store/cvStore'; +import { exportPdf, requestExportJob, pollJobStatus, downloadJob, cancelJob } from '../api/export'; + +interface ExportControlsProps { + endpoint?: string; + className?: string; +} + +const ExportControls: React.FC = ({ endpoint = 'http://localhost:4000/export/pdf', className = '' }) => { + const cv = useCvStore(state => state.cv); + const templateId = useCvStore(state => state.cv.templateId); + const updateTemplateId = useCvStore(state => state.updateTemplateId); + const [status, setStatus] = useState<'idle' | 'exporting' | 'error' | 'done'>('idle'); + const [error, setError] = useState(null); + const [lastFilename, setLastFilename] = useState(null); + const [asyncMode, setAsyncMode] = useState(true); + const currentJobId = useRef(null); + + async function handleExport() { + setStatus('exporting'); + setError(null); + try { + if (!asyncMode) { + const { filename } = await exportPdf(cv, endpoint, templateId); + setLastFilename(filename); + setStatus('done'); + setTimeout(() => setStatus('idle'), 1500); + return; + } + const jobId = await requestExportJob(cv, endpoint, templateId); + currentJobId.current = jobId; + const statusResult = await pollJobStatus(jobId, new URL(endpoint).origin); + if (statusResult.status === 'error') { + throw new Error(statusResult.error || 'Export job failed'); + } + if (statusResult.status === 'canceled') { + setStatus('idle'); + setError('Export canceled'); + currentJobId.current = null; + return; + } + const { filename } = await downloadJob(jobId, new URL(endpoint).origin); + setLastFilename(filename); + setStatus('done'); + setTimeout(() => setStatus('idle'), 1500); + } catch (err: unknown) { + let message = 'Export failed'; + if (typeof err === 'object' && err !== null) { + const resp = (err as { response?: { data?: { error?: string } } }).response; + if (resp?.data?.error) { + message = resp.data.error; + } else if ('message' in err && typeof (err as { message?: string }).message === 'string') { + message = (err as { message?: string }).message as string; + } + } + setError(message); + setStatus('error'); + } + } + + async function handleCancel() { + if (!currentJobId.current) return; + try { + await cancelJob(currentJobId.current, new URL(endpoint).origin); + setStatus('idle'); + setError(null); + currentJobId.current = null; + } catch (err) { + // Keep subtle; cancellation failures shouldn't block + console.warn('Cancel failed', err); + } + } + + async function handleRetry() { + // reset and re-run export + setError(null); + await handleExport(); + } + + return ( +
+ + + + + setAsyncMode(e.target.checked)} + disabled={status === 'exporting'} + /> + + + + {status === 'exporting' && asyncMode && ( + + )} + + {status === 'error' && ( + + )} + {status === 'error' && ( + {error} + )} + {status === 'done' && lastFilename && ( + Downloaded: {lastFilename} + )} +
+ ); +}; + +export default ExportControls; \ No newline at end of file diff --git a/cv-engine/src/components/PreviewPanel.tsx b/cv-engine/src/components/PreviewPanel.tsx index c751e88..043345f 100644 --- a/cv-engine/src/components/PreviewPanel.tsx +++ b/cv-engine/src/components/PreviewPanel.tsx @@ -1,7 +1,9 @@ import React, { useMemo, useState } from 'react'; import { useCvStore } from '../store/cvStore'; import ATSTemplate from '../templates/ATSTemplate'; +import ClassicTemplate from '../templates/ClassicTemplate'; import { buildPrintableHtml } from '../utils/printable'; +import ExportControls from './ExportControls'; interface PreviewPanelProps { className?: string; @@ -37,6 +39,7 @@ const PreviewPanel: React.FC = ({ className = '' }) => { Printable + @@ -45,6 +48,7 @@ const PreviewPanel: React.FC = ({ className = '' }) => { <> {/* Render the appropriate template based on templateId */} {templateId === 'ats' && } + {templateId === 'classic' && } {/* Add more template options here as they are implemented */} )} diff --git a/cv-engine/src/templates/ClassicTemplate.tsx b/cv-engine/src/templates/ClassicTemplate.tsx new file mode 100644 index 0000000..f234b22 --- /dev/null +++ b/cv-engine/src/templates/ClassicTemplate.tsx @@ -0,0 +1,133 @@ +import React from 'react'; +import type { CV } from '../schema/cvSchema'; + +interface ClassicTemplateProps { + cv: CV; +} + +// Simple classic visual template for inline preview +const ClassicTemplate: React.FC = ({ cv }) => { + const personal = cv.personal || {}; + const summary = cv.summary || ''; + const work = cv.work || []; + const education = cv.education || []; + const skills = cv.skills || []; + const languages = cv.languages || []; + const certifications = cv.certifications || []; + + return ( +
+
+

+ {personal.firstName} {personal.lastName} +

+
+ {personal.email && {personal.email}} + {personal.phone && {personal.phone}} + {(personal.city || personal.country) && ( + + {personal.city}{personal.city && personal.country ? ', ' : ''}{personal.country} + + )} +
+
+ + {summary && ( +
+

Professional Summary

+
+
+ )} + + {work.length > 0 && ( +
+

Work Experience

+
+ {work.map((job) => ( +
+
+
+

{job.title}

+
{job.company}
+
+
+ {job.startDate} - {job.endDate || 'Present'} + {job.location &&
{job.location}
} +
+
+ {job.bullets && job.bullets.length > 0 && ( +
    + {job.bullets.map((b, i) => ( +
  • {b}
  • + ))} +
+ )} +
+ ))} +
+
+ )} + + {education.length > 0 && ( +
+

Education

+
+ {education.map((ed) => ( +
+
+
+

{ed.degree}

+
{ed.school}
+
+
+ {ed.startDate} {ed.endDate && <>- {ed.endDate}} +
+
+ {/* GPA not present in schema; omit for now */} +
+ ))} +
+
+ )} + + {skills.length > 0 && ( +
+

Skills

+
+ {skills.map((s) => ( + + {s.name}{s.level ? ` — ${s.level}` : ''} + + ))} +
+
+ )} + + {languages.length > 0 && ( +
+

Languages

+
+ {languages.map((l) => ( + + {l.name}{l.level ? ` — ${l.level}` : ''} + + ))} +
+
+ )} + + {certifications.length > 0 && ( +
+

Certifications

+
    + {certifications.map((c) => ( +
  • {c.name}{c.issuer ? ` - ${c.issuer}` : ''}{c.date ? ` (${c.date})` : ''}
  • + ))} +
+
+ )} +
+ ); +}; + +export default ClassicTemplate; \ No newline at end of file diff --git a/cv-engine/src/types/shared-printable.d.ts b/cv-engine/src/types/shared-printable.d.ts new file mode 100644 index 0000000..19f5a56 --- /dev/null +++ b/cv-engine/src/types/shared-printable.d.ts @@ -0,0 +1,4 @@ +declare module '../../../shared-printable/buildPrintableHtml.js' { + import type { CV } from '../schema/cvSchema'; + export function buildPrintableHtml(cv: CV): string; +} \ No newline at end of file diff --git a/cv-engine/src/utils/printable.ts b/cv-engine/src/utils/printable.ts index d918542..fcd87c0 100644 --- a/cv-engine/src/utils/printable.ts +++ b/cv-engine/src/utils/printable.ts @@ -1,156 +1,12 @@ import type { CV } from '../schema/cvSchema'; -import { escapeText, sanitizeHtml } from '../schema/cvSchema'; - -const formatDate = (dateStr?: string): string => { - if (!dateStr) return ''; - try { - const d = new Date(dateStr); - if (isNaN(d.getTime())) return dateStr; - return d.toLocaleDateString(undefined, { year: 'numeric', month: 'short' }); - } catch { - return dateStr; - } -}; +import { sanitizeHtml } from '../schema/cvSchema'; +import { buildPrintableHtml as sharedBuild } from '../../../shared-printable/buildPrintableHtml.js'; +// Thin wrapper to ensure client-side sanitization and shared rendering parity export const buildPrintableHtml = (cv: CV): string => { - const { personal, summary, work, education, skills, languages, certifications } = cv; - - const summaryHtml = summary ? sanitizeHtml(summary) : ''; - - const styles = ` - - `; - - const headerLinks = (personal.links || []) - .map(l => `${escapeText(l.label || l.url)}`) - .join(' · '); - - const headerHtml = ` -
-

${escapeText(personal.firstName)} ${escapeText(personal.lastName)}

-
- ${personal.email ? `${escapeText(personal.email)}` : ''} - ${personal.phone ? `${escapeText(personal.phone)}` : ''} - ${(personal.city || personal.country) ? `${personal.city ? escapeText(personal.city) : ''}${personal.city && personal.country ? ', ' : ''}${personal.country ? escapeText(personal.country) : ''}` : ''} - ${headerLinks ? `${headerLinks}` : ''} -
-
- `; - - const summarySection = summaryHtml ? ` -
-

Professional Summary

-
${summaryHtml}
-
- ` : ''; - - const workSection = (work && work.length) ? ` -
-

Work Experience

- ${work.map(job => ` -
-
-
-

${escapeText(job.title)}

-
${escapeText(job.company)}
-
-
- ${formatDate(job.startDate)} - ${job.endDate ? formatDate(job.endDate) : 'Present'} - ${job.location ? `
${escapeText(job.location)}
` : ''} -
-
- ${(job.bullets && job.bullets.length) ? ` -
    - ${job.bullets.map(b => `
  • ${escapeText(b)}
  • `).join('')} -
- ` : ''} -
- `).join('')} -
- ` : ''; - - const eduSection = (education && education.length) ? ` -
-

Education

- ${education.map(edu => ` -
-
-
-

${escapeText(edu.degree)}

-
${escapeText(edu.school)}
-
-
- ${formatDate(edu.startDate)} - ${edu.endDate ? formatDate(edu.endDate) : 'Present'} -
-
- ${edu.notes ? `
${escapeText(edu.notes)}
` : ''} -
- `).join('')} -
- ` : ''; - - const skillsSection = (skills && skills.length) ? ` -
-

Skills

-
- ${skills.map(s => `${escapeText(s.name)}${s.level ? ` (${s.level})` : ''}`).join('')} -
-
- ` : ''; - - const languagesSection = (languages && languages.length) ? ` -
-

Languages

-
- ${languages.map(l => `${escapeText(l.name)}${l.level ? ` (${l.level})` : ''}`).join('')} -
-
- ` : ''; - - const certsSection = (certifications && certifications.length) ? ` -
-

Certifications

-
    - ${certifications.map(c => `
  • ${escapeText(c.name)}${c.issuer ? ` - ${escapeText(c.issuer)}` : ''}${c.date ? ` (${formatDate(c.date)})` : ''}
  • `).join('')} -
-
- ` : ''; - - return ` - - - - - Printable CV - ${styles} - - -
- ${headerHtml} - ${summarySection} - ${workSection} - ${eduSection} - ${skillsSection} - ${languagesSection} - ${certsSection} -
- - `; + const cleaned: CV = { + ...cv, + summary: cv.summary ? sanitizeHtml(cv.summary) : '' + }; + return sharedBuild(cleaned); }; \ No newline at end of file diff --git a/cv-engine/tsconfig.app.json b/cv-engine/tsconfig.app.json index a9b5a59..feda789 100644 --- a/cv-engine/tsconfig.app.json +++ b/cv-engine/tsconfig.app.json @@ -24,5 +24,8 @@ "noFallthroughCasesInSwitch": true, "noUncheckedSideEffectImports": true }, - "include": ["src"] + "include": [ + "src", + "../shared-printable/buildPrintableHtml.d.ts" + ] } diff --git a/cv-engine/vite.config.ts b/cv-engine/vite.config.ts index 8b0f57b..a9f4da2 100644 --- a/cv-engine/vite.config.ts +++ b/cv-engine/vite.config.ts @@ -4,4 +4,9 @@ import react from '@vitejs/plugin-react' // https://vite.dev/config/ export default defineConfig({ plugins: [react()], + server: { + fs: { + allow: ['..'] + } + } }) diff --git a/cv-export-server/.gitignore b/cv-export-server/.gitignore new file mode 100644 index 0000000..a547bf3 --- /dev/null +++ b/cv-export-server/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw? diff --git a/cv-export-server/package-lock.json b/cv-export-server/package-lock.json new file mode 100644 index 0000000..7e9aaa2 --- /dev/null +++ b/cv-export-server/package-lock.json @@ -0,0 +1,2123 @@ +{ + "name": "cv-export-server", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "cv-export-server", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "cors": "^2.8.5", + "express": "^5.1.0", + "puppeteer": "^24.23.0", + "sanitize-html": "^2.17.0" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz", + "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==", + "license": "MIT", + "dependencies": { + "@babel/helper-validator-identifier": "^7.27.1", + "js-tokens": "^4.0.0", + "picocolors": "^1.1.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.27.1", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", + "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@puppeteer/browsers": { + "version": "2.10.10", + "resolved": "https://registry.npmjs.org/@puppeteer/browsers/-/browsers-2.10.10.tgz", + "integrity": "sha512-3ZG500+ZeLql8rE0hjfhkycJjDj0pI/btEh3L9IkWUYcOrgP0xCNRq3HbtbqOPbvDhFaAWD88pDFtlLv8ns8gA==", + "license": "Apache-2.0", + "dependencies": { + "debug": "^4.4.3", + "extract-zip": "^2.0.1", + "progress": "^2.0.3", + "proxy-agent": "^6.5.0", + "semver": "^7.7.2", + "tar-fs": "^3.1.0", + "yargs": "^17.7.2" + }, + "bin": { + "browsers": "lib/cjs/main-cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "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/@types/node": { + "version": "24.6.2", + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.6.2.tgz", + "integrity": "sha512-d2L25Y4j+W3ZlNAeMKcy7yDsK425ibcAOO2t7aPTz6gNMH0z2GThtwENCDc0d/Pw9wgyRqE5Px1wkV7naz8ang==", + "license": "MIT", + "optional": true, + "dependencies": { + "undici-types": "~7.13.0" + } + }, + "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/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/agent-base": { + "version": "7.1.4", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", + "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "license": "Python-2.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/b4a": { + "version": "1.7.3", + "resolved": "https://registry.npmjs.org/b4a/-/b4a-1.7.3.tgz", + "integrity": "sha512-5Q2mfq2WfGuFp3uS//0s6baOJLMoVduPYVeNmDYxu5OUA1/cBfvr2RIS7vi62LdNj/urk1hfmj867I3qt6uZ7Q==", + "license": "Apache-2.0", + "peerDependencies": { + "react-native-b4a": "*" + }, + "peerDependenciesMeta": { + "react-native-b4a": { + "optional": true + } + } + }, + "node_modules/bare-events": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/bare-events/-/bare-events-2.7.0.tgz", + "integrity": "sha512-b3N5eTW1g7vXkw+0CXh/HazGTcO5KYuu/RCNaJbDMPI6LHDi+7qe8EmxKUVe1sUbY2KZOVZFyj62x0OEz9qyAA==", + "license": "Apache-2.0" + }, + "node_modules/bare-fs": { + "version": "4.4.5", + "resolved": "https://registry.npmjs.org/bare-fs/-/bare-fs-4.4.5.tgz", + "integrity": "sha512-TCtu93KGLu6/aiGWzMr12TmSRS6nKdfhAnzTQRbXoSWxkbb9eRd53jQ51jG7g1gYjjtto3hbBrrhzg6djcgiKg==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "bare-events": "^2.5.4", + "bare-path": "^3.0.0", + "bare-stream": "^2.6.4", + "bare-url": "^2.2.2", + "fast-fifo": "^1.3.2" + }, + "engines": { + "bare": ">=1.16.0" + }, + "peerDependencies": { + "bare-buffer": "*" + }, + "peerDependenciesMeta": { + "bare-buffer": { + "optional": true + } + } + }, + "node_modules/bare-os": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/bare-os/-/bare-os-3.6.2.tgz", + "integrity": "sha512-T+V1+1srU2qYNBmJCXZkUY5vQ0B4FSlL3QDROnKQYOqeiQR8UbjNHlPa+TIbM4cuidiN9GaTaOZgSEgsvPbh5A==", + "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.7.0", + "resolved": "https://registry.npmjs.org/bare-stream/-/bare-stream-2.7.0.tgz", + "integrity": "sha512-oyXQNicV1y8nc2aKffH+BUHFRXmx6VrPzlnaEvMhram0nPBrKcEdcyBg5r08D0i8VxngHFAiVyn1QKXpSG0B8A==", + "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/bare-url": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/bare-url/-/bare-url-2.2.2.tgz", + "integrity": "sha512-g+ueNGKkrjMazDG3elZO1pNs3HY5+mMmOet1jtKyhOaCnkLzitxf26z7hoAEkDNgdNmnc1KIlt/dw6Po6xZMpA==", + "license": "Apache-2.0", + "optional": true, + "dependencies": { + "bare-path": "^3.0.0" + } + }, + "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/body-parser": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", + "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.0", + "http-errors": "^2.0.0", + "iconv-lite": "^0.6.3", + "on-finished": "^2.4.1", + "qs": "^6.14.0", + "raw-body": "^3.0.0", + "type-is": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "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/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "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/chromium-bidi": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/chromium-bidi/-/chromium-bidi-9.1.0.tgz", + "integrity": "sha512-rlUzQ4WzIAWdIbY/viPShhZU2n21CxDUgazXVbw4Hu1MwaeUSEksSeM6DqPgpRjCLXRk702AVRxJxoOz0dw4OA==", + "license": "Apache-2.0", + "dependencies": { + "mitt": "^3.0.1", + "zod": "^3.24.1" + }, + "peerDependencies": { + "devtools-protocol": "*" + } + }, + "node_modules/cliui": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz", + "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/content-disposition": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", + "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/cors": { + "version": "2.8.5", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", + "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "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/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/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deepmerge": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", + "integrity": "sha512-3sUqbMEc77XqpdNO7FRyRog+eW3ph+GYCbj+rK+uYyRMuwsVy0rMiVtPn+QJlKFvWP/1PYpapqYn0Me2knFn+A==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "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/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/devtools-protocol": { + "version": "0.0.1508733", + "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.1508733.tgz", + "integrity": "sha512-QJ1R5gtck6nDcdM+nlsaJXcelPEI7ZxSMw1ujHpO1c4+9l+Nue5qlebi9xO1Z2MGr92bFOQTW7/rrheh5hHxDg==", + "license": "BSD-3-Clause" + }, + "node_modules/dom-serializer": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz", + "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==", + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.2", + "entities": "^4.2.0" + }, + "funding": { + "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1" + } + }, + "node_modules/domelementtype": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz", + "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "BSD-2-Clause" + }, + "node_modules/domhandler": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz", + "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==", + "license": "BSD-2-Clause", + "dependencies": { + "domelementtype": "^2.3.0" + }, + "engines": { + "node": ">= 4" + }, + "funding": { + "url": "https://github.com/fb55/domhandler?sponsor=1" + } + }, + "node_modules/domutils": { + "version": "3.2.2", + "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.2.2.tgz", + "integrity": "sha512-6kZKyUajlDuqlHKVX1w7gyslj9MPIXzIFiz/rGu35uC1wMi+kMhQwGhl4lt9unC9Vb9INnY9Z3/ZA3+FhASLaw==", + "license": "BSD-2-Clause", + "dependencies": { + "dom-serializer": "^2.0.0", + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3" + }, + "funding": { + "url": "https://github.com/fb55/domutils?sponsor=1" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "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/entities": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz", + "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.12" + }, + "funding": { + "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.4", + "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.4.tgz", + "integrity": "sha512-sqQamAnR14VgCr1A618A3sGrygcpK+HEbenA/HiEAkkUwcZIIB/tgWqHFxWgOyDh4nB4JCRimh79dR5Ywc9MDQ==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.2.1" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "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/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/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/events-universal": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/events-universal/-/events-universal-1.0.1.tgz", + "integrity": "sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==", + "license": "Apache-2.0", + "dependencies": { + "bare-events": "^2.7.0" + } + }, + "node_modules/express": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", + "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.0", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "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/fast-fifo": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/fast-fifo/-/fast-fifo-1.3.2.tgz", + "integrity": "sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==", + "license": "MIT" + }, + "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/finalhandler": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", + "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "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/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/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/htmlparser2": { + "version": "8.0.2", + "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.2.tgz", + "integrity": "sha512-GYdjWKDkbRLkZ5geuHs5NY1puJ+PXwP7+fHPRz06Eirsb9ugf6d8kkXav6ADhcODhFFPMIXyxkxSuMf3D6NCFA==", + "funding": [ + "https://github.com/fb55/htmlparser2?sponsor=1", + { + "type": "github", + "url": "https://github.com/sponsors/fb55" + } + ], + "license": "MIT", + "dependencies": { + "domelementtype": "^2.3.0", + "domhandler": "^5.0.3", + "domutils": "^3.0.1", + "entities": "^4.4.0" + } + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-errors/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", + "engines": { + "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/https-proxy-agent": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", + "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "license": "MIT", + "dependencies": { + "agent-base": "^7.1.2", + "debug": "4" + }, + "engines": { + "node": ">= 14" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "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/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ip-address": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz", + "integrity": "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==", + "license": "MIT", + "engines": { + "node": ">= 12" + } + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "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/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-plain-object": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz", + "integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "license": "MIT" + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "license": "MIT", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "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/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/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/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mitt": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mitt/-/mitt-3.0.1.tgz", + "integrity": "sha512-vKivATfr97l2/QBCYAkXYDbrIWPM2IIKEl7YPhjCvKlG3kE2gm+uBo6nEXK3M5/Ffh/FLpKExzOQ3JJoJGFKBw==", + "license": "MIT" + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "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/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "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/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-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/parse-srcset": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/parse-srcset/-/parse-srcset-1.0.2.tgz", + "integrity": "sha512-/2qh0lav6CmI15FzA3i/2Bzk2zCgQhGMkvhOhKNcBVQ1ldgpbfiNTVslmooUmWJcADi1f1kIeynbDRVzNlfR6Q==", + "license": "MIT" + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-to-regexp": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", + "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "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/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "license": "ISC" + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "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/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "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-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/puppeteer": { + "version": "24.23.0", + "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-24.23.0.tgz", + "integrity": "sha512-BVR1Lg8sJGKXY79JARdIssFWK2F6e1j+RyuJP66w4CUmpaXjENicmA3nNpUXA8lcTdDjAndtP+oNdni3T/qQqA==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@puppeteer/browsers": "2.10.10", + "chromium-bidi": "9.1.0", + "cosmiconfig": "^9.0.0", + "devtools-protocol": "0.0.1508733", + "puppeteer-core": "24.23.0", + "typed-query-selector": "^2.12.0" + }, + "bin": { + "puppeteer": "lib/cjs/puppeteer/node/cli.js" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/puppeteer-core": { + "version": "24.23.0", + "resolved": "https://registry.npmjs.org/puppeteer-core/-/puppeteer-core-24.23.0.tgz", + "integrity": "sha512-yl25C59gb14sOdIiSnJ08XiPP+O2RjuyZmEG+RjYmCXO7au0jcLf7fRiyii96dXGUBW7Zwei/mVKfxMx/POeFw==", + "license": "Apache-2.0", + "dependencies": { + "@puppeteer/browsers": "2.10.10", + "chromium-bidi": "9.1.0", + "debug": "^4.4.3", + "devtools-protocol": "0.0.1508733", + "typed-query-selector": "^2.12.0", + "webdriver-bidi-protocol": "0.3.6", + "ws": "^8.18.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.1.tgz", + "integrity": "sha512-9G8cA+tuMS75+6G/TzW8OtLzmBDMo8p1JRxN5AZ+LAp8uxGA8V8GZm4GQ4/N5QNQEnLmg6SS7wyuSmbKepiKqA==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.7.0", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/raw-body/node_modules/iconv-lite": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", + "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "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/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/sanitize-html": { + "version": "2.17.0", + "resolved": "https://registry.npmjs.org/sanitize-html/-/sanitize-html-2.17.0.tgz", + "integrity": "sha512-dLAADUSS8rBwhaevT12yCezvioCA+bmUTPH/u57xKPT8d++voeYE6HeluA/bPbQ15TwDBG2ii+QZIEmYx8VdxA==", + "license": "MIT", + "dependencies": { + "deepmerge": "^4.2.2", + "escape-string-regexp": "^4.0.0", + "htmlparser2": "^8.0.0", + "is-plain-object": "^5.0.0", + "parse-srcset": "^1.0.2", + "postcss": "^8.3.11" + } + }, + "node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", + "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.5", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "mime-types": "^3.0.1", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/serve-static": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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/socks": { + "version": "2.8.7", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz", + "integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==", + "license": "MIT", + "dependencies": { + "ip-address": "^10.0.1", + "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.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/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/streamx": { + "version": "2.23.0", + "resolved": "https://registry.npmjs.org/streamx/-/streamx-2.23.0.tgz", + "integrity": "sha512-kn+e44esVfn2Fa/O0CPFcex27fjIL6MkVae0Mm6q+E6f0hWv578YCERbv+4m02cjxvDsPKLnmxral/rR6lBMAg==", + "license": "MIT", + "dependencies": { + "events-universal": "^1.0.0", + "fast-fifo": "^1.3.2", + "text-decoder": "^1.1.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tar-fs": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-3.1.1.tgz", + "integrity": "sha512-LZA0oaPOc2fVo82Txf3gw+AkEd38szODlptMYejQUhndHMLQ9M059uXR+AfS7DNo0NpINvSqDsvyaCrBVkptWg==", + "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", + "integrity": "sha512-qJj60CXt7IU1Ffyc3NJMjh6EkuCFej46zUqJ4J7pqYlThyd9bO0XBTmcOIhSzZJVWfsLks0+nle/j538YAW9RQ==", + "license": "MIT", + "dependencies": { + "b4a": "^1.6.4", + "fast-fifo": "^1.2.0", + "streamx": "^2.15.0" + } + }, + "node_modules/text-decoder": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/text-decoder/-/text-decoder-1.2.3.tgz", + "integrity": "sha512-3/o9z3X0X0fTupwsYvR03pJ/DjWuqqrfwBgTQzdWDiQSm9KitAyz/9WqsT2JQW7KV2m+bC2ol/zqpW37NHxLaA==", + "license": "Apache-2.0", + "dependencies": { + "b4a": "^1.6.4" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "license": "MIT", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "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/undici-types": { + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.13.0.tgz", + "integrity": "sha512-Ov2Rr9Sx+fRgagJ5AX0qvItZG/JKKoBRAVITs1zk7IqZGTJUwgUr7qoYBpWwakpWilTZFM98rG/AFRocu10iIQ==", + "license": "MIT", + "optional": true + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/webdriver-bidi-protocol": { + "version": "0.3.6", + "resolved": "https://registry.npmjs.org/webdriver-bidi-protocol/-/webdriver-bidi-protocol-0.3.6.tgz", + "integrity": "sha512-mlGndEOA9yK9YAbvtxaPTqdi/kaCWYYfwrZvGzcmkr/3lWM+tQj53BxtpVd6qbC6+E5OnHXgCcAhre6AkXzxjA==", + "license": "Apache-2.0" + }, + "node_modules/wrap-ansi": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", + "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/y18n": { + "version": "5.0.8", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", + "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/yargs": { + "version": "17.7.2", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz", + "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==", + "license": "MIT", + "dependencies": { + "cliui": "^8.0.1", + "escalade": "^3.1.1", + "get-caller-file": "^2.0.5", + "require-directory": "^2.1.1", + "string-width": "^4.2.3", + "y18n": "^5.0.5", + "yargs-parser": "^21.1.1" + }, + "engines": { + "node": ">=12" + } + }, + "node_modules/yargs-parser": { + "version": "21.1.1", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz", + "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==", + "license": "ISC", + "engines": { + "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/zod": { + "version": "3.25.76", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.25.76.tgz", + "integrity": "sha512-gzUt/qt81nXsFGKIFcC3YnfEAx5NkunCfnDlvuBSSFS02bcXu4Lmea0AFIUwbLWxWPx3d9p8S5QoaujKcNQxcQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + } + } +} diff --git a/cv-export-server/package.json b/cv-export-server/package.json new file mode 100644 index 0000000..6f39885 --- /dev/null +++ b/cv-export-server/package.json @@ -0,0 +1,21 @@ +{ + "name": "cv-export-server", + "version": "1.0.0", + "description": "", + "type": "module", + "main": "server.js", + "scripts": { + "start": "node server.js", + "dev": "NODE_ENV=development node server.js", + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "cors": "^2.8.5", + "express": "^5.1.0", + "puppeteer": "^24.23.0", + "sanitize-html": "^2.17.0" + } +} diff --git a/cv-export-server/server.js b/cv-export-server/server.js new file mode 100644 index 0000000..ab13606 --- /dev/null +++ b/cv-export-server/server.js @@ -0,0 +1,281 @@ +import express from 'express'; +import cors from 'cors'; +import puppeteer from 'puppeteer'; +import sanitizeHtml from 'sanitize-html'; +import { buildPrintableHtml as sharedBuild } from '../shared-printable/buildPrintableHtml.js'; + +// Minimal escape function to avoid breaking HTML; summary is sanitized client-side. +function escapeText(str) { + if (str == null) return ''; + return String(str) + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/\"/g, '"') + .replace(/'/g, '''); +} + +function buildPrintableHtml(cv) { + const { personal = {}, summary = '', work = [], education = [], skills = [], languages = [], certifications = [] } = cv || {}; + const styles = ` + + `; + + const headerLinks = (personal.links || []) + .map(l => `${escapeText(l.label || l.url)}`) + .join(' · '); + + const headerHtml = ` +
+

${escapeText(personal.firstName || '')} ${escapeText(personal.lastName || '')}

+
+ ${personal.email ? `${escapeText(personal.email)}` : ''} + ${personal.phone ? `${escapeText(personal.phone)}` : ''} + ${(personal.city || personal.country) ? `${personal.city ? escapeText(personal.city) : ''}${personal.city && personal.country ? ', ' : ''}${personal.country ? escapeText(personal.country) : ''}` : ''} + ${headerLinks ? `${headerLinks}` : ''} +
+
+ `; + + const cleanSummary = typeof summary === 'string' ? sanitizeHtml(summary, { + allowedTags: ['p', 'strong', 'em', 'ul', 'ol', 'li', 'br', 'a'], + allowedAttributes: { a: ['href', 'rel', 'target'] } + }) : ''; + + const summarySection = cleanSummary ? ` +
+

Professional Summary

+
${cleanSummary}
+
+ ` : ''; + + const workSection = (work && work.length) ? ` +
+

Work Experience

+ ${work.map(job => ` +
+
+
+

${escapeText(job.title)}

+
${escapeText(job.company)}
+
+
+ ${escapeText(job.startDate || '')} - ${escapeText(job.endDate || 'Present')} + ${job.location ? `
${escapeText(job.location)}
` : ''} +
+
+ ${(job.bullets && job.bullets.length) ? ` +
    + ${job.bullets.map(b => `
  • ${escapeText(b)}
  • `).join('')} +
+ ` : ''} +
+ `).join('')} +
+ ` : ''; + + const eduSection = (education && education.length) ? ` +
+

Education

+ ${education.map(ed => ` +
+
+
+

${escapeText(ed.degree)}

+
${escapeText(ed.school)}
+
+
+ ${escapeText(ed.startDate || '')} - ${escapeText(ed.endDate || '')} +
+
+ ${ed.gpa ? `
GPA: ${escapeText(ed.gpa)}
` : ''} +
+ `).join('')} +
+ ` : ''; + + const skillsSection = (skills && skills.length) ? ` +
+

Skills

+
+ ${skills.map(s => `${escapeText(s.name)}${s.level ? ` — ${escapeText(s.level)}` : ''}`).join('')} +
+
+ ` : ''; + + const languagesSection = (languages && languages.length) ? ` +
+

Languages

+
+ ${languages.map(l => `${escapeText(l.name)}${l.level ? ` — ${escapeText(l.level)}` : ''}`).join('')} +
+
+ ` : ''; + + const certsSection = (certifications && certifications.length) ? ` +
+

Certifications

+
    + ${certifications.map(c => `
  • ${escapeText(c.name)}${c.issuer ? ` - ${escapeText(c.issuer)}` : ''}${c.date ? ` (${escapeText(c.date)})` : ''}
  • `).join('')} +
+
+ ` : ''; + + return ` + + + + + Printable CV + ${styles} + + +
+ ${headerHtml} + ${summarySection} + ${workSection} + ${eduSection} + ${skillsSection} + ${languagesSection} + ${certsSection} +
+ + `; +} + +const app = express(); +app.use(cors()); +app.use(express.json({ limit: '1mb' })); + +// Pluggable job store with in-memory default +class InMemoryJobStore { + constructor() { + this.map = new Map(); + } + create(job) { + const id = `job_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`; + this.map.set(id, { ...job, id }); + return id; + } + get(id) { return this.map.get(id); } + set(id, data) { this.map.set(id, { ...(this.map.get(id) || {}), ...data }); } + cancel(id) { + const job = this.map.get(id); + if (!job) return false; + this.map.set(id, { ...job, status: 'canceled', error: null, buffer: null }); + return true; + } +} + +const jobs = new InMemoryJobStore(); + +app.post('/export/pdf', async (req, res) => { + try { + // Treat presence of the query param as async, and allow common truthy values + const asyncFlag = ( + typeof req.query.async !== 'undefined' + ? ['1', 'true', 'on', ''].includes(String(req.query.async).toLowerCase()) + : req.body?.async === true + ); + const templateId = req.query.templateId || req.body?.templateId || null; + const cv = req.body || {}; + const sanitizedCv = { + ...cv, + summary: typeof cv.summary === 'string' ? sanitizeHtml(cv.summary, { + allowedTags: ['p', 'strong', 'em', 'ul', 'ol', 'li', 'br', 'a'], + allowedAttributes: { a: ['href', 'rel', 'target'] } + }) : '' + }; + + const firstName = (sanitizedCv.personal && sanitizedCv.personal.firstName) ? sanitizedCv.personal.firstName : 'Candidate'; + const lastName = (sanitizedCv.personal && sanitizedCv.personal.lastName) ? sanitizedCv.personal.lastName : 'CV'; + const filename = `${firstName}_${lastName}_CV.pdf`.replace(/\s+/g, '_'); + + if (asyncFlag) { + const jobId = jobs.create({ status: 'queued', filename, error: null, buffer: null, templateId }); + setImmediate(async () => { + try { + const current = jobs.get(jobId); + if (current?.status === 'canceled') return; // honor cancel + const html = sharedBuild(sanitizedCv); + const browser = await puppeteer.launch({ args: ['--no-sandbox', '--disable-setuid-sandbox'] }); + const page = await browser.newPage(); + await page.setContent(html, { waitUntil: 'networkidle0' }); + const pdfBuffer = await page.pdf({ format: 'A4', printBackground: true }); + await browser.close(); + jobs.set(jobId, { status: 'done', filename, error: null, buffer: pdfBuffer, templateId }); + } catch (e) { + console.error('Async export error:', e); + jobs.set(jobId, { status: 'error', filename, error: 'Failed to create PDF', buffer: null, templateId }); + } + }); + return res.json({ jobId }); + } + + // Synchronous path + const html = sharedBuild(sanitizedCv); + const browser = await puppeteer.launch({ args: ['--no-sandbox', '--disable-setuid-sandbox'] }); + const page = await browser.newPage(); + await page.setContent(html, { waitUntil: 'networkidle0' }); + const pdfBuffer = await page.pdf({ format: 'A4', printBackground: true }); + await browser.close(); + + res.set({ + 'Content-Type': 'application/pdf', + 'Content-Disposition': `attachment; filename="${filename}"` + }); + res.send(pdfBuffer); + } catch (err) { + console.error('Export error:', err); + res.status(500).json({ error: 'Failed to create PDF' }); + } +}); + +// Job status endpoint +app.get('/export/status/:id', (req, res) => { + const job = jobs.get(req.params.id); + if (!job) return res.status(404).json({ error: 'Job not found' }); + res.json({ status: job.status, filename: job.filename, error: job.error }); +}); + +// Job download endpoint +app.get('/export/download/:id', (req, res) => { + const job = jobs.get(req.params.id); + if (!job) return res.status(404).json({ error: 'Job not found' }); + if (job.status !== 'done' || !job.buffer) return res.status(409).json({ error: 'Job not ready' }); + res.set({ + 'Content-Type': 'application/pdf', + 'Content-Disposition': `attachment; filename="${job.filename}"` + }); + res.send(job.buffer); +}); + +// Cancel job endpoint +app.post('/export/cancel/:id', (req, res) => { + const id = req.params.id; + const ok = jobs.cancel(id); + if (!ok) return res.status(404).json({ error: 'Job not found' }); + return res.json({ status: 'canceled' }); +}); + +const PORT = process.env.PORT || 4000; +app.listen(PORT, () => { + console.log(`Export server listening on http://localhost:${PORT}`); +}); \ No newline at end of file diff --git a/headers.txt b/headers.txt new file mode 100644 index 0000000..f276838 --- /dev/null +++ b/headers.txt @@ -0,0 +1,11 @@ +HTTP/1.1 200 OK +X-Powered-By: Express +Access-Control-Allow-Origin: * +Content-Type: application/pdf +Content-Disposition: attachment; filename="Ada_Lovelace_CV.pdf" +Content-Length: 13298 +ETag: W/"33f2-1gLBdxasSpROFsvw/XUGR63Xjhg" +Date: Sun, 05 Oct 2025 23:50:44 GMT +Connection: keep-alive +Keep-Alive: timeout=5 + diff --git a/job.json b/job.json new file mode 100644 index 0000000000000000000000000000000000000000..423b32bdd2af43c3aaa0d9011a444210ce82a9cc GIT binary patch literal 13298 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 literal 0 HcmV?d00001 diff --git a/out.json b/out.json new file mode 100644 index 0000000..35c69a3 --- /dev/null +++ b/out.json @@ -0,0 +1 @@ +{"jobId":"job_1759708234671_izajbw"} \ No newline at end of file diff --git a/out.pdf b/out.pdf new file mode 100644 index 0000000000000000000000000000000000000000..090c4752e85d07a443005463ef28b0da10532908 GIT binary patch literal 13298 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 literal 0 HcmV?d00001 diff --git a/shared-printable/buildPrintableHtml.d.ts b/shared-printable/buildPrintableHtml.d.ts new file mode 100644 index 0000000..9fbafc9 --- /dev/null +++ b/shared-printable/buildPrintableHtml.d.ts @@ -0,0 +1,2 @@ +export type CV = import('../cv-engine/src/schema/cvSchema').CV; +export function buildPrintableHtml(cv: CV): string; \ No newline at end of file diff --git a/shared-printable/buildPrintableHtml.js b/shared-printable/buildPrintableHtml.js new file mode 100644 index 0000000..8775eac --- /dev/null +++ b/shared-printable/buildPrintableHtml.js @@ -0,0 +1,152 @@ +// ESM module: shared printable HTML builder +// Note: callers must sanitize any HTML fields (e.g., summary) before passing. + +function escapeText(str) { + if (str == null) return ''; + return String(str) + .replace(/&/g, '&') + .replace(//g, '>') + .replace(/\"/g, '"') + .replace(/'/g, '''); +} + +export function buildPrintableHtml(cv) { + const { personal = {}, summary = '', work = [], education = [], skills = [], languages = [], certifications = [] } = cv || {}; + const styles = ` + + `; + + const headerLinks = (personal.links || []) + .map(l => `${escapeText(l.label || l.url)}`) + .join(' · '); + + const headerHtml = ` +
+

${escapeText(personal.firstName || '')} ${escapeText(personal.lastName || '')}

+
+ ${personal.email ? `${escapeText(personal.email)}` : ''} + ${personal.phone ? `${escapeText(personal.phone)}` : ''} + ${(personal.city || personal.country) ? `${personal.city ? escapeText(personal.city) : ''}${personal.city && personal.country ? ', ' : ''}${personal.country ? escapeText(personal.country) : ''}` : ''} + ${headerLinks ? `${headerLinks}` : ''} +
+
+ `; + + const summarySection = summary ? ` +
+

Professional Summary

+
${summary}
+
+ ` : ''; + + const workSection = (work && work.length) ? ` +
+

Work Experience

+ ${work.map(job => ` +
+
+
+

${escapeText(job.title)}

+
${escapeText(job.company)}
+
+
+ ${escapeText(job.startDate || '')} - ${escapeText(job.endDate || 'Present')} + ${job.location ? `
${escapeText(job.location)}
` : ''} +
+
+ ${(job.bullets && job.bullets.length) ? ` +
    + ${job.bullets.map(b => `
  • ${escapeText(b)}
  • `).join('')} +
+ ` : ''} +
+ `).join('')} +
+ ` : ''; + + const eduSection = (education && education.length) ? ` +
+

Education

+ ${education.map(ed => ` +
+
+
+

${escapeText(ed.degree)}

+
${escapeText(ed.school)}
+
+
+ ${escapeText(ed.startDate || '')} - ${escapeText(ed.endDate || '')} +
+
+ ${ed.gpa ? `
GPA: ${escapeText(ed.gpa)}
` : ''} +
+ `).join('')} +
+ ` : ''; + + const skillsSection = (skills && skills.length) ? ` +
+

Skills

+
+ ${skills.map(s => `${escapeText(s.name)}${s.level ? ` — ${escapeText(s.level)}` : ''}`).join('')} +
+
+ ` : ''; + + const languagesSection = (languages && languages.length) ? ` +
+

Languages

+
+ ${languages.map(l => `${escapeText(l.name)}${l.level ? ` — ${escapeText(l.level)}` : ''}`).join('')} +
+
+ ` : ''; + + const certsSection = (certifications && certifications.length) ? ` +
+

Certifications

+
    + ${certifications.map(c => `
  • ${escapeText(c.name)}${c.issuer ? ` - ${escapeText(c.issuer)}` : ''}${c.date ? ` (${escapeText(c.date)})` : ''}
  • `).join('')} +
+
+ ` : ''; + + return ` + + + + + Printable CV + ${styles} + + +
+ ${headerHtml} + ${summarySection} + ${workSection} + ${eduSection} + ${skillsSection} + ${languagesSection} + ${certsSection} +
+ + `; +} \ No newline at end of file