From d40a288a4edc1eea0d8f9b0e90fbfddd1be9d2f0 Mon Sep 17 00:00:00 2001 From: Albrecht Degering Date: Mon, 20 Jul 2026 18:13:43 +0200 Subject: [PATCH] feat: integrate PDF Tools with toolbox portal --- .gitignore | 5 +- .npmrc | 1 + CHANGELOG.md | 9 +- README.md | 26 +- eslint.config.mjs | 2 +- licenses/core-js-3.49.0-LICENSE.txt | 24 ++ package-lock.json | 504 ++++++++++++++------------ package.json | 12 +- public/site.webmanifest | 6 +- public/toolbox-app.json | 39 ++ scripts/generate-toolbox-manifest.mjs | 64 ++++ scripts/package-release.mjs | 100 +++++ scripts/release-archive.d.ts | 12 + scripts/release-archive.mjs | 123 +++++++ src/App.tsx | 21 +- src/components/Layout.tsx | 47 --- src/main.tsx | 1 + src/styles.css | 83 +---- src/toolboxApp.definition.json | 36 ++ src/toolboxApp.test.ts | 10 + src/toolboxApp.ts | 9 + tests/releaseArchive.test.ts | 96 +++++ vite.config.ts | 3 +- 23 files changed, 879 insertions(+), 354 deletions(-) create mode 100644 .npmrc create mode 100644 licenses/core-js-3.49.0-LICENSE.txt create mode 100644 public/toolbox-app.json create mode 100644 scripts/generate-toolbox-manifest.mjs create mode 100644 scripts/package-release.mjs create mode 100644 scripts/release-archive.d.ts create mode 100644 scripts/release-archive.mjs delete mode 100644 src/components/Layout.tsx create mode 100644 src/toolboxApp.definition.json create mode 100644 src/toolboxApp.test.ts create mode 100644 src/toolboxApp.ts create mode 100644 tests/releaseArchive.test.ts diff --git a/.gitignore b/.gitignore index f09bff4..4872871 100644 --- a/.gitignore +++ b/.gitignore @@ -141,4 +141,7 @@ vite.config.ts.timestamp-* .vite/ todo.txt -chatgpt_continuation.md5 +chatgpt_continuation.md5 + +# Generated release artifacts +release/ diff --git a/.npmrc b/.npmrc new file mode 100644 index 0000000..90e6041 --- /dev/null +++ b/.npmrc @@ -0,0 +1 @@ +@add-ideas:registry=https://git.add-ideas.de/api/packages/zemion/npm/ diff --git a/CHANGELOG.md b/CHANGELOG.md index 7353106..25e4992 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,13 +4,20 @@ All notable changes to `pdf-tools` are documented here. The project follows a pragmatic versioning scheme while the app is still below `1.0.0`: minor versions mark coherent user-facing milestones; patch versions mark fixes and small improvements. -## 0.3.4 — PDF.js browser compatibility fix +## 0.3.4 — PDF.js compatibility and Toolbox integration + +### Added + +- Added a generated Toolbox App Contract manifest and shared Toolbox Shell integration with standalone fallback. +- Added deterministic, checksummed release ZIP packaging for artifact-based toolbox assembly. ### Changed - Centralized PDF.js setup in a shared module so thumbnail rendering and page preview use the same legacy PDF.js build and worker configuration. - Added a local PDF.js worker wrapper that installs a `Uint8Array.prototype.toHex` compatibility shim before PDF.js worker code runs. - Added the same `toHex` compatibility shim in the main app bundle for browsers that do not provide it natively. +- Made Vite asset URLs relative so the app can be hosted at arbitrary nested paths. +- Corrected the documented licence identifier to `AGPL-3.0-only`. - Bumped the app/package version to `0.3.4`. ## 0.3.2 — Multi-file merge queue release diff --git a/README.md b/README.md index 6762564..afe8b2f 100644 --- a/README.md +++ b/README.md @@ -120,6 +120,17 @@ browser <-- HTTPS --> static web server / reverse proxy --> built app assets There is no application server to operate. Administrators only need to host the built files from `dist/`. +The production build is relocatable and can also be assembled into an +[Add Ideas Toolbox](https://git.add-ideas.de/zemion/toolbox-portal) deployment. +`toolbox-app.json` describes this app through the framework-independent +[Toolbox App Contract](https://git.add-ideas.de/zemion/toolbox-sdk). The same +artifact remains fully usable on its own. + +When launched with a same-origin catalogue URL, for example +`?toolbox=/toolbox.catalog.json`, the shared first-party shell adds a toolbox +home link and application switcher. Missing, invalid, and cross-origin +catalogues are ignored so the app falls back to standalone mode. + ### Build and test from source ```bash @@ -138,6 +149,19 @@ npm run build The production build is written to `dist/`. +Create a versioned, checksummed release artifact with: + +```bash +npm run release:artifact +``` + +This writes `release/pdf-tools-.zip` and its `.sha256` file. The ZIP +contains the static app, its toolbox manifest and changelog, plus the application +source-code location, license, and the full license/notice texts found for +bundled runtime dependencies under `LICENSES/`. Existing artifacts for the same +version are never replaced implicitly; after intentionally reviewing a rebuild, use +`npm run release:artifact -- --force`. + Useful individual development commands: ```bash @@ -272,4 +296,4 @@ The roadmap and non-goals now live in [`ROADMAP.md`](ROADMAP.md). Open roadmap i ## License -GPL-3.0. See `LICENSE`. +AGPL-3.0-only. See `LICENSE`. diff --git a/eslint.config.mjs b/eslint.config.mjs index dff2c36..dbf1ae3 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -43,7 +43,7 @@ export default tseslint.config( }, }, { - files: ['*.config.{js,ts}', 'eslint.config.js'], + files: ['*.config.{js,ts}', 'eslint.config.js', 'scripts/**/*.mjs'], languageOptions: { globals: { ...globals.node, diff --git a/licenses/core-js-3.49.0-LICENSE.txt b/licenses/core-js-3.49.0-LICENSE.txt new file mode 100644 index 0000000..731f5fc --- /dev/null +++ b/licenses/core-js-3.49.0-LICENSE.txt @@ -0,0 +1,24 @@ +core-js 3.49.0 — MIT License +Source: https://github.com/zloirock/core-js/tree/v3.49.0 + +Copyright (c) 2013–2025 Denis Pushkarev (zloirock.ru) + +Copyright (c) 2025–2026 CoreJS Company (core-js.io) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/package-lock.json b/package-lock.json index 0879784..8d21b71 100644 --- a/package-lock.json +++ b/package-lock.json @@ -7,7 +7,10 @@ "": { "name": "pdf-tools", "version": "0.3.4", + "license": "AGPL-3.0-only", "dependencies": { + "@add-ideas/toolbox-contract": "^0.1.0", + "@add-ideas/toolbox-shell-react": "^0.1.0", "fflate": "^0.8.3", "pdf-lib": "^1.17.1", "pdfjs-dist": "^5.7.284", @@ -15,6 +18,7 @@ "react-dom": "^19.2.6" }, "devDependencies": { + "@add-ideas/toolbox-testkit": "^0.1.0", "@eslint/js": "^10.0.1", "@testing-library/react": "^16.3.2", "@testing-library/user-event": "^14.6.1", @@ -35,6 +39,38 @@ "vitest": "^4.1.6" } }, + "node_modules/@add-ideas/toolbox-contract": { + "version": "0.1.1", + "resolved": "https://git.add-ideas.de/api/packages/zemion/npm/%40add-ideas%2Ftoolbox-contract/-/0.1.1/toolbox-contract-0.1.1.tgz", + "integrity": "sha512-0q2whA5Y4SqG4PNNleXYR8nGbT3UYsZ1x+6ZWVmxuc/WjMxmujhD7ilIISmFzmnNdbyEyv/Zpr+U0fnHqGODqw==", + "license": "Apache-2.0" + }, + "node_modules/@add-ideas/toolbox-shell-react": { + "version": "0.1.1", + "resolved": "https://git.add-ideas.de/api/packages/zemion/npm/%40add-ideas%2Ftoolbox-shell-react/-/0.1.1/toolbox-shell-react-0.1.1.tgz", + "integrity": "sha512-Di2mu1BoJrWJg4/v5jDExROXDpvLXorlUgqcsBQV8ea/LklKoBFTAfgTkk/23HZ2BzixyMIjrhl9kD507s+iPQ==", + "license": "Apache-2.0", + "dependencies": { + "@add-ideas/toolbox-contract": "0.1.1" + }, + "peerDependencies": { + "react": ">=18 <20", + "react-dom": ">=18 <20" + } + }, + "node_modules/@add-ideas/toolbox-testkit": { + "version": "0.1.1", + "resolved": "https://git.add-ideas.de/api/packages/zemion/npm/%40add-ideas%2Ftoolbox-testkit/-/0.1.1/toolbox-testkit-0.1.1.tgz", + "integrity": "sha512-o+H7LfM7ipdgmYz18S0lLuN6JsXUR+8ubAbcjTWTk8rSqNfp1JyeoRHOPbJVEHXrQwIFe/OB86UzRpex2gnKUQ==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@add-ideas/toolbox-contract": "0.1.1" + }, + "bin": { + "toolbox-check": "dist/cli.js" + } + }, "node_modules/@asamuzakjp/css-color": { "version": "5.1.11", "resolved": "https://registry.npmjs.org/@asamuzakjp/css-color/-/css-color-5.1.11.tgz", @@ -87,13 +123,13 @@ "license": "MIT" }, "node_modules/@babel/code-frame": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", - "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.7.tgz", + "integrity": "sha512-Aup7aUOfpbAUg2ROOJN6Iw5f9DMBlzu0mIkm/malLQFN/YQgO48wCj0Kxa3sEHJvPVFg7siR+qRInwXd2qhQKw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-validator-identifier": "^7.28.5", + "@babel/helper-validator-identifier": "^7.29.7", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" }, @@ -102,9 +138,9 @@ } }, "node_modules/@babel/compat-data": { - "version": "7.29.3", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.3.tgz", - "integrity": "sha512-LIVqM46zQWZhj17qA8wb4nW/ixr2y1Nw+r1etiAWgRM6U1IqP+LNhL1yg440jYZR72jCWcWbLWzIosH+uP1fqg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.7.tgz", + "integrity": "sha512-locTkQyKvwIEgBzVrn8693ebc97F2U8ZHjbXwDXJ5Fn2TCpNwTlKcaKLkdHop5c/icOFE7qt7Q9JC5hnKNa6Gg==", "dev": true, "license": "MIT", "engines": { @@ -112,21 +148,21 @@ } }, "node_modules/@babel/core": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", - "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.7.tgz", + "integrity": "sha512-RgHBCvtjbOK2gXSNBNIkNoEc9qoVEtau3hj8gEqKQuL3HZAibKarWFEI3Lfm6EYKkLalOh8eSrj9b+ch9H/VBA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", - "@babel/helper-compilation-targets": "^7.28.6", - "@babel/helper-module-transforms": "^7.28.6", - "@babel/helpers": "^7.28.6", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/traverse": "^7.29.0", - "@babel/types": "^7.29.0", + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-compilation-targets": "^7.29.7", + "@babel/helper-module-transforms": "^7.29.7", + "@babel/helpers": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7", "@jridgewell/remapping": "^2.3.5", "convert-source-map": "^2.0.0", "debug": "^4.1.0", @@ -143,14 +179,14 @@ } }, "node_modules/@babel/generator": { - "version": "7.29.1", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", - "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.7.tgz", + "integrity": "sha512-DkXD5OJQaAQIdZ1bt3UZdEnHAn9Imd3IVBdX03UFe+ony9Ojw5pzr9YVKGDY1jt+Gcn/FnGkNf8r+Vj5NOJWtQ==", "dev": true, "license": "MIT", "dependencies": { - "@babel/parser": "^7.29.0", - "@babel/types": "^7.29.0", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7", "@jridgewell/gen-mapping": "^0.3.12", "@jridgewell/trace-mapping": "^0.3.28", "jsesc": "^3.0.2" @@ -160,14 +196,14 @@ } }, "node_modules/@babel/helper-compilation-targets": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", - "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.29.7.tgz", + "integrity": "sha512-wem6WaBj4NaVYVdNhLPPVacES6ZJ+KBBfSkTMD3YZxbP3rm3Di85tJU5ljaUNhaOynt+Aj0xruhYuzQBt8n71g==", "dev": true, "license": "MIT", "dependencies": { - "@babel/compat-data": "^7.28.6", - "@babel/helper-validator-option": "^7.27.1", + "@babel/compat-data": "^7.29.7", + "@babel/helper-validator-option": "^7.29.7", "browserslist": "^4.24.0", "lru-cache": "^5.1.1", "semver": "^6.3.1" @@ -187,9 +223,9 @@ } }, "node_modules/@babel/helper-globals": { - "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", - "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.29.7.tgz", + "integrity": "sha512-3nQVUAtvkKH9zahfWgw96Jc/uFOmjACE1kQz82E2lqWmHBgjzbNlsC22nuQTfahmWeQtTq5nQ/4Nnd2A1wj4zA==", "dev": true, "license": "MIT", "engines": { @@ -197,29 +233,29 @@ } }, "node_modules/@babel/helper-module-imports": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.28.6.tgz", - "integrity": "sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.29.7.tgz", + "integrity": "sha512-ejHwrQQYcm9xnTivShn2IDOlIzInN34AXskvq9QicvCtEzq1Vzclu/tKF8Jq1Cg8JG2GL6/EmjgsCT7lXepE3g==", "dev": true, "license": "MIT", "dependencies": { - "@babel/traverse": "^7.28.6", - "@babel/types": "^7.28.6" + "@babel/traverse": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/helper-module-transforms": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", - "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.29.7.tgz", + "integrity": "sha512-UPUVSyXbOh627KiCIGQSgwWzGeBKLkaJ9PJEdrngIwMSzxLR4jS4+f1f1jb7VzBbg8nFLaYotvVPFCTqdrmTAg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-module-imports": "^7.28.6", - "@babel/helper-validator-identifier": "^7.28.5", - "@babel/traverse": "^7.28.6" + "@babel/helper-module-imports": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7", + "@babel/traverse": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -229,9 +265,9 @@ } }, "node_modules/@babel/helper-string-parser": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz", - "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.29.7.tgz", + "integrity": "sha512-Pb5ijPrZ89GDH8223L4UP8i6QApWxs04RbPQJTeWDV0/keR2E36MeKnyr6LYmUUvqRRI+Iv87SuF1W6ErINzYw==", "dev": true, "license": "MIT", "engines": { @@ -239,9 +275,9 @@ } }, "node_modules/@babel/helper-validator-identifier": { - "version": "7.28.5", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", - "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.29.7.tgz", + "integrity": "sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==", "dev": true, "license": "MIT", "engines": { @@ -249,9 +285,9 @@ } }, "node_modules/@babel/helper-validator-option": { - "version": "7.27.1", - "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.27.1.tgz", - "integrity": "sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.29.7.tgz", + "integrity": "sha512-N9ZErrD+yW5geCDtBqnOoxmR8+tNKiGuxKlDpuJxfsqpa2dFcexaziGAE/qoHLiDDreVNMupxGmSoNlyvsA3gw==", "dev": true, "license": "MIT", "engines": { @@ -259,27 +295,27 @@ } }, "node_modules/@babel/helpers": { - "version": "7.29.2", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", - "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.7.tgz", + "integrity": "sha512-1k2lAGRMfHTcwuNYcCNUmaUffmQv8KWMfh2iJUUeRlwlwH4FdNG7mfPI10NPfLHJFThE4Tyr4mv7kTNZOiPuBg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/template": "^7.28.6", - "@babel/types": "^7.29.0" + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/parser": { - "version": "7.29.3", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.3.tgz", - "integrity": "sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.7.tgz", + "integrity": "sha512-hnORnjP/1P/zFEndoeX+n+t1RwWRJiJpM/jO7FW32Kn9r5+sJB2JWOdYo4L6k78j15eCwY3Gm/7364B1EMwtNg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/types": "^7.29.0" + "@babel/types": "^7.29.7" }, "bin": { "parser": "bin/babel-parser.js" @@ -299,33 +335,33 @@ } }, "node_modules/@babel/template": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", - "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.29.7.tgz", + "integrity": "sha512-puq+Gf35oI24FeN11LkoUQFqv9uwNeWpxXZi/Ji3rRIoKAzKnxRaZ+Gkj0vKS9ZCiTESfng1N9LyOyXvo+m+Gg==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.28.6", - "@babel/parser": "^7.28.6", - "@babel/types": "^7.28.6" + "@babel/code-frame": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/types": "^7.29.7" }, "engines": { "node": ">=6.9.0" } }, "node_modules/@babel/traverse": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", - "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.7.tgz", + "integrity": "sha512-EhlfNQtZ+NK22w5BM61ciuiq1m58ed33Wr1Xan//ZRTy6hgjnwyCffRYwzsGXdASJSUJ1guZILsErh1eQcl+zw==", "dev": true, "license": "MIT", "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", - "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/types": "^7.29.0", + "@babel/code-frame": "^7.29.7", + "@babel/generator": "^7.29.7", + "@babel/helper-globals": "^7.29.7", + "@babel/parser": "^7.29.7", + "@babel/template": "^7.29.7", + "@babel/types": "^7.29.7", "debug": "^4.3.1" }, "engines": { @@ -333,14 +369,14 @@ } }, "node_modules/@babel/types": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", - "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "version": "7.29.7", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.7.tgz", + "integrity": "sha512-4zBIxpPzowiZpusoFkyGVwakdRJUyuH5PxQ/PrqghfdFWWasvnCdPfQXHrenDai+gyLARulZjZowCOj6fjT4pA==", "dev": true, "license": "MIT", "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" + "@babel/helper-string-parser": "^7.29.7", + "@babel/helper-validator-identifier": "^7.29.7" }, "engines": { "node": ">=6.9.0" @@ -500,14 +536,14 @@ } }, "node_modules/@emnapi/core": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.10.0.tgz", - "integrity": "sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/core/-/core-1.11.1.tgz", + "integrity": "sha512-RSvbQmHzdKzNsLYa/wHrbc3KN4sYLKAdPZxqiM2HATqv/SBk2/ENSHpvXGaLOMcsAyz0poEGqkmmKYG3OWiJEQ==", "dev": true, "license": "MIT", "optional": true, "dependencies": { - "@emnapi/wasi-threads": "1.2.1", + "@emnapi/wasi-threads": "1.2.2", "tslib": "^2.4.0" } }, @@ -520,9 +556,9 @@ "optional": true }, "node_modules/@emnapi/runtime": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.10.0.tgz", - "integrity": "sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==", + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.11.1.tgz", + "integrity": "sha512-vgj7R3y3Wgx24IQaGPA/R6YFXLHVMOZ0uVEyIQPaWs+rd1AzfEMXlAC22FYwO1XkKR6NPsq7mUandH8oIRdZFw==", "dev": true, "license": "MIT", "optional": true, @@ -539,9 +575,9 @@ "optional": true }, "node_modules/@emnapi/wasi-threads": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.1.tgz", - "integrity": "sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==", + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@emnapi/wasi-threads/-/wasi-threads-1.2.2.tgz", + "integrity": "sha512-c95qOXkHdydNKhscBTebqEC1CVAZpyqOfVfBzQ1qgzyl3gfeldUjIggDbIZgDKsHLgnsM+igH7TJ/eAasaVuMA==", "dev": true, "license": "MIT", "optional": true, @@ -1070,14 +1106,14 @@ } }, "node_modules/@napi-rs/wasm-runtime": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.4.tgz", - "integrity": "sha512-3NQNNgA1YSlJb/kMH1ildASP9HW7/7kYnRI2szWJaofaS1hWmbGI4H+d3+22aGzXXN9IJ+n+GiFVcGipJP18ow==", + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/@napi-rs/wasm-runtime/-/wasm-runtime-1.1.6.tgz", + "integrity": "sha512-ZLv/JdUfkvOy9eCnnBaGfiO+XimbjebAeO+MRQqD/B+FR1tnRN0tpKSJHRbE8sFfS6aqsXZ67TQjfwfsxULVbg==", "dev": true, "license": "MIT", "optional": true, "dependencies": { - "@tybys/wasm-util": "^0.10.1" + "@tybys/wasm-util": "^0.10.3" }, "funding": { "type": "github", @@ -1089,9 +1125,9 @@ } }, "node_modules/@oxc-project/types": { - "version": "0.130.0", - "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.130.0.tgz", - "integrity": "sha512-ibD2usx9JRu7f5pu2tMKMI4cpA4NgXJQoYRP4pQ7Pxmn1l6k/53qWtQWZayhYy3X4QZkt90Ot+mJEaeXouio6Q==", + "version": "0.139.0", + "resolved": "https://registry.npmjs.org/@oxc-project/types/-/types-0.139.0.tgz", + "integrity": "sha512-r9gHphtCs+1M7J0pw6Sn/hh/Wpa/iQrOOkrNAlVLF/gHq+/CJmHIWKKUUhdWjcD6CIa8idarspCsASiXCXvFUw==", "dev": true, "license": "MIT", "funding": { @@ -1117,9 +1153,9 @@ } }, "node_modules/@rolldown/binding-android-arm64": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.0.1.tgz", - "integrity": "sha512-fJI3I0r3C3Oj/zdBCpaCmBRZYf07xpaq4yCfDDoSFm+beWNzbIl26puW8RraUdugoJw/95zerNOn6jasAhzSmg==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-android-arm64/-/binding-android-arm64-1.1.5.tgz", + "integrity": "sha512-lZg8fqIv2v7FF237bwMgzGZEJvGL79/s5knJ/i6FmsGF4XXlzccZ4jb+TrFIxtSSxFtIpdsgrPZeMk1I9AFcyQ==", "cpu": [ "arm64" ], @@ -1134,9 +1170,9 @@ } }, "node_modules/@rolldown/binding-darwin-arm64": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.0.1.tgz", - "integrity": "sha512-cKnAhWEsV7TPcA/5EAteDp6KcJZBQ2G+BqE7zayMMi7kMvwRsbv7WT9aOnn0WNl4SKEIf43vjS31iUPu80nzXg==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-arm64/-/binding-darwin-arm64-1.1.5.tgz", + "integrity": "sha512-51Bnx9pNiMRKSUNtBfySkNJ9vMU9Hh3I1ozDd6gyPPYzaXCfnptUcEZxXGYFn+ul2dtcMUiqGR1Yai2K10uoTw==", "cpu": [ "arm64" ], @@ -1151,9 +1187,9 @@ } }, "node_modules/@rolldown/binding-darwin-x64": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.0.1.tgz", - "integrity": "sha512-YKrVwQjIRBPo+5G/u03wGjbdy4q7pyzCe93DK9VJ7zkVmeg8LJ7GbgsiHWdR4xSoe4CAXRD7Bcjgbtr64bkXNg==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-darwin-x64/-/binding-darwin-x64-1.1.5.tgz", + "integrity": "sha512-Tm+gbfC0aHu1tBA/JvKQh32S0K6YgCHkiAF4/W6xX0K0RmNuc94VeK419dJoE65R5aRxmo+noZQSWrAMF6yb6g==", "cpu": [ "x64" ], @@ -1168,9 +1204,9 @@ } }, "node_modules/@rolldown/binding-freebsd-x64": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.0.1.tgz", - "integrity": "sha512-z/oBsREo46SsFqBwYtFe0kpJeBijAT48O/WXLI4suiCLBkr03RTtTJMCzSdDd2znlh8VJizL09XVkQgk8IZonw==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-freebsd-x64/-/binding-freebsd-x64-1.1.5.tgz", + "integrity": "sha512-JMzDKCCXq93YccG5gz3hvOs1oXRKAf0XYpfOS88e+wZrC8Iugj6j68867vrYZkvpDDpKn/KoKORThmchMpF6TA==", "cpu": [ "x64" ], @@ -1185,9 +1221,9 @@ } }, "node_modules/@rolldown/binding-linux-arm-gnueabihf": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.0.1.tgz", - "integrity": "sha512-ik8q7GM11zxvYxFc2PeDcT6TBvhCQMaUxfph/M5l9sKuTs/Sjg3L+Byw0F7w0ZVLBZmx30P+gG0ECzzN+MFcmQ==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm-gnueabihf/-/binding-linux-arm-gnueabihf-1.1.5.tgz", + "integrity": "sha512-uML21j2K5TfPGutKxub+M+nLjZIrWjXQ5Grx4lCe/nimTj9B4L63zHpjXLl4y0L3mcm2htEQIb06oCG/szerNw==", "cpu": [ "arm" ], @@ -1202,13 +1238,16 @@ } }, "node_modules/@rolldown/binding-linux-arm64-gnu": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.0.1.tgz", - "integrity": "sha512-QoSx2EkyrrdZ6kcyE8stqZ62t0Yra8Fs5ia9lOxJrh6TMQJK7gQKmscdTHf7pOXKREKrVwOtJcQG3qVSfc866A==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-gnu/-/binding-linux-arm64-gnu-1.1.5.tgz", + "integrity": "sha512-navSiuTMogvnQoZoM/v+l3ZWo50/NTwSHSzheABx/RCnmUPaKwq9qSo4Br2OYRs21+Fz8uFqITZM3H4opOB0/Q==", "cpu": [ "arm64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1219,13 +1258,16 @@ } }, "node_modules/@rolldown/binding-linux-arm64-musl": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.0.1.tgz", - "integrity": "sha512-uwNwFpwKeNiZawfAWBgg0VIztPTV3ihhh1vV334h9ivnNLorxnQMU6Fz8wG1Zb4Qh9LC1/MkcyT3YlDXG3Rsgg==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-arm64-musl/-/binding-linux-arm64-musl-1.1.5.tgz", + "integrity": "sha512-lAryqH7IteztmCXQXk0etKj4wBQ7Gx5S6LjKhsgp9zb8I5bsuvU/2llH1hDQcjsFeqIsovMVN339/8pUDDBXxA==", "cpu": [ "arm64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -1236,13 +1278,16 @@ } }, "node_modules/@rolldown/binding-linux-ppc64-gnu": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.0.1.tgz", - "integrity": "sha512-zY1bul7OWr7DFBiJ++wofXvnr8B45ce3QsQUhKrIhXsygAh7bTkwyeM1bi1a2g5C/yC/N8TZyGDEoMfm/l9mpg==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-ppc64-gnu/-/binding-linux-ppc64-gnu-1.1.5.tgz", + "integrity": "sha512-fsK/sNBnxzBlL4O1JNrZakVQxPspqpED5dLtNsZS9oOKmtSpdNIzxH2kkol5HYTWJN47sE20ztMJPxfZ89qGOg==", "cpu": [ "ppc64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1253,13 +1298,16 @@ } }, "node_modules/@rolldown/binding-linux-s390x-gnu": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.0.1.tgz", - "integrity": "sha512-0frlsT/f4Ft6I7SMESTKnF3cZsdicQn1dCMkF/jT9wDLE+gGoiQfv1nmT9e+s7s/fekvvy6tZM2jHvI2tkbJDQ==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-s390x-gnu/-/binding-linux-s390x-gnu-1.1.5.tgz", + "integrity": "sha512-gLYb4BIadlfTOYT5gO503n8zQjXflgzpD0FcyKh0Mzx3rqCZKnHoJWV9xe1KXUJ5lx2JfcSHr/mhzS0PC/McAA==", "cpu": [ "s390x" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1270,13 +1318,16 @@ } }, "node_modules/@rolldown/binding-linux-x64-gnu": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.0.1.tgz", - "integrity": "sha512-XABVmGp9Tg0WspTVvwduTc4fpqy6JnAUrSQe6OuyqD/03nI7r0O9OWUkMIwFrjKAIqolvqoA4ZrJppgwE0Gxmw==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-gnu/-/binding-linux-x64-gnu-1.1.5.tgz", + "integrity": "sha512-FjcpEKUyJygHgs1o50VYNvkt5+7Le/VEdYt0AkRpkL33MnyQfwr8l5mXwMmfmTbyMPr5vJLC+8/Gd9gXnwU1QQ==", "cpu": [ "x64" ], "dev": true, + "libc": [ + "glibc" + ], "license": "MIT", "optional": true, "os": [ @@ -1287,13 +1338,16 @@ } }, "node_modules/@rolldown/binding-linux-x64-musl": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.0.1.tgz", - "integrity": "sha512-bV4fzswuzVcKD90o/VM6QqKxnxlDq0g2BISDLNVmxrnhpv1DDbyPhCIjYfvzYLV+MvkKKnQt2Q6AO86SEBULUQ==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-linux-x64-musl/-/binding-linux-x64-musl-1.1.5.tgz", + "integrity": "sha512-Me+PfPI2TMeOQk0gYWfLQZtTktrmzbr8cDboqX83XKc7UrgAi55gF+2dUkWdxd19n55Essp2yeca+O9N5rBxHg==", "cpu": [ "x64" ], "dev": true, + "libc": [ + "musl" + ], "license": "MIT", "optional": true, "os": [ @@ -1304,9 +1358,9 @@ } }, "node_modules/@rolldown/binding-openharmony-arm64": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.0.1.tgz", - "integrity": "sha512-/Mh0Zhq3OP7fVs0kcQHZP6lZEthMGTaSf8UBQYSFEZDWGXXlEC+nJ6EqenaK2t4LBXMe3A+K/G2BVXXdtOr4PQ==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-openharmony-arm64/-/binding-openharmony-arm64-1.1.5.tgz", + "integrity": "sha512-yc5WrLzXks6zCQfn9Oxr8pORKyl/pF+QjHmW/Qx3qu0oyrrNC+y2JLTU1E2rcWYAmzlnqngWXHQjy51VzW70Vw==", "cpu": [ "arm64" ], @@ -1321,9 +1375,9 @@ } }, "node_modules/@rolldown/binding-wasm32-wasi": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.0.1.tgz", - "integrity": "sha512-+1xc9X45l8ufsBAm6Gjvx2qDRIY9lTVt0cgWNcJ+1gdhXvkbxePA60yRTwSTuXL09CMhyJmjpV7E3NoyxbqFQQ==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-wasm32-wasi/-/binding-wasm32-wasi-1.1.5.tgz", + "integrity": "sha512-VbQGPX2b4r48TAMIM2cjgluIM1HYutm4pcTEJsle7iEP7sB1dFqtPLBVbdLAZCxy1txCcPxf4QFf4v8uvltPqA==", "cpu": [ "wasm32" ], @@ -1331,18 +1385,18 @@ "license": "MIT", "optional": true, "dependencies": { - "@emnapi/core": "1.10.0", - "@emnapi/runtime": "1.10.0", - "@napi-rs/wasm-runtime": "^1.1.4" + "@emnapi/core": "1.11.1", + "@emnapi/runtime": "1.11.1", + "@napi-rs/wasm-runtime": "^1.1.6" }, "engines": { "node": "^20.19.0 || >=22.12.0" } }, "node_modules/@rolldown/binding-win32-arm64-msvc": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.0.1.tgz", - "integrity": "sha512-1D+UqZdfnuR+Jy1GgMJwi85bD40H21uNmOPRWQhw4oRSuolZ/B5rixZ45DK2KXOTCvmVCecauWgEhbw8bI7tOw==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-arm64-msvc/-/binding-win32-arm64-msvc-1.1.5.tgz", + "integrity": "sha512-gHv82k63z4qpV5+Q1y/12KrK0ltWBukVDI8nZcbT7Tt/ZlOIVwppazneq0F93oDxTo3IgAMEDIoQh3E2n6mVsw==", "cpu": [ "arm64" ], @@ -1357,9 +1411,9 @@ } }, "node_modules/@rolldown/binding-win32-x64-msvc": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.0.1.tgz", - "integrity": "sha512-INAycaWuhlOK3wk4mRHGsdgwYWmd9cChdPdE9bwWmy6rn9VqVNYNFGhOdXrofXUxwHIncSiPNb8tNm8knDVIeQ==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/@rolldown/binding-win32-x64-msvc/-/binding-win32-x64-msvc-1.1.5.tgz", + "integrity": "sha512-tTZuDBPw85tEN5PQi1pnEBzDy0Z49HtScLAbD5t6hyeU92A95pRWaSMw1GZZi/RwgSgUIl0xrSlXIT/9QzvYSA==", "cpu": [ "x64" ], @@ -1373,6 +1427,13 @@ "node": "^20.19.0 || >=22.12.0" } }, + "node_modules/@rolldown/pluginutils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", + "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", + "dev": true, + "license": "MIT" + }, "node_modules/@standard-schema/spec": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", @@ -1444,9 +1505,9 @@ } }, "node_modules/@tybys/wasm-util": { - "version": "0.10.2", - "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.2.tgz", - "integrity": "sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==", + "version": "0.10.3", + "resolved": "https://registry.npmjs.org/@tybys/wasm-util/-/wasm-util-0.10.3.tgz", + "integrity": "sha512-F3fo1MYrRJYL3zER0OUOmkutjr1Vp23m7OsSgp7nq4SP6OqX6C/56XFIPAl5bt3zaBRjmW7SGz3u/6LwFpYcOg==", "dev": true, "license": "MIT", "optional": true, @@ -1808,13 +1869,6 @@ } } }, - "node_modules/@vitejs/plugin-react/node_modules/@rolldown/pluginutils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", - "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", - "dev": true, - "license": "MIT" - }, "node_modules/@vitest/expect": { "version": "4.1.6", "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-4.1.6.tgz", @@ -1998,9 +2052,9 @@ } }, "node_modules/baseline-browser-mapping": { - "version": "2.10.30", - "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.30.tgz", - "integrity": "sha512-xjOFN16Ha1+Rz4nFYKqHU/LSB+gx/Vi3yQLX7r7sAW+Wa+8hhF2h4pvqTrTMc8+WcDBEunnUurr46Jvv0jk3Vg==", + "version": "2.10.43", + "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.43.tgz", + "integrity": "sha512-AjYpR78kDWAY3Efj+cDTFH9t9SCoL7OoTp1BOb0mQV7S+6CiLwnWM3FyxhJtdPufDFKzmCSFoUncKjWgJEZTCQ==", "dev": true, "license": "Apache-2.0", "bin": { @@ -2034,9 +2088,9 @@ } }, "node_modules/browserslist": { - "version": "4.28.2", - "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", - "integrity": "sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==", + "version": "4.28.6", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.6.tgz", + "integrity": "sha512-FQBYNK15VMslhLHpA7+n+n1GOlF1kId2xcCg7/j95f24AOF6VDYMNH4mFxF7KuaTdv627faazpOAjFzMrfJOUw==", "dev": true, "funding": [ { @@ -2054,10 +2108,10 @@ ], "license": "MIT", "dependencies": { - "baseline-browser-mapping": "^2.10.12", - "caniuse-lite": "^1.0.30001782", - "electron-to-chromium": "^1.5.328", - "node-releases": "^2.0.36", + "baseline-browser-mapping": "^2.10.42", + "caniuse-lite": "^1.0.30001803", + "electron-to-chromium": "^1.5.389", + "node-releases": "^2.0.51", "update-browserslist-db": "^1.2.3" }, "bin": { @@ -2068,9 +2122,9 @@ } }, "node_modules/caniuse-lite": { - "version": "1.0.30001792", - "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001792.tgz", - "integrity": "sha512-hVLMUZFgR4JJ6ACt1uEESvQN1/dBVqPAKY0hgrV70eN3391K6juAfTjKZLKvOMsx8PxA7gsY1/tLMMTcfFLLpw==", + "version": "1.0.30001806", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001806.tgz", + "integrity": "sha512-72Cuvd95zbSYPKq6Fhg8eDJRlzgWDf7/mtoZv6Qe/DYNCEBdNxoA3+rZAU2ZhGCpZlns3EssFavaZomckT5Uuw==", "dev": true, "funding": [ { @@ -2217,9 +2271,9 @@ "peer": true }, "node_modules/electron-to-chromium": { - "version": "1.5.357", - "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.357.tgz", - "integrity": "sha512-NHlTIQDK8fmVwHwuIzmXYEJ1Ewq3D9wDNc0cWXxDGysP6Pb21giwGNkxiTifyKy/4SoPuN5l6GLP1W9Sv7zB2g==", + "version": "1.5.393", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.5.393.tgz", + "integrity": "sha512-kiDJdIUawuEIcp9XoICKp1iTYDEbgguIPq526N1Q7jIQDeQ3CqoMx71025PI/7E48Ddtw2HuWsVjY7afEgNxmg==", "dev": true, "license": "ISC" }, @@ -2525,7 +2579,7 @@ }, "node_modules/fflate": { "version": "0.8.3", - "resolved": "https://packages.applied-caas-gateway1.internal.api.openai.org/artifactory/api/npm/npm-public/fflate/-/fflate-0.8.3.tgz", + "resolved": "https://registry.npmjs.org/fflate/-/fflate-0.8.3.tgz", "integrity": "sha512-tbZNuJrLwGUp3zshBtdy4W+ORxZuIh8a5ilyIEQDC5rY1f3U20JMry0Ll3WBzU58EZKsEuJFXhb5gwv8CsPvgA==", "license": "MIT" }, @@ -3176,9 +3230,9 @@ "license": "MIT" }, "node_modules/nanoid": { - "version": "3.3.11", - "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", - "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "version": "3.3.16", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.16.tgz", + "integrity": "sha512-bzlKTyNJ7+LdGIIwy8ijFpIqEQIvafahV7eYykJ8Cvh42EdJeODoJ6gUJXpQJvej1BddH8OqTXZNE/KfbWAu8Q==", "dev": true, "funding": [ { @@ -3202,11 +3256,14 @@ "license": "MIT" }, "node_modules/node-releases": { - "version": "2.0.44", - "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.44.tgz", - "integrity": "sha512-5WUyunoPMsvvEhS8AxHtRzP+oA8UCkJ7YRxatWKjngndhDGLiqEVAQKWjFAiAiuL8zMRGzGSJxFnLetoa43qGQ==", + "version": "2.0.51", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.51.tgz", + "integrity": "sha512-wRNIrw4DmVLKQlbgOMdkMx27Wrpzes2hh5Jtbi2bjPd+4wJstWIqP5A+lscnqbm0xxmT5Bpg8Lec5ItEBwx6BQ==", "dev": true, - "license": "MIT" + "license": "MIT", + "engines": { + "node": ">=18" + } }, "node_modules/obug": { "version": "2.1.1", @@ -3347,9 +3404,9 @@ "license": "ISC" }, "node_modules/picomatch": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.5.tgz", + "integrity": "sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==", "dev": true, "license": "MIT", "engines": { @@ -3360,9 +3417,9 @@ } }, "node_modules/postcss": { - "version": "8.5.14", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.14.tgz", - "integrity": "sha512-SoSL4+OSEtR99LHFZQiJLkT59C5B1amGO1NzTwj7TT1qCUgUO6hxOvzkOYxD+vMrXBM3XJIKzokoERdqQq/Zmg==", + "version": "8.5.20", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.20.tgz", + "integrity": "sha512-lW616l85ucIQL+FocMmL7pQFPqBmwejrCMg+iPxyImlrANNJG9NHq/RkyCZopDhd8C3LA03PHRJDjkbGu8vvug==", "dev": true, "funding": [ { @@ -3380,7 +3437,7 @@ ], "license": "MIT", "dependencies": { - "nanoid": "^3.3.11", + "nanoid": "^3.3.16", "picocolors": "^1.1.1", "source-map-js": "^1.2.1" }, @@ -3480,13 +3537,13 @@ } }, "node_modules/rolldown": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.0.1.tgz", - "integrity": "sha512-X0KQHljNnEkWNqqiz9zJrGunh1B0HgOxLXvnFpCOcadzcy5qohZ3tqMEUg00vncoRovXuK3ZqCT9KnnKzoInFQ==", + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/rolldown/-/rolldown-1.1.5.tgz", + "integrity": "sha512-t9z29cJjXf/vxQ8dyhCSpt6H6aSwHTk8cT5I3iy6SMXuFpk5mB6PL6XfC8PCwrPTx93udwKUm9HRteAlTGBLiA==", "dev": true, "license": "MIT", "dependencies": { - "@oxc-project/types": "=0.130.0", + "@oxc-project/types": "=0.139.0", "@rolldown/pluginutils": "^1.0.0" }, "bin": { @@ -3496,30 +3553,23 @@ "node": "^20.19.0 || >=22.12.0" }, "optionalDependencies": { - "@rolldown/binding-android-arm64": "1.0.1", - "@rolldown/binding-darwin-arm64": "1.0.1", - "@rolldown/binding-darwin-x64": "1.0.1", - "@rolldown/binding-freebsd-x64": "1.0.1", - "@rolldown/binding-linux-arm-gnueabihf": "1.0.1", - "@rolldown/binding-linux-arm64-gnu": "1.0.1", - "@rolldown/binding-linux-arm64-musl": "1.0.1", - "@rolldown/binding-linux-ppc64-gnu": "1.0.1", - "@rolldown/binding-linux-s390x-gnu": "1.0.1", - "@rolldown/binding-linux-x64-gnu": "1.0.1", - "@rolldown/binding-linux-x64-musl": "1.0.1", - "@rolldown/binding-openharmony-arm64": "1.0.1", - "@rolldown/binding-wasm32-wasi": "1.0.1", - "@rolldown/binding-win32-arm64-msvc": "1.0.1", - "@rolldown/binding-win32-x64-msvc": "1.0.1" + "@rolldown/binding-android-arm64": "1.1.5", + "@rolldown/binding-darwin-arm64": "1.1.5", + "@rolldown/binding-darwin-x64": "1.1.5", + "@rolldown/binding-freebsd-x64": "1.1.5", + "@rolldown/binding-linux-arm-gnueabihf": "1.1.5", + "@rolldown/binding-linux-arm64-gnu": "1.1.5", + "@rolldown/binding-linux-arm64-musl": "1.1.5", + "@rolldown/binding-linux-ppc64-gnu": "1.1.5", + "@rolldown/binding-linux-s390x-gnu": "1.1.5", + "@rolldown/binding-linux-x64-gnu": "1.1.5", + "@rolldown/binding-linux-x64-musl": "1.1.5", + "@rolldown/binding-openharmony-arm64": "1.1.5", + "@rolldown/binding-wasm32-wasi": "1.1.5", + "@rolldown/binding-win32-arm64-msvc": "1.1.5", + "@rolldown/binding-win32-x64-msvc": "1.1.5" } }, - "node_modules/rolldown/node_modules/@rolldown/pluginutils": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.1.tgz", - "integrity": "sha512-2j9bGt5Jh8hj+vPtgzPtl72j0yRxHAyumoo6TNfAjsLB04UtpSvPbPcDcBMxz7n+9CYB0c1GxQFxYRg2jimqGw==", - "dev": true, - "license": "MIT" - }, "node_modules/saxes": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/saxes/-/saxes-6.0.0.tgz", @@ -3628,9 +3678,9 @@ } }, "node_modules/tinyglobby": { - "version": "0.2.16", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", - "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "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": { @@ -3771,9 +3821,9 @@ } }, "node_modules/undici": { - "version": "7.25.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-7.25.0.tgz", - "integrity": "sha512-xXnp4kTyor2Zq+J1FfPI6Eq3ew5h6Vl0F/8d9XU5zZQf1tX9s2Su1/3PiMmUANFULpmksxkClamIZcaUqryHsQ==", + "version": "7.28.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-7.28.0.tgz", + "integrity": "sha512-cRZYrTDwWznlnRiPjggAGxZXanty6M8RV1ff8Wm4LWXBp7/IG8v5DnOm74DtUBp9OONpK75YlPnIjQqX0dBDtA==", "dev": true, "license": "MIT", "engines": { @@ -3829,17 +3879,17 @@ } }, "node_modules/vite": { - "version": "8.0.13", - "resolved": "https://registry.npmjs.org/vite/-/vite-8.0.13.tgz", - "integrity": "sha512-MFtjBYgzmSxmgA4RAfjIyXWpGe1oALnjgUTzzV7QLx/TKxCzjtMH6Fd9/eVK+5Fg1qNoz5VAwsmMs/NofrmJvw==", + "version": "8.1.5", + "resolved": "https://registry.npmjs.org/vite/-/vite-8.1.5.tgz", + "integrity": "sha512-7ULLwsCdYx/nRyrpiEwvqb5TFHrMVZyBt+rg/OAXT7rgj/z+DtTDyKFeLAdDkubDVDKD8jOsndmy7m55XcfUsw==", "dev": true, "license": "MIT", "dependencies": { "lightningcss": "^1.32.0", - "picomatch": "^4.0.4", - "postcss": "^8.5.14", - "rolldown": "1.0.1", - "tinyglobby": "^0.2.16" + "picomatch": "^4.0.5", + "postcss": "^8.5.17", + "rolldown": "~1.1.5", + "tinyglobby": "^0.2.17" }, "bin": { "vite": "bin/vite.js" @@ -3855,7 +3905,7 @@ }, "peerDependencies": { "@types/node": "^20.19.0 || >=22.12.0", - "@vitejs/devtools": "^0.1.18", + "@vitejs/devtools": "^0.3.0", "esbuild": "^0.27.0 || ^0.28.0", "jiti": ">=1.21.0", "less": "^4.0.0", diff --git a/package.json b/package.json index ad61cd6..d4ec7a2 100644 --- a/package.json +++ b/package.json @@ -2,9 +2,12 @@ "name": "pdf-tools", "version": "0.3.4", "private": true, + "license": "AGPL-3.0-only", "type": "module", "scripts": { + "predev": "npm run manifest:generate", "dev": "vite", + "prebuild": "npm run manifest:generate", "build": "vite build", "preview": "vite preview", "typecheck": "tsc --noEmit", @@ -12,10 +15,16 @@ "format": "prettier --write .", "format:check": "prettier --check .", "test": "vitest run --environment jsdom", + "test:toolbox": "toolbox-check dist", "test:watch": "vitest --environment jsdom", - "check": "npm run typecheck && npm run lint && npm run test && npm run build" + "manifest:generate": "node scripts/generate-toolbox-manifest.mjs", + "manifest:check": "node scripts/generate-toolbox-manifest.mjs --check", + "release:artifact": "npm run check && node scripts/package-release.mjs", + "check": "npm run manifest:check && npm run typecheck && npm run lint && npm run test && npm run build && npm run test:toolbox" }, "dependencies": { + "@add-ideas/toolbox-contract": "^0.1.0", + "@add-ideas/toolbox-shell-react": "^0.1.0", "fflate": "^0.8.3", "pdf-lib": "^1.17.1", "pdfjs-dist": "^5.7.284", @@ -23,6 +32,7 @@ "react-dom": "^19.2.6" }, "devDependencies": { + "@add-ideas/toolbox-testkit": "^0.1.0", "@eslint/js": "^10.0.1", "@testing-library/react": "^16.3.2", "@testing-library/user-event": "^14.6.1", diff --git a/public/site.webmanifest b/public/site.webmanifest index 3d0c1e1..ae15211 100644 --- a/public/site.webmanifest +++ b/public/site.webmanifest @@ -3,13 +3,13 @@ "short_name": "PDFTools", "icons": [ { - "src": "/web-app-manifest-192x192.png", + "src": "./web-app-manifest-192x192.png", "sizes": "192x192", "type": "image/png", "purpose": "maskable" }, { - "src": "/web-app-manifest-512x512.png", + "src": "./web-app-manifest-512x512.png", "sizes": "512x512", "type": "image/png", "purpose": "maskable" @@ -18,4 +18,4 @@ "theme_color": "#ffffff", "background_color": "#ffffff", "display": "standalone" -} \ No newline at end of file +} diff --git a/public/toolbox-app.json b/public/toolbox-app.json new file mode 100644 index 0000000..3c24e93 --- /dev/null +++ b/public/toolbox-app.json @@ -0,0 +1,39 @@ +{ + "$schema": "https://git.add-ideas.de/zemion/toolbox-sdk/raw/branch/main/schemas/toolbox-app.v1.schema.json", + "schemaVersion": 1, + "id": "de.add-ideas.pdf-tools", + "name": "PDF Workbench", + "description": "Page-level PDF operations performed locally in the browser.", + "entry": "./", + "icon": "./favicon.svg", + "categories": ["documents", "pdf"], + "tags": ["merge", "split", "rotate", "reorder"], + "actions": [ + { + "id": "source", + "label": "Source", + "url": "https://git.add-ideas.de/zemion/pdf-tools/src/tag/v0.3.4" + } + ], + "integration": { + "contextVersion": 1, + "launchModes": ["navigate", "new-tab"], + "embedding": "unsupported" + }, + "requirements": { + "secureContext": true, + "workers": true, + "indexedDb": true, + "crossOriginIsolated": false + }, + "privacy": { + "processing": "local", + "fileUploads": false, + "telemetry": false + }, + "source": { + "repository": "https://git.add-ideas.de/zemion/pdf-tools", + "license": "AGPL-3.0-only" + }, + "version": "0.3.4" +} diff --git a/scripts/generate-toolbox-manifest.mjs b/scripts/generate-toolbox-manifest.mjs new file mode 100644 index 0000000..77984b8 --- /dev/null +++ b/scripts/generate-toolbox-manifest.mjs @@ -0,0 +1,64 @@ +import { readFile, writeFile } from 'node:fs/promises'; +import path from 'node:path'; +import process from 'node:process'; +import { fileURLToPath } from 'node:url'; +import { parseToolboxApp } from '@add-ideas/toolbox-contract'; +import { format } from 'prettier'; + +const projectRoot = path.resolve( + path.dirname(fileURLToPath(import.meta.url)), + '..' +); +const packageJsonPath = path.join(projectRoot, 'package.json'); +const definitionPath = path.join( + projectRoot, + 'src', + 'toolboxApp.definition.json' +); +const versionSourcePath = path.join(projectRoot, 'src', 'version.ts'); +const manifestPath = path.join(projectRoot, 'public', 'toolbox-app.json'); + +const [packageJson, definition, versionSource] = await Promise.all([ + readJson(packageJsonPath), + readJson(definitionPath), + readFile(versionSourcePath, 'utf8'), +]); + +const sourceVersion = versionSource.match( + /APP_VERSION\s*=\s*['"]([^'"]+)['"]/ +)?.[1]; + +if (!sourceVersion) { + throw new Error('Could not read APP_VERSION from src/version.ts.'); +} + +if (sourceVersion !== packageJson.version) { + throw new Error( + `Version mismatch: package.json is ${packageJson.version}, while src/version.ts is ${sourceVersion}.` + ); +} + +const manifest = { + $schema: + 'https://git.add-ideas.de/zemion/toolbox-sdk/raw/branch/main/schemas/toolbox-app.v1.schema.json', + schemaVersion: 1, + ...definition, + version: packageJson.version, +}; +parseToolboxApp(manifest); +const output = await format(JSON.stringify(manifest), { parser: 'json' }); + +if (process.argv.includes('--check')) { + const existing = await readFile(manifestPath, 'utf8').catch(() => ''); + if (existing !== output) { + throw new Error( + 'public/toolbox-app.json is out of date. Run npm run manifest:generate.' + ); + } +} else { + await writeFile(manifestPath, output); +} + +async function readJson(filePath) { + return JSON.parse(await readFile(filePath, 'utf8')); +} diff --git a/scripts/package-release.mjs b/scripts/package-release.mjs new file mode 100644 index 0000000..a2155b9 --- /dev/null +++ b/scripts/package-release.mjs @@ -0,0 +1,100 @@ +import { createHash } from 'node:crypto'; +import { access, mkdir, readFile, writeFile } from 'node:fs/promises'; +import path from 'node:path'; +import { fileURLToPath } from 'node:url'; +import { + collectDirectoryFiles, + createDeterministicZip, + createThirdPartyLicenseBundle, +} from './release-archive.mjs'; + +const projectRoot = path.resolve( + path.dirname(fileURLToPath(import.meta.url)), + '..' +); +const packageJson = JSON.parse( + await readFile(path.join(projectRoot, 'package.json'), 'utf8') +); +const releaseDirectory = path.join(projectRoot, 'release'); +const archiveName = `pdf-tools-${packageJson.version}.zip`; +const archivePath = path.join(releaseDirectory, archiveName); +const checksumPath = path.join( + releaseDirectory, + `pdf-tools-${packageJson.version}.sha256` +); +const force = process.argv.slice(2).includes('--force'); +const unknownArguments = process.argv + .slice(2) + .filter((argument) => argument !== '--force'); +if (unknownArguments.length > 0) { + throw new Error(`Unknown release argument: ${unknownArguments.join(', ')}`); +} + +await mkdir(releaseDirectory, { recursive: true }); +if (!force) { + await assertDoesNotExist(archivePath); + await assertDoesNotExist(checksumPath); +} + +const files = await collectDirectoryFiles(path.join(projectRoot, 'dist')); + +files['CHANGELOG.md'] = await readFile(path.join(projectRoot, 'CHANGELOG.md')); +files['SOURCE.md'] = Buffer.from( + [ + '# Corresponding source', + '', + `The source code corresponding to PDF Tools ${packageJson.version} is available at:`, + '', + `https://git.add-ideas.de/zemion/pdf-tools/src/tag/v${packageJson.version}`, + '', + 'Third-party components remain available under the separate terms reproduced in LICENSES/.', + '', + ].join('\n'), + 'utf8' +); +files['LICENSES/pdf-tools-LICENSE.txt'] = await readFile( + path.join(projectRoot, 'LICENSE') +); +files['LICENSES/core-js-3.49.0-LICENSE.txt'] = await readFile( + path.join(projectRoot, 'licenses', 'core-js-3.49.0-LICENSE.txt') +); +files['LICENSES/THIRD-PARTY-LICENSES.txt'] = + await createThirdPartyLicenseBundle(projectRoot, [ + '@add-ideas/toolbox-contract', + '@add-ideas/toolbox-shell-react', + '@pdf-lib/standard-fonts', + '@pdf-lib/upng', + 'fflate', + 'pako', + 'pdf-lib', + 'pdfjs-dist', + 'react', + 'react-dom', + 'scheduler', + 'tslib', + ]); + +const archive = createDeterministicZip(files); +const checksum = createHash('sha256').update(archive).digest('hex'); + +const writeFlag = force ? 'w' : 'wx'; +await writeFile(archivePath, archive, { flag: writeFlag }); +await writeFile(checksumPath, `${checksum} ${archiveName}\n`, { + encoding: 'utf8', + flag: writeFlag, +}); + +console.log(`Created release/${archiveName}`); +console.log(`SHA-256 ${checksum}`); + +async function assertDoesNotExist(outputPath) { + try { + await access(outputPath); + } catch (error) { + if (error && typeof error === 'object' && error.code === 'ENOENT') return; + throw error; + } + throw new Error( + `${path.relative(projectRoot, outputPath)} already exists; pass --force to replace this exact version.` + ); +} diff --git a/scripts/release-archive.d.ts b/scripts/release-archive.d.ts new file mode 100644 index 0000000..7030c06 --- /dev/null +++ b/scripts/release-archive.d.ts @@ -0,0 +1,12 @@ +export function compareCodePoints(left: string, right: string): number; +export function createDeterministicZip( + files: Record +): Uint8Array; +export function collectDirectoryFiles( + directory: string, + prefix?: string +): Promise>; +export function createThirdPartyLicenseBundle( + projectRoot: string, + packageNames: string[] +): Promise; diff --git a/scripts/release-archive.mjs b/scripts/release-archive.mjs new file mode 100644 index 0000000..6389b34 --- /dev/null +++ b/scripts/release-archive.mjs @@ -0,0 +1,123 @@ +import { readFile, readdir } from 'node:fs/promises'; +import path from 'node:path'; +import { zipSync } from 'fflate'; + +const LICENSE_FILE = /^(?:licen[cs]e|copying|notice)(?:[._-].*)?$/iu; + +export function compareCodePoints(left, right) { + return left < right ? -1 : left > right ? 1 : 0; +} + +export function createDeterministicZip(files) { + const orderedFiles = Object.fromEntries( + Object.entries(files).sort(([left], [right]) => + compareCodePoints(left, right) + ) + ); + return zipSync(orderedFiles, { + level: 9, + // ZIP stores a wall-clock DOS timestamp. Constructing the epoch in local + // time keeps its encoded bytes stable in every process timezone. + mtime: new Date(1980, 0, 1, 0, 0, 0), + }); +} + +export async function collectDirectoryFiles(directory, prefix = '') { + const result = {}; + const entries = await readdir(directory, { withFileTypes: true }); + entries.sort((left, right) => compareCodePoints(left.name, right.name)); + + for (const entry of entries) { + const absolutePath = path.join(directory, entry.name); + const relativePath = path.posix.join(prefix, entry.name); + + if (entry.isDirectory()) { + Object.assign( + result, + await collectDirectoryFiles(absolutePath, relativePath) + ); + } else if (entry.isFile()) { + result[relativePath] = await readFile(absolutePath); + } else { + throw new Error( + `Unsupported filesystem entry in release input: ${relativePath}` + ); + } + } + + return result; +} + +export async function createThirdPartyLicenseBundle(projectRoot, packageNames) { + const sections = []; + + for (const packageName of [...new Set(packageNames)].sort( + compareCodePoints + )) { + const packageDirectory = path.join( + projectRoot, + 'node_modules', + ...packageName.split('/') + ); + const packageJson = JSON.parse( + await readFile(path.join(packageDirectory, 'package.json'), 'utf8') + ); + const licenseFiles = await collectLicenseFiles(packageDirectory); + if (licenseFiles.length === 0) { + throw new Error(`No license text found for ${packageName}.`); + } + + const contents = []; + for (const licenseFile of licenseFiles) { + const text = await readFile( + path.join(packageDirectory, ...licenseFile.split('/')), + 'utf8' + ); + contents.push( + `--- ${licenseFile} ---\n${text.replaceAll('\r\n', '\n').trim()}\n` + ); + } + sections.push( + [ + `================================================================================`, + `${packageJson.name ?? packageName}@${packageJson.version ?? 'unknown'}`, + `Declared license: ${String(packageJson.license ?? 'unspecified')}`, + `================================================================================`, + ...contents, + ].join('\n') + ); + } + + return Buffer.from( + [ + 'Third-party software license texts bundled with PDF Tools', + 'Generated deterministically from the installed production packages.', + '', + ...sections, + '', + ].join('\n'), + 'utf8' + ); +} + +async function collectLicenseFiles(directory, prefix = '') { + const matches = []; + const entries = await readdir(directory, { withFileTypes: true }); + entries.sort((left, right) => compareCodePoints(left.name, right.name)); + + for (const entry of entries) { + if (entry.name === 'node_modules') continue; + const relativePath = path.posix.join(prefix, entry.name); + const absolutePath = path.join(directory, entry.name); + if (entry.isDirectory()) { + matches.push(...(await collectLicenseFiles(absolutePath, relativePath))); + } else if (entry.isFile() && LICENSE_FILE.test(entry.name)) { + matches.push(relativePath); + } else if (!entry.isFile()) { + throw new Error( + `Unsupported filesystem entry in license input: ${relativePath}` + ); + } + } + return matches; +} diff --git a/src/App.tsx b/src/App.tsx index ef2e8ff..18fcb6b 100644 --- a/src/App.tsx +++ b/src/App.tsx @@ -1,5 +1,5 @@ import React, { useCallback, useEffect, useState } from 'react'; -import Layout from './components/Layout'; +import { AppShell } from '@add-ideas/toolbox-shell-react'; import FileLoader from './components/FileLoader'; import ReorderPanel from './components/ReorderPanel'; import ActionsPanel from './components/ActionsPanel'; @@ -43,6 +43,7 @@ import { createSplitResultsZip, createSplitZipFilename, } from './pdf/pdfZipService'; +import { toolboxApp } from './toolboxApp'; import { createSelectionPdfName, createSelectionWorkspaceName, @@ -1139,7 +1140,21 @@ const App: React.FC = () => { previewVisualIndex >= 0 && previewVisualIndex < pages.length - 1; return ( - setHelpOpen(true)}> + setHelpOpen(true)} + aria-haspopup="dialog" + title="Open help and keyboard shortcuts (F1 or ?)" + > + Help + + } + > {mergeQueueOpen && mergeQueueItems.length > 0 && ( @@ -1252,7 +1267,7 @@ const App: React.FC = () => { setHelpOpen(false)} /> - + ); }; diff --git a/src/components/Layout.tsx b/src/components/Layout.tsx deleted file mode 100644 index 885a55c..0000000 --- a/src/components/Layout.tsx +++ /dev/null @@ -1,47 +0,0 @@ -import React from 'react'; -import { APP_VERSION } from '../version'; - -interface LayoutProps { - children: React.ReactNode; - onOpenHelp?: () => void; -} - -const Layout: React.FC = ({ children, onOpenHelp }) => { - return ( -
-
-
-
- 📄 -
-

PDF Workbench

- All in your browser -
-
- -
- {onOpenHelp && ( - - )} - -
- v{APP_VERSION} -
-
-
-
- -
{children}
-
- ); -}; - -export default Layout; diff --git a/src/main.tsx b/src/main.tsx index 5610739..bd1d61e 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -1,6 +1,7 @@ import React from 'react'; import ReactDOM from 'react-dom/client'; import App from './App'; +import '@add-ideas/toolbox-shell-react/styles.css'; import './styles.css'; ReactDOM.createRoot(document.getElementById('root') as HTMLElement).render( diff --git a/src/styles.css b/src/styles.css index a0e694e..c92b7ae 100644 --- a/src/styles.css +++ b/src/styles.css @@ -124,45 +124,20 @@ button.secondary { margin-top: 0.5rem; } -.app-root { - min-height: 100vh; - background-color: #f3f4f6; +.pdf-toolbox-shell { + color-scheme: light; + --toolbox-background: #f3f4f6; + --toolbox-surface: #ffffff; + --toolbox-text: #111827; + --toolbox-muted: #4b5563; + --toolbox-border: #d1d5db; + --toolbox-accent: #2563eb; + --toolbox-accent-contrast: #ffffff; } -.app-header { - position: sticky; - top: 0; - z-index: 10; - background: #111827; - color: #e5e7eb; - padding: 0.6rem 1rem; - box-shadow: 0 1px 3px rgba(15, 23, 42, 0.4); -} - -.app-header-title { - display: flex; - align-items: center; - gap: 0.6rem; -} - -.app-header h1 { - font-size: 1rem; - margin: 0; -} - -.app-header small { - color: #9ca3af; - font-size: 0.8rem; -} - -.app-logo { - font-size: 1.4rem; -} - -.app-main { +.pdf-toolbox-shell .toolbox-shell__main { padding: 0.75rem; max-width: 900px; - margin: 0 auto; display: flex; flex-direction: column; gap: 0.75rem; @@ -184,36 +159,11 @@ button.secondary { border-top: 1px solid #e5e7eb; } -.app-header-content { - display: flex; - align-items: center; - justify-content: space-between; - gap: 1rem; -} - -.app-version { - flex: 0 0 auto; - border-radius: 999px; - background: #374151; - color: #d1d5db; - padding: 0.4rem 0.7rem; - font-size: 0.85rem; - font-weight: 500; - line-height: 1; - white-space: nowrap; -} - -.app-header-actions { - display: flex; - align-items: center; - gap: 0.5rem; -} - .app-help-button { - border: 1px solid #4b5563; + border: 1px solid var(--toolbox-border); border-radius: 999px; background: transparent; - color: #e5e7eb; + color: var(--toolbox-accent); padding: 0.35rem 0.65rem; font-size: 0.85rem; font-weight: 500; @@ -223,8 +173,9 @@ button.secondary { .app-help-button:hover, .app-help-button:focus-visible { - background: #374151; - outline: none; + background: color-mix(in srgb, var(--toolbox-accent) 10%, transparent); + outline: 2px solid var(--toolbox-accent); + outline-offset: 2px; } .help-dialog-backdrop { @@ -393,10 +344,6 @@ button.secondary { .help-concepts dl { grid-template-columns: 1fr; } - - .app-header-content { - align-items: flex-start; - } } .merge-queue-list { diff --git a/src/toolboxApp.definition.json b/src/toolboxApp.definition.json new file mode 100644 index 0000000..86e3e27 --- /dev/null +++ b/src/toolboxApp.definition.json @@ -0,0 +1,36 @@ +{ + "id": "de.add-ideas.pdf-tools", + "name": "PDF Workbench", + "description": "Page-level PDF operations performed locally in the browser.", + "entry": "./", + "icon": "./favicon.svg", + "categories": ["documents", "pdf"], + "tags": ["merge", "split", "rotate", "reorder"], + "actions": [ + { + "id": "source", + "label": "Source", + "url": "https://git.add-ideas.de/zemion/pdf-tools/src/tag/v0.3.4" + } + ], + "integration": { + "contextVersion": 1, + "launchModes": ["navigate", "new-tab"], + "embedding": "unsupported" + }, + "requirements": { + "secureContext": true, + "workers": true, + "indexedDb": true, + "crossOriginIsolated": false + }, + "privacy": { + "processing": "local", + "fileUploads": false, + "telemetry": false + }, + "source": { + "repository": "https://git.add-ideas.de/zemion/pdf-tools", + "license": "AGPL-3.0-only" + } +} diff --git a/src/toolboxApp.test.ts b/src/toolboxApp.test.ts new file mode 100644 index 0000000..67be793 --- /dev/null +++ b/src/toolboxApp.test.ts @@ -0,0 +1,10 @@ +import { describe, expect, it } from 'vitest'; +import { parseToolboxApp } from '@add-ideas/toolbox-contract'; +import generatedManifest from '../public/toolbox-app.json'; +import { toolboxApp } from './toolboxApp'; + +describe('toolbox app definition', () => { + it('matches the generated public manifest', () => { + expect(toolboxApp).toEqual(parseToolboxApp(generatedManifest)); + }); +}); diff --git a/src/toolboxApp.ts b/src/toolboxApp.ts new file mode 100644 index 0000000..05eab2d --- /dev/null +++ b/src/toolboxApp.ts @@ -0,0 +1,9 @@ +import { parseToolboxApp } from '@add-ideas/toolbox-contract'; +import definition from './toolboxApp.definition.json'; +import { APP_VERSION } from './version'; + +export const toolboxApp = parseToolboxApp({ + schemaVersion: 1, + ...definition, + version: APP_VERSION, +}); diff --git a/tests/releaseArchive.test.ts b/tests/releaseArchive.test.ts new file mode 100644 index 0000000..375389a --- /dev/null +++ b/tests/releaseArchive.test.ts @@ -0,0 +1,96 @@ +import { createHash } from 'node:crypto'; +import { spawnSync } from 'node:child_process'; +import { + mkdir, + mkdtemp, + readFile, + rm, + symlink, + writeFile, +} from 'node:fs/promises'; +import os from 'node:os'; +import path from 'node:path'; +import { describe, expect, it } from 'vitest'; +import { + compareCodePoints, + collectDirectoryFiles, + createThirdPartyLicenseBundle, +} from '../scripts/release-archive.mjs'; + +describe('release archive helpers', () => { + it('sorts archive paths by code point', () => { + expect(['z', 'a', 'ä'].sort(compareCodePoints)).toEqual(['a', 'z', 'ä']); + }); + + it('creates identical ZIP bytes in different process timezones', () => { + const hashes = ['UTC', 'Europe/Berlin', 'America/New_York'].map( + (timezone) => { + const script = ` + import { createHash } from 'node:crypto'; + import { createDeterministicZip } from './scripts/release-archive.mjs'; + const archive = createDeterministicZip({ + 'z.txt': Buffer.from('last'), + 'a.txt': Buffer.from('first'), + }); + process.stdout.write(createHash('sha256').update(archive).digest('hex')); + `; + const result = spawnSync( + process.execPath, + ['--input-type=module', '--eval', script], + { + cwd: process.cwd(), + encoding: 'utf8', + env: { ...process.env, TZ: timezone }, + } + ); + expect(result.status, result.stderr).toBe(0); + return result.stdout; + } + ); + expect(new Set(hashes)).toHaveLength(1); + }); + + it('collects installed runtime license and notice texts', async () => { + const bundle = await createThirdPartyLicenseBundle(process.cwd(), [ + 'react', + 'pdfjs-dist', + '@add-ideas/toolbox-contract', + ]); + const text = bundle.toString('utf8'); + expect(text).toContain('react@'); + expect(text).toContain('pdfjs-dist@'); + expect(text).toContain('@add-ideas/toolbox-contract@'); + expect(createHash('sha256').update(bundle).digest('hex')).toMatch( + /^[a-f0-9]{64}$/ + ); + }); + + it('rejects symbolic links in release input', async () => { + const temporaryDirectory = await mkdtemp( + path.join(os.tmpdir(), 'pdf-tools-release-') + ); + try { + const outsideFile = path.join(temporaryDirectory, 'outside.txt'); + const inputDirectory = path.join(temporaryDirectory, 'input'); + await writeFile(outsideFile, 'private'); + await mkdir(inputDirectory); + await symlink(outsideFile, path.join(inputDirectory, 'linked.txt')); + + await expect(collectDirectoryFiles(inputDirectory)).rejects.toThrow( + 'Unsupported filesystem entry in release input: linked.txt' + ); + } finally { + await rm(temporaryDirectory, { recursive: true, force: true }); + } + }); + + it('carries the embedded core-js license used by the PDF.js legacy build', async () => { + const license = await readFile( + 'licenses/core-js-3.49.0-LICENSE.txt', + 'utf8' + ); + expect(license).toContain('core-js 3.49.0'); + expect(license).toContain('CoreJS Company'); + expect(license).toContain('Permission is hereby granted'); + }); +}); diff --git a/vite.config.ts b/vite.config.ts index 5135cc3..5083886 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -2,9 +2,10 @@ import { defineConfig } from 'vite'; import react from '@vitejs/plugin-react'; export default defineConfig({ + base: './', plugins: [react()], server: { host: true, - allowedHosts: ['pdftools.add-ideas.de'], // ← ADD THIS + allowedHosts: ['pdftools.add-ideas.de'], }, });