From ea4888726ae0eaf57eebf22bc14eddc8c9f48efc Mon Sep 17 00:00:00 2001 From: GomdimApps Date: Thu, 13 Aug 2026 12:50:06 -0300 Subject: [PATCH] feat: add Vitest configuration and comprehensive unit/integration test suite for core printer components and utility modules. --- .github/workflows/test.yml | 31 + AGENTS.md | 70 +- README.md | 1 + ...rwidth-scales-columns-and-imagemaxwidth.md | 10 + .../04-feedbeforecut-defaults-to-zero.md | 2 + .../05-bwip-js-validates-pdf417-capacity.md | 4 + .../06-bwip-js-default-eclevel-differs.md | 3 + .../09-clone-printers-lack-native-pdf417.md | 4 + ...10-clone-printers-mangle-rule-character.md | 3 + package-lock.json | 2661 ++++++++++++++++- package.json | 11 +- test/Images/image.test.ts | 106 + test/Preview/itf.test.ts | 31 + test/Preview/pdf417.raster.test.ts | 57 + test/Preview/qrcode.raster.test.ts | 43 + test/Printer/ReceiptBuilder.barcode.test.ts | 29 + test/Printer/ReceiptBuilder.image.test.ts | 40 + test/Printer/ReceiptBuilder.pdf417.test.ts | 51 + test/Printer/ReceiptBuilder.qrcode.test.ts | 50 + test/Printer/ReceiptBuilder.rule.test.ts | 27 + test/Printer/ReceiptBuilder.text.test.ts | 62 + test/Printer/SafeMode.test.ts | 93 + test/Text/justify.test.ts | 21 + test/Text/sendLine.test.ts | 62 + test/Text/wrap.test.ts | 39 + test/config.test.ts | 26 + test/helpers/assertBytes.ts | 19 + test/helpers/dom.ts | 74 + test/helpers/encoder.ts | 20 + test/helpers/fixtures.ts | 30 + test/helpers/receipt.ts | 20 + test/tsconfig.json | 8 + vitest.config.ts | 7 + 33 files changed, 3708 insertions(+), 7 deletions(-) create mode 100644 .github/workflows/test.yml create mode 100644 test/Images/image.test.ts create mode 100644 test/Preview/itf.test.ts create mode 100644 test/Preview/pdf417.raster.test.ts create mode 100644 test/Preview/qrcode.raster.test.ts create mode 100644 test/Printer/ReceiptBuilder.barcode.test.ts create mode 100644 test/Printer/ReceiptBuilder.image.test.ts create mode 100644 test/Printer/ReceiptBuilder.pdf417.test.ts create mode 100644 test/Printer/ReceiptBuilder.qrcode.test.ts create mode 100644 test/Printer/ReceiptBuilder.rule.test.ts create mode 100644 test/Printer/ReceiptBuilder.text.test.ts create mode 100644 test/Printer/SafeMode.test.ts create mode 100644 test/Text/justify.test.ts create mode 100644 test/Text/sendLine.test.ts create mode 100644 test/Text/wrap.test.ts create mode 100644 test/config.test.ts create mode 100644 test/helpers/assertBytes.ts create mode 100644 test/helpers/dom.ts create mode 100644 test/helpers/encoder.ts create mode 100644 test/helpers/fixtures.ts create mode 100644 test/helpers/receipt.ts create mode 100644 test/tsconfig.json create mode 100644 vitest.config.ts diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..13ce08f --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,31 @@ +name: test + +on: + push: + branches: [main] + pull_request: + +jobs: + test: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-node@v4 + with: + node-version: 22 # Vitest 3's own supported range: ^18 || ^20 || >=22 (package.json#engines) + + # `npm ci` pulls the `canvas` devDependency (see AGENTS.md's Testing + # section) — it ships prebuilt binaries for linux-x64-glibc, which + # ubuntu-latest is, so no apt-get should be needed. If a future runner + # image ever lacks a prebuilt and `canvas` falls back to compiling from + # source, uncomment the block below (node-canvas's own documented + # Debian/Ubuntu build dependencies): + # + # - run: sudo apt-get update && sudo apt-get install -y build-essential libcairo2-dev libpango1.0-dev libjpeg-dev libgif-dev librsvg2-dev + + - run: npm ci + - run: npm test + - run: npx tsc --noEmit + - run: npx tsc --noEmit -p test/tsconfig.json + - run: npm run build diff --git a/AGENTS.md b/AGENTS.md index ebc951e..3e06ee9 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -60,6 +60,13 @@ src/Preview/ demo/index.html # manual test page — Tailwind CDN, loads ../build/web-escpos-printer.js + app.js demo/app.js # demo page's own logic — no build step Dockerfile / docker-compose.yml / scripts/nginx/default.conf # serves demo/ + build/ on :3000 + +test/ # Vitest suite — see "## Testing" below + helpers/ # buildEncoder/buildBytes/withDom/pixelFixture/assertBytes + config.test.ts, Text/, Images/, Preview/, Printer/ # mirrors the src/ tree above + +vitest.config.ts # test.include: test/**/*.test.ts +.github/workflows/test.yml # runs `npm test` on push/PR ``` ## Critical gotchas (read before editing ReceiptBuilder.ts / Text / Preview) @@ -144,11 +151,11 @@ time. Full report linked where one exists under `docs/notes/`. one → check the other. `wrapText`, `justifyLine`, image sizing/dithering are already shared for this reason; new alignment/layout logic should be shared the same way. -- **No test framework.** Verification: `npm run build` + `npx tsc --noEmit` - + throwaway Node scripts that `import('@point-of-sale/receipt-printer-encoder')` - and inspect real encoded bytes + the Docker demo for real hardware. - Reproduce encoder bugs against the *real* installed library before - trusting a fix. +- **Automated tests + the Docker demo.** `npm test` (see "## Testing" below) + covers everything DOM-free-or-jsdom-reachable; the Docker demo is still + the only way to confirm real hardware behavior. Reproduce encoder bugs + against the *real* installed library before trusting a fix — same + standard the test suite itself follows (no mocked encoder anywhere). - **Comments explain "why", not "what."** - **Don't hand-type `\uXXXX` escapes directly** — this environment has corrupted literal `\u0300-\u036f` (in `stripAccents()`) into raw @@ -182,6 +189,59 @@ fork/rebuild needed. Not a "gotcha" (no hardware bug behind it, this is an intentional API) — see the README's "Manual Bluetooth profile" section for consumer-facing docs. +## Testing + +```sh +npm test # node:test, ~70 assertions, a few hundred ms +npx tsc --noEmit # src/, unaffected by test/ — unchanged from before +npx tsc --noEmit -p test/tsconfig.json # type-checks test/ itself (own tsconfig: adds "node" types, noEmit) +npm run build +``` + +`npm test` runs `vitest run` (config: `vitest.config.ts`, just +`test.include: ['test/**/*.test.ts']`) — test files `import { describe, it, +expect } from 'vitest'`, not `node:test`/`node:assert`. Vitest was chosen +over `node:test` specifically because its resolver (Vite's, ESM-native, +resolves extensions automatically) has no trouble with this repo's +extension-less relative imports (`moduleResolution: "Bundler"`-style) or +with dependencies whose `package.json#exports` defines only an `"import"` +condition (e.g. `@bwip-js/browser`) — confirmed directly. `tsx` was tried +first for this and hard-failed (`ERR_PACKAGE_PATH_NOT_EXPORTED`) on exactly +that: without `"type": "module"` in *this* package's `package.json` (which +can't be added — see below), `tsx` resolves bare specifiers via a +CommonJS-style algorithm that can't see an import-only `exports` map — a +plain Node `node:test` setup needed its own custom loader hook to work +around the same issue; Vitest needs none of that. Don't add +`"type": "module"` to fix this a different way: it would make +`build/web-escpos-printer.js` (the UMD bundle) unrequireable, breaking the +documented `require('web-escpos-printer')` compatibility path. + +Default test environment is Node, not Vitest's built-in `jsdom` — the +DOM-dependent tests below use this repo's own `test/helpers/dom.ts#withDom()` +(manual jsdom+`canvas` setup, scoped per test) instead, so DOM-free test +files never see a jsdom global leak into them. + +**Scope**: everything DOM-free (the real encoder, `ReceiptBuilder.ts`'s +dispatch logic, `Text/`, `config.ts`, `SafeMode.ts`, +`resolvePdf417Columns()`) plus, via the `jsdom` + `canvas` devDependencies +(`test/helpers/dom.ts`'s `withDom()`), the DOM-dependent layer too — +`Images/image.ts`'s real source loading and the `Preview/` raster builders' +actual pixel output (`buildQrCodeRasterImage`/`buildPdf417RasterImage`/ +`buildItf`). jsdom's own `HTMLCanvasElement`/`Image` pixel decoding is +wired to the `canvas` npm package specifically (not `@napi-rs/canvas` — +confirmed, jsdom doesn't know how to talk to it). Not covered: real +Bluetooth/QZ transport connections and `profiles.ts`'s `findProfile()` +(would need a mocked Web Bluetooth API, not requested), and pixel-perfect +golden-image diffing (tests assert structural correctness — dimensions, +multiple-of-8 padding, non-white pixels present — not exact pixel match). + +Most `docs/notes/*.md` gotchas now have a pinned regression test — see each +note's own "Pinned by" line where present. A few genuinely can't be: real +BLE characteristic behavior (#03) and Windows driver routing (#08) need +actual hardware/OS, and bundle size (#07) is a build-output assertion, not +a runtime one. Add a new `test/**/*.test.ts` file (mirroring the `src/` +path of what it tests) for any new gotcha the same way. + ## Docker demo `docker compose up --build -d` → `http://localhost:3000/`. Rebuild after diff --git a/README.md b/README.md index 82139ea..22b7125 100644 --- a/README.md +++ b/README.md @@ -251,6 +251,7 @@ npm install npm run build # UMD + ESM + .d.ts (what gets published to npm) npm run build:standalone # only build/web-escpos-printer.js npm run build:dev # same as build, in watch mode +npm test # node:test suite against the real encoder — see AGENTS.md's "Testing" section ``` ## License diff --git a/docs/notes/02-paperwidth-scales-columns-and-imagemaxwidth.md b/docs/notes/02-paperwidth-scales-columns-and-imagemaxwidth.md index 17d2d5e..0fa2606 100644 --- a/docs/notes/02-paperwidth-scales-columns-and-imagemaxwidth.md +++ b/docs/notes/02-paperwidth-scales-columns-and-imagemaxwidth.md @@ -9,5 +9,15 @@ nothing for images/barcodes. `PaperWidth` is `'58mm' | '80mm' | '112mm'` — `80mm` cross-checked against real hardware (576 dots); `112mm` is an estimate, not hardware-verified. +**Known bug, found by the test suite below, not yet fixed**: `112mm` maps +to `columns: 56`, but the real encoder's constructor only accepts columns +of 32/35/42/44/48 (confirmed by reading the installed library) — it throws +`"The width of the paper must me either 32, 35, 42, 44 or 48 columns"`. +`paperWidth: '112mm'` therefore currently fails to build *any* receipt at +all, not just images. `58mm`(32)/`80mm`(42) are unaffected (both valid). +Pinned (as a currently-failing-on-purpose regression) by +`test/Printer/ReceiptBuilder.pdf417.test.ts`'s "paperWidth" suite — flip +that test to `doesNotReject` once this is actually fixed. + --- Referenced from [AGENTS.md](../../AGENTS.md)'s "Critical gotchas" section (gotcha #2). diff --git a/docs/notes/04-feedbeforecut-defaults-to-zero.md b/docs/notes/04-feedbeforecut-defaults-to-zero.md index 8b8669f..bee5ef0 100644 --- a/docs/notes/04-feedbeforecut-defaults-to-zero.md +++ b/docs/notes/04-feedbeforecut-defaults-to-zero.md @@ -15,5 +15,7 @@ reason — `ReceiptBuilder.ts` always passes it explicitly; `PreviewRenderer.ts` mirrors the same gap before its "✂ cut" mark (see AGENTS.md's "preview/print parity" rule in "Coding conventions"). +Pinned by `test/config.test.ts` (`DEFAULT_CONFIG.feedBeforeCut` must stay `4`). + --- Referenced from [AGENTS.md](../../AGENTS.md)'s "Critical gotchas" section (gotcha #4). diff --git a/docs/notes/05-bwip-js-validates-pdf417-capacity.md b/docs/notes/05-bwip-js-validates-pdf417-capacity.md index 1897f32..8884fef 100644 --- a/docs/notes/05-bwip-js-validates-pdf417-capacity.md +++ b/docs/notes/05-bwip-js-validates-pdf417-capacity.md @@ -11,5 +11,9 @@ run before committing to a non-auto `columns`, falling back to fully-automatic (the pre-existing, safe behavior) when it doesn't fit. Never pass a fixed `columns` to the real encoder without this check. +Pinned by `test/Preview/pdf417.raster.test.ts`'s `resolvePdf417Columns` +suite (auto mode must fall back to `undefined` instead of forwarding an +overflowing `columns`) and its own capacity-error propagation test. + --- Referenced from [AGENTS.md](../../AGENTS.md)'s "Critical gotchas" section (gotcha #5). diff --git a/docs/notes/06-bwip-js-default-eclevel-differs.md b/docs/notes/06-bwip-js-default-eclevel-differs.md index 8f56faf..f2e66a8 100644 --- a/docs/notes/06-bwip-js-default-eclevel-differs.md +++ b/docs/notes/06-bwip-js-default-eclevel-differs.md @@ -11,5 +11,8 @@ shape mismatch that survived even after `columns` was aligned ([gotcha #5](05-bw `errorlevel`, so both `buildPdf417()` and `resolvePdf417Columns()` render/validate against the same level the real print already assumes. +Pinned by `test/Printer/ReceiptBuilder.pdf417.test.ts` (an explicit +`errorlevel: 1` must produce byte-identical output to leaving it unset). + --- Referenced from [AGENTS.md](../../AGENTS.md)'s "Critical gotchas" section (gotcha #6). diff --git a/docs/notes/09-clone-printers-lack-native-pdf417.md b/docs/notes/09-clone-printers-lack-native-pdf417.md index 5fd721b..82cf3f1 100644 --- a/docs/notes/09-clone-printers-lack-native-pdf417.md +++ b/docs/notes/09-clone-printers-lack-native-pdf417.md @@ -22,5 +22,9 @@ correctly) vs. a clone Bluetooth printer (silently drops it, same bytes). bwip-js renderer `renderPreview()` already uses for the PDF417 preview. Other element types may gain the same flag later. +Pinned by `test/Printer/SafeMode.test.ts` (the shared substitution +mechanism) and `test/Printer/ReceiptBuilder.pdf417.test.ts`/`qrcode.test.ts` +(safeMode produces different bytes than the native command, end-to-end). + --- Referenced from [AGENTS.md](../../AGENTS.md)'s "Critical gotchas" section (gotcha #9). diff --git a/docs/notes/10-clone-printers-mangle-rule-character.md b/docs/notes/10-clone-printers-mangle-rule-character.md index bea2a76..dda9114 100644 --- a/docs/notes/10-clone-printers-mangle-rule-character.md +++ b/docs/notes/10-clone-printers-mangle-rule-character.md @@ -26,5 +26,8 @@ a valid plain-text substitute here. Off by default: the native rule character works fine on printers that support it, and produces a slightly different (solid vs. dashed) look. +Pinned by `test/Printer/ReceiptBuilder.rule.test.ts` (native `rule()` never +contains the plain-ASCII safeMode line, and vice versa). + --- Referenced from [AGENTS.md](../../AGENTS.md)'s "Critical gotchas" section (gotcha #10). diff --git a/package-lock.json b/package-lock.json index ae1e329..bcdbab5 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,14 +16,57 @@ "qz-tray": "^2.2.6" }, "devDependencies": { + "@types/jsdom": "^30.0.0", + "@types/node": "^26.2.0", "@types/qz-tray": "^2.2.2", "@types/web-bluetooth": "^0.0.21", + "canvas": "^3.2.3", + "jsdom": "^27.0.1", "ts-loader": "^9.5.4", "typescript": "^6.0.2", + "vitest": "^3.2.7", "webpack": "^5.102.1", "webpack-cli": "^5.1.4" + }, + "engines": { + "node": ">=22.6.0" + } + }, + "node_modules/@asamuzakjp/css-color": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-4.1.2.tgz", + "integrity": "sha512-NfBUvBaYgKIuq6E/RBLY1m0IohzNHAYyaJGuTK79Z23uNwmz2jl1mPsC5ZxCCxylinKhT1Amn5oNTlx1wN8cQg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@csstools/css-calc": "^3.0.0", + "@csstools/css-color-parser": "^4.0.1", + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0", + "lru-cache": "^11.2.5" } }, + "node_modules/@asamuzakjp/dom-selector": { + "version": "6.8.1", + "resolved": "https://registry.npmjs.org/@asamuzakjp/dom-selector/-/dom-selector-6.8.1.tgz", + "integrity": "sha512-MvRz1nCqW0fsy8Qz4dnLIvhOlMzqDVBabZx6lH+YywFDdjXhMY37SmpV1XFX3JzG5GWHn63j6HX6QPr3lZXHvQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/nwsapi": "^2.3.9", + "bidi-js": "^1.0.3", + "css-tree": "^3.1.0", + "is-potential-custom-element-name": "^1.0.1", + "lru-cache": "^11.2.6" + } + }, + "node_modules/@asamuzakjp/nwsapi": { + "version": "2.3.9", + "resolved": "https://registry.npmjs.org/@asamuzakjp/nwsapi/-/nwsapi-2.3.9.tgz", + "integrity": "sha512-n8GuYSrI9bF7FFZ/SjhwevlHc8xaVlb/7HmHelnc/PZXBD2ZR49NnN9sMMuDdEGPeeRQ5d0hqlSlEpgCX3Wl0Q==", + "dev": true, + "license": "MIT" + }, "node_modules/@bwip-js/browser": { "version": "4.11.2", "resolved": "https://registry.npmjs.org/@bwip-js/browser/-/browser-4.11.2.tgz", @@ -36,6 +79,146 @@ "integrity": "sha512-QdObRRjRbcXGmM1tmJ+MrHcaz1MftF2+W7YI+MsphnsCrmtyfS0d5qJbk0MeSbUeyM/jCb0hmnkXPsy026L7dA==", "license": "MIT" }, + "node_modules/@csstools/color-helpers": { + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/@csstools/color-helpers/-/color-helpers-6.1.0.tgz", + "integrity": "sha512-064IFJdjTfUqnjpCVpMOdbr8FLQBhinbZj6yRv2An2E41O/pLEXqfFRWqGq/SxlE5PEUYTlvWsG2r8MswAVvkg==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "engines": { + "node": ">=20.19.0" + } + }, + "node_modules/@csstools/css-calc": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-3.3.0.tgz", + "integrity": "sha512-c5ihYsPkdG6JCkU2zTMm4+k6r7RXuGxtWYhu5DHMIiF1FHzrfmHL5so11AoFpUv/tu61xfcmT4AmKoFfMPoqdQ==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-color-parser": { + "version": "4.1.10", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-4.1.10.tgz", + "integrity": "sha512-UZhQLIUyJaaMepqehrCODwCg2KW25vFvLWBmqYFaPclYvvxzj/sG8LBOhBFCp11i9uE7t1EyS+RAoV9tztPFyw==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "dependencies": { + "@csstools/color-helpers": "^6.1.0", + "@csstools/css-calc": "^3.3.0" + }, + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-parser-algorithms": "^4.0.0", + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-parser-algorithms": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-4.0.0.tgz", + "integrity": "sha512-+B87qS7fIG3L5h3qwJ/IFbjoVoOe/bpOdh9hAjXbvx0o8ImEmUsGXN0inFOnk2ChCFgqkkGFQ+TpM5rbhkKe4w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + }, + "peerDependencies": { + "@csstools/css-tokenizer": "^4.0.0" + } + }, + "node_modules/@csstools/css-syntax-patches-for-csstree": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/@csstools/css-syntax-patches-for-csstree/-/css-syntax-patches-for-csstree-1.1.7.tgz", + "integrity": "sha512-fQ+05118eQS1cofO3aJpB5efgpBZMvIzwr/sbC8kDLVA5XLG8q1kJV5yzrUAI1f7lvhPnm8fgIjzFB8/O/5Dig==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT-0", + "peerDependencies": { + "css-tree": "^3.2.1" + }, + "peerDependenciesMeta": { + "css-tree": { + "optional": true + } + } + }, + "node_modules/@csstools/css-tokenizer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-4.0.0.tgz", + "integrity": "sha512-QxULHAm7cNu72w97JUNCBFODFaXpbDg+dP8b/oWFAZ2MTRppA3U00Y2L1HqaS4J6yBqxwa/Y3nMBaxVKbB/NsA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/csstools" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/csstools" + } + ], + "license": "MIT", + "engines": { + "node": ">=20.19.0" + } + }, "node_modules/@discoveryjs/json-ext": { "version": "0.5.7", "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz", @@ -46,6 +229,448 @@ "node": ">=10.0.0" } }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.2.tgz", + "integrity": "sha512-XExcO+dvLKvVtNTibSTBej1NCAbaGhWn9Ww1ZPx80qsahhPFe/8jgWP0IchNe0F3HwkU7n8ejhH8bjonqht8mQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.2.tgz", + "integrity": "sha512-kXXoiPVVGQcnIYGOeaovwOURpniDBpSq4A03qkQ+BMQqtGG6HYap3xne9C1O1yo4TR3qxlCX5IqqmX6fFo2Lqg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.2.tgz", + "integrity": "sha512-5YfKeeI8qWfBZIX+u2xZC3Zlb3Os/gLS2sbEKM+I4ZOcsWmHS2WLysCcQZDAFRslDUU5Oiq44gf6PYN1vGwG5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.2.tgz", + "integrity": "sha512-O387ite7SzUyCcy3JQX4P4bLtEA7bLLkx+esve5JHnyYfNTxcVpXZo9jhdB0lTKN44gztELTdU7nS8Nr16Fs1Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.2.tgz", + "integrity": "sha512-n4KqkOQrraxHJcgjM1RvwbigfQKIKJVpM7xp+KsxiyUSrRdIXnt73VhrPAx0fV44hgfmIVKjxMN9J1t5jySVkw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.2.tgz", + "integrity": "sha512-uq6suIWYP37qzGddBKPw5QEQPi6HiLGsO7UmkpfyaYNQ3D+rN6w6WfwH+nuqcGXWvawGwxOEroO4YGnFh95azw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.2.tgz", + "integrity": "sha512-n+I0BTSRIoy+d6RPKnEVwql5UwBJolytvY4mAOIEJorKlqgPII8ix6slVVrfZ5Tnj7glIZvloylbB/EJPMWEXw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.2.tgz", + "integrity": "sha512-78XJTJkvPs0kz2w61301PJjXl4g7q3JqiYMZ/M/yVI73EHBrCRTgkhu9oqG7vPqq+a/yadEW8aD+agKlk5xrmg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.2.tgz", + "integrity": "sha512-XlDnu2q5yoqems+xay6wSAcg9DDD7K9RLKZEBOMZm3ckNpJBvOX20tSfby8KfrrhINDyv9V2YVZKY/SpoGJI8w==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.2.tgz", + "integrity": "sha512-pW4AC0P3it8c7do9MVM4p51FzHzdM/TZrerurgRcHJ2WTa1VQ1CIq18xncfpBJw4ojkiZZrKW2yIBWBP92j6Ug==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.2.tgz", + "integrity": "sha512-CYbnj78HsIeA+DhgUKgFCfvNsTHFhMMrinUrMZpDXJXKN8T3XViTZ/+wtHeVxEWY8ewSzTFN+nRmSwO2tZaLUQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.2.tgz", + "integrity": "sha512-buwkd8nsph4R+ajRvw0qM5Hja/TXQow3ptzWO2EbG/cqcIkHloRrdlBtQlshyYGTNFvfkfJ5tpPLVkY4DtsPfQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.2.tgz", + "integrity": "sha512-ZVykbDyk7519VwiNb9Lcj9m8XM6v5V9uKPvrEMkkEedVewf+0itkhahp4HDpgERXhwLRpWFypsGbG/J8s0QjJA==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.2.tgz", + "integrity": "sha512-CAXl+Dtd9UUuJd8pKKdwh6MLm3MUMiqMPmhZ3tTSXPqfyQ3vDl6R5hZdZ/kYojK4ofXtdfSv1tFq8XzWx3heNQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.2.tgz", + "integrity": "sha512-GeXCej4IQtU1B+QlDV8W/RRvbzI3O/Stss+/bCXv4lZls5WGRtu2a+3JkA3i4qIUlMXpcHebWpF8AkJhATowuA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.2.tgz", + "integrity": "sha512-3H1weTYZPxt/WOhByszQZybS9w5lKzUn1FDMsgEChbHWQwHYQQRfBxgCcZvPhjHfKyJjIievvMmEUawJrdY9Dg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.2.tgz", + "integrity": "sha512-4xTZr1FUmSoQW4XIWmit3tzQrUTZM+N3P0XV8xROKYF50XfI7xeO90+1bZvNwxIufQ9hDQVRJH5YhgPVF8A/HQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.2.tgz", + "integrity": "sha512-sSATRjPeDBg3pdgHoQfoYBob11Kk1FGa9lui5RIHZCoCkJa9QKlvl3/vKz2usCmYYjs7ymJR/2Nnsqe+Hjt5nw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.2.tgz", + "integrity": "sha512-lqnzCV+mM0gIADaKihiCg6ifgfU2L3h5E33rNQBN1Y4MaVGnzryzmvvf7UHxprpQdE8hpqLolJ9Rl+SkIRDpyw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.2.tgz", + "integrity": "sha512-AL2qJILH7lNjrDmCQDvdxMfAUIv8KMNZOvrwAQ8i8//ntL9FflhOyMJ8OZSMBb8/AWXe3/5v5S20y3zCoZWKoQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.2.tgz", + "integrity": "sha512-QtiuPytchRyC4rwUKhexJdQKvDuZ6hWloi3igqPQNUJCS1/v9EiO3UTOXR6A3FoMo4fnAKbWJdqaIwhOzh8qEw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/openharmony-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.2.tgz", + "integrity": "sha512-WkhYDmpTjLvGlScA1rwjRUmhl4k8oXR3cIbtqWmELgU/dFeHHlEllxDvdWcNJV9rbzCexB5vz8gtNewWLgCT7Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.2.tgz", + "integrity": "sha512-GPMSkTOtMnv2U2F8gxe4Io6qmVs+YKyp832Etqqxr0hFngmXQ3rzwytelm3GIn7T4VviRUlf3sOgBOiTdvaf7g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.2.tgz", + "integrity": "sha512-PIhhEkE9uPBleRBrQEJpUn7MBnibZzbGzYWPmY3x+YoVg/95zbjB4CxPPOQ8l5tYYM4mMaCthF8/1DIfBQQyWQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.2.tgz", + "integrity": "sha512-YmJbfTlvU7Sdn9BB+4PRES4oB6pxgS37MAONj+hBr/cpXS1aBPKXxNnDbu+QCWPj0o9dgyxeq79g6c5P8KeuYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.2.tgz", + "integrity": "sha512-5ebpxr3nWMzrL/rnUI755Jkuee0bHL/Gq0WTF9lvcpv73wAp5eu8MfBUgWK9bhWvZjj7yX8etf/8tI8Ney695g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", @@ -96,6 +721,23 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@napi-rs/lzma-linux-x64-gnu": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz", + "integrity": "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^22.20 || ^24.12 || >=25" + } + }, "node_modules/@point-of-sale/codepage-encoder": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/@point-of-sale/codepage-encoder/-/codepage-encoder-3.0.2.tgz", @@ -115,6 +757,374 @@ "resize-image-data": "^0.3.1" } }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.62.4.tgz", + "integrity": "sha512-RrPokAb7dmbxFoeO3TloqHyOjgye8RkBhSqmp4aJMIex4c9r46ZstPnleDQOq1t46VOVjwIuwNogIqbodV1Vvg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.62.4.tgz", + "integrity": "sha512-JKuJc+pnpks2pjy7L/N3v/cAkZxYlnmuZoD840ldbMI5KDbC4iO9NKwPKYdjYFCMAIIlBzYSFHxIJVYzRo2/8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.62.4.tgz", + "integrity": "sha512-krw5uS2STmvJ02x0uTXHbqQNuz+9eZ1iw+qXk9dmW2gvV4jV7O2hEoOnuhFrpOPiel1mBFtqbxYZZtC46hXLOw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.62.4.tgz", + "integrity": "sha512-wsTxtgApb4PrOsNJIm0FZ1h3WvCC+k9uxLJ4ad75hgoS4NiRes2SoJFlDAyMwiUY8IssDqGcHbXuN0sx1tfF1A==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.62.4.tgz", + "integrity": "sha512-GUOnQlyZe3yAXhWOtOMsn5Qkrv5E5mZXa0thbARWi5Ei2szlVXJFQhddZ4HbAzh8q92w5twp+CQvs/eFanz9YQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.62.4.tgz", + "integrity": "sha512-/Y7f3QuxjzPKsjA/rfEDa3+0vXqyjmJ50Ln8dPpCmWkKTrUoWHG1cWhTqaAMLob2m2nESWuC7yGrREz019Ztqg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.62.4.tgz", + "integrity": "sha512-81wiiX3v7aqy+T+bT61TJ78yJjRquqFFTTbAPt08imfQQzkPIW8t6aJbkTagtCCrXMNc9D66+geqlK7ydLPNqA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.62.4.tgz", + "integrity": "sha512-9kmDIvNZqdoHOBZgNtpTBeLWYO/LVipM3H/j62P8848/l/VPEQL6N3uxU9pvP1oZAsXyC2MEnFP3ovRjo7WYNQ==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.62.4.tgz", + "integrity": "sha512-CcnXHWnXg69g+DX5VWL3FHts3qMRN2uVEHX+BZvGLdd07/gXkn3ePjYtO1LDJvxkGKVHMclKBRa1QUTH+6toYQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.62.4.tgz", + "integrity": "sha512-iFOibiHnTRuhrWLlRsOQFdZJJIa7S8OwkneJr4ocALP16u5yk6lWLINFwhHaEqBFMsKDUZofLkGos7+CPzGB3g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.62.4.tgz", + "integrity": "sha512-XnWYMI7euHlb5a871xPja+Gm7DRCFU+FGRrtS2sMq9N8FvqtpagUy6gD4YOemC5MRk9xbh8+jYMEJbigFQwsgA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.62.4.tgz", + "integrity": "sha512-qGDAlO0U8xedCcsdRm9oaoQY8DAx/QT7uIxJWhCdx0ceIWX783UC9QSYkdpzAe29wNiVfp24+bZdQmn49o45SQ==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.62.4.tgz", + "integrity": "sha512-ru4H6ezD7ysA5EiEK6qkkaEb4modH8CTej6kUy/gQi20u3kB3G7Zn8snXXkeJSCOFKG/rbPPtM/+9Wgas1961w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.62.4.tgz", + "integrity": "sha512-2W4MO5WQVJnbJaZdvDb9rhBDuFU1nKIepPFpJUBsTh2k1YY2g+ODViaWuyOAjQ5cOP7NvrvLzt3wvHOoiAvc7w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.62.4.tgz", + "integrity": "sha512-+fxjfuoAmVMCYV5QyjoIpu0cp5DOiOTeqYFk1AVaxGr+/ravWLX89XfQmptsoWcaVy/TGf2hexzbUOrCQIL1CQ==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.62.4.tgz", + "integrity": "sha512-jTn8JfHGL4djjFxPuM06LmNUJDsst2jeVlsd9OmIH6zc5sC9K6rIuO4YajXatLUpBmBKl6b35ro1QZocLi+tcA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.62.4.tgz", + "integrity": "sha512-oCJCJL4pXsoDcP2QZ+JVlPTIRc6266zsIaeJJsWImmF7HO0W8nb6HuSgZlMWxJwaPf8ehbSw8yo0EUw925hKsA==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.62.4.tgz", + "integrity": "sha512-W69hukhZ3KKNRCaMIEzKvcFye42hh0FE1+YoYaf5+Ikacuftoco6yO/xouz0hc5d5W/s3yBro5jRiuEE/Q5vUw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.62.4.tgz", + "integrity": "sha512-qiXbGG2jkjXhzXpsFZSR2Xpb8DN/UaxYsbb/STbuR/6fpaDgRmmaq1B/LmtF2wQFOFOSsK2jdE0RZ3a0zHn4QA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.62.4.tgz", + "integrity": "sha512-nWeM//hxv8mIo6jD7Hu4o48DVmV9pbV6gsKaWU+4NFyqHoPKwrkRiZGLKUhOBk8qNmDmpwFtPKg80Bo/Tn4xiQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.62.4.tgz", + "integrity": "sha512-s62SQ/vgsRSvMwDkOEfTqfgASF0f26ZNaQuTA6Aok5lrikf89yI2W0gFHvZb2Jpgc6N8JnOKZgCK2iciO3CsxQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.62.4.tgz", + "integrity": "sha512-J6wGf8TVGbXJq+HH+ttTvrcfNKPbuZecV6KT1B8I18BC5IURUh5kl4Yl5OEP5eFIUoI5BWxCsyYMhFsDx8kekw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.62.4.tgz", + "integrity": "sha512-zmfrQd/0wu6oJs8Vq8KwY/YtsKSsLtKe/HwAP4Wqy8LhWjeT55fHRAkOhYQ12wI3ayS4Tt12d5CDRD7N96SAYQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.62.4.tgz", + "integrity": "sha512-qPzHqdj9rfUD+w79dtE07zi/kFwKyCJqplp5K5ygeLTp7jLpAoc16OAH39HSmRC9UpozaecsleI8uAdEj6v2yw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.62.4.tgz", + "integrity": "sha512-zD6NdeWEByGE9QF9vCrlJ5YQB4oq9q91kPZS37Jwj5hOkvR1lTBSpsKhKDw4IJtbQ35LsTS1HD9DZYGKIshU1Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@types/chai": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/deep-eql": "*", + "assertion-error": "^2.0.1" + } + }, + "node_modules/@types/deep-eql": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@types/deep-eql/-/deep-eql-4.0.2.tgz", + "integrity": "sha512-c9h9dVVMigMPc4bwTvC5dxqtqJZwQPePsWjPlpSOnojbor6pGqdk541lfA7AqFQr5pB1BRdq0juY9db81BwyFw==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/estree": { "version": "1.0.9", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", @@ -122,6 +1132,26 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/jsdom": { + "version": "30.0.0", + "resolved": "https://registry.npmjs.org/@types/jsdom/-/jsdom-30.0.0.tgz", + "integrity": "sha512-uAHGxujGE0cDaKGdK28zgDotFtNA7MKq5DXl8LrfdxdCI8VHcg15oJz+amHTChPNI5JpgEPQWc2xFdrw3em/nQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*", + "@types/tough-cookie": "*", + "parse5": "^8.0.0", + "undici-types": "^8.9.0" + } + }, + "node_modules/@types/jsdom/node_modules/undici-types": { + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-8.10.0.tgz", + "integrity": "sha512-ibvdovq3nCFs8Msrd95BW+zUOq+aOVbT+wpHUoPWhztbHEoPc6oof51iFDB6Es8lTKvNvVW9jNSAB8dwrKTMGg==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/json-schema": { "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", @@ -146,6 +1176,13 @@ "dev": true, "license": "MIT" }, + "node_modules/@types/tough-cookie": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/@types/tough-cookie/-/tough-cookie-4.0.5.tgz", + "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/web-bluetooth": { "version": "0.0.21", "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.21.tgz", @@ -153,6 +1190,121 @@ "dev": true, "license": "MIT" }, + "node_modules/@vitest/expect": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-3.2.7.tgz", + "integrity": "sha512-E8eBXaKibuvH2pSZErOjdVb5vF4PbKYcrnluBTYxEk1l/VhhwZg1kZQsdtjq+CsF5CFydf2Rdkz7jDHKSisi3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/spy": "3.2.7", + "@vitest/utils": "3.2.7", + "chai": "^5.2.0", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/mocker": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-3.2.7.tgz", + "integrity": "sha512-Trr0hYO9CM3Wj6ksWHRhK9IZpIY6wTMO5u/MqXurMxT57sWBaOPEtP3Oq60ihZuh5JsiagKfz95OcxdEP6dBrA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "3.2.7", + "estree-walker": "^3.0.3", + "magic-string": "^0.30.17" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "msw": "^2.4.9", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "peerDependenciesMeta": { + "msw": { + "optional": true + }, + "vite": { + "optional": true + } + } + }, + "node_modules/@vitest/pretty-format": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-3.2.7.tgz", + "integrity": "sha512-KUHlwqVu0sRlhCdyPdQ/wBoTfRahjUky1MubOmYw9fWfIZy1gNoHpuaaQBPAaMaVYdQYHJLurzj8ECCj5OwTqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-3.2.7.tgz", + "integrity": "sha512-sB9y4ovltoQP+WaUPwmSxO9WIg9Ig694Di5PalVPsYHklAdE027mehpWF2SQSVq+k6sFgaivbTjTJwZLSHbedA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "3.2.7", + "pathe": "^2.0.3", + "strip-literal": "^3.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-3.2.7.tgz", + "integrity": "sha512-7C+MwShwtBSI5Buwoyg3s/iY1eHL9PKAf+O1wVh/TdnjXUtkoL/9YQtre90i4MtNXM6edP1wJ2zOBpfCyhIS7g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.7", + "magic-string": "^0.30.17", + "pathe": "^2.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-3.2.7.tgz", + "integrity": "sha512-Q2eQGI6d2L/hBtZ0qNuKcAGid68XK6cv1xsoaIma6PaJhHPoqcEJhYpXZ/5myCMqkNgtP6UKuBhbc0nHKnrkuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^4.0.3" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-3.2.7.tgz", + "integrity": "sha512-x6BDOd7dyo3PFLY3I9/HJ25X/6OurhGXk2/B9gOZNPF7XDVjeBK4k01lQE5uvDpbuheErh91qYuE1E2OEjK3Rw==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/pretty-format": "3.2.7", + "loupe": "^3.1.4", + "tinyrainbow": "^2.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, "node_modules/@webassemblyjs/ast": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.14.1.tgz", @@ -388,6 +1540,16 @@ "node": ">=0.4.0" } }, + "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==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, "node_modules/ajv": { "version": "8.20.0", "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.20.0.tgz", @@ -452,6 +1614,37 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/assertion-error": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", + "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "dev": true, + "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/baseline-browser-mapping": { "version": "2.11.12", "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.11.12.tgz", @@ -465,6 +1658,28 @@ "node": ">=6.0.0" } }, + "node_modules/bidi-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/bidi-js/-/bidi-js-1.0.3.tgz", + "integrity": "sha512-RKshQI1R3YQ+n9YJz2QQ147P66ELpa1FQEg20Dk8oW9t2KgLbpDLLp9aGZ7y8WHSshDknG0bknqGw5/tyCs5tw==", + "dev": true, + "license": "MIT", + "dependencies": { + "require-from-string": "^2.0.2" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, "node_modules/browserslist": { "version": "4.28.7", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.7.tgz", @@ -499,6 +1714,31 @@ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" } }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "dev": true, + "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", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, "node_modules/buffer-from": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", @@ -506,6 +1746,16 @@ "dev": true, "license": "MIT" }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/caniuse-lite": { "version": "1.0.30001809", "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001809.tgz", @@ -527,6 +1777,21 @@ ], "license": "CC-BY-4.0" }, + "node_modules/canvas": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/canvas/-/canvas-3.2.3.tgz", + "integrity": "sha512-PzE5nJZPz72YUAfo8oTp0u3fqqY7IzlTubneAihqDYAUcBk7ryeCmBbdJBEdaH0bptSOe2VT2Zwcb3UaFyaSWw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "node-addon-api": "^7.0.0", + "prebuild-install": "^7.1.3" + }, + "engines": { + "node": "^18.12.0 || >= 20.9.0" + } + }, "node_modules/canvas-dither": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/canvas-dither/-/canvas-dither-1.0.1.tgz", @@ -539,6 +1804,23 @@ "integrity": "sha512-UhHeKeWGS23Bm5teZiHk6NEOme1OKCpv6arFovV89dydt0Xz+U4HkPXAKBZS/hmpR15x5U9tDj1ieMQMTwvCAA==", "license": "MIT" }, + "node_modules/chai": { + "version": "5.3.3", + "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz", + "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^2.0.1", + "check-error": "^2.1.1", + "deep-eql": "^5.0.1", + "loupe": "^3.1.0", + "pathval": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", @@ -556,6 +1838,23 @@ "url": "https://github.com/chalk/chalk?sponsor=1" } }, + "node_modules/check-error": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz", + "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 16" + } + }, + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true, + "license": "ISC" + }, "node_modules/chrome-trace-event": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.4.tgz", @@ -630,6 +1929,131 @@ "node": ">= 8" } }, + "node_modules/css-tree": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/css-tree/-/css-tree-3.2.1.tgz", + "integrity": "sha512-X7sjQzceUhu1u7Y/ylrRZFU2FS6LRiFVp6rKLPg23y3x3c3DOKAwuXGDp+PAGjh6CSnCjYeAul8pcT8bAl+lSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "mdn-data": "2.27.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12.20.0 || ^14.13.0 || >=15.0.0" + } + }, + "node_modules/cssstyle": { + "version": "5.3.7", + "resolved": "https://registry.npmjs.org/cssstyle/-/cssstyle-5.3.7.tgz", + "integrity": "sha512-7D2EPVltRrsTkhpQmksIu+LxeWAIEk6wRDMJ1qljlv+CKHJM+cJLlfhWIzNA44eAsHXSNe3+vO6DW1yCYx8SuQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/css-color": "^4.1.1", + "@csstools/css-syntax-patches-for-csstree": "^1.0.21", + "css-tree": "^3.1.0", + "lru-cache": "^11.2.4" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/data-urls": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/data-urls/-/data-urls-6.0.1.tgz", + "integrity": "sha512-euIQENZg6x8mj3fO6o9+fOW8MimUI4PpD/fZBhJfeioZVy9TUpM4UY7KjQNVZFlqwJ0UdzRDzkycB997HEq1BQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-mimetype": "^5.0.0", + "whatwg-url": "^15.1.0" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/data-urls/node_modules/whatwg-mimetype": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-5.0.0.tgz", + "integrity": "sha512-sXcNcHOC51uPGF0P/D4NVtrkjSU2fNsm9iog4ZvZJsL3rjoDAzXZhkm2MWt1y+PUdggKAYVoMAIYcs78wJ51Cw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=20" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decimal.js": { + "version": "10.6.0", + "resolved": "https://registry.npmjs.org/decimal.js/-/decimal.js-10.6.0.tgz", + "integrity": "sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==", + "dev": true, + "license": "MIT" + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-eql": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz", + "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, "node_modules/electron-to-chromium": { "version": "1.5.402", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.402.tgz", @@ -637,6 +2061,16 @@ "dev": true, "license": "ISC" }, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, "node_modules/enhanced-resolve": { "version": "5.24.5", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.24.5.tgz", @@ -651,6 +2085,19 @@ "node": ">=10.13.0" } }, + "node_modules/entities": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/entities/-/entities-8.0.0.tgz", + "integrity": "sha512-zwfzJecQ/Uej6tusMqwAqU/6KL2XaB2VZ2Jg54Je6ahNBGNH6Ek6g3jjNCF0fG9EWQKGZNddNjU5F1ZQn/sBnA==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=20.19.0" + }, + "funding": { + "url": "https://github.com/fb55/entities?sponsor=1" + } + }, "node_modules/envinfo": { "version": "7.21.0", "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.21.0.tgz", @@ -681,6 +2128,48 @@ "dev": true, "license": "MIT" }, + "node_modules/esbuild": { + "version": "0.28.2", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.2.tgz", + "integrity": "sha512-HKVLS8dvII+xoKW9kmqxbRKrnWEXfJJr/FZhhJmiqIB0e053QNYFqOBouTMO/k5sID4MvCiUCvv8b9M4h32wIA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.28.2", + "@esbuild/android-arm": "0.28.2", + "@esbuild/android-arm64": "0.28.2", + "@esbuild/android-x64": "0.28.2", + "@esbuild/darwin-arm64": "0.28.2", + "@esbuild/darwin-x64": "0.28.2", + "@esbuild/freebsd-arm64": "0.28.2", + "@esbuild/freebsd-x64": "0.28.2", + "@esbuild/linux-arm": "0.28.2", + "@esbuild/linux-arm64": "0.28.2", + "@esbuild/linux-ia32": "0.28.2", + "@esbuild/linux-loong64": "0.28.2", + "@esbuild/linux-mips64el": "0.28.2", + "@esbuild/linux-ppc64": "0.28.2", + "@esbuild/linux-riscv64": "0.28.2", + "@esbuild/linux-s390x": "0.28.2", + "@esbuild/linux-x64": "0.28.2", + "@esbuild/netbsd-arm64": "0.28.2", + "@esbuild/netbsd-x64": "0.28.2", + "@esbuild/openbsd-arm64": "0.28.2", + "@esbuild/openbsd-x64": "0.28.2", + "@esbuild/openharmony-arm64": "0.28.2", + "@esbuild/sunos-x64": "0.28.2", + "@esbuild/win32-arm64": "0.28.2", + "@esbuild/win32-ia32": "0.28.2", + "@esbuild/win32-x64": "0.28.2" + } + }, "node_modules/escalade": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", @@ -738,6 +2227,16 @@ "node": ">=4.0" } }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, "node_modules/events": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", @@ -748,6 +2247,26 @@ "node": ">=0.8.x" } }, + "node_modules/expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "dev": true, + "license": "(MIT OR WTFPL)", + "engines": { + "node": ">=6" + } + }, + "node_modules/expect-type": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz", + "integrity": "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -782,6 +2301,24 @@ "node": ">= 4.9.1" } }, + "node_modules/fdir": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/fdir/-/fdir-6.5.0.tgz", + "integrity": "sha512-tIbYtZbucOs0BRGqPJkshJUYdL+SDH7dVM8gjy+ERp3WAUjLEFJE+02kanyHtwjWOnwrKYBiwAmM0p4kLJAnXg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.0.0" + }, + "peerDependencies": { + "picomatch": "^3 || ^4" + }, + "peerDependenciesMeta": { + "picomatch": { + "optional": true + } + } + }, "node_modules/find-up": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", @@ -806,6 +2343,28 @@ "flat": "cli.js" } }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "dev": true, + "license": "MIT" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "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", @@ -816,6 +2375,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", + "dev": true, + "license": "MIT" + }, "node_modules/graceful-fs": { "version": "4.2.11", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", @@ -846,6 +2412,81 @@ "node": ">= 0.4" } }, + "node_modules/html-encoding-sniffer": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/html-encoding-sniffer/-/html-encoding-sniffer-4.0.0.tgz", + "integrity": "sha512-Y22oTqIU4uuPgEemfz7NDJz6OeKf12Lsu+QC+s3BVpda64lTiMYCyGwg5ki4vFxkMwQdeZDl2adZoqUgdFuTgQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-encoding": "^3.1.1" + }, + "engines": { + "node": ">=18" + } + }, + "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==", + "dev": true, + "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==", + "dev": true, + "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==", + "dev": true, + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "dev": true, + "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": "BSD-3-Clause" + }, "node_modules/import-local": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", @@ -866,6 +2507,20 @@ "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==", + "dev": true, + "license": "ISC" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true, + "license": "ISC" + }, "node_modules/interpret": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/interpret/-/interpret-3.1.1.tgz", @@ -905,6 +2560,13 @@ "node": ">=0.10.0" } }, + "node_modules/is-potential-custom-element-name": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-potential-custom-element-name/-/is-potential-custom-element-name-1.0.1.tgz", + "integrity": "sha512-bCYeRA2rVibKZd+s2625gGnGF/t7DSqDs4dP7CrLA1m7jKWz6pps0LpYLJN8Q64HtmPKJ1hrN3nzPNKFEKOUiQ==", + "dev": true, + "license": "MIT" + }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", @@ -953,6 +2615,53 @@ "url": "https://github.com/chalk/supports-color?sponsor=1" } }, + "node_modules/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/jsdom": { + "version": "27.0.1", + "resolved": "https://registry.npmjs.org/jsdom/-/jsdom-27.0.1.tgz", + "integrity": "sha512-SNSQteBL1IlV2zqhwwolaG9CwhIhTvVHWg3kTss/cLE7H/X4644mtPQqYvCfsSrGQWt9hSZcgOXX8bOZaMN+kA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@asamuzakjp/dom-selector": "^6.7.2", + "cssstyle": "^5.3.1", + "data-urls": "^6.0.0", + "decimal.js": "^10.6.0", + "html-encoding-sniffer": "^4.0.0", + "http-proxy-agent": "^7.0.2", + "https-proxy-agent": "^7.0.6", + "is-potential-custom-element-name": "^1.0.1", + "parse5": "^8.0.0", + "rrweb-cssom": "^0.8.0", + "saxes": "^6.0.0", + "symbol-tree": "^3.2.4", + "tough-cookie": "^6.0.0", + "w3c-xmlserializer": "^5.0.0", + "webidl-conversions": "^8.0.0", + "whatwg-encoding": "^3.1.1", + "whatwg-mimetype": "^4.0.0", + "whatwg-url": "^15.1.0", + "ws": "^8.18.3", + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=20" + }, + "peerDependencies": { + "canvas": "^3.0.0" + }, + "peerDependenciesMeta": { + "canvas": { + "optional": true + } + } + }, "node_modules/json-schema-traverse": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", @@ -983,6 +2692,40 @@ "node": ">=8" } }, + "node_modules/loupe": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz", + "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "11.5.2", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.2.tgz", + "integrity": "sha512-4pfM1Ff0x50o0tQwb5ucw/RzNyD0/YJME6IVcStalZuMWxdt3sR3huStTtxz4PUmvZfRguvDejasvQ2kifR11g==", + "dev": true, + "license": "BlueOak-1.0.0", + "engines": { + "node": "20 || >=22" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "node_modules/mdn-data": { + "version": "2.27.1", + "resolved": "https://registry.npmjs.org/mdn-data/-/mdn-data-2.27.1.tgz", + "integrity": "sha512-9Yubnt3e8A0OKwxYSXyhLymGW4sCufcLG6VdiDdUGVkPhpqLxlvP5vl1983gQjJl3tqbrM731mjaZaP68AgosQ==", + "dev": true, + "license": "CC0-1.0" + }, "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", @@ -1000,6 +2743,29 @@ "node": ">= 0.6" } }, + "node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/minimizer-webpack-plugin": { "version": "5.6.1", "resolved": "https://registry.npmjs.org/minimizer-webpack-plugin/-/minimizer-webpack-plugin-5.6.1.tgz", @@ -1061,6 +2827,46 @@ } } }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "dev": true, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/nanoid": { + "version": "3.3.18", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.18.tgz", + "integrity": "sha512-DTg4MJbGMWkfi6VZFdNt2/caMbQy4Ou+Op/hJQvGEWcnVfoA1QA+xzRKAzw9jD6+GVOOeYr/mIcuDSdug6F6+w==", + "dev": true, + "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/napi-build-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", + "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", + "dev": true, + "license": "MIT" + }, "node_modules/neo-async": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", @@ -1068,6 +2874,26 @@ "dev": true, "license": "MIT" }, + "node_modules/node-abi": { + "version": "3.94.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.94.0.tgz", + "integrity": "sha512-W5ZNO5KRPB5TkYmGVD9F6YqhsglXJzE6etpbmT+f6EQElhiX/UTG551cnsRGvLG3fyZEg9HwaDmNmj5nwJ4z9g==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "dev": true, + "license": "MIT" + }, "node_modules/node-releases": { "version": "2.0.53", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.53.tgz", @@ -1078,6 +2904,16 @@ "node": ">=18" } }, + "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==", + "dev": true, + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, "node_modules/p-limit": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", @@ -1117,6 +2953,19 @@ "node": ">=6" } }, + "node_modules/parse5": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/parse5/-/parse5-8.0.1.tgz", + "integrity": "sha512-z1e/HMG90obSGeidlli3hj7cbocou0/wa5HacvI3ASx34PecNjNQeaHNo5WIZpWofN9kgkqV1q5YvXe3F0FoPw==", + "dev": true, + "license": "MIT", + "dependencies": { + "entities": "^8.0.0" + }, + "funding": { + "url": "https://github.com/inikulin/parse5?sponsor=1" + } + }, "node_modules/path-exists": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", @@ -1144,6 +2993,23 @@ "dev": true, "license": "MIT" }, + "node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathval": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz", + "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14.16" + } + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -1177,6 +3043,84 @@ "node": ">=8" } }, + "node_modules/postcss": { + "version": "8.5.26", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.26.tgz", + "integrity": "sha512-u82N74LFzG8ca+dD8puPnplTXoGH4fTPpVGuIbt36G3qvNlkvfD0lEAZSxaly3KX8TS/L1A1gsCEmvKmBcVbkQ==", + "dev": true, + "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.17", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/prebuild-install": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", + "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", + "deprecated": "No longer maintained. Please contact the author of the relevant native addon; alternatives are available.", + "dev": true, + "license": "MIT", + "dependencies": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^2.0.0", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/pump": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", + "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", + "dev": true, + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/qrcode-generator": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/qrcode-generator/-/qrcode-generator-2.0.4.tgz", @@ -1189,6 +3133,37 @@ "integrity": "sha512-b9w0vcjHSdKiOq1jL/KXfb3IvgaIc/BeQv9JuJ3nTEoqSUZfIGKLAyKTS+akHSXrR64R0UwEDmw20GvD2di/Pg==", "license": "LGPL-2.1" }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "dev": true, + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/rechoir": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", @@ -1269,6 +3244,100 @@ "node": ">=8" } }, + "node_modules/rollup": { + "version": "4.62.4", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.62.4.tgz", + "integrity": "sha512-RXOqwaPsBGjMNMa4sQjDjHieHEZDFoj/Rdr46l2MU5DfEs16wHJPC2RPTPHWhNl+M3aI472LLqFkFKut4SblOg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.9" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@napi-rs/lzma-linux-x64-gnu": "1.5.1", + "@rollup/rollup-android-arm-eabi": "4.62.4", + "@rollup/rollup-android-arm64": "4.62.4", + "@rollup/rollup-darwin-arm64": "4.62.4", + "@rollup/rollup-darwin-x64": "4.62.4", + "@rollup/rollup-freebsd-arm64": "4.62.4", + "@rollup/rollup-freebsd-x64": "4.62.4", + "@rollup/rollup-linux-arm-gnueabihf": "4.62.4", + "@rollup/rollup-linux-arm-musleabihf": "4.62.4", + "@rollup/rollup-linux-arm64-gnu": "4.62.4", + "@rollup/rollup-linux-arm64-musl": "4.62.4", + "@rollup/rollup-linux-loong64-gnu": "4.62.4", + "@rollup/rollup-linux-loong64-musl": "4.62.4", + "@rollup/rollup-linux-ppc64-gnu": "4.62.4", + "@rollup/rollup-linux-ppc64-musl": "4.62.4", + "@rollup/rollup-linux-riscv64-gnu": "4.62.4", + "@rollup/rollup-linux-riscv64-musl": "4.62.4", + "@rollup/rollup-linux-s390x-gnu": "4.62.4", + "@rollup/rollup-linux-x64-gnu": "4.62.4", + "@rollup/rollup-linux-x64-musl": "4.62.4", + "@rollup/rollup-openbsd-x64": "4.62.4", + "@rollup/rollup-openharmony-arm64": "4.62.4", + "@rollup/rollup-win32-arm64-msvc": "4.62.4", + "@rollup/rollup-win32-ia32-msvc": "4.62.4", + "@rollup/rollup-win32-x64-gnu": "4.62.4", + "@rollup/rollup-win32-x64-msvc": "4.62.4", + "fsevents": "~2.3.2" + } + }, + "node_modules/rrweb-cssom": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rrweb-cssom/-/rrweb-cssom-0.8.0.tgz", + "integrity": "sha512-guoltQEx+9aMf2gDZ0s62EcV8lsXR+0w8915TC3ITdn2YueuNjdAYh/levpU9nFaoChh9RUS5ZdQMrKfVEN9tw==", + "dev": true, + "license": "MIT" + }, + "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==", + "dev": true, + "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==", + "dev": true, + "license": "MIT" + }, + "node_modules/saxes": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", + "integrity": "sha512-xAg7SOnEhrm5zI3puOOKyy1OMcMlIJZYNJY7xLBwSze0UjhPLnWfj2GF2EpT0jmzaJKIWKHLsaSSajf35bcYnA==", + "dev": true, + "license": "ISC", + "dependencies": { + "xmlchars": "^2.2.0" + }, + "engines": { + "node": ">=v12.22.7" + } + }, "node_modules/schema-utils": { "version": "4.3.3", "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-4.3.3.tgz", @@ -1289,6 +3358,19 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/semver": { + "version": "7.8.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.5.tgz", + "integrity": "sha512-Y7/KDsb8LjooZpwaqGyulO6DQlksgCncchHGk+sZIY4SBvUocMBEFH5Ur1fI4dV+Jvl0w6cjvucaIi40puRioA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/shallow-clone": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", @@ -1325,6 +3407,60 @@ "node": ">=8" } }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "dev": true, + "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/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "dev": true, + "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", + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, "node_modules/source-map": { "version": "0.7.6", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.6.tgz", @@ -1335,6 +3471,16 @@ "node": ">= 12" } }, + "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==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/source-map-support": { "version": "0.5.21", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.21.tgz", @@ -1351,11 +3497,58 @@ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, - "license": "BSD-3-Clause", + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true, + "license": "MIT" + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "dev": true, + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "dev": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } }, + "node_modules/strip-literal": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.1.0.tgz", + "integrity": "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^9.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, "node_modules/supports-color": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", @@ -1382,6 +3575,13 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/symbol-tree": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/symbol-tree/-/symbol-tree-3.2.4.tgz", + "integrity": "sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==", + "dev": true, + "license": "MIT" + }, "node_modules/tapable": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.3.3.tgz", @@ -1396,6 +3596,36 @@ "url": "https://opencollective.com/webpack" } }, + "node_modules/tar-fs": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.5.tgz", + "integrity": "sha512-OboTd8mmMhZDNPV+UjQcK9yKAatXu2aJ+r1w4im1Otd4M4fl2hwvdoXUxIYHFTHWK/3y3FarBP70v3vwmGlOxw==", + "dev": true, + "license": "MIT", + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/terser": { "version": "5.49.2", "resolved": "https://registry.npmjs.org/terser/-/terser-5.49.2.tgz", @@ -1415,6 +3645,113 @@ "node": ">=10" } }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyexec": { + "version": "0.3.2", + "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz", + "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinyglobby": { + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fdir": "^6.5.0", + "picomatch": "^4.0.4" + }, + "engines": { + "node": ">=12.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/SuperchupuDev" + } + }, + "node_modules/tinypool": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz", + "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^18.0.0 || >=20.0.0" + } + }, + "node_modules/tinyrainbow": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-2.0.0.tgz", + "integrity": "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.4.tgz", + "integrity": "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tldts": { + "version": "7.4.10", + "resolved": "https://registry.npmjs.org/tldts/-/tldts-7.4.10.tgz", + "integrity": "sha512-GgouD1B+sWwvkaEq8vXC15DjQitxbvs12oIXELpconwm+Tg3zfcEv4jgzq3vtKverDXsg3VI8aRgNL2Nra0Iog==", + "dev": true, + "license": "MIT", + "dependencies": { + "tldts-core": "^7.4.10" + }, + "bin": { + "tldts": "bin/cli.js" + } + }, + "node_modules/tldts-core": { + "version": "7.4.10", + "resolved": "https://registry.npmjs.org/tldts-core/-/tldts-core-7.4.10.tgz", + "integrity": "sha512-KnQjp53ZekKgm/r3l+u8kJGGzYgrWdP8+Mql7a4vijh2WE0IrZWspQj/TpTxDho/YxO+AnOZnIjQcCD+q6iJsw==", + "dev": true, + "license": "MIT" + }, + "node_modules/tough-cookie": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.2.tgz", + "integrity": "sha512-exgYmnmL/sJpR3upZfXG5PoatXQii55xAiXGXzY+sROLZ/Y+SLcp9PgJNI9Vz37HpQ74WvDcLT8eqm+kV3FzrA==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "tldts": "^7.0.5" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/tr46": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-6.0.0.tgz", + "integrity": "sha512-bLVMLPtstlZ4iMQHpFHTR7GAGj2jxi8Dg0s2h2MafAE4uSWF98FC/3MomU51iQAMf8/qDUbKWf5GxuvvVcXEhw==", + "dev": true, + "license": "MIT", + "dependencies": { + "punycode": "^2.3.1" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/ts-loader": { "version": "9.6.2", "resolved": "https://registry.npmjs.org/ts-loader/-/ts-loader-9.6.2.tgz", @@ -1440,6 +3777,19 @@ } } }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, "node_modules/typescript": { "version": "6.0.2", "resolved": "https://registry.npmjs.org/typescript/-/typescript-6.0.2.tgz", @@ -1492,6 +3842,204 @@ "browserslist": ">= 4.21.0" } }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true, + "license": "MIT" + }, + "node_modules/vite": { + "version": "7.3.6", + "resolved": "https://registry.npmjs.org/vite/-/vite-7.3.6.tgz", + "integrity": "sha512-4XP60spRGjSZFf1qYH+dJIkK2znL3zQfl9KkOV9MkkRR/3Dls0dxaBsQPTloEc5BLXWPL9vsOxopxyKoMmDueg==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.27.0 || ^0.28.0", + "fdir": "^6.5.0", + "picomatch": "^4.0.3", + "postcss": "^8.5.6", + "rollup": "^4.43.0", + "tinyglobby": "^0.2.15" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^20.19.0 || >=22.12.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^20.19.0 || >=22.12.0", + "jiti": ">=1.21.0", + "less": "^4.0.0", + "lightningcss": "^1.21.0", + "sass": "^1.70.0", + "sass-embedded": "^1.70.0", + "stylus": ">=0.54.8", + "sugarss": "^5.0.0", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "node_modules/vite-node": { + "version": "3.2.4", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-3.2.4.tgz", + "integrity": "sha512-EbKSKh+bh1E1IFxeO0pg1n4dvoOTt0UDiXMd/qn++r98+jPO1xtJilvXldeuQ8giIB5IkpjCgMleHMNEsGH6pg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.4.1", + "es-module-lexer": "^1.7.0", + "pathe": "^2.0.3", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vite-node/node_modules/es-module-lexer": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz", + "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==", + "dev": true, + "license": "MIT" + }, + "node_modules/vitest": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-3.2.7.tgz", + "integrity": "sha512-KrxIJ62Fd89gfysR4WotlgZABiz2dqFPgqGzX7s+CwsqLFomRH7777ZcrOD6+WVAh7khPQP41A+BKbpcJFrdEg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/chai": "^5.2.2", + "@vitest/expect": "3.2.7", + "@vitest/mocker": "3.2.7", + "@vitest/pretty-format": "^3.2.7", + "@vitest/runner": "3.2.7", + "@vitest/snapshot": "3.2.7", + "@vitest/spy": "3.2.7", + "@vitest/utils": "3.2.7", + "chai": "^5.2.0", + "debug": "^4.4.1", + "expect-type": "^1.2.1", + "magic-string": "^0.30.17", + "pathe": "^2.0.3", + "picomatch": "^4.0.2", + "std-env": "^3.9.0", + "tinybench": "^2.9.0", + "tinyexec": "^0.3.2", + "tinyglobby": "^0.2.14", + "tinypool": "^1.1.1", + "tinyrainbow": "^2.0.0", + "vite": "^5.0.0 || ^6.0.0 || ^7.0.0-0", + "vite-node": "3.2.4", + "why-is-node-running": "^2.3.0" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/debug": "^4.1.12", + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "@vitest/browser": "3.2.7", + "@vitest/ui": "3.2.7", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/debug": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/w3c-xmlserializer": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/w3c-xmlserializer/-/w3c-xmlserializer-5.0.0.tgz", + "integrity": "sha512-o8qghlI8NZHU1lLPrpi2+Uq7abh4GGPpYANlalzWxyWteJOCsr/P+oPBA49TOLu5FTZO4d3F9MnWJfiMo4BkmA==", + "dev": true, + "license": "MIT", + "dependencies": { + "xml-name-validator": "^5.0.0" + }, + "engines": { + "node": ">=18" + } + }, "node_modules/watchpack": { "version": "2.5.2", "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.5.2.tgz", @@ -1505,6 +4053,16 @@ "node": ">=10.13.0" } }, + "node_modules/webidl-conversions": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-8.0.1.tgz", + "integrity": "sha512-BMhLD/Sw+GbJC21C/UgyaZX41nPt8bUTg+jWyDeg7e7YN4xOM05YPSIXceACnXVtqyEw/LMClUQMtMZ+PGGpqQ==", + "dev": true, + "license": "BSD-2-Clause", + "engines": { + "node": ">=20" + } + }, "node_modules/webpack": { "version": "5.109.2", "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.109.2.tgz", @@ -1630,6 +4188,44 @@ "node": ">=10.13.0" } }, + "node_modules/whatwg-encoding": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz", + "integrity": "sha512-6qN4hJdMwfYBtE3YBTTHhoeuUrDBPZmbQaxWAqSALV/MeEnR5z1xd8UKud2RAkFoPkmB+hli1TZSnyi84xz1vQ==", + "deprecated": "Use @exodus/bytes instead for a more spec-conformant and faster implementation", + "dev": true, + "license": "MIT", + "dependencies": { + "iconv-lite": "0.6.3" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-mimetype": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/whatwg-mimetype/-/whatwg-mimetype-4.0.0.tgz", + "integrity": "sha512-QaKxh0eNIi2mE9p2vEdzfagOKHCcj1pJ56EEHGQOVxp8r9/iszLUUV7v89x9O1p/T+NlTM5W7jW6+cz4Fq1YVg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/whatwg-url": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-15.1.0.tgz", + "integrity": "sha512-2ytDk0kiEj/yu90JOAp44PVPUkO9+jVhyf+SybKlRHSDlvOOZhdPIrr7xTH64l4WixO2cP+wQIcgujkGBPPz6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "^6.0.0", + "webidl-conversions": "^8.0.0" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", @@ -1646,12 +4242,75 @@ "node": ">= 8" } }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/wildcard": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.1.tgz", "integrity": "sha512-CC1bOL87PIWSBhDcTrdeLo6eGT7mCFtrg0uIJtqJUFyK+eJnzl8A1niH56uu7KMa5XFrtiV+AQuHO3n7DsHnLQ==", "dev": true, "license": "MIT" + }, + "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==", + "dev": true, + "license": "ISC" + }, + "node_modules/ws": { + "version": "8.21.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.3.tgz", + "integrity": "sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==", + "dev": true, + "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/xml-name-validator": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-5.0.0.tgz", + "integrity": "sha512-EvGK8EJ3DhaHfbRlETOWAS5pO9MZITeauHKJyb8wyajUfQUenkIg2MvLDTZ4T/TgIcm3HU0TFBgWWboAZ30UHg==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, + "node_modules/xmlchars": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", + "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==", + "dev": true, + "license": "MIT" } } } diff --git a/package.json b/package.json index afd1411..4b93f6f 100644 --- a/package.json +++ b/package.json @@ -33,7 +33,8 @@ "build:types": "tsc -p tsconfig.build.json", "build:dev": "webpack --mode development --watch", "prepublishOnly": "npm run build", - "test": "echo \"Error: no test specified\" && exit 1" + "test": "vitest run", + "test:watch": "vitest" }, "keywords": [ "pos", @@ -53,6 +54,9 @@ ], "author": "", "license": "MIT", + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, "dependencies": { "@bwip-js/browser": "^4.11.2", "@point-of-sale/receipt-printer-encoder": "^3.0.3", @@ -61,10 +65,15 @@ "qz-tray": "^2.2.6" }, "devDependencies": { + "@types/jsdom": "^30.0.0", + "@types/node": "^26.2.0", "@types/qz-tray": "^2.2.2", "@types/web-bluetooth": "^0.0.21", + "canvas": "^3.2.3", + "jsdom": "^27.0.1", "ts-loader": "^9.5.4", "typescript": "^6.0.2", + "vitest": "^3.2.7", "webpack": "^5.102.1", "webpack-cli": "^5.1.4" } diff --git a/test/Images/image.test.ts b/test/Images/image.test.ts new file mode 100644 index 0000000..6377ace --- /dev/null +++ b/test/Images/image.test.ts @@ -0,0 +1,106 @@ +import { describe, it, expect } from 'vitest' +import { loadImageFromSource, prepareImageForEncoder } from '../../src/Images/image' +import { withDom } from '../helpers/dom' +import { pixelFixture } from '../helpers/fixtures' +import { buildBytes } from '../helpers/receipt' +import { asciiBytes, containsBytes } from '../helpers/assertBytes' + +describe('loadImageFromSource', () => { + it('loads a base64 dataURL source into a real HTMLImageElement', () => + withDom(async () => { + const fixture = pixelFixture(16, 24) + const img = await loadImageFromSource(fixture.dataUrl) + expect(img.naturalWidth).toBe(16) + expect(img.naturalHeight).toBe(24) + })) + + it('loads a Blob source (via URL.createObjectURL)', () => + withDom(async (dom) => { + const fixture = pixelFixture(16, 16) + const blob = dom.createLoadableBlob(fixture.bytes, 'image/png') + const img = await loadImageFromSource(blob) + expect(img.naturalWidth).toBe(16) + expect(img.naturalHeight).toBe(16) + })) + + it('resolves an already-loaded HTMLImageElement immediately, without re-fetching', () => + withDom(async () => { + const fixture = pixelFixture(8, 8) + const img = await loadImageFromSource(fixture.dataUrl) // loaded once + const resolved = await loadImageFromSource(img) // passed straight back in + expect(resolved).toBe(img) + })) +}) + +describe('prepareImageForEncoder', () => { + it('keeps the aspect ratio and rounds both dimensions down to a multiple of 8', () => + withDom(async () => { + const fixture = pixelFixture(20, 30) // aspect ratio 1.5 + const img = await loadImageFromSource(fixture.dataUrl) + const prepared = prepareImageForEncoder(img, { maxWidth: 384, minWidth: 1, minHeight: 1 }) + expect(prepared).toBeTruthy() + expect(prepared!.width % 8).toBe(0) + expect(prepared!.height % 8).toBe(0) + // printWidth = 20 (within bounds), finalWidth = 20 - 20%8 = 16 + expect(prepared!.width).toBe(16) + })) + + it('clamps a narrower-than-minWidth image up to minWidth', () => + withDom(async () => { + const fixture = pixelFixture(10, 10) + const img = await loadImageFromSource(fixture.dataUrl) + const prepared = prepareImageForEncoder(img, { maxWidth: 384, minWidth: 100, minHeight: 1 }) + expect(prepared).toBeTruthy() + // printWidth clamped to 100, finalWidth = 100 - 100%8 = 96 + expect(prepared!.width).toBe(96) + })) + + it('clamps a wider-than-maxWidth image down to maxWidth', () => + withDom(async () => { + const fixture = pixelFixture(500, 500) + const img = await loadImageFromSource(fixture.dataUrl) + const prepared = prepareImageForEncoder(img, { maxWidth: 100, minWidth: 1, minHeight: 1 }) + expect(prepared).toBeTruthy() + // printWidth clamped to 100, finalWidth = 100 - 100%8 = 96 + expect(prepared!.width).toBe(96) + })) + + it('returns null (not a throw) when rounding down leaves nothing to print', () => + withDom(async () => { + const fixture = pixelFixture(10, 10) + const img = await loadImageFromSource(fixture.dataUrl) + // printWidth clamped into [1,4] (< 8) -> finalWidth = printWidth - printWidth%8 = 0 + const prepared = prepareImageForEncoder(img, { maxWidth: 4, minWidth: 1, minHeight: 1 }) + expect(prepared).toBeNull() + })) +}) + +describe('image element, end-to-end through buildReceiptBytes()', () => { + it('a normal image produces non-empty encoder.image() bytes', () => + withDom(async () => { + const fixture = pixelFixture(32, 32) + const bytes = await buildBytes([{ type: 'image', source: fixture.dataUrl }]) + expect(bytes.length).toBeGreaterThan(0) + })) + + it('a degenerate image (rounds to 0px) is skipped with a warning, not a thrown error — the rest of the receipt still prints', () => + withDom(async () => { + const fixture = pixelFixture(10, 10) + const originalWarn = console.warn + let warned = false + console.warn = () => { + warned = true + } + let bytes: Uint8Array + try { + bytes = await buildBytes([ + { type: 'image', source: fixture.dataUrl, maxWidth: 4, minWidth: 1, minHeight: 1 }, + { type: 'text', value: 'still here' }, + ]) + } finally { + console.warn = originalWarn + } + expect(warned, 'expected applyImageElement to console.warn on a degenerate image').toBe(true) + expect(containsBytes(bytes, asciiBytes('still here'))).toBe(true) + })) +}) diff --git a/test/Preview/itf.test.ts b/test/Preview/itf.test.ts new file mode 100644 index 0000000..9474a66 --- /dev/null +++ b/test/Preview/itf.test.ts @@ -0,0 +1,31 @@ +import { describe, it, expect } from 'vitest' +import { buildItf } from '../../src/Preview/content/itf' +import { withDom } from '../helpers/dom' + +describe('buildItf (Preview-only ITF/interleaved2of5 rendering)', () => { + it('renders an even-length digit value to a real, non-empty drawing', () => + withDom(() => { + const result = buildItf('123456', 2, 40) + expect('drawing' in result, `expected success, got ${'error' in result ? result.error : ''}`).toBe(true) + if (!('drawing' in result)) return + expect(result.drawing.widthPx).toBeGreaterThan(0) + expect(result.drawing.heightPx).toBe(40) + })) + + it('auto-pads an odd-length value with a leading zero instead of failing', () => + withDom(() => { + const odd = buildItf('12345', 2, 40) + const padded = buildItf('012345', 2, 40) + expect('drawing' in odd && 'drawing' in padded).toBe(true) + if ('drawing' in odd && 'drawing' in padded) { + // Same effective barcode -> same rendered width. + expect(odd.drawing.widthPx).toBe(padded.drawing.widthPx) + } + })) + + it('rejects non-digit input with a readable error instead of throwing', () => + withDom(() => { + const result = buildItf('abc', 2, 40) + expect('error' in result).toBe(true) + })) +}) diff --git a/test/Preview/pdf417.raster.test.ts b/test/Preview/pdf417.raster.test.ts new file mode 100644 index 0000000..732b6a2 --- /dev/null +++ b/test/Preview/pdf417.raster.test.ts @@ -0,0 +1,57 @@ +import { describe, it, expect } from 'vitest' +import { buildPdf417RasterImage, resolvePdf417Columns } from '../../src/Preview/content/pdf417' +import { withDom } from '../helpers/dom' + +function hasNonWhitePixel(canvas: HTMLCanvasElement): boolean { + const ctx = canvas.getContext('2d') + if (!ctx) throw new Error('no 2d context') + const { data } = ctx.getImageData(0, 0, canvas.width, canvas.height) + for (let i = 0; i < data.length; i += 4) { + if (data[i] !== 255 || data[i + 1] !== 255 || data[i + 2] !== 255) return true + } + return false +} + +describe('buildPdf417RasterImage (safeMode raster for pdf417)', () => { + it('renders a real PDF417 symbol padded to a multiple of 8, with actual dark pixels drawn', () => + withDom(async () => { + const result = buildPdf417RasterImage({ value: 'hello world' }, 576) + expect('image' in result, `expected success, got ${'error' in result ? result.error : ''}`).toBe(true) + if (!('image' in result)) return + expect(result.image.width % 8).toBe(0) + expect(result.image.height % 8).toBe(0) + expect(hasNonWhitePixel(result.image.canvas)).toBe(true) + })) + + it('fails (does not crop/scale) when the padded symbol would exceed the paper content width', () => + withDom(async () => { + const result = buildPdf417RasterImage({ value: 'hello world' }, 8) + expect('error' in result).toBe(true) + })) + + it('propagates bwip-js\'s own capacity error when explicit columns/rows are too small for the value (docs/notes/05)', () => + withDom(async () => { + const longValue = 'x'.repeat(2000) + const result = buildPdf417RasterImage({ value: longValue, columns: 1, rows: 3 }, 10000) + expect('error' in result).toBe(true) + })) +}) + +describe('resolvePdf417Columns (docs/notes/05, docs/notes/06)', () => { + it('returns the explicit columns unchanged when set, skipping the capacity dry-run entirely', () => { + expect(resolvePdf417Columns({ value: 'hi', columns: 7 }, 1)).toBe(7) + }) + + it('auto mode: falls back to undefined (fully automatic) instead of forwarding a columns count that would overflow the real encoder', () => { + // A tiny width budget makes preferredColumns() clamp to 1 column, which + // can't possibly fit this much data — must come back undefined, never a + // columns value the real (non-validating) encoder.pdf417() would choke on. + const longValue = 'x'.repeat(2000) + expect(resolvePdf417Columns({ value: longValue }, 1)).toBeUndefined() + }) + + it('auto mode: returns a real columns count for a value that comfortably fits', () => { + const columns = resolvePdf417Columns({ value: 'hi' }, 576) + expect(typeof columns).toBe('number') + }) +}) diff --git a/test/Preview/qrcode.raster.test.ts b/test/Preview/qrcode.raster.test.ts new file mode 100644 index 0000000..ff3b7ff --- /dev/null +++ b/test/Preview/qrcode.raster.test.ts @@ -0,0 +1,43 @@ +import { describe, it, expect } from 'vitest' +import { buildQrCodeRasterImage } from '../../src/Preview/core/qrcode' +import { withDom } from '../helpers/dom' + +function hasNonWhitePixel(canvas: HTMLCanvasElement): boolean { + const ctx = canvas.getContext('2d') + if (!ctx) throw new Error('no 2d context') + const { data } = ctx.getImageData(0, 0, canvas.width, canvas.height) + for (let i = 0; i < data.length; i += 4) { + if (data[i] !== 255 || data[i + 1] !== 255 || data[i + 2] !== 255) return true + } + return false +} + +describe('buildQrCodeRasterImage (safeMode raster for qrcode)', () => { + it('renders a real QR symbol padded to a multiple of 8, with actual dark pixels drawn', () => + withDom(async () => { + const result = buildQrCodeRasterImage({ value: 'https://example.com' }, 384) + expect('image' in result, `expected success, got ${'error' in result ? result.error : ''}`).toBe(true) + if (!('image' in result)) return + expect(result.image.width % 8).toBe(0) + expect(result.image.height % 8).toBe(0) + expect(result.image.canvas.width).toBe(result.image.width) + expect(result.image.canvas.height).toBe(result.image.height) + expect(hasNonWhitePixel(result.image.canvas)).toBe(true) + })) + + it('fails (does not crop/scale) when the padded symbol would exceed the paper content width', () => + withDom(async () => { + const result = buildQrCodeRasterImage({ value: 'https://example.com/a-fairly-long-url-to-force-a-bigger-qr-code' }, 8) + expect('error' in result).toBe(true) + })) + + it('a larger cell size produces a larger raster (size option is honored)', () => + withDom(async () => { + const small = buildQrCodeRasterImage({ value: 'hi', size: 2 }, 1000) + const large = buildQrCodeRasterImage({ value: 'hi', size: 10 }, 1000) + expect('image' in small && 'image' in large).toBe(true) + if ('image' in small && 'image' in large) { + expect(large.image.width).toBeGreaterThan(small.image.width) + } + })) +}) diff --git a/test/Printer/ReceiptBuilder.barcode.test.ts b/test/Printer/ReceiptBuilder.barcode.test.ts new file mode 100644 index 0000000..9fb3d60 --- /dev/null +++ b/test/Printer/ReceiptBuilder.barcode.test.ts @@ -0,0 +1,29 @@ +import { describe, it, expect } from 'vitest' +import { buildBytes } from '../helpers/receipt' + +describe('ReceiptBuilder: barcode element, end-to-end through buildReceiptBytes()', () => { + it('code128 (the default symbology) produces non-empty bytes', async () => { + const bytes = await buildBytes([{ type: 'barcode', value: '123456789012' }]) + expect(bytes.length).toBeGreaterThan(0) + }) + + it('itf produces non-empty bytes for a digits-only value (real encoder\'s symbology name is "itf", not "interleaved2of5" — that\'s bwip-js\'s/Preview-only naming)', async () => { + const bytes = await buildBytes([{ type: 'barcode', value: '123456', symbology: 'itf' }]) + expect(bytes.length).toBeGreaterThan(0) + }) + + it('an explicit height/width changes the encoded bytes vs. the defaults (encoder only accepts width 1-3)', async () => { + const defaults = await buildBytes([{ type: 'barcode', value: '123456789012' }]) + const custom = await buildBytes([{ type: 'barcode', value: '123456789012', height: 120, width: 3 }]) + expect(defaults).not.toEqual(custom) + }) + + it('regression guard: an unsupported symbology name does NOT throw — the real encoder silently skips it (its own "relaxed" error mode)', async () => { + // Confirmed against the real installed library: unlike bwip-js (Preview, + // strict), the real encoder's default error mode just no-ops on an + // unknown/unsupported symbology instead of throwing. If a future + // encoder version starts throwing here instead, this test should flip + // to assert the rejection. + await buildBytes([{ type: 'barcode', value: '123456', symbology: 'not-a-real-symbology' }]) + }) +}) diff --git a/test/Printer/ReceiptBuilder.image.test.ts b/test/Printer/ReceiptBuilder.image.test.ts new file mode 100644 index 0000000..3e0d7aa --- /dev/null +++ b/test/Printer/ReceiptBuilder.image.test.ts @@ -0,0 +1,40 @@ +import { describe, it, expect } from 'vitest' +import { buildEncoder } from '../helpers/encoder' + +/** + * Direct contract tests against the real encoder's `.image()` — the + * primitive every safeMode raster (pdf417/qrcode) and applyImageElement() + * ultimately call. Uses a plain `{ data, width, height }` object (the real + * encoder's DOM-free input branch, confirmed by reading its source) so this + * file needs no jsdom/canvas — see Images/image.test.ts for the full + * DOM-backed applyImageElement() pipeline. + */ +function solidImageData(width: number, height: number): ImageData { + const data = new Uint8ClampedArray(width * height * 4) + for (let i = 0; i < data.length; i += 4) { + data[i] = 0 + data[i + 1] = 0 + data[i + 2] = 0 + data[i + 3] = 255 + } + return { data, width, height, colorSpace: 'srgb' } as ImageData +} + +describe('encoder.image() contract', () => { + it('throws when width is not a multiple of 8', () => { + const encoder = buildEncoder() + expect(() => encoder.image(solidImageData(10, 8), 10, 8)).toThrow() + }) + + it('throws when height is not a multiple of 8', () => { + const encoder = buildEncoder() + expect(() => encoder.image(solidImageData(8, 10), 8, 10)).toThrow() + }) + + it('accepts a plain {data,width,height} object with dimensions that are multiples of 8', () => { + const encoder = buildEncoder() + expect(() => encoder.image(solidImageData(16, 8), 16, 8, 'threshold')).not.toThrow() + const bytes = encoder.encode() + expect(bytes.length).toBeGreaterThan(0) + }) +}) diff --git a/test/Printer/ReceiptBuilder.pdf417.test.ts b/test/Printer/ReceiptBuilder.pdf417.test.ts new file mode 100644 index 0000000..0212769 --- /dev/null +++ b/test/Printer/ReceiptBuilder.pdf417.test.ts @@ -0,0 +1,51 @@ +import { describe, it, expect } from 'vitest' +import { buildBytes } from '../helpers/receipt' +import { withDom } from '../helpers/dom' + +describe('ReceiptBuilder: pdf417 element, end-to-end through buildReceiptBytes()', () => { + it('native pdf417 produces non-empty bytes', async () => { + const bytes = await buildBytes([{ type: 'pdf417', value: 'hello world' }]) + expect(bytes.length).toBeGreaterThan(0) + }) + + it('an explicit errorlevel changes the encoded bytes vs. the default (docs/notes/06: default must stay 1, matching the real encoder)', async () => { + const withDefault = await buildBytes([{ type: 'pdf417', value: 'hello world' }]) + const withErrorlevel1 = await buildBytes([{ type: 'pdf417', value: 'hello world', errorlevel: 1 }]) + const withErrorlevel4 = await buildBytes([{ type: 'pdf417', value: 'hello world', errorlevel: 4 }]) + // The real encoder's own default errorlevel is 1 — an explicit 1 must + // produce identical bytes to leaving it unset, while a different level + // must diverge. If the encoder's default ever changes out from under + // this library, this is the test that would catch it. + expect(withDefault).toEqual(withErrorlevel1) + expect(withDefault).not.toEqual(withErrorlevel4) + }) + + it('safeMode: true renders a raster image instead of the native command (docs/notes/09)', () => + withDom(async () => { + const bytes = await buildBytes([{ type: 'pdf417', value: 'hello world', safeMode: true }]) + expect(bytes.length).toBeGreaterThan(0) + })) + + it('safeMode true and false produce different bytes for the same value', () => + withDom(async () => { + const native = await buildBytes([{ type: 'pdf417', value: 'hello world' }]) + const safe = await buildBytes([{ type: 'pdf417', value: 'hello world', safeMode: true }]) + expect(native).not.toEqual(safe) + })) +}) + +describe('ReceiptBuilder: paperWidth (58mm/80mm/112mm)', () => { + it('58mm and 80mm build a full receipt without throwing', async () => { + await buildBytes([{ type: 'text', value: 'hi' }], { paperWidth: '58mm' }) + await buildBytes([{ type: 'text', value: 'hi' }], { paperWidth: '80mm' }) + }) + + it('BUG (pre-existing, not introduced by this test suite): 112mm currently throws building ANY receipt, because the real encoder only accepts columns of 32/35/42/44/48, and PAPER_WIDTH_SPECS maps 112mm to 56', async () => { + // If this test starts failing because 112mm was fixed, that's good news + // — drop the .rejects assertion and update PAPER_WIDTH_SPECS/docs/notes + // accordingly. + await expect(buildBytes([{ type: 'text', value: 'hi' }], { paperWidth: '112mm' })).rejects.toThrow( + /width of the paper must me either 32, 35, 42, 44 or 48 columns/, + ) + }) +}) diff --git a/test/Printer/ReceiptBuilder.qrcode.test.ts b/test/Printer/ReceiptBuilder.qrcode.test.ts new file mode 100644 index 0000000..46ae571 --- /dev/null +++ b/test/Printer/ReceiptBuilder.qrcode.test.ts @@ -0,0 +1,50 @@ +import { describe, it, expect } from 'vitest' +import { buildBytes } from '../helpers/receipt' +import { withDom } from '../helpers/dom' + +describe('ReceiptBuilder: qrcode element, end-to-end through buildReceiptBytes()', () => { + it('native qrcode produces non-empty bytes', async () => { + const bytes = await buildBytes([{ type: 'qrcode', value: 'https://example.com' }]) + expect(bytes.length).toBeGreaterThan(0) + }) + + it('an explicit size changes the encoded bytes vs. the default', async () => { + const withDefault = await buildBytes([{ type: 'qrcode', value: 'https://example.com' }]) + const withSize = await buildBytes([{ type: 'qrcode', value: 'https://example.com', size: 8 }]) + expect(withDefault).not.toEqual(withSize) + }) + + it('safeMode: true renders a raster image instead of the native command', () => + withDom(async () => { + const bytes = await buildBytes([{ type: 'qrcode', value: 'https://example.com', safeMode: true }]) + expect(bytes.length).toBeGreaterThan(0) + })) + + it('safeMode true and false produce different bytes for the same value', () => + withDom(async () => { + const native = await buildBytes([{ type: 'qrcode', value: 'https://example.com' }]) + const safe = await buildBytes([{ type: 'qrcode', value: 'https://example.com', safeMode: true }]) + expect(native).not.toEqual(safe) + })) + + it('safeMode: true with an unreasonably narrow paper skips the element with a console.warn, instead of throwing', () => + withDom(async () => { + const originalWarn = console.warn + let warned = false + console.warn = () => { + warned = true + } + let bytes: Uint8Array + try { + bytes = await buildBytes( + [{ type: 'qrcode', value: 'https://example.com/a-fairly-long-url-to-force-a-bigger-qr-code', safeMode: true }], + {}, + { imageMaxWidth: 8 }, + ) + } finally { + console.warn = originalWarn + } + expect(warned).toBe(true) + expect(bytes.length).toBeGreaterThanOrEqual(0) // didn't throw — that's the point + })) +}) diff --git a/test/Printer/ReceiptBuilder.rule.test.ts b/test/Printer/ReceiptBuilder.rule.test.ts new file mode 100644 index 0000000..42484ad --- /dev/null +++ b/test/Printer/ReceiptBuilder.rule.test.ts @@ -0,0 +1,27 @@ +import { describe, it, expect } from 'vitest' +import { buildBytes } from '../helpers/receipt' +import { asciiBytes, containsBytes } from '../helpers/assertBytes' + +/** + * docs/notes/10-clone-printers-mangle-rule-character.md: encoder.rule() + * sends a cp437 box-drawing character some clone printers' font tables + * don't match, printing garbage instead of a line. safeMode: true swaps in + * a plain ASCII '-' line, which is safe across every codepage. + */ +describe('ReceiptBuilder: rule element', () => { + it('native rule() does not contain the plain-ASCII safeMode line', async () => { + const bytes = await buildBytes([{ type: 'rule' }], {}, { columns: 32 }) + expect(containsBytes(bytes, asciiBytes('-'.repeat(32)))).toBe(false) + }) + + it('safeMode: true sends a plain ASCII dash line, columns wide', async () => { + const bytes = await buildBytes([{ type: 'rule', safeMode: true }], {}, { columns: 32 }) + expect(containsBytes(bytes, asciiBytes('-'.repeat(32)))).toBe(true) + }) + + it('safeMode false (default) and safeMode true produce different bytes for the same columns', async () => { + const native = await buildBytes([{ type: 'rule' }], {}, { columns: 32 }) + const safe = await buildBytes([{ type: 'rule', safeMode: true }], {}, { columns: 32 }) + expect(native).not.toEqual(safe) + }) +}) diff --git a/test/Printer/ReceiptBuilder.text.test.ts b/test/Printer/ReceiptBuilder.text.test.ts new file mode 100644 index 0000000..98e1faa --- /dev/null +++ b/test/Printer/ReceiptBuilder.text.test.ts @@ -0,0 +1,62 @@ +import { describe, it, expect } from 'vitest' +import { buildBytes } from '../helpers/receipt' +import { asciiBytes, containsBytes } from '../helpers/assertBytes' +import { wrapText } from '../../src/Text/wrap' +import { justifyLine } from '../../src/Text/justify' + +// The real encoder only accepts columns of 32/35/42/44/48 (see +// ReceiptBuilder.pdf417.test.ts's "112mm" case) — 32 throughout. +const COLUMNS = 32 + +describe('ReceiptBuilder: text element, end-to-end through buildReceiptBytes()', () => { + it('wraps at the resolved column width — one encoder.newline() per wrapped line', async () => { + const bytes = await buildBytes([{ type: 'text', value: 'a'.repeat(70) }], {}, { columns: COLUMNS }) + const newlineCount = [...bytes].filter((byte) => byte === 0x0a).length + expect(newlineCount).toBe(wrapText('a'.repeat(70), COLUMNS).length) + }) + + it('right-aligned text keeps its left padding in the final receipt bytes (integration of sendLine.ts)', async () => { + const bytes = await buildBytes([{ type: 'text', value: 'abc', align: 'right' }], {}, { columns: COLUMNS }) + expect(containsBytes(bytes, asciiBytes(' '.repeat(COLUMNS - 3) + 'abc'))).toBe(true) + }) + + it('justify stretches every wrapped line except the paragraph\'s last', async () => { + const value = 'one two three four five six seven eight nine ten eleven twelve' + const bytes = await buildBytes([{ type: 'text', value, align: 'justify' }], {}, { columns: COLUMNS }) + + const wrapped = wrapText(value, COLUMNS) + expect(wrapped.length, 'test fixture needs to actually wrap into 2+ lines').toBeGreaterThan(1) + + const firstLineJustified = justifyLine(wrapped[0].text, COLUMNS) + expect(containsBytes(bytes, asciiBytes(firstLineJustified))).toBe(true) + + const lastLine = wrapped[wrapped.length - 1].text + expect(containsBytes(bytes, asciiBytes(lastLine)), "paragraph's last line must stay ragged/unjustified").toBe(true) + }) + + it('stripAccents strips accents by default', async () => { + const bytes = await buildBytes([{ type: 'text', value: 'café' }], {}, { columns: COLUMNS }) + expect(containsBytes(bytes, asciiBytes('cafe'))).toBe(true) + }) + + it('stripAccents: false leaves accented text for sendLine()\'s non-ASCII fallback (encoder.text()) to handle, without throwing', async () => { + // Await directly, not expect(...).resolves — a rejection here fails the + // test on its own; there's no meaningful matcher for "didn't throw". + await buildBytes([{ type: 'text', value: 'café', align: 'right' }], { stripAccents: false }, { columns: COLUMNS }) + }) + + it('bold/underline/size add command bytes without throwing, and do not carry over to a following plain element', async () => { + const styled = await buildBytes([{ type: 'text', value: 'x', bold: true, underline: true, size: 2 }]) + const plain = await buildBytes([{ type: 'text', value: 'x' }]) + expect(styled.length).toBeGreaterThan(plain.length) + + // Two elements: styled then plain — the plain one's own text still + // shows up (bold/underline/size don't need decoding to fail loudly if + // they broke something downstream). + const combined = await buildBytes([ + { type: 'text', value: 'first', bold: true, underline: true, size: 2 }, + { type: 'text', value: 'second' }, + ]) + expect(containsBytes(combined, asciiBytes('second'))).toBe(true) + }) +}) diff --git a/test/Printer/SafeMode.test.ts b/test/Printer/SafeMode.test.ts new file mode 100644 index 0000000..4ec3afd --- /dev/null +++ b/test/Printer/SafeMode.test.ts @@ -0,0 +1,93 @@ +import { describe, it, expect } from 'vitest' +import { safeMode, safeModeText, type SafeModeRaster } from '../../src/Printer/Utils/SafeMode' +import { buildEncoder } from '../helpers/encoder' + +/** + * DOM-free tests of the shared safeMode()/safeModeText() substitution + * mechanism itself — the contract every safeMode element (pdf417/qrcode/ + * rule) relies on. Uses a fake `buildRaster` callback and a plain + * `{data,width,height}` "canvas" (the real encoder's DOM-free image() input + * branch), so this pins the mechanism without needing jsdom/canvas — see + * Preview/qrcode.raster.test.ts and pdf417.raster.test.ts for the real + * canvas-backed raster generation this mechanism calls in production. + */ +function fakeRaster(width = 8, height = 8): SafeModeRaster { + return { + image: { + canvas: { data: new Uint8ClampedArray(width * height * 4), width, height } as unknown as CanvasImageSource, + width, + height, + }, + } +} + +describe('safeMode()', () => { + it('inactive (undefined/false): returns false and never calls buildRaster', () => { + const encoder = buildEncoder() + let called = false + const active = safeMode(encoder, undefined, 'test', () => { + called = true + return fakeRaster() + }) + expect(active).toBe(false) + expect(called).toBe(false) + + const activeFalse = safeMode(encoder, false, 'test', () => { + called = true + return fakeRaster() + }) + expect(activeFalse).toBe(false) + expect(called).toBe(false) + }) + + it('active + successful build: calls encoder.image() with the raster\'s exact dims and returns true', () => { + const encoder = buildEncoder() + const active = safeMode(encoder, true, 'test', () => fakeRaster(16, 8)) + expect(active).toBe(true) + const bytes = encoder.encode() + expect(bytes.length).toBeGreaterThan(0) + }) + + it('active + build error: returns true (native command still skipped) without calling encoder.image(), and warns', () => { + const encoder = buildEncoder() + const originalWarn = console.warn + let warned = false + console.warn = () => { + warned = true + } + let active: boolean + try { + active = safeMode(encoder, true, 'test', () => ({ error: 'boom' })) + } finally { + console.warn = originalWarn + } + expect(active).toBe(true) + expect(warned).toBe(true) + // Nothing beyond initialize() was queued into the encoder — compare + // against a fresh encoder's own initialize()-only bytes, since + // initialize() itself is not zero-length (reset/codepage-setup bytes). + const bytes = encoder.encode() + const baseline = buildEncoder().encode() + expect(bytes).toEqual(baseline) + }) +}) + +describe('safeModeText()', () => { + it('inactive: returns false and never calls apply', () => { + let called = false + const active = safeModeText(undefined, () => { + called = true + }) + expect(active).toBe(false) + expect(called).toBe(false) + }) + + it('active: calls apply exactly once and returns true', () => { + let calls = 0 + const active = safeModeText(true, () => { + calls += 1 + }) + expect(active).toBe(true) + expect(calls).toBe(1) + }) +}) diff --git a/test/Text/justify.test.ts b/test/Text/justify.test.ts new file mode 100644 index 0000000..f243c50 --- /dev/null +++ b/test/Text/justify.test.ts @@ -0,0 +1,21 @@ +import { describe, it, expect } from 'vitest' +import { justifyLine } from '../../src/Text/justify' + +describe('justifyLine', () => { + it('distributes slack evenly across gaps, extra space going to the leftmost gaps first', () => { + // words total length 3, columns 10 -> totalGap 7, gapCount 2, base 3, extra 1 + const result = justifyLine('a b c', 10) + expect(result.length).toBe(10) + expect(result).toBe('a b c') + }) + + it('returns the line unchanged when there is 1 word or fewer', () => { + expect(justifyLine('solo', 10)).toBe('solo') + expect(justifyLine('', 10)).toBe('') + }) + + it('returns the line unchanged when words already fill or exceed columns (no slack to distribute)', () => { + expect(justifyLine('aaaaa bbbbb', 10)).toBe('aaaaa bbbbb') + expect(justifyLine('aaaaaa bbbbb', 10)).toBe('aaaaaa bbbbb') + }) +}) diff --git a/test/Text/sendLine.test.ts b/test/Text/sendLine.test.ts new file mode 100644 index 0000000..8196efa --- /dev/null +++ b/test/Text/sendLine.test.ts @@ -0,0 +1,62 @@ +import { describe, it, expect } from 'vitest' +import { sendLine } from '../../src/Text/sendLine' +import { buildEncoder } from '../helpers/encoder' +import { asciiBytes, containsBytes } from '../helpers/assertBytes' + +// The real encoder only accepts columns of 32/35/42/44/48 (see +// ReceiptBuilder.pdf417.test.ts's "112mm" case) — 32 here, expected padding +// computed from it rather than hardcoded, so the numbers stay consistent if +// this ever changes. +const COLUMNS = 32 + +/** + * Pins the regression sendLine.ts's own comment documents: `encoder.text()` + * trims leading/trailing whitespace internally (confirmed against the real + * installed encoder below), silently eating alignment padding — sendLine() + * exists specifically to route pure-ASCII padded lines through `raw()` + * instead, which doesn't have that problem. + */ +describe('sendLine', () => { + it('right-aligned ASCII text keeps its left padding, via raw() not text()', () => { + const encoder = buildEncoder({ columns: COLUMNS }) + sendLine(encoder, 'abc', COLUMNS, 'right') + const bytes = encoder.encode() + const expected = ' '.repeat(COLUMNS - 3) + 'abc' + expect(containsBytes(bytes, asciiBytes(expected))).toBe(true) + }) + + it('center-aligned ASCII text keeps its floor()-ed left padding', () => { + const encoder = buildEncoder({ columns: COLUMNS }) + sendLine(encoder, 'abc', COLUMNS, 'center') + const bytes = encoder.encode() + const expected = ' '.repeat(Math.floor((COLUMNS - 3) / 2)) + 'abc' + expect(containsBytes(bytes, asciiBytes(expected))).toBe(true) + }) + + it('left-aligned ASCII text is sent unpadded', () => { + const encoder = buildEncoder({ columns: COLUMNS }) + sendLine(encoder, 'abc', COLUMNS, 'left') + const bytes = encoder.encode() + expect(containsBytes(bytes, asciiBytes('abc'))).toBe(true) + expect(containsBytes(bytes, asciiBytes(' abc'))).toBe(false) + }) + + it('regression: encoder.text() alone (the pre-fix approach) trims that same leading padding', () => { + const encoder = buildEncoder({ columns: COLUMNS }) + const padded = ' '.repeat(COLUMNS - 3) + 'abc' // what padLine() would have produced, sent the naive way + encoder.text(padded) + const bytes = encoder.encode() + expect( + containsBytes(bytes, asciiBytes(padded)), + 'encoder.text() should have trimmed the leading spaces here — confirms why sendLine() routes padded lines through raw() instead', + ).toBe(false) + }) + + it('non-ASCII content bypasses raw() and falls back to encoder.text() without throwing', () => { + const encoder = buildEncoder({ columns: COLUMNS }) + // "café" — a literal precomposed character, never a hand-typed \uXXXX escape (AGENTS.md gotcha). + expect(() => sendLine(encoder, 'café', COLUMNS, 'left')).not.toThrow() + const bytes = encoder.encode() + expect(bytes.length).toBeGreaterThan(0) + }) +}) diff --git a/test/Text/wrap.test.ts b/test/Text/wrap.test.ts new file mode 100644 index 0000000..316bb2f --- /dev/null +++ b/test/Text/wrap.test.ts @@ -0,0 +1,39 @@ +import { describe, it, expect } from 'vitest' +import { wrapText } from '../../src/Text/wrap' + +function texts(lines: ReturnType): string[] { + return lines.map((line) => line.text) +} + +describe('wrapText', () => { + it('greedily fills lines up to columns', () => { + expect(texts(wrapText('aaa bbb ccc', 8))).toEqual(['aaa bbb', 'ccc']) + }) + + it('keeps a word that exactly fits the column width on one line', () => { + expect(texts(wrapText('12345678', 8))).toEqual(['12345678']) + }) + + it('hard-splits an oversized single word, hyphenating every chunk but the last', () => { + expect(texts(wrapText('abcdefghij', 4))).toEqual(['abc-', 'def-', 'ghij']) + }) + + it('falls back to a plain hard split with no hyphen when columns <= 1', () => { + expect(texts(wrapText('abc', 1))).toEqual(['a', 'b', 'c']) + }) + + it('splits on \\n into independent paragraphs, each wrapped separately', () => { + expect(texts(wrapText('aa bb\ncc dd', 5))).toEqual(['aa bb', 'cc dd']) + }) + + it('marks only the last wrapped line of each paragraph as isLastLineOfParagraph', () => { + const lines = wrapText('aa bb cc\ndd', 5) + expect(texts(lines)).toEqual(['aa bb', 'cc', 'dd']) + expect(lines.map((line) => line.isLastLineOfParagraph)).toEqual([false, true, true]) + }) + + it('an empty paragraph produces a single empty line, not zero lines', () => { + expect(texts(wrapText('', 5))).toEqual(['']) + expect(texts(wrapText('a\n\nb', 5))).toEqual(['a', '', 'b']) + }) +}) diff --git a/test/config.test.ts b/test/config.test.ts new file mode 100644 index 0000000..69d4b9a --- /dev/null +++ b/test/config.test.ts @@ -0,0 +1,26 @@ +import { describe, it, expect } from 'vitest' +import { resolveColumns, resolveImageMaxWidth, PAPER_WIDTH_SPECS, DEFAULT_CONFIG } from '../config' + +describe('config: paperWidth resolution (docs/notes/02-paperwidth-scales-columns-and-imagemaxwidth.md)', () => { + it('58mm/80mm/112mm map to their documented columns/imageMaxWidth', () => { + expect(PAPER_WIDTH_SPECS['58mm']).toEqual({ columns: 32, imageMaxWidth: 384 }) + expect(PAPER_WIDTH_SPECS['80mm']).toEqual({ columns: 42, imageMaxWidth: 576 }) + expect(PAPER_WIDTH_SPECS['112mm']).toEqual({ columns: 56, imageMaxWidth: 832 }) + }) + + it('resolveColumns: explicit value wins over paperWidth, which wins over the fallback', () => { + expect(resolveColumns(99, '80mm', 10)).toBe(99) + expect(resolveColumns(undefined, '80mm', 10)).toBe(42) + expect(resolveColumns(undefined, undefined, 10)).toBe(10) + }) + + it('resolveImageMaxWidth: same precedence, and actually scales with paperWidth — regression for the bug where only columns scaled', () => { + expect(resolveImageMaxWidth(undefined, '58mm', 999)).toBe(384) + expect(resolveImageMaxWidth(undefined, '112mm', 999)).toBe(832) + expect(resolveImageMaxWidth(undefined, '112mm', 999)).not.toBe(DEFAULT_CONFIG.imageMaxWidth) + }) + + it('DEFAULT_CONFIG.feedBeforeCut is 4, not the encoder default of 0 (docs/notes/04)', () => { + expect(DEFAULT_CONFIG.feedBeforeCut).toBe(4) + }) +}) diff --git a/test/helpers/assertBytes.ts b/test/helpers/assertBytes.ts new file mode 100644 index 0000000..0978a0c --- /dev/null +++ b/test/helpers/assertBytes.ts @@ -0,0 +1,19 @@ +/** 1-based char codes of `text` — for building byte needles to search encoded output for. */ +export function asciiBytes(text: string): number[] { + return [...text].map((char) => char.charCodeAt(0)) +} + +/** Index of the first occurrence of `needle` inside `haystack`, or -1. Plain byte-array search — encoded receipts are small, no need for anything smarter. */ +export function indexOfBytes(haystack: Uint8Array, needle: number[]): number { + outer: for (let i = 0; i <= haystack.length - needle.length; i++) { + for (let j = 0; j < needle.length; j++) { + if (haystack[i + j] !== needle[j]) continue outer + } + return i + } + return -1 +} + +export function containsBytes(haystack: Uint8Array, needle: number[]): boolean { + return indexOfBytes(haystack, needle) !== -1 +} diff --git a/test/helpers/dom.ts b/test/helpers/dom.ts new file mode 100644 index 0000000..ef8a5f6 --- /dev/null +++ b/test/helpers/dom.ts @@ -0,0 +1,74 @@ +import { JSDOM } from 'jsdom' + +const DOM_GLOBALS = ['document', 'window', 'Image', 'HTMLImageElement', 'HTMLCanvasElement', 'Blob', 'URL'] as const +type DomGlobalKey = (typeof DOM_GLOBALS)[number] + +export interface DomFixtures { + /** + * Wraps `bytes` in a real `Blob` whose `URL.createObjectURL()` result + * actually decodes to those bytes in this jsdom instance. jsdom doesn't + * implement `createObjectURL()`/blob: URL decoding at all (by design), so + * `withDom()` polyfills it — but only for blobs created through this + * function, since only here are the bytes known synchronously (a real + * `createObjectURL()` call is synchronous; reading a `Blob`'s bytes back + * out isn't). Functionally equivalent for `loadImageFromSource()`'s Blob + * branch, which only cares that `img.src = ` decodes to the + * right pixels. + */ + createLoadableBlob(bytes: Uint8Array, type: string): Blob +} + +/** + * Installs a jsdom Window's DOM globals (document/Image/canvas/Blob/URL) for + * the duration of `fn`, then restores whatever was there before (nothing, in + * every test file that uses this) — so DOM-free test files never see a + * leaked global from a DOM test that ran earlier in the same process. + * + * `resources: 'usable'` plus the `canvas` devDependency together are what + * let jsdom's `Image`/`HTMLCanvasElement` decode and draw real pixels + * instead of throwing/no-op'ing — jsdom's own image/canvas plumbing looks + * for the `canvas` npm package specifically (confirmed against this + * project's jsdom@27/canvas@3 versions; @napi-rs/canvas does NOT work here, + * jsdom doesn't know how to talk to it). + */ +export async function withDom(fn: (fixtures: DomFixtures) => T | Promise): Promise { + const dom = new JSDOM('', { + pretendToBeVisual: true, + resources: 'usable', + url: 'http://localhost/', + }) + const { window } = dom + + const previous = {} as Record + const globalRecord = globalThis as unknown as Record + for (const key of DOM_GLOBALS) previous[key] = globalRecord[key] + for (const key of DOM_GLOBALS) globalRecord[key] = (window as unknown as Record)[key] + + const objectUrls = new WeakMap() + window.URL.createObjectURL = ((blob: Blob) => { + const url = objectUrls.get(blob) + if (!url) throw new Error('withDom(): blob not created via createLoadableBlob() — no data: URL registered for it') + return url + }) as typeof URL.createObjectURL + window.URL.revokeObjectURL = (() => {}) as typeof URL.revokeObjectURL + + function createLoadableBlob(bytes: Uint8Array, type: string): Blob { + // Buffer.from(), not `bytes` directly — a plain Uint8Array's `.buffer` + // is typed as ArrayBufferLike (could be a SharedArrayBuffer), which + // BlobPart's stricter ArrayBuffer-only typing rejects. + const buffer = Buffer.from(bytes) + const blob = new window.Blob([buffer], { type }) + objectUrls.set(blob, `data:${type};base64,${buffer.toString('base64')}`) + return blob + } + + try { + return await fn({ createLoadableBlob }) + } finally { + for (const key of DOM_GLOBALS) { + if (previous[key] === undefined) delete globalRecord[key] + else globalRecord[key] = previous[key] + } + window.close() + } +} diff --git a/test/helpers/encoder.ts b/test/helpers/encoder.ts new file mode 100644 index 0000000..a09809a --- /dev/null +++ b/test/helpers/encoder.ts @@ -0,0 +1,20 @@ +import ReceiptPrinterEncoder from '@point-of-sale/receipt-printer-encoder' +import type { ReceiptPrinterEncoderOptions } from '@point-of-sale/receipt-printer-encoder' + +/** + * Builds a real, initialized ReceiptPrinterEncoder — same construction + * ReceiptBuilder.ts uses (columns/language/feedBeforeCut), minus the + * PrintJob-specific plumbing. Tests that need to drive the encoder directly + * (not through buildReceiptBytes()) should go through this, not + * `new ReceiptPrinterEncoder()` — keeps the "known-good defaults" in one + * place instead of copy-pasted per test file. + * + * Note: the real encoder only accepts `columns` of 32/35/42/44/48 (confirmed + * by reading the installed library — see ReceiptBuilder.pdf417.test.ts's + * "112mm" case for why this matters). + */ +export function buildEncoder(overrides: ReceiptPrinterEncoderOptions = {}): ReceiptPrinterEncoder { + const encoder = new ReceiptPrinterEncoder({ columns: 32, language: 'esc-pos', feedBeforeCut: 4, ...overrides }) + encoder.initialize() + return encoder +} diff --git a/test/helpers/fixtures.ts b/test/helpers/fixtures.ts new file mode 100644 index 0000000..5187800 --- /dev/null +++ b/test/helpers/fixtures.ts @@ -0,0 +1,30 @@ +import { createCanvas } from 'canvas' + +export interface PixelFixture { + bytes: Uint8Array + dataUrl: string + width: number + height: number +} + +/** + * A real, freshly-encoded small PNG — left half red, right half blue, so + * pixel-level assertions can check both halves survived a resize/dither. + * Generated at runtime via the `canvas` devDependency instead of a + * hand-typed/hardcoded base64 string: a hand-typed one turned out to + * actually be invalid (confirmed — `canvas`'s decoder failed on it with an + * opaque "out of memory" error), and this repo's own AGENTS.md already + * warns against hand-typing binary-ish literals in this environment. + */ +export function pixelFixture(width = 16, height = 16): PixelFixture { + const canvas = createCanvas(width, height) + const ctx = canvas.getContext('2d') + ctx.fillStyle = '#ff0000' + ctx.fillRect(0, 0, width / 2, height) + ctx.fillStyle = '#0000ff' + ctx.fillRect(width / 2, 0, width / 2, height) + + const dataUrl = canvas.toDataURL('image/png') + const bytes = new Uint8Array(Buffer.from(dataUrl.split(',')[1], 'base64')) + return { bytes, dataUrl, width, height } +} diff --git a/test/helpers/receipt.ts b/test/helpers/receipt.ts new file mode 100644 index 0000000..9a2af62 --- /dev/null +++ b/test/helpers/receipt.ts @@ -0,0 +1,20 @@ +import { buildReceiptBytes } from '../../src/Printer/ReceiptBuilder' +import { DEFAULT_CONFIG } from '../../config' +import type { PrintJob, PrintJobElement, WebEscposPrinterConfig } from '../../src/types' + +/** + * Runs the real ReceiptBuilder.ts pipeline end-to-end — the exact function + * WebEscposPrinter.printReceipt() calls — so tests exercise the actual + * integration point instead of a hand-rolled mirror of its internals. + * `cut: false` by default so assertions can focus on the element(s) under + * test without the trailing feed/cut bytes; override via `jobOverrides`. + */ +export function buildBytes( + content: PrintJobElement[], + jobOverrides: Partial> = {}, + defaultsOverrides: Partial = {}, +): Promise { + const defaults: WebEscposPrinterConfig = { ...DEFAULT_CONFIG, ...defaultsOverrides } + const job: PrintJob = { cut: false, content, ...jobOverrides } + return buildReceiptBytes(job, defaults) +} diff --git a/test/tsconfig.json b/test/tsconfig.json new file mode 100644 index 0000000..b6c044d --- /dev/null +++ b/test/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../tsconfig.json", + "compilerOptions": { + "types": ["node", "web-bluetooth"], + "noEmit": true + }, + "include": ["**/*.ts", "../src/**/*.ts", "../config.ts", "../index.ts"] +} diff --git a/vitest.config.ts b/vitest.config.ts new file mode 100644 index 0000000..3e42797 --- /dev/null +++ b/vitest.config.ts @@ -0,0 +1,7 @@ +import { defineConfig } from 'vitest/config' + +export default defineConfig({ + test: { + include: ['test/**/*.test.ts'], + }, +})