diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md new file mode 100644 index 00000000..6ded402a --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -0,0 +1,47 @@ +--- +name: Bug report +about: Report a problem with Markdown Printer +title: '' +labels: bug +assignees: '' +--- + +## What happened? + + + +## Steps to reproduce + +1. +2. +3. + +## Page (if applicable) + + + +## Diagnostics + + + +```json +PASTE DIAGNOSTICS HERE +``` + +## Pro version (native host) logs — optional + + diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index 187c378a..9427f466 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -16,9 +16,9 @@ jobs: uses: actions/checkout@v6 - name: Setup pnpm - uses: pnpm/action-setup@v4 + uses: pnpm/action-setup@v6 with: - version: 10 + version: 11 - name: Setup Node.js uses: actions/setup-node@v6 @@ -89,8 +89,7 @@ jobs: --chrome-extension-id pfplfifdaaaalkefgnknfgoiabegcbmf \ --chrome-client-id "${{ vars.CHROME_CLIENT_ID }}" \ --chrome-client-secret "${{ secrets.CHROME_CLIENT_SECRET }}" \ - --chrome-refresh-token "${{ secrets.CHROME_REFRESH_TOKEN }}" \ - --chrome-skip-submit-review + --chrome-refresh-token "${{ secrets.CHROME_REFRESH_TOKEN }}" - name: Skip Chrome publish if: steps.chrome-check.outputs.needs_publish != 'true' diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index ee659148..e92223ee 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,16 +12,17 @@ jobs: strategy: matrix: - node-version: [18.x, 20.x] + # ESLint 10 needs Node >=20.19, >=22.13, or >=24. + node-version: [20.x, 22.x, 24.x] steps: - name: Checkout code uses: actions/checkout@v6 - name: Setup pnpm - uses: pnpm/action-setup@v4 + uses: pnpm/action-setup@v6 with: - version: 10 + version: 11 - name: Setup Node.js ${{ matrix.node-version }} uses: actions/setup-node@v6 @@ -46,7 +47,7 @@ jobs: - name: Upload coverage reports if: matrix.node-version == '20.x' - uses: codecov/codecov-action@v5 + uses: codecov/codecov-action@v6 with: files: ./coverage/lcov.info token: ${{ secrets.CODECOV_TOKEN }} diff --git a/.gitignore b/.gitignore index 308bc318..852c0075 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,7 @@ dist/ *.zip coverage/ .env + +# Generated by build.js — sources live in src/ +extension-chrome/ +extension-firefox/ diff --git a/.prettierignore b/.prettierignore index b7b699f4..34d4c9ee 100644 --- a/.prettierignore +++ b/.prettierignore @@ -3,4 +3,5 @@ dist/ pnpm-lock.yaml *.zip .DS_Store +src/turndown.js extension-*/turndown.js diff --git a/CLAUDE.md b/CLAUDE.md index a7ba0ad6..ae62f2c3 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -14,6 +14,27 @@ - Ensure all tests pass - Verify no linting errors +## Version Bump Checklist + +**ALWAYS do these before bumping version:** + +1. **Update description files** with changelog for new version: + - `description.md` (English) + - `description-fr.md` (French) + - `description-he.md` (Hebrew) + - `description-hi.md` (Hindi) + +2. **Bump version** in `package.json` only. + The Chrome and Firefox manifests are generated by `build.js` from + `package.json`, so no manual edits are needed (and `extension-chrome/` + and `extension-firefox/` are gitignored). + +3. **Run pre-commit checks**: format, test, lint + +4. **Commit and push to develop** + +5. **Merge to main** to trigger auto-tag and publish + ## Common Commands ```bash diff --git a/PUBLISHING.md b/PUBLISHING.md index 7b12c4d1..a4690616 100644 --- a/PUBLISHING.md +++ b/PUBLISHING.md @@ -78,13 +78,10 @@ The workflow will automatically run and publish to outdated stores. ### Update versions across all manifests ```bash -# Update version in package.json +# Update version in package.json (the only place — extension manifests +# are regenerated by build.js from package.json). npm version patch # or minor, or major -# Manually update the version in: -# - extension-chrome/manifest.json -# - extension-firefox/manifest.json - # Build and commit pnpm run build git add . @@ -108,22 +105,11 @@ Publication Summary ### Auto-Submit Behavior -The workflow is configured with `--skip-submit-review` for Chrome and Edge. This means: - -- **Uploads are automatic**: New versions are uploaded to the stores -- **Review submission is manual**: You must manually submit for review in each store's dashboard - -This prevents errors when a version is already pending review (stores reject new submissions during review). - -**To publish a new version:** - -1. Run the workflow (uploads to stores) -2. Visit each store's dashboard: - - [Chrome Web Store Developer Dashboard](https://chrome.google.com/webstore/devconsole) - - [Edge Partner Center](https://partner.microsoft.com/en-us/dashboard/microsoftedge/overview) -3. Click "Submit for review" manually +- **Chrome**: uploads are submitted for review automatically. +- **Edge**: uploads use `--edge-skip-submit-review`. Uploads are automatic, but you must click "Submit for review" in the [Edge Partner Center](https://partner.microsoft.com/en-us/dashboard/microsoftedge/overview). +- **Firefox**: automatically submits for review (cannot be skipped). -**Firefox** automatically submits for review (cannot be skipped). +**Note for Chrome:** if a previous version is still under review when the workflow runs, the publish step will fail with "Item already in review". Wait for the pending review to clear before tagging a new version. ## Troubleshooting @@ -133,7 +119,7 @@ Wait an hour after setting up the Chrome Web Store API before generating the ref ### Chrome/Edge: "Item already in review" -This shouldn't happen with `--skip-submit-review` enabled. If you see this error, a version is currently under review. Wait for review completion before uploading a new version. +A version is currently under review. Wait for review completion before tagging a new version. (Chrome auto-submits, so this can happen on rapid successive releases.) ### Firefox: "Submission failed" diff --git a/README.md b/README.md index 58d3778e..96692ebb 100644 --- a/README.md +++ b/README.md @@ -32,9 +32,11 @@ Perfect for documentation, articles, and note-taking. ### Manual Installation (For Development) +> **Note:** The `extension-chrome/` and `extension-firefox/` directories are generated by the build script. Run `pnpm install && pnpm run build` after cloning. + #### Chrome/Edge: -1. Clone or download this repository +1. Clone this repository, then run `pnpm install && pnpm run build` 2. Open Chrome/Edge and navigate to `chrome://extensions/` or `edge://extensions/` 3. Enable "Developer mode" in the top right 4. Click "Load unpacked" @@ -43,7 +45,7 @@ Perfect for documentation, articles, and note-taking. #### Firefox: -1. Clone or download this repository +1. Clone this repository, then run `pnpm install && pnpm run build` 2. Open Firefox and navigate to `about:debugging` 3. Click "This Firefox" in the left sidebar 4. Click "Load Temporary Add-on" @@ -130,22 +132,27 @@ Contributions welcome! Feel free to open issues or submit pull requests. ### Development -The extension uses a shared codebase for Chrome and Firefox: +The extension uses a single shared source tree for Chrome and Firefox: ``` -src/ - background.js # Source of truth - edit this file -extension-chrome/ - background.js # Copied from src/ during build -extension-firefox/ - background.js # Copied from src/ during build +src/ # Source of truth — edit files here + background.js + popup.html + popup.js + turndown.js + icon{16,48,128}.png +_locales/ # Shared translations +build.js # Generates extension-{chrome,firefox}/ from src/ + +extension-chrome/ # Generated (gitignored) +extension-firefox/ # Generated (gitignored) ``` **Workflow:** -1. Edit `src/background.js` -2. Run `pnpm run build` to copy to both extensions -3. Load unpacked extension from `extension-chrome/` or `extension-firefox/` +1. Edit files in `src/` +2. Run `pnpm run build` to (re)generate `extension-chrome/` and `extension-firefox/` +3. Load the unpacked extension from `extension-chrome/` or `extension-firefox/` 4. Run `pnpm run format && pnpm run test && pnpm run lint` before committing ## 🔗 Links diff --git a/SOURCE_BUILD.md b/SOURCE_BUILD.md index 852117de..537f6cfe 100644 --- a/SOURCE_BUILD.md +++ b/SOURCE_BUILD.md @@ -57,15 +57,16 @@ This extension uses the following open-source library: ## Source File Locations -All source files are in the repository root and `extension-firefox/` directory: +All canonical source files live in `src/`. The `extension-firefox/` directory is generated by `build.js` and is gitignored. - `/build.js` - Build automation script - `/package.json` - Version and build configuration -- `/extension-firefox/background.js` - Main extension logic -- `/extension-firefox/popup.html` - Popup UI -- `/extension-firefox/popup.js` - Popup script -- `/extension-firefox/turndown.js` - Third-party library (Turndown.js v7.2.0) -- `/extension-firefox/icon*.png` - Extension icons +- `/src/background.js` - Main extension logic +- `/src/popup.html` - Popup UI +- `/src/popup.js` - Popup script +- `/src/turndown.js` - Third-party library (Turndown.js v7.2.0) +- `/src/icon*.png` - Extension icons +- `/_locales/` - Translations (copied into the extension directories by the build) ## Notes diff --git a/_locales/en/messages.json b/_locales/en/messages.json index 559c2aea..e93dfaf7 100644 --- a/_locales/en/messages.json +++ b/_locales/en/messages.json @@ -14,5 +14,61 @@ "contextMenuTitle": { "message": "Save as Markdown", "description": "Context menu item title" + }, + "savingButton": { + "message": "Saving...", + "description": "Save button label while a save is in progress" + }, + "savedButton": { + "message": "Saved!", + "description": "Save button label after a successful save" + }, + "errorRetry": { + "message": "Error - Try again", + "description": "Save button label after a save failure" + }, + "ratingPromptTitle": { + "message": "Enjoying Markdown Printer?", + "description": "Initial rating prompt title" + }, + "ratingYes": { + "message": "👍 Yes", + "description": "Rating prompt positive response" + }, + "ratingNo": { + "message": "👎 Not really", + "description": "Rating prompt negative response" + }, + "ratingHappyTitle": { + "message": "Great! Mind leaving a quick review?", + "description": "Rating prompt follow-up after positive response" + }, + "ratingReviewBtn": { + "message": "Leave a review", + "description": "Button that opens the store review page" + }, + "ratingUnhappyTitle": { + "message": "Sorry! What went wrong?", + "description": "Rating prompt follow-up after negative response" + }, + "ratingReportBtn": { + "message": "Report an issue", + "description": "Button that opens the GitHub issue tracker" + }, + "ratingDismiss": { + "message": "Dismiss", + "description": "Accessible label for the rating prompt close button" + }, + "copyDiagnostics": { + "message": "Copy diagnostics", + "description": "Button that copies the diagnostic bundle to the clipboard" + }, + "diagnosticsCopied": { + "message": "Copied!", + "description": "Diagnostics button label after a successful copy" + }, + "diagnosticsCopyFailed": { + "message": "Copy failed", + "description": "Diagnostics button label after a failed copy" } } diff --git a/_locales/fr/messages.json b/_locales/fr/messages.json index 34372648..8714c3ca 100644 --- a/_locales/fr/messages.json +++ b/_locales/fr/messages.json @@ -14,5 +14,61 @@ "contextMenuTitle": { "message": "Enregistrer en Markdown", "description": "Context menu item title" + }, + "savingButton": { + "message": "Enregistrement...", + "description": "Save button label while a save is in progress" + }, + "savedButton": { + "message": "Enregistré !", + "description": "Save button label after a successful save" + }, + "errorRetry": { + "message": "Erreur - Réessayer", + "description": "Save button label after a save failure" + }, + "ratingPromptTitle": { + "message": "Vous aimez Markdown Printer ?", + "description": "Initial rating prompt title" + }, + "ratingYes": { + "message": "👍 Oui", + "description": "Rating prompt positive response" + }, + "ratingNo": { + "message": "👎 Pas vraiment", + "description": "Rating prompt negative response" + }, + "ratingHappyTitle": { + "message": "Super ! Pourriez-vous laisser un avis ?", + "description": "Rating prompt follow-up after positive response" + }, + "ratingReviewBtn": { + "message": "Laisser un avis", + "description": "Button that opens the store review page" + }, + "ratingUnhappyTitle": { + "message": "Désolé ! Qu'est-ce qui n'a pas fonctionné ?", + "description": "Rating prompt follow-up after negative response" + }, + "ratingReportBtn": { + "message": "Signaler un problème", + "description": "Button that opens the GitHub issue tracker" + }, + "ratingDismiss": { + "message": "Fermer", + "description": "Accessible label for the rating prompt close button" + }, + "copyDiagnostics": { + "message": "Copier les diagnostics", + "description": "Button that copies the diagnostic bundle to the clipboard" + }, + "diagnosticsCopied": { + "message": "Copié !", + "description": "Diagnostics button label after a successful copy" + }, + "diagnosticsCopyFailed": { + "message": "Échec de la copie", + "description": "Diagnostics button label after a failed copy" } } diff --git a/_locales/he/messages.json b/_locales/he/messages.json index 02634f20..9622a711 100644 --- a/_locales/he/messages.json +++ b/_locales/he/messages.json @@ -14,5 +14,61 @@ "contextMenuTitle": { "message": "שמור כ-Markdown", "description": "Context menu item title" + }, + "savingButton": { + "message": "שומר...", + "description": "Save button label while a save is in progress" + }, + "savedButton": { + "message": "נשמר!", + "description": "Save button label after a successful save" + }, + "errorRetry": { + "message": "שגיאה - נסה שוב", + "description": "Save button label after a save failure" + }, + "ratingPromptTitle": { + "message": "נהנים מ-Markdown Printer?", + "description": "Initial rating prompt title" + }, + "ratingYes": { + "message": "👍 כן", + "description": "Rating prompt positive response" + }, + "ratingNo": { + "message": "👎 לא ממש", + "description": "Rating prompt negative response" + }, + "ratingHappyTitle": { + "message": "מצוין! אפשר להשאיר ביקורת קצרה?", + "description": "Rating prompt follow-up after positive response" + }, + "ratingReviewBtn": { + "message": "כתבו ביקורת", + "description": "Button that opens the store review page" + }, + "ratingUnhappyTitle": { + "message": "מצטערים! מה השתבש?", + "description": "Rating prompt follow-up after negative response" + }, + "ratingReportBtn": { + "message": "דווחו על תקלה", + "description": "Button that opens the GitHub issue tracker" + }, + "ratingDismiss": { + "message": "סגירה", + "description": "Accessible label for the rating prompt close button" + }, + "copyDiagnostics": { + "message": "העתק נתוני אבחון", + "description": "Button that copies the diagnostic bundle to the clipboard" + }, + "diagnosticsCopied": { + "message": "הועתק!", + "description": "Diagnostics button label after a successful copy" + }, + "diagnosticsCopyFailed": { + "message": "ההעתקה נכשלה", + "description": "Diagnostics button label after a failed copy" } } diff --git a/_locales/hi/messages.json b/_locales/hi/messages.json index 21a679fd..4254d274 100644 --- a/_locales/hi/messages.json +++ b/_locales/hi/messages.json @@ -14,5 +14,61 @@ "contextMenuTitle": { "message": "Markdown के रूप में सहेजें", "description": "Context menu item title" + }, + "savingButton": { + "message": "सहेजा जा रहा है...", + "description": "Save button label while a save is in progress" + }, + "savedButton": { + "message": "सहेजा गया!", + "description": "Save button label after a successful save" + }, + "errorRetry": { + "message": "त्रुटि - पुनः प्रयास करें", + "description": "Save button label after a save failure" + }, + "ratingPromptTitle": { + "message": "क्या आपको Markdown Printer पसंद आ रहा है?", + "description": "Initial rating prompt title" + }, + "ratingYes": { + "message": "👍 हाँ", + "description": "Rating prompt positive response" + }, + "ratingNo": { + "message": "👎 ज़्यादा नहीं", + "description": "Rating prompt negative response" + }, + "ratingHappyTitle": { + "message": "बढ़िया! क्या आप एक त्वरित समीक्षा देंगे?", + "description": "Rating prompt follow-up after positive response" + }, + "ratingReviewBtn": { + "message": "समीक्षा दें", + "description": "Button that opens the store review page" + }, + "ratingUnhappyTitle": { + "message": "क्षमा करें! क्या गलत हुआ?", + "description": "Rating prompt follow-up after negative response" + }, + "ratingReportBtn": { + "message": "समस्या रिपोर्ट करें", + "description": "Button that opens the GitHub issue tracker" + }, + "ratingDismiss": { + "message": "बंद करें", + "description": "Accessible label for the rating prompt close button" + }, + "copyDiagnostics": { + "message": "डायग्नोस्टिक्स कॉपी करें", + "description": "Button that copies the diagnostic bundle to the clipboard" + }, + "diagnosticsCopied": { + "message": "कॉपी हो गया!", + "description": "Diagnostics button label after a successful copy" + }, + "diagnosticsCopyFailed": { + "message": "कॉपी विफल", + "description": "Diagnostics button label after a failed copy" } } diff --git a/build.js b/build.js index 5c369f58..943bc008 100755 --- a/build.js +++ b/build.js @@ -16,7 +16,18 @@ const buildChrome = args.length === 0 || args.includes('chrome'); const buildFirefox = args.length === 0 || args.includes('firefox'); // Shared source files (copied from src/ to both extensions) -const sharedSourceFiles = ['background.js']; +const sharedSourceFiles = [ + 'background.js', + 'log-buffer.js', + 'logger.js', + 'rating-policy.js', + 'popup.html', + 'popup.js', + 'turndown.js', + 'icon16.png', + 'icon48.png', + 'icon128.png', +]; // Common directories to copy const commonDirs = ['_locales']; @@ -61,7 +72,11 @@ const firefoxManifest = { }, }, background: { - scripts: ['background.js'], + // Firefox MV3 runs background.scripts in an event-page context where + // importScripts() is unavailable, so logger and its dependency must be + // listed here. Order matters: log-buffer.js exposes helpers consumed + // by logger.js, which in turn exposes mdpLog used by background.js. + scripts: ['log-buffer.js', 'logger.js', 'background.js'], }, }; diff --git a/description-fr.md b/description-fr.md index f4ffa9fc..e6afcf90 100644 --- a/description-fr.md +++ b/description-fr.md @@ -55,6 +55,21 @@ Trouvé un bug ou une suggestion ? Signalez-le sur GitHub ou envoyez un email à ## Historique des versions +### v1.2.0 (2026-05-11) + +• Ajout d'un bouton « Copier les diagnostics » dans le popup — capture l'activité récente pour les rapports de bug sans partager de données de navigation +• Nouveau prompt de feedback optionnel dans le popup qui n'apparaît qu'après un usage prolongé +• Version Pro : rotation améliorée des journaux dans ~/.markdown-printer/logs/ pour faciliter le dépannage +• Suivi des erreurs en arrière-plan pour aider à diagnostiquer les échecs silencieux +• Localisation de toutes les nouvelles chaînes en anglais, français, hébreu et hindi + +### v1.1.1 (2025-11-24) + +• Amélioration de la capture de contenu pour les sites de documentation (docs API, tutoriels) +• Défilement 2x plus rapide pendant la capture de page +• Ajout d'une ligne d'attribution avec version et lien auteur +• Correction de l'overlay de progression apparaissant dans la sortie + ### v1.1.0 (2025-10-10) • Ajout de la prise en charge de l'internationalisation pour l'hébreu, l'hindi et le français diff --git a/description-he.md b/description-he.md index 182456bd..9ce7481e 100644 --- a/description-he.md +++ b/description-he.md @@ -55,6 +55,21 @@ Markdown הוא הפורמט האופטימלי להזנת הקשר לכלי AI: ## היסטוריית גרסאות +### v1.2.0 (2026-05-11) + +• נוסף כפתור "העתק נתוני אבחון" בחלון הקופץ — לוכד פעילות אחרונה לדיווחי באגים מבלי לשתף נתוני גלישה +• הוסף פרומפט משוב אופציונלי בחלון הקופץ שמופיע רק לאחר שימוש ממושך +• גרסת Pro: רוטציית יומנים משופרת תחת ~/.markdown-printer/logs/ לפתרון בעיות קל יותר +• מעקב שגיאות ברקע שעוזר לאבחן כשלים שקטים +• כל המחרוזות החדשות תורגמו לאנגלית, צרפתית, עברית והינדית + +### v1.1.1 (2025-11-24) + +• שיפור לכידת תוכן עבור אתרי תיעוד (מסמכי API, מדריכים) +• גלילה מהירה פי 2 במהלך לכידת דף +• נוספה שורת ייחוס עם גרסה וקישור למחבר +• תוקן overlay התקדמות שהופיע בפלט + ### v1.1.0 (2025-10-10) • נוספה תמיכה בינלאומית לעברית, הינדי וצרפתית diff --git a/description-hi.md b/description-hi.md index 86378ae4..dc55d2a7 100644 --- a/description-hi.md +++ b/description-hi.md @@ -55,6 +55,21 @@ GitHub पर स्रोत कोड देखें: https://github.com/levz ## संस्करण इतिहास +### v1.2.0 (2026-05-11) + +• पॉपअप में "डायग्नोस्टिक्स कॉपी करें" बटन जोड़ा गया — बग रिपोर्ट के लिए हाल की गतिविधि कैप्चर करता है, बिना कोई ब्राउज़िंग डेटा साझा किए +• विस्तारित उपयोग के बाद ही दिखने वाला नया वैकल्पिक इन-पॉपअप फ़ीडबैक प्रॉम्प्ट +• Pro संस्करण: आसान ट्रबलशूटिंग के लिए ~/.markdown-printer/logs/ के अंतर्गत बेहतर लॉग रोटेशन +• साइलेंट विफलताओं का निदान करने में मदद के लिए बैकग्राउंड एरर ट्रैकिंग +• सभी नई स्ट्रिंग्स अंग्रेज़ी, फ़्रेंच, हिब्रू और हिंदी में स्थानीयकृत + +### v1.1.1 (2025-11-24) + +• डॉक्यूमेंटेशन साइट्स (API डॉक्स, ट्यूटोरियल) के लिए कंटेंट कैप्चर में सुधार +• पेज कैप्चर के दौरान 2x तेज़ स्क्रॉलिंग +• संस्करण और लेखक लिंक के साथ एट्रिब्यूशन लाइन जोड़ी गई +• आउटपुट में दिखने वाले प्रोग्रेस ओवरले को ठीक किया गया + ### v1.1.0 (2025-10-10) • हिब्रू, हिंदी और फ्रेंच के लिए अंतर्राष्ट्रीयकरण समर्थन जोड़ा गया diff --git a/description.md b/description.md index 64b91094..2ce493f3 100644 --- a/description.md +++ b/description.md @@ -47,6 +47,21 @@ Found a bug or have a suggestion? Report it on GitHub or email hi@lev.engineer ## Version History +### v1.2.0 (2026-05-11) + +• Added "Copy diagnostics" button in the popup — captures recent activity for bug reports without sharing any browsing data +• New optional in-popup feedback prompt that only appears after extended use +• Pro version: improved log rotation under ~/.markdown-printer/logs/ for easier troubleshooting +• Background error tracking to help diagnose silent failures +• Localized all new strings in English, French, Hebrew, and Hindi + +### v1.1.1 (2025-11-24) + +• Improved content capture for documentation sites (API docs, tutorials) +• 2x faster scrolling during page capture +• Added attribution line with version and author link +• Fixed progress overlay appearing in output + ### v1.1.0 (2025-10-10) • Added internationalization support for Hebrew, Hindi, and French diff --git a/eslint.config.js b/eslint.config.js index 54847a78..bf6910e8 100644 --- a/eslint.config.js +++ b/eslint.config.js @@ -10,6 +10,9 @@ module.exports = [ '**/turndown.js', 'native-host/**', 'extension-pro/**', + // Generated by build.js — lint the canonical sources in src/ instead. + 'extension-chrome/**', + 'extension-firefox/**', ], }, { @@ -21,6 +24,9 @@ module.exports = [ // Browser globals window: 'readonly', document: 'readonly', + navigator: 'readonly', + self: 'readonly', + globalThis: 'readonly', console: 'readonly', URL: 'readonly', Blob: 'readonly', @@ -32,6 +38,13 @@ module.exports = [ chrome: 'readonly', browser: 'readonly', TurndownService: 'readonly', + importScripts: 'readonly', + // Markdown Printer modules (exposed on the global by their respective + // src/ files; in tests they're imported via CommonJS instead). + mdpLog: 'readonly', + mdpGetDiagnostics: 'readonly', + mdpLogBuffer: 'readonly', + mdpRatingPolicy: 'readonly', }, }, rules: { @@ -64,7 +77,17 @@ module.exports = [ }, }, { - files: ['build.js', 'utils.js', 'test/**/*.js', 'eslint.config.js', 'jest.config.js'], + // The UMD-lite wrappers in src/log-buffer.js and src/rating-policy.js + // reference `module` so they can also be required from Node tests. + files: [ + 'build.js', + 'utils.js', + 'test/**/*.js', + 'eslint.config.js', + 'jest.config.js', + 'src/log-buffer.js', + 'src/rating-policy.js', + ], languageOptions: { globals: { // Node.js globals diff --git a/extension-chrome/_locales/en/messages.json b/extension-chrome/_locales/en/messages.json deleted file mode 100644 index 559c2aea..00000000 --- a/extension-chrome/_locales/en/messages.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "extensionName": { - "message": "Markdown Printer", - "description": "Name of the extension" - }, - "extensionDescription": { - "message": "Save web pages as Markdown files to your Downloads folder. No setup required!", - "description": "Description of the extension" - }, - "savePageButton": { - "message": "Save Page as Markdown", - "description": "Button text in popup" - }, - "contextMenuTitle": { - "message": "Save as Markdown", - "description": "Context menu item title" - } -} diff --git a/extension-chrome/_locales/fr/messages.json b/extension-chrome/_locales/fr/messages.json deleted file mode 100644 index 34372648..00000000 --- a/extension-chrome/_locales/fr/messages.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "extensionName": { - "message": "Markdown Printer", - "description": "Name of the extension" - }, - "extensionDescription": { - "message": "Enregistrez des pages web en tant que fichiers Markdown dans votre dossier Téléchargements. Aucune configuration requise !", - "description": "Description of the extension" - }, - "savePageButton": { - "message": "Enregistrer la page en Markdown", - "description": "Button text in popup" - }, - "contextMenuTitle": { - "message": "Enregistrer en Markdown", - "description": "Context menu item title" - } -} diff --git a/extension-chrome/_locales/he/messages.json b/extension-chrome/_locales/he/messages.json deleted file mode 100644 index 02634f20..00000000 --- a/extension-chrome/_locales/he/messages.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "extensionName": { - "message": "Markdown Printer", - "description": "Name of the extension" - }, - "extensionDescription": { - "message": "שמור דפי אינטרנט כקבצי Markdown לתיקיית ההורדות. ללא הגדרות נדרשות!", - "description": "Description of the extension" - }, - "savePageButton": { - "message": "שמור דף כ-Markdown", - "description": "Button text in popup" - }, - "contextMenuTitle": { - "message": "שמור כ-Markdown", - "description": "Context menu item title" - } -} diff --git a/extension-chrome/_locales/hi/messages.json b/extension-chrome/_locales/hi/messages.json deleted file mode 100644 index 21a679fd..00000000 --- a/extension-chrome/_locales/hi/messages.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "extensionName": { - "message": "Markdown Printer", - "description": "Name of the extension" - }, - "extensionDescription": { - "message": "वेब पेजों को Markdown फ़ाइलों के रूप में अपने डाउनलोड फ़ोल्डर में सहेजें। किसी सेटअप की आवश्यकता नहीं!", - "description": "Description of the extension" - }, - "savePageButton": { - "message": "पेज को Markdown के रूप में सहेजें", - "description": "Button text in popup" - }, - "contextMenuTitle": { - "message": "Markdown के रूप में सहेजें", - "description": "Context menu item title" - } -} diff --git a/extension-chrome/background.js b/extension-chrome/background.js deleted file mode 100644 index faa854de..00000000 --- a/extension-chrome/background.js +++ /dev/null @@ -1,712 +0,0 @@ -// Cross-browser compatibility -const browserAPI = typeof browser !== 'undefined' ? browser : chrome; - -// Create context menu on installation -browserAPI.runtime.onInstalled.addListener(() => { - browserAPI.contextMenus.create({ - id: 'saveAsMarkdown', - title: browserAPI.i18n.getMessage('contextMenuTitle'), - contexts: ['page'], - }); -}); - -// Handle context menu click -browserAPI.contextMenus.onClicked.addListener((info, tab) => { - if (info.menuItemId === 'saveAsMarkdown') { - savePageAsMarkdown(tab.id); - } -}); - -// Handle messages from popup -browserAPI.runtime.onMessage.addListener((request, sender, sendResponse) => { - if (request.action === 'saveAsMarkdown') { - browserAPI.tabs.query({ active: true, currentWindow: true }, async tabs => { - if (tabs[0]) { - try { - await savePageAsMarkdown(tabs[0].id); - sendResponse({ success: true }); - } catch (error) { - sendResponse({ success: false, error: error.message }); - } - } - }); - return true; // Keep the message channel open for async response - } -}); - -async function savePageAsMarkdown(tabId) { - try { - // Inject Turndown library and conversion script - await browserAPI.scripting - .executeScript({ - target: { tabId: tabId }, - files: ['turndown.js'], - }) - .catch(error => { - // Better error message for protected pages - if ( - error.message.includes('cannot be scripted') || - error.message.includes('Cannot access') || - error.message.includes('extensions gallery') - ) { - throw new Error( - 'Cannot save this page - extensions are blocked on browser internal pages and extension stores' - ); - } - throw error; - }); - - // Inject script to convert and get markdown - const results = await browserAPI.scripting.executeScript({ - target: { tabId: tabId }, - func: extractAndConvertToMarkdown, - }); - - if (!results || !results[0]) { - throw new Error('Failed to extract page content'); - } - - const result = results[0].result; - - // If operation was cancelled, exit without showing save dialog - if (!result || result === null) { - return; - } - - const { markdown, title, url } = result; - - // Generate filename - const timestamp = new Date().toISOString().split('T')[0]; - const sanitizedTitle = sanitizeFilename(title || 'untitled'); - const filename = `${sanitizedTitle}-${timestamp}.md`; - - // Get extension version - const version = browserAPI.runtime.getManifest().version; - - // Add metadata header with attribution - const content = `# ${title}\n\n**Source:** ${url}\n**Saved:** ${new Date().toISOString()}\n\n*Generated with [markdown-printer](https://github.com/levz0r/markdown-printer) (v${version}) by [Lev Gelfenbuim](https://lev.engineer)*\n\n---\n\n${markdown}`; - - // For Firefox, we need to use a different approach - // Check if we're in Firefox by checking for browser.downloads - const isFirefox = typeof browser !== 'undefined' && browser.downloads; - - if (isFirefox) { - // Firefox: Use blob URL approach with special handling - // Create a temporary object URL in a way that works in Firefox background scripts - // We'll inject a helper script into the page to create the blob URL - await browserAPI.scripting.executeScript({ - target: { tabId: tabId }, - func: (content, filename) => { - const blob = new Blob([content], { type: 'text/plain' }); - const url = URL.createObjectURL(blob); - // Trigger download from page context - const a = document.createElement('a'); - a.href = url; - a.download = filename; - a.click(); - URL.revokeObjectURL(url); - return true; - }, - args: [content, filename], - }); - } else { - // Chrome: Use data URL - const blob = new Blob([content], { type: 'text/markdown;charset=utf-8' }); - const reader = new FileReader(); - - const dataUrl = await new Promise((resolve, reject) => { - reader.onload = () => resolve(reader.result); - reader.onerror = reject; - reader.readAsDataURL(blob); - }); - - await browserAPI.downloads.download({ - url: dataUrl, - filename: filename, - saveAs: true, - }); - } - } catch (error) { - console.error('Error saving markdown:', error); - throw error; - } -} - -// This function runs in the page context -async function extractAndConvertToMarkdown() { - // Normalize HTML content for consistent hashing - // Removes attributes, IDs, classes, and normalizes whitespace - function normalizeContent(element) { - const clone = element.cloneNode(true); - - // Remove all unwanted elements - const unwantedSelectors = [ - 'script', - 'style', - 'noscript', - 'iframe', - 'svg', - 'nav', - 'header', - 'footer', - '.sidebar', - '.navigation', - '.menu', - '[class*="sidebar"]', - '[class*="navigation"]', - 'button', - 'input', - 'select', - 'textarea', - ]; - - unwantedSelectors.forEach(selector => { - const elements = clone.querySelectorAll(selector); - elements.forEach(el => el.remove()); - }); - - // Get text content and normalize whitespace - const text = clone.textContent || ''; - return text.replace(/\s+/g, ' ').trim(); - } - - // Function to check if element is in viewport - function isInViewport(element) { - const rect = element.getBoundingClientRect(); - const windowHeight = window.innerHeight || document.documentElement.clientHeight; - const windowWidth = window.innerWidth || document.documentElement.clientWidth; - - // Element is in viewport if any part of it is visible - return rect.top < windowHeight && rect.bottom > 0 && rect.left < windowWidth && rect.right > 0; - } - - // Calculate Jaccard similarity between two strings - function calculateSimilarity(str1, str2) { - // Split into words and filter out very short words - const words1 = new Set( - str1 - .toLowerCase() - .split(/\s+/) - .filter(w => w.length > 2) - ); - const words2 = new Set( - str2 - .toLowerCase() - .split(/\s+/) - .filter(w => w.length > 2) - ); - - if (words1.size === 0 && words2.size === 0) { - return 1; - } - if (words1.size === 0 || words2.size === 0) { - return 0; - } - - const intersection = new Set([...words1].filter(x => words2.has(x))); - const union = new Set([...words1, ...words2]); - - return intersection.size / union.size; - } - - // Function to capture currently visible content blocks - function captureVisibleContentBlocks() { - const capturedBlocks = []; - - try { - // Find content blocks - prioritize semantic elements - const blockSelectors = [ - 'article', - 'section', - 'main > div', - '[role="main"] > div', - '.content > div', - '.documentation-content > div', - 'main > *', - '[role="main"] > *', - ]; - - const foundBlocks = new Set(); - - // Try ALL selectors and combine results (don't stop at first match) - for (const selector of blockSelectors) { - const elements = document.querySelectorAll(selector); - - if (elements.length > 0) { - elements.forEach(element => { - // Skip if already found this element - if (foundBlocks.has(element)) { - return; - } - - // Only capture if element is in viewport and has substantial content - if (isInViewport(element)) { - const text = element.textContent || ''; - if (text.trim().length > 100) { - foundBlocks.add(element); - - // Clone and convert to markdown - const cloned = element.cloneNode(true); - - // Remove unwanted elements from clone - const unwantedSelectors = [ - 'script', - 'style', - 'noscript', - 'iframe', - 'svg', - 'nav', - 'header', - 'footer', - 'button:not([role="tab"])', - 'input', - 'select', - 'textarea', - '#markdown-printer-overlay', - ]; - - unwantedSelectors.forEach(sel => { - const elements = cloned.querySelectorAll(sel); - elements.forEach(el => el.remove()); - }); - - const tempTurndown = new TurndownService({ - headingStyle: 'atx', - codeBlockStyle: 'fenced', - bulletListMarker: '-', - }); - tempTurndown.remove(['script', 'style', 'noscript', 'iframe', 'svg']); - - const markdown = tempTurndown.turndown(cloned); - - if (markdown && markdown.trim().length > 100) { - capturedBlocks.push({ - markdown: markdown, - normalizedContent: normalizeContent(element), - }); - } - } - } - }); - } - } - - // Fallback: If no blocks found, try capturing the entire main content area - if (capturedBlocks.length === 0) { - const mainSelectors = ['main', '[role="main"]', 'article', '#content', '.content', 'body']; - - for (const selector of mainSelectors) { - const mainElement = document.querySelector(selector); - if (mainElement) { - const text = mainElement.textContent || ''; - if (text.trim().length > 100) { - const cloned = mainElement.cloneNode(true); - - // Remove unwanted elements - const unwantedSelectors = [ - 'script', - 'style', - 'noscript', - 'iframe', - 'svg', - 'nav', - 'header', - 'footer', - 'aside', - '.sidebar', - '.navigation', - '.menu', - 'button', - 'input', - 'select', - 'textarea', - '#markdown-printer-overlay', - ]; - - unwantedSelectors.forEach(sel => { - const elements = cloned.querySelectorAll(sel); - elements.forEach(el => el.remove()); - }); - - const tempTurndown = new TurndownService({ - headingStyle: 'atx', - codeBlockStyle: 'fenced', - bulletListMarker: '-', - }); - tempTurndown.remove(['script', 'style', 'noscript', 'iframe', 'svg']); - - const markdown = tempTurndown.turndown(cloned); - - if (markdown && markdown.trim().length > 100) { - capturedBlocks.push({ - markdown: markdown, - normalizedContent: normalizeContent(mainElement), - }); - break; // Found content, stop trying - } - } - } - } - } - - return capturedBlocks; - } catch (error) { - console.error('Error capturing content blocks:', error); - return []; - } - } - - // Function to scroll through the entire page to trigger lazy loading - async function scrollToBottom() { - // Arrays to store captured content - const capturedContent = []; - const capturedHashes = new Set(); - - // First, try to expand any collapsed/hidden sections - const expandCollapsedSections = () => { - // Find and click on common expandable elements - const expandableSelectors = [ - 'details:not([open])', - '[aria-expanded="false"]', - '.collapsed', - '.expand', - '.accordion:not(.active)', - '[data-collapsed="true"]', - 'button[aria-expanded="false"]', - ]; - - let expandedCount = 0; - expandableSelectors.forEach(selector => { - const elements = document.querySelectorAll(selector); - elements.forEach(el => { - try { - if (el.tagName === 'DETAILS') { - el.open = true; - } else if (el.click) { - el.click(); - } - expandedCount++; - } catch (_e) { - // Ignore errors - } - }); - }); - return expandedCount; - }; - - // Try to expand sections before scrolling - expandCollapsedSections(); - await new Promise(resolve => setTimeout(resolve, 500)); - - // Create progress indicator overlay - const overlay = document.createElement('div'); - overlay.id = 'markdown-printer-overlay'; - overlay.style.cssText = ` - position: fixed; - top: 20px; - right: 20px; - background: rgba(0, 0, 0, 0.9); - color: white; - padding: 20px; - border-radius: 8px; - z-index: 999999; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); - min-width: 300px; - opacity: 0; - transition: opacity 0.3s ease-in-out; - `; - - const title = document.createElement('div'); - title.textContent = 'Printing...'; - title.style.cssText = 'font-weight: bold; margin-bottom: 10px; font-size: 16px;'; - - const percentageText = document.createElement('div'); - percentageText.id = 'percentage-text'; - percentageText.style.cssText = - 'font-size: 24px; font-weight: bold; margin-bottom: 5px; color: #4CAF50;'; - percentageText.textContent = '0%'; - - const status = document.createElement('div'); - status.id = 'scroll-status'; - status.style.cssText = 'margin-bottom: 10px; font-size: 14px; opacity: 0.8;'; - - const progressBar = document.createElement('div'); - progressBar.style.cssText = ` - width: 100%; - height: 4px; - background: rgba(255, 255, 255, 0.2); - border-radius: 2px; - overflow: hidden; - margin-bottom: 10px; - `; - - const progressFill = document.createElement('div'); - progressFill.id = 'progress-fill'; - progressFill.style.cssText = ` - height: 100%; - background: #4CAF50; - width: 0%; - transition: width 0.3s ease; - `; - progressBar.appendChild(progressFill); - - const cancelButton = document.createElement('button'); - cancelButton.textContent = 'Abort'; - cancelButton.style.cssText = ` - width: 100%; - padding: 8px; - background: #f44336; - color: white; - border: none; - border-radius: 4px; - cursor: pointer; - font-size: 14px; - font-weight: bold; - transition: background 0.2s ease; - `; - cancelButton.onmouseover = () => (cancelButton.style.background = '#d32f2f'); - cancelButton.onmouseout = () => (cancelButton.style.background = '#f44336'); - - overlay.appendChild(title); - overlay.appendChild(percentageText); - overlay.appendChild(status); - overlay.appendChild(progressBar); - overlay.appendChild(cancelButton); - document.body.appendChild(overlay); - - // Trigger fade-in animation - window.requestAnimationFrame(() => { - overlay.style.opacity = '1'; - }); - - let cancelled = false; - cancelButton.onclick = () => { - cancelled = true; - status.textContent = 'Stopping...'; - cancelButton.disabled = true; - cancelButton.style.opacity = '0.5'; - }; - - // Make all hidden content sections visible (common in documentation sites) - const makeAllContentVisible = () => { - // Target common documentation content containers - const contentSelectors = [ - 'article', - 'section', - 'main', - '[role="main"]', - '[class*="content"]', - '[class*="documentation"]', - '[class*="api"]', - '[class*="endpoint"]', - '[id*="content"]', - ]; - - contentSelectors.forEach(selector => { - const elements = document.querySelectorAll(selector); - elements.forEach(el => { - const style = window.getComputedStyle(el); - if (style.display === 'none' || style.visibility === 'hidden') { - el.style.display = 'block'; - el.style.visibility = 'visible'; - el.style.opacity = '1'; - } - // Also unhide all children - el.querySelectorAll('*').forEach(child => { - const childStyle = window.getComputedStyle(child); - if (childStyle.display === 'none' || childStyle.visibility === 'hidden') { - child.style.display = 'block'; - child.style.visibility = 'visible'; - child.style.opacity = '1'; - } - }); - }); - }); - }; - - // First, make content visible - status.textContent = 'Loading content...'; - makeAllContentVisible(); - await new Promise(resolve => setTimeout(resolve, 1000)); - - // Update progress display - const updateProgress = (current, total, stableCount, sectionsCount) => { - const percentage = Math.min(100, Math.round((current / total) * 100)); - progressFill.style.width = percentage + '%'; - percentageText.textContent = percentage + '%'; - status.textContent = `${current.toLocaleString()}px / ${total.toLocaleString()}px`; - if (sectionsCount > 0) { - status.textContent += ` | ${sectionsCount} sections`; - } - if (stableCount > 0) { - status.textContent += ` (${stableCount}/3 stable)`; - } - }; - - // Smooth scroll function that triggers events - const smoothScrollTo = async targetY => { - const startY = window.scrollY; - const distance = targetY - startY; - const duration = 150; // ms (reduced for faster scrolling) - const startTime = window.performance.now(); - - return new Promise(resolve => { - const scroll = currentTime => { - const elapsed = currentTime - startTime; - const progress = Math.min(elapsed / duration, 1); - const easeProgress = progress * (2 - progress); // ease out - - window.scrollTo(0, startY + distance * easeProgress); - - // Dispatch scroll event to trigger listeners - window.dispatchEvent(new window.Event('scroll')); - - if (progress < 1) { - window.requestAnimationFrame(scroll); - } else { - resolve(); - } - }; - window.requestAnimationFrame(scroll); - }); - }; - - // Start from the top - await smoothScrollTo(0); - await new Promise(resolve => setTimeout(resolve, 100)); - - let lastHeight = document.documentElement.scrollHeight; - let stableScrollCount = 0; - - // Scroll down in increments (2x viewport height for faster scrolling) - const scrollStep = window.innerHeight * 2; - let currentPosition = 0; - - while (!cancelled) { - // Smooth scroll to current position - await smoothScrollTo(currentPosition); - - // Wait for content to load (reduced for faster scrolling) - await new Promise(resolve => setTimeout(resolve, 200)); - - // Capture visible content blocks at current position - const blocks = captureVisibleContentBlocks(); - blocks.forEach(block => { - // Check if this content is similar to anything we've already captured - const isDuplicate = Array.from(capturedHashes).some(existingContent => { - const similarity = calculateSimilarity(block.normalizedContent, existingContent); - return similarity > 0.85; // 85% similar = duplicate - }); - - if (!isDuplicate) { - capturedHashes.add(block.normalizedContent); - capturedContent.push(block.markdown); - } - }); - - const newHeight = document.documentElement.scrollHeight; - updateProgress(currentPosition, newHeight, stableScrollCount, capturedContent.length); - - // If we've reached the bottom and height hasn't changed for 3 consecutive attempts - if (currentPosition >= newHeight) { - if (newHeight === lastHeight) { - stableScrollCount++; - if (stableScrollCount >= 3) { - percentageText.textContent = '100%'; - progressFill.style.width = '100%'; - status.textContent = `Complete! Captured ${capturedContent.length} sections`; - break; - } - } else { - stableScrollCount = 0; - } - } - - // Update tracking variables - lastHeight = newHeight; - currentPosition += scrollStep; - } - - // Ensure we scroll all the way to the bottom and wait for content to render - if (!cancelled) { - await smoothScrollTo(document.documentElement.scrollHeight); - await new Promise(resolve => setTimeout(resolve, 1000)); - - // Expand any sections that became available during scrolling - expandCollapsedSections(); - await new Promise(resolve => setTimeout(resolve, 500)); - - // Do a second full scroll pass - some content only loads after first pass - currentPosition = 0; - const secondPassHeight = document.documentElement.scrollHeight; - status.textContent = 'Second pass: loading remaining content...'; - - while (currentPosition < secondPassHeight && !cancelled) { - await smoothScrollTo(currentPosition); - await new Promise(resolve => setTimeout(resolve, 200)); - - // Capture any new content blocks in second pass - const blocks = captureVisibleContentBlocks(); - blocks.forEach(block => { - // Check if this content is similar to anything we've already captured - const isDuplicate = Array.from(capturedHashes).some(existingContent => { - const similarity = calculateSimilarity(block.normalizedContent, existingContent); - return similarity > 0.85; // 85% similar = duplicate - }); - - if (!isDuplicate) { - capturedHashes.add(block.normalizedContent); - capturedContent.push(block.markdown); - } - }); - - currentPosition += scrollStep; - } - - // Final stay at bottom to ensure everything loads - await smoothScrollTo(document.documentElement.scrollHeight); - await new Promise(resolve => setTimeout(resolve, 1000)); - - // One more expansion attempt - expandCollapsedSections(); - await new Promise(resolve => setTimeout(resolve, 500)); - } - - // Fade out and remove overlay - overlay.style.opacity = '0'; - await new Promise(resolve => setTimeout(resolve, 300)); - overlay.remove(); - - // Return status and captured content - return { cancelled, capturedContent }; - } - - // Scroll through the page first - const scrollResult = await scrollToBottom(); - - // If scrolling was cancelled, return null to indicate no download should occur - if (scrollResult.cancelled) { - return null; - } - - // Combine all captured content sections - const { capturedContent } = scrollResult; - - // Join all sections with double newlines for spacing - const combinedMarkdown = capturedContent.join('\n\n---\n\n'); - - return { - markdown: combinedMarkdown, - title: document.title, - url: window.location.href, - }; -} - -function sanitizeFilename(filename) { - return filename - .replace(/[<>:"/\\|?*]/g, '-') - .replace(/\s+/g, '-') - .replace(/-+/g, '-') - .substring(0, 200); -} diff --git a/extension-chrome/manifest.json b/extension-chrome/manifest.json deleted file mode 100644 index 103a5a0d..00000000 --- a/extension-chrome/manifest.json +++ /dev/null @@ -1,26 +0,0 @@ -{ - "manifest_version": 3, - "name": "__MSG_extensionName__", - "version": "1.1.1", - "description": "__MSG_extensionDescription__", - "default_locale": "en", - "author": "Lev Gelfenbuim", - "homepage_url": "https://github.com/levz0r/markdown-printer", - "permissions": ["activeTab", "contextMenus", "downloads", "scripting"], - "background": { - "service_worker": "background.js" - }, - "icons": { - "16": "icon16.png", - "48": "icon48.png", - "128": "icon128.png" - }, - "action": { - "default_popup": "popup.html", - "default_icon": { - "16": "icon16.png", - "48": "icon48.png", - "128": "icon128.png" - } - } -} diff --git a/extension-chrome/popup.html b/extension-chrome/popup.html deleted file mode 100644 index 7514bc89..00000000 --- a/extension-chrome/popup.html +++ /dev/null @@ -1,93 +0,0 @@ - - - - - - - -

- - - -

- - - - - diff --git a/extension-chrome/popup.js b/extension-chrome/popup.js deleted file mode 100644 index 512fcec9..00000000 --- a/extension-chrome/popup.js +++ /dev/null @@ -1,44 +0,0 @@ -// Cross-browser compatibility -const browserAPI = typeof browser !== 'undefined' ? browser : chrome; - -// Set localized text on load -document.addEventListener('DOMContentLoaded', () => { - // Set text direction for RTL languages - const uiLanguage = browserAPI.i18n.getUILanguage(); - const rtlLanguages = ['he', 'ar', 'fa', 'ur']; - const isRTL = rtlLanguages.some(lang => uiLanguage.startsWith(lang)); - - if (isRTL) { - document.body.dir = 'rtl'; - } - - document.getElementById('extensionName').textContent = - browserAPI.i18n.getMessage('extensionName'); - document.getElementById('saveBtn').textContent = browserAPI.i18n.getMessage('savePageButton'); - - // Display version number - const manifest = browserAPI.runtime.getManifest(); - document.getElementById('version').textContent = `v${manifest.version}`; -}); - -document.getElementById('saveBtn').addEventListener('click', async () => { - const button = document.getElementById('saveBtn'); - const originalText = browserAPI.i18n.getMessage('savePageButton'); - button.disabled = true; - button.textContent = 'Saving...'; - - try { - await browserAPI.runtime.sendMessage({ action: 'saveAsMarkdown' }); - button.textContent = 'Saved!'; - setTimeout(() => { - button.textContent = originalText; - button.disabled = false; - }, 1500); - } catch (_error) { - button.textContent = 'Error - Try again'; - button.disabled = false; - setTimeout(() => { - button.textContent = originalText; - }, 2000); - } -}); diff --git a/extension-firefox/_locales/en/messages.json b/extension-firefox/_locales/en/messages.json deleted file mode 100644 index 559c2aea..00000000 --- a/extension-firefox/_locales/en/messages.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "extensionName": { - "message": "Markdown Printer", - "description": "Name of the extension" - }, - "extensionDescription": { - "message": "Save web pages as Markdown files to your Downloads folder. No setup required!", - "description": "Description of the extension" - }, - "savePageButton": { - "message": "Save Page as Markdown", - "description": "Button text in popup" - }, - "contextMenuTitle": { - "message": "Save as Markdown", - "description": "Context menu item title" - } -} diff --git a/extension-firefox/_locales/fr/messages.json b/extension-firefox/_locales/fr/messages.json deleted file mode 100644 index 34372648..00000000 --- a/extension-firefox/_locales/fr/messages.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "extensionName": { - "message": "Markdown Printer", - "description": "Name of the extension" - }, - "extensionDescription": { - "message": "Enregistrez des pages web en tant que fichiers Markdown dans votre dossier Téléchargements. Aucune configuration requise !", - "description": "Description of the extension" - }, - "savePageButton": { - "message": "Enregistrer la page en Markdown", - "description": "Button text in popup" - }, - "contextMenuTitle": { - "message": "Enregistrer en Markdown", - "description": "Context menu item title" - } -} diff --git a/extension-firefox/_locales/he/messages.json b/extension-firefox/_locales/he/messages.json deleted file mode 100644 index 02634f20..00000000 --- a/extension-firefox/_locales/he/messages.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "extensionName": { - "message": "Markdown Printer", - "description": "Name of the extension" - }, - "extensionDescription": { - "message": "שמור דפי אינטרנט כקבצי Markdown לתיקיית ההורדות. ללא הגדרות נדרשות!", - "description": "Description of the extension" - }, - "savePageButton": { - "message": "שמור דף כ-Markdown", - "description": "Button text in popup" - }, - "contextMenuTitle": { - "message": "שמור כ-Markdown", - "description": "Context menu item title" - } -} diff --git a/extension-firefox/_locales/hi/messages.json b/extension-firefox/_locales/hi/messages.json deleted file mode 100644 index 21a679fd..00000000 --- a/extension-firefox/_locales/hi/messages.json +++ /dev/null @@ -1,18 +0,0 @@ -{ - "extensionName": { - "message": "Markdown Printer", - "description": "Name of the extension" - }, - "extensionDescription": { - "message": "वेब पेजों को Markdown फ़ाइलों के रूप में अपने डाउनलोड फ़ोल्डर में सहेजें। किसी सेटअप की आवश्यकता नहीं!", - "description": "Description of the extension" - }, - "savePageButton": { - "message": "पेज को Markdown के रूप में सहेजें", - "description": "Button text in popup" - }, - "contextMenuTitle": { - "message": "Markdown के रूप में सहेजें", - "description": "Context menu item title" - } -} diff --git a/extension-firefox/background.js b/extension-firefox/background.js deleted file mode 100644 index faa854de..00000000 --- a/extension-firefox/background.js +++ /dev/null @@ -1,712 +0,0 @@ -// Cross-browser compatibility -const browserAPI = typeof browser !== 'undefined' ? browser : chrome; - -// Create context menu on installation -browserAPI.runtime.onInstalled.addListener(() => { - browserAPI.contextMenus.create({ - id: 'saveAsMarkdown', - title: browserAPI.i18n.getMessage('contextMenuTitle'), - contexts: ['page'], - }); -}); - -// Handle context menu click -browserAPI.contextMenus.onClicked.addListener((info, tab) => { - if (info.menuItemId === 'saveAsMarkdown') { - savePageAsMarkdown(tab.id); - } -}); - -// Handle messages from popup -browserAPI.runtime.onMessage.addListener((request, sender, sendResponse) => { - if (request.action === 'saveAsMarkdown') { - browserAPI.tabs.query({ active: true, currentWindow: true }, async tabs => { - if (tabs[0]) { - try { - await savePageAsMarkdown(tabs[0].id); - sendResponse({ success: true }); - } catch (error) { - sendResponse({ success: false, error: error.message }); - } - } - }); - return true; // Keep the message channel open for async response - } -}); - -async function savePageAsMarkdown(tabId) { - try { - // Inject Turndown library and conversion script - await browserAPI.scripting - .executeScript({ - target: { tabId: tabId }, - files: ['turndown.js'], - }) - .catch(error => { - // Better error message for protected pages - if ( - error.message.includes('cannot be scripted') || - error.message.includes('Cannot access') || - error.message.includes('extensions gallery') - ) { - throw new Error( - 'Cannot save this page - extensions are blocked on browser internal pages and extension stores' - ); - } - throw error; - }); - - // Inject script to convert and get markdown - const results = await browserAPI.scripting.executeScript({ - target: { tabId: tabId }, - func: extractAndConvertToMarkdown, - }); - - if (!results || !results[0]) { - throw new Error('Failed to extract page content'); - } - - const result = results[0].result; - - // If operation was cancelled, exit without showing save dialog - if (!result || result === null) { - return; - } - - const { markdown, title, url } = result; - - // Generate filename - const timestamp = new Date().toISOString().split('T')[0]; - const sanitizedTitle = sanitizeFilename(title || 'untitled'); - const filename = `${sanitizedTitle}-${timestamp}.md`; - - // Get extension version - const version = browserAPI.runtime.getManifest().version; - - // Add metadata header with attribution - const content = `# ${title}\n\n**Source:** ${url}\n**Saved:** ${new Date().toISOString()}\n\n*Generated with [markdown-printer](https://github.com/levz0r/markdown-printer) (v${version}) by [Lev Gelfenbuim](https://lev.engineer)*\n\n---\n\n${markdown}`; - - // For Firefox, we need to use a different approach - // Check if we're in Firefox by checking for browser.downloads - const isFirefox = typeof browser !== 'undefined' && browser.downloads; - - if (isFirefox) { - // Firefox: Use blob URL approach with special handling - // Create a temporary object URL in a way that works in Firefox background scripts - // We'll inject a helper script into the page to create the blob URL - await browserAPI.scripting.executeScript({ - target: { tabId: tabId }, - func: (content, filename) => { - const blob = new Blob([content], { type: 'text/plain' }); - const url = URL.createObjectURL(blob); - // Trigger download from page context - const a = document.createElement('a'); - a.href = url; - a.download = filename; - a.click(); - URL.revokeObjectURL(url); - return true; - }, - args: [content, filename], - }); - } else { - // Chrome: Use data URL - const blob = new Blob([content], { type: 'text/markdown;charset=utf-8' }); - const reader = new FileReader(); - - const dataUrl = await new Promise((resolve, reject) => { - reader.onload = () => resolve(reader.result); - reader.onerror = reject; - reader.readAsDataURL(blob); - }); - - await browserAPI.downloads.download({ - url: dataUrl, - filename: filename, - saveAs: true, - }); - } - } catch (error) { - console.error('Error saving markdown:', error); - throw error; - } -} - -// This function runs in the page context -async function extractAndConvertToMarkdown() { - // Normalize HTML content for consistent hashing - // Removes attributes, IDs, classes, and normalizes whitespace - function normalizeContent(element) { - const clone = element.cloneNode(true); - - // Remove all unwanted elements - const unwantedSelectors = [ - 'script', - 'style', - 'noscript', - 'iframe', - 'svg', - 'nav', - 'header', - 'footer', - '.sidebar', - '.navigation', - '.menu', - '[class*="sidebar"]', - '[class*="navigation"]', - 'button', - 'input', - 'select', - 'textarea', - ]; - - unwantedSelectors.forEach(selector => { - const elements = clone.querySelectorAll(selector); - elements.forEach(el => el.remove()); - }); - - // Get text content and normalize whitespace - const text = clone.textContent || ''; - return text.replace(/\s+/g, ' ').trim(); - } - - // Function to check if element is in viewport - function isInViewport(element) { - const rect = element.getBoundingClientRect(); - const windowHeight = window.innerHeight || document.documentElement.clientHeight; - const windowWidth = window.innerWidth || document.documentElement.clientWidth; - - // Element is in viewport if any part of it is visible - return rect.top < windowHeight && rect.bottom > 0 && rect.left < windowWidth && rect.right > 0; - } - - // Calculate Jaccard similarity between two strings - function calculateSimilarity(str1, str2) { - // Split into words and filter out very short words - const words1 = new Set( - str1 - .toLowerCase() - .split(/\s+/) - .filter(w => w.length > 2) - ); - const words2 = new Set( - str2 - .toLowerCase() - .split(/\s+/) - .filter(w => w.length > 2) - ); - - if (words1.size === 0 && words2.size === 0) { - return 1; - } - if (words1.size === 0 || words2.size === 0) { - return 0; - } - - const intersection = new Set([...words1].filter(x => words2.has(x))); - const union = new Set([...words1, ...words2]); - - return intersection.size / union.size; - } - - // Function to capture currently visible content blocks - function captureVisibleContentBlocks() { - const capturedBlocks = []; - - try { - // Find content blocks - prioritize semantic elements - const blockSelectors = [ - 'article', - 'section', - 'main > div', - '[role="main"] > div', - '.content > div', - '.documentation-content > div', - 'main > *', - '[role="main"] > *', - ]; - - const foundBlocks = new Set(); - - // Try ALL selectors and combine results (don't stop at first match) - for (const selector of blockSelectors) { - const elements = document.querySelectorAll(selector); - - if (elements.length > 0) { - elements.forEach(element => { - // Skip if already found this element - if (foundBlocks.has(element)) { - return; - } - - // Only capture if element is in viewport and has substantial content - if (isInViewport(element)) { - const text = element.textContent || ''; - if (text.trim().length > 100) { - foundBlocks.add(element); - - // Clone and convert to markdown - const cloned = element.cloneNode(true); - - // Remove unwanted elements from clone - const unwantedSelectors = [ - 'script', - 'style', - 'noscript', - 'iframe', - 'svg', - 'nav', - 'header', - 'footer', - 'button:not([role="tab"])', - 'input', - 'select', - 'textarea', - '#markdown-printer-overlay', - ]; - - unwantedSelectors.forEach(sel => { - const elements = cloned.querySelectorAll(sel); - elements.forEach(el => el.remove()); - }); - - const tempTurndown = new TurndownService({ - headingStyle: 'atx', - codeBlockStyle: 'fenced', - bulletListMarker: '-', - }); - tempTurndown.remove(['script', 'style', 'noscript', 'iframe', 'svg']); - - const markdown = tempTurndown.turndown(cloned); - - if (markdown && markdown.trim().length > 100) { - capturedBlocks.push({ - markdown: markdown, - normalizedContent: normalizeContent(element), - }); - } - } - } - }); - } - } - - // Fallback: If no blocks found, try capturing the entire main content area - if (capturedBlocks.length === 0) { - const mainSelectors = ['main', '[role="main"]', 'article', '#content', '.content', 'body']; - - for (const selector of mainSelectors) { - const mainElement = document.querySelector(selector); - if (mainElement) { - const text = mainElement.textContent || ''; - if (text.trim().length > 100) { - const cloned = mainElement.cloneNode(true); - - // Remove unwanted elements - const unwantedSelectors = [ - 'script', - 'style', - 'noscript', - 'iframe', - 'svg', - 'nav', - 'header', - 'footer', - 'aside', - '.sidebar', - '.navigation', - '.menu', - 'button', - 'input', - 'select', - 'textarea', - '#markdown-printer-overlay', - ]; - - unwantedSelectors.forEach(sel => { - const elements = cloned.querySelectorAll(sel); - elements.forEach(el => el.remove()); - }); - - const tempTurndown = new TurndownService({ - headingStyle: 'atx', - codeBlockStyle: 'fenced', - bulletListMarker: '-', - }); - tempTurndown.remove(['script', 'style', 'noscript', 'iframe', 'svg']); - - const markdown = tempTurndown.turndown(cloned); - - if (markdown && markdown.trim().length > 100) { - capturedBlocks.push({ - markdown: markdown, - normalizedContent: normalizeContent(mainElement), - }); - break; // Found content, stop trying - } - } - } - } - } - - return capturedBlocks; - } catch (error) { - console.error('Error capturing content blocks:', error); - return []; - } - } - - // Function to scroll through the entire page to trigger lazy loading - async function scrollToBottom() { - // Arrays to store captured content - const capturedContent = []; - const capturedHashes = new Set(); - - // First, try to expand any collapsed/hidden sections - const expandCollapsedSections = () => { - // Find and click on common expandable elements - const expandableSelectors = [ - 'details:not([open])', - '[aria-expanded="false"]', - '.collapsed', - '.expand', - '.accordion:not(.active)', - '[data-collapsed="true"]', - 'button[aria-expanded="false"]', - ]; - - let expandedCount = 0; - expandableSelectors.forEach(selector => { - const elements = document.querySelectorAll(selector); - elements.forEach(el => { - try { - if (el.tagName === 'DETAILS') { - el.open = true; - } else if (el.click) { - el.click(); - } - expandedCount++; - } catch (_e) { - // Ignore errors - } - }); - }); - return expandedCount; - }; - - // Try to expand sections before scrolling - expandCollapsedSections(); - await new Promise(resolve => setTimeout(resolve, 500)); - - // Create progress indicator overlay - const overlay = document.createElement('div'); - overlay.id = 'markdown-printer-overlay'; - overlay.style.cssText = ` - position: fixed; - top: 20px; - right: 20px; - background: rgba(0, 0, 0, 0.9); - color: white; - padding: 20px; - border-radius: 8px; - z-index: 999999; - font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; - box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); - min-width: 300px; - opacity: 0; - transition: opacity 0.3s ease-in-out; - `; - - const title = document.createElement('div'); - title.textContent = 'Printing...'; - title.style.cssText = 'font-weight: bold; margin-bottom: 10px; font-size: 16px;'; - - const percentageText = document.createElement('div'); - percentageText.id = 'percentage-text'; - percentageText.style.cssText = - 'font-size: 24px; font-weight: bold; margin-bottom: 5px; color: #4CAF50;'; - percentageText.textContent = '0%'; - - const status = document.createElement('div'); - status.id = 'scroll-status'; - status.style.cssText = 'margin-bottom: 10px; font-size: 14px; opacity: 0.8;'; - - const progressBar = document.createElement('div'); - progressBar.style.cssText = ` - width: 100%; - height: 4px; - background: rgba(255, 255, 255, 0.2); - border-radius: 2px; - overflow: hidden; - margin-bottom: 10px; - `; - - const progressFill = document.createElement('div'); - progressFill.id = 'progress-fill'; - progressFill.style.cssText = ` - height: 100%; - background: #4CAF50; - width: 0%; - transition: width 0.3s ease; - `; - progressBar.appendChild(progressFill); - - const cancelButton = document.createElement('button'); - cancelButton.textContent = 'Abort'; - cancelButton.style.cssText = ` - width: 100%; - padding: 8px; - background: #f44336; - color: white; - border: none; - border-radius: 4px; - cursor: pointer; - font-size: 14px; - font-weight: bold; - transition: background 0.2s ease; - `; - cancelButton.onmouseover = () => (cancelButton.style.background = '#d32f2f'); - cancelButton.onmouseout = () => (cancelButton.style.background = '#f44336'); - - overlay.appendChild(title); - overlay.appendChild(percentageText); - overlay.appendChild(status); - overlay.appendChild(progressBar); - overlay.appendChild(cancelButton); - document.body.appendChild(overlay); - - // Trigger fade-in animation - window.requestAnimationFrame(() => { - overlay.style.opacity = '1'; - }); - - let cancelled = false; - cancelButton.onclick = () => { - cancelled = true; - status.textContent = 'Stopping...'; - cancelButton.disabled = true; - cancelButton.style.opacity = '0.5'; - }; - - // Make all hidden content sections visible (common in documentation sites) - const makeAllContentVisible = () => { - // Target common documentation content containers - const contentSelectors = [ - 'article', - 'section', - 'main', - '[role="main"]', - '[class*="content"]', - '[class*="documentation"]', - '[class*="api"]', - '[class*="endpoint"]', - '[id*="content"]', - ]; - - contentSelectors.forEach(selector => { - const elements = document.querySelectorAll(selector); - elements.forEach(el => { - const style = window.getComputedStyle(el); - if (style.display === 'none' || style.visibility === 'hidden') { - el.style.display = 'block'; - el.style.visibility = 'visible'; - el.style.opacity = '1'; - } - // Also unhide all children - el.querySelectorAll('*').forEach(child => { - const childStyle = window.getComputedStyle(child); - if (childStyle.display === 'none' || childStyle.visibility === 'hidden') { - child.style.display = 'block'; - child.style.visibility = 'visible'; - child.style.opacity = '1'; - } - }); - }); - }); - }; - - // First, make content visible - status.textContent = 'Loading content...'; - makeAllContentVisible(); - await new Promise(resolve => setTimeout(resolve, 1000)); - - // Update progress display - const updateProgress = (current, total, stableCount, sectionsCount) => { - const percentage = Math.min(100, Math.round((current / total) * 100)); - progressFill.style.width = percentage + '%'; - percentageText.textContent = percentage + '%'; - status.textContent = `${current.toLocaleString()}px / ${total.toLocaleString()}px`; - if (sectionsCount > 0) { - status.textContent += ` | ${sectionsCount} sections`; - } - if (stableCount > 0) { - status.textContent += ` (${stableCount}/3 stable)`; - } - }; - - // Smooth scroll function that triggers events - const smoothScrollTo = async targetY => { - const startY = window.scrollY; - const distance = targetY - startY; - const duration = 150; // ms (reduced for faster scrolling) - const startTime = window.performance.now(); - - return new Promise(resolve => { - const scroll = currentTime => { - const elapsed = currentTime - startTime; - const progress = Math.min(elapsed / duration, 1); - const easeProgress = progress * (2 - progress); // ease out - - window.scrollTo(0, startY + distance * easeProgress); - - // Dispatch scroll event to trigger listeners - window.dispatchEvent(new window.Event('scroll')); - - if (progress < 1) { - window.requestAnimationFrame(scroll); - } else { - resolve(); - } - }; - window.requestAnimationFrame(scroll); - }); - }; - - // Start from the top - await smoothScrollTo(0); - await new Promise(resolve => setTimeout(resolve, 100)); - - let lastHeight = document.documentElement.scrollHeight; - let stableScrollCount = 0; - - // Scroll down in increments (2x viewport height for faster scrolling) - const scrollStep = window.innerHeight * 2; - let currentPosition = 0; - - while (!cancelled) { - // Smooth scroll to current position - await smoothScrollTo(currentPosition); - - // Wait for content to load (reduced for faster scrolling) - await new Promise(resolve => setTimeout(resolve, 200)); - - // Capture visible content blocks at current position - const blocks = captureVisibleContentBlocks(); - blocks.forEach(block => { - // Check if this content is similar to anything we've already captured - const isDuplicate = Array.from(capturedHashes).some(existingContent => { - const similarity = calculateSimilarity(block.normalizedContent, existingContent); - return similarity > 0.85; // 85% similar = duplicate - }); - - if (!isDuplicate) { - capturedHashes.add(block.normalizedContent); - capturedContent.push(block.markdown); - } - }); - - const newHeight = document.documentElement.scrollHeight; - updateProgress(currentPosition, newHeight, stableScrollCount, capturedContent.length); - - // If we've reached the bottom and height hasn't changed for 3 consecutive attempts - if (currentPosition >= newHeight) { - if (newHeight === lastHeight) { - stableScrollCount++; - if (stableScrollCount >= 3) { - percentageText.textContent = '100%'; - progressFill.style.width = '100%'; - status.textContent = `Complete! Captured ${capturedContent.length} sections`; - break; - } - } else { - stableScrollCount = 0; - } - } - - // Update tracking variables - lastHeight = newHeight; - currentPosition += scrollStep; - } - - // Ensure we scroll all the way to the bottom and wait for content to render - if (!cancelled) { - await smoothScrollTo(document.documentElement.scrollHeight); - await new Promise(resolve => setTimeout(resolve, 1000)); - - // Expand any sections that became available during scrolling - expandCollapsedSections(); - await new Promise(resolve => setTimeout(resolve, 500)); - - // Do a second full scroll pass - some content only loads after first pass - currentPosition = 0; - const secondPassHeight = document.documentElement.scrollHeight; - status.textContent = 'Second pass: loading remaining content...'; - - while (currentPosition < secondPassHeight && !cancelled) { - await smoothScrollTo(currentPosition); - await new Promise(resolve => setTimeout(resolve, 200)); - - // Capture any new content blocks in second pass - const blocks = captureVisibleContentBlocks(); - blocks.forEach(block => { - // Check if this content is similar to anything we've already captured - const isDuplicate = Array.from(capturedHashes).some(existingContent => { - const similarity = calculateSimilarity(block.normalizedContent, existingContent); - return similarity > 0.85; // 85% similar = duplicate - }); - - if (!isDuplicate) { - capturedHashes.add(block.normalizedContent); - capturedContent.push(block.markdown); - } - }); - - currentPosition += scrollStep; - } - - // Final stay at bottom to ensure everything loads - await smoothScrollTo(document.documentElement.scrollHeight); - await new Promise(resolve => setTimeout(resolve, 1000)); - - // One more expansion attempt - expandCollapsedSections(); - await new Promise(resolve => setTimeout(resolve, 500)); - } - - // Fade out and remove overlay - overlay.style.opacity = '0'; - await new Promise(resolve => setTimeout(resolve, 300)); - overlay.remove(); - - // Return status and captured content - return { cancelled, capturedContent }; - } - - // Scroll through the page first - const scrollResult = await scrollToBottom(); - - // If scrolling was cancelled, return null to indicate no download should occur - if (scrollResult.cancelled) { - return null; - } - - // Combine all captured content sections - const { capturedContent } = scrollResult; - - // Join all sections with double newlines for spacing - const combinedMarkdown = capturedContent.join('\n\n---\n\n'); - - return { - markdown: combinedMarkdown, - title: document.title, - url: window.location.href, - }; -} - -function sanitizeFilename(filename) { - return filename - .replace(/[<>:"/\\|?*]/g, '-') - .replace(/\s+/g, '-') - .replace(/-+/g, '-') - .substring(0, 200); -} diff --git a/extension-firefox/icon128.png b/extension-firefox/icon128.png deleted file mode 100644 index 828c2408..00000000 Binary files a/extension-firefox/icon128.png and /dev/null differ diff --git a/extension-firefox/icon16.png b/extension-firefox/icon16.png deleted file mode 100644 index 8ef9e779..00000000 Binary files a/extension-firefox/icon16.png and /dev/null differ diff --git a/extension-firefox/icon48.png b/extension-firefox/icon48.png deleted file mode 100644 index b11cfd4d..00000000 Binary files a/extension-firefox/icon48.png and /dev/null differ diff --git a/extension-firefox/manifest.json b/extension-firefox/manifest.json deleted file mode 100644 index 22f36c55..00000000 --- a/extension-firefox/manifest.json +++ /dev/null @@ -1,35 +0,0 @@ -{ - "manifest_version": 3, - "name": "__MSG_extensionName__", - "version": "1.1.1", - "description": "__MSG_extensionDescription__", - "default_locale": "en", - "author": "Lev Gelfenbuim", - "homepage_url": "https://github.com/levz0r/markdown-printer", - "permissions": ["activeTab", "contextMenus", "downloads", "scripting"], - "background": { - "scripts": ["background.js"] - }, - "icons": { - "16": "icon16.png", - "48": "icon48.png", - "128": "icon128.png" - }, - "action": { - "default_popup": "popup.html", - "default_icon": { - "16": "icon16.png", - "48": "icon48.png", - "128": "icon128.png" - } - }, - "browser_specific_settings": { - "gecko": { - "id": "markdown-printer@lev.engineer", - "strict_min_version": "121.0", - "data_collection_permissions": { - "required": ["none"] - } - } - } -} diff --git a/extension-firefox/popup.html b/extension-firefox/popup.html deleted file mode 100644 index 7514bc89..00000000 --- a/extension-firefox/popup.html +++ /dev/null @@ -1,93 +0,0 @@ - - - - - - - -

- - - -

- - - - - diff --git a/extension-firefox/popup.js b/extension-firefox/popup.js deleted file mode 100644 index 512fcec9..00000000 --- a/extension-firefox/popup.js +++ /dev/null @@ -1,44 +0,0 @@ -// Cross-browser compatibility -const browserAPI = typeof browser !== 'undefined' ? browser : chrome; - -// Set localized text on load -document.addEventListener('DOMContentLoaded', () => { - // Set text direction for RTL languages - const uiLanguage = browserAPI.i18n.getUILanguage(); - const rtlLanguages = ['he', 'ar', 'fa', 'ur']; - const isRTL = rtlLanguages.some(lang => uiLanguage.startsWith(lang)); - - if (isRTL) { - document.body.dir = 'rtl'; - } - - document.getElementById('extensionName').textContent = - browserAPI.i18n.getMessage('extensionName'); - document.getElementById('saveBtn').textContent = browserAPI.i18n.getMessage('savePageButton'); - - // Display version number - const manifest = browserAPI.runtime.getManifest(); - document.getElementById('version').textContent = `v${manifest.version}`; -}); - -document.getElementById('saveBtn').addEventListener('click', async () => { - const button = document.getElementById('saveBtn'); - const originalText = browserAPI.i18n.getMessage('savePageButton'); - button.disabled = true; - button.textContent = 'Saving...'; - - try { - await browserAPI.runtime.sendMessage({ action: 'saveAsMarkdown' }); - button.textContent = 'Saved!'; - setTimeout(() => { - button.textContent = originalText; - button.disabled = false; - }, 1500); - } catch (_error) { - button.textContent = 'Error - Try again'; - button.disabled = false; - setTimeout(() => { - button.textContent = originalText; - }, 2000); - } -}); diff --git a/extension-firefox/turndown.js b/extension-firefox/turndown.js deleted file mode 100644 index ad5b04c2..00000000 --- a/extension-firefox/turndown.js +++ /dev/null @@ -1,973 +0,0 @@ -var TurndownService = (function () { - 'use strict'; - - function extend (destination) { - for (var i = 1; i < arguments.length; i++) { - var source = arguments[i]; - for (var key in source) { - if (source.hasOwnProperty(key)) destination[key] = source[key]; - } - } - return destination - } - - function repeat (character, count) { - return Array(count + 1).join(character) - } - - function trimLeadingNewlines (string) { - return string.replace(/^\n*/, '') - } - - function trimTrailingNewlines (string) { - // avoid match-at-end regexp bottleneck, see #370 - var indexEnd = string.length; - while (indexEnd > 0 && string[indexEnd - 1] === '\n') indexEnd--; - return string.substring(0, indexEnd) - } - - var blockElements = [ - 'ADDRESS', 'ARTICLE', 'ASIDE', 'AUDIO', 'BLOCKQUOTE', 'BODY', 'CANVAS', - 'CENTER', 'DD', 'DIR', 'DIV', 'DL', 'DT', 'FIELDSET', 'FIGCAPTION', 'FIGURE', - 'FOOTER', 'FORM', 'FRAMESET', 'H1', 'H2', 'H3', 'H4', 'H5', 'H6', 'HEADER', - 'HGROUP', 'HR', 'HTML', 'ISINDEX', 'LI', 'MAIN', 'MENU', 'NAV', 'NOFRAMES', - 'NOSCRIPT', 'OL', 'OUTPUT', 'P', 'PRE', 'SECTION', 'TABLE', 'TBODY', 'TD', - 'TFOOT', 'TH', 'THEAD', 'TR', 'UL' - ]; - - function isBlock (node) { - return is(node, blockElements) - } - - var voidElements = [ - 'AREA', 'BASE', 'BR', 'COL', 'COMMAND', 'EMBED', 'HR', 'IMG', 'INPUT', - 'KEYGEN', 'LINK', 'META', 'PARAM', 'SOURCE', 'TRACK', 'WBR' - ]; - - function isVoid (node) { - return is(node, voidElements) - } - - function hasVoid (node) { - return has(node, voidElements) - } - - var meaningfulWhenBlankElements = [ - 'A', 'TABLE', 'THEAD', 'TBODY', 'TFOOT', 'TH', 'TD', 'IFRAME', 'SCRIPT', - 'AUDIO', 'VIDEO' - ]; - - function isMeaningfulWhenBlank (node) { - return is(node, meaningfulWhenBlankElements) - } - - function hasMeaningfulWhenBlank (node) { - return has(node, meaningfulWhenBlankElements) - } - - function is (node, tagNames) { - return tagNames.indexOf(node.nodeName) >= 0 - } - - function has (node, tagNames) { - return ( - node.getElementsByTagName && - tagNames.some(function (tagName) { - return node.getElementsByTagName(tagName).length - }) - ) - } - - var rules = {}; - - rules.paragraph = { - filter: 'p', - - replacement: function (content) { - return '\n\n' + content + '\n\n' - } - }; - - rules.lineBreak = { - filter: 'br', - - replacement: function (content, node, options) { - return options.br + '\n' - } - }; - - rules.heading = { - filter: ['h1', 'h2', 'h3', 'h4', 'h5', 'h6'], - - replacement: function (content, node, options) { - var hLevel = Number(node.nodeName.charAt(1)); - - if (options.headingStyle === 'setext' && hLevel < 3) { - var underline = repeat((hLevel === 1 ? '=' : '-'), content.length); - return ( - '\n\n' + content + '\n' + underline + '\n\n' - ) - } else { - return '\n\n' + repeat('#', hLevel) + ' ' + content + '\n\n' - } - } - }; - - rules.blockquote = { - filter: 'blockquote', - - replacement: function (content) { - content = content.replace(/^\n+|\n+$/g, ''); - content = content.replace(/^/gm, '> '); - return '\n\n' + content + '\n\n' - } - }; - - rules.list = { - filter: ['ul', 'ol'], - - replacement: function (content, node) { - var parent = node.parentNode; - if (parent.nodeName === 'LI' && parent.lastElementChild === node) { - return '\n' + content - } else { - return '\n\n' + content + '\n\n' - } - } - }; - - rules.listItem = { - filter: 'li', - - replacement: function (content, node, options) { - content = content - .replace(/^\n+/, '') // remove leading newlines - .replace(/\n+$/, '\n') // replace trailing newlines with just a single one - .replace(/\n/gm, '\n '); // indent - var prefix = options.bulletListMarker + ' '; - var parent = node.parentNode; - if (parent.nodeName === 'OL') { - var start = parent.getAttribute('start'); - var index = Array.prototype.indexOf.call(parent.children, node); - prefix = (start ? Number(start) + index : index + 1) + '. '; - } - return ( - prefix + content + (node.nextSibling && !/\n$/.test(content) ? '\n' : '') - ) - } - }; - - rules.indentedCodeBlock = { - filter: function (node, options) { - return ( - options.codeBlockStyle === 'indented' && - node.nodeName === 'PRE' && - node.firstChild && - node.firstChild.nodeName === 'CODE' - ) - }, - - replacement: function (content, node, options) { - return ( - '\n\n ' + - node.firstChild.textContent.replace(/\n/g, '\n ') + - '\n\n' - ) - } - }; - - rules.fencedCodeBlock = { - filter: function (node, options) { - return ( - options.codeBlockStyle === 'fenced' && - node.nodeName === 'PRE' && - node.firstChild && - node.firstChild.nodeName === 'CODE' - ) - }, - - replacement: function (content, node, options) { - var className = node.firstChild.getAttribute('class') || ''; - var language = (className.match(/language-(\S+)/) || [null, ''])[1]; - var code = node.firstChild.textContent; - - var fenceChar = options.fence.charAt(0); - var fenceSize = 3; - var fenceInCodeRegex = new RegExp('^' + fenceChar + '{3,}', 'gm'); - - var match; - while ((match = fenceInCodeRegex.exec(code))) { - if (match[0].length >= fenceSize) { - fenceSize = match[0].length + 1; - } - } - - var fence = repeat(fenceChar, fenceSize); - - return ( - '\n\n' + fence + language + '\n' + - code.replace(/\n$/, '') + - '\n' + fence + '\n\n' - ) - } - }; - - rules.horizontalRule = { - filter: 'hr', - - replacement: function (content, node, options) { - return '\n\n' + options.hr + '\n\n' - } - }; - - rules.inlineLink = { - filter: function (node, options) { - return ( - options.linkStyle === 'inlined' && - node.nodeName === 'A' && - node.getAttribute('href') - ) - }, - - replacement: function (content, node) { - var href = node.getAttribute('href'); - var title = cleanAttribute(node.getAttribute('title')); - if (title) title = ' "' + title + '"'; - return '[' + content + '](' + href + title + ')' - } - }; - - rules.referenceLink = { - filter: function (node, options) { - return ( - options.linkStyle === 'referenced' && - node.nodeName === 'A' && - node.getAttribute('href') - ) - }, - - replacement: function (content, node, options) { - var href = node.getAttribute('href'); - var title = cleanAttribute(node.getAttribute('title')); - if (title) title = ' "' + title + '"'; - var replacement; - var reference; - - switch (options.linkReferenceStyle) { - case 'collapsed': - replacement = '[' + content + '][]'; - reference = '[' + content + ']: ' + href + title; - break - case 'shortcut': - replacement = '[' + content + ']'; - reference = '[' + content + ']: ' + href + title; - break - default: - var id = this.references.length + 1; - replacement = '[' + content + '][' + id + ']'; - reference = '[' + id + ']: ' + href + title; - } - - this.references.push(reference); - return replacement - }, - - references: [], - - append: function (options) { - var references = ''; - if (this.references.length) { - references = '\n\n' + this.references.join('\n') + '\n\n'; - this.references = []; // Reset references - } - return references - } - }; - - rules.emphasis = { - filter: ['em', 'i'], - - replacement: function (content, node, options) { - if (!content.trim()) return '' - return options.emDelimiter + content + options.emDelimiter - } - }; - - rules.strong = { - filter: ['strong', 'b'], - - replacement: function (content, node, options) { - if (!content.trim()) return '' - return options.strongDelimiter + content + options.strongDelimiter - } - }; - - rules.code = { - filter: function (node) { - var hasSiblings = node.previousSibling || node.nextSibling; - var isCodeBlock = node.parentNode.nodeName === 'PRE' && !hasSiblings; - - return node.nodeName === 'CODE' && !isCodeBlock - }, - - replacement: function (content) { - if (!content) return '' - content = content.replace(/\r?\n|\r/g, ' '); - - var extraSpace = /^`|^ .*?[^ ].* $|`$/.test(content) ? ' ' : ''; - var delimiter = '`'; - var matches = content.match(/`+/gm) || []; - while (matches.indexOf(delimiter) !== -1) delimiter = delimiter + '`'; - - return delimiter + extraSpace + content + extraSpace + delimiter - } - }; - - rules.image = { - filter: 'img', - - replacement: function (content, node) { - var alt = cleanAttribute(node.getAttribute('alt')); - var src = node.getAttribute('src') || ''; - var title = cleanAttribute(node.getAttribute('title')); - var titlePart = title ? ' "' + title + '"' : ''; - return src ? '![' + alt + ']' + '(' + src + titlePart + ')' : '' - } - }; - - function cleanAttribute (attribute) { - return attribute ? attribute.replace(/(\n+\s*)+/g, '\n') : '' - } - - /** - * Manages a collection of rules used to convert HTML to Markdown - */ - - function Rules (options) { - this.options = options; - this._keep = []; - this._remove = []; - - this.blankRule = { - replacement: options.blankReplacement - }; - - this.keepReplacement = options.keepReplacement; - - this.defaultRule = { - replacement: options.defaultReplacement - }; - - this.array = []; - for (var key in options.rules) this.array.push(options.rules[key]); - } - - Rules.prototype = { - add: function (key, rule) { - this.array.unshift(rule); - }, - - keep: function (filter) { - this._keep.unshift({ - filter: filter, - replacement: this.keepReplacement - }); - }, - - remove: function (filter) { - this._remove.unshift({ - filter: filter, - replacement: function () { - return '' - } - }); - }, - - forNode: function (node) { - if (node.isBlank) return this.blankRule - var rule; - - if ((rule = findRule(this.array, node, this.options))) return rule - if ((rule = findRule(this._keep, node, this.options))) return rule - if ((rule = findRule(this._remove, node, this.options))) return rule - - return this.defaultRule - }, - - forEach: function (fn) { - for (var i = 0; i < this.array.length; i++) fn(this.array[i], i); - } - }; - - function findRule (rules, node, options) { - for (var i = 0; i < rules.length; i++) { - var rule = rules[i]; - if (filterValue(rule, node, options)) return rule - } - return void 0 - } - - function filterValue (rule, node, options) { - var filter = rule.filter; - if (typeof filter === 'string') { - if (filter === node.nodeName.toLowerCase()) return true - } else if (Array.isArray(filter)) { - if (filter.indexOf(node.nodeName.toLowerCase()) > -1) return true - } else if (typeof filter === 'function') { - if (filter.call(rule, node, options)) return true - } else { - throw new TypeError('`filter` needs to be a string, array, or function') - } - } - - /** - * The collapseWhitespace function is adapted from collapse-whitespace - * by Luc Thevenard. - * - * The MIT License (MIT) - * - * Copyright (c) 2014 Luc Thevenard - * - * 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. - */ - - /** - * collapseWhitespace(options) removes extraneous whitespace from an the given element. - * - * @param {Object} options - */ - function collapseWhitespace (options) { - var element = options.element; - var isBlock = options.isBlock; - var isVoid = options.isVoid; - var isPre = options.isPre || function (node) { - return node.nodeName === 'PRE' - }; - - if (!element.firstChild || isPre(element)) return - - var prevText = null; - var keepLeadingWs = false; - - var prev = null; - var node = next(prev, element, isPre); - - while (node !== element) { - if (node.nodeType === 3 || node.nodeType === 4) { // Node.TEXT_NODE or Node.CDATA_SECTION_NODE - var text = node.data.replace(/[ \r\n\t]+/g, ' '); - - if ((!prevText || / $/.test(prevText.data)) && - !keepLeadingWs && text[0] === ' ') { - text = text.substr(1); - } - - // `text` might be empty at this point. - if (!text) { - node = remove(node); - continue - } - - node.data = text; - - prevText = node; - } else if (node.nodeType === 1) { // Node.ELEMENT_NODE - if (isBlock(node) || node.nodeName === 'BR') { - if (prevText) { - prevText.data = prevText.data.replace(/ $/, ''); - } - - prevText = null; - keepLeadingWs = false; - } else if (isVoid(node) || isPre(node)) { - // Avoid trimming space around non-block, non-BR void elements and inline PRE. - prevText = null; - keepLeadingWs = true; - } else if (prevText) { - // Drop protection if set previously. - keepLeadingWs = false; - } - } else { - node = remove(node); - continue - } - - var nextNode = next(prev, node, isPre); - prev = node; - node = nextNode; - } - - if (prevText) { - prevText.data = prevText.data.replace(/ $/, ''); - if (!prevText.data) { - remove(prevText); - } - } - } - - /** - * remove(node) removes the given node from the DOM and returns the - * next node in the sequence. - * - * @param {Node} node - * @return {Node} node - */ - function remove (node) { - var next = node.nextSibling || node.parentNode; - - node.parentNode.removeChild(node); - - return next - } - - /** - * next(prev, current, isPre) returns the next node in the sequence, given the - * current and previous nodes. - * - * @param {Node} prev - * @param {Node} current - * @param {Function} isPre - * @return {Node} - */ - function next (prev, current, isPre) { - if ((prev && prev.parentNode === current) || isPre(current)) { - return current.nextSibling || current.parentNode - } - - return current.firstChild || current.nextSibling || current.parentNode - } - - /* - * Set up window for Node.js - */ - - var root = (typeof window !== 'undefined' ? window : {}); - - /* - * Parsing HTML strings - */ - - function canParseHTMLNatively () { - var Parser = root.DOMParser; - var canParse = false; - - // Adapted from https://gist.github.com/1129031 - // Firefox/Opera/IE throw errors on unsupported types - try { - // WebKit returns null on unsupported types - if (new Parser().parseFromString('', 'text/html')) { - canParse = true; - } - } catch (e) {} - - return canParse - } - - function createHTMLParser () { - var Parser = function () {}; - - { - if (shouldUseActiveX()) { - Parser.prototype.parseFromString = function (string) { - var doc = new window.ActiveXObject('htmlfile'); - doc.designMode = 'on'; // disable on-page scripts - doc.open(); - doc.write(string); - doc.close(); - return doc - }; - } else { - Parser.prototype.parseFromString = function (string) { - var doc = document.implementation.createHTMLDocument(''); - doc.open(); - doc.write(string); - doc.close(); - return doc - }; - } - } - return Parser - } - - function shouldUseActiveX () { - var useActiveX = false; - try { - document.implementation.createHTMLDocument('').open(); - } catch (e) { - if (window.ActiveXObject) useActiveX = true; - } - return useActiveX - } - - var HTMLParser = canParseHTMLNatively() ? root.DOMParser : createHTMLParser(); - - function RootNode (input, options) { - var root; - if (typeof input === 'string') { - var doc = htmlParser().parseFromString( - // DOM parsers arrange elements in the and . - // Wrapping in a custom element ensures elements are reliably arranged in - // a single element. - '' + input + '', - 'text/html' - ); - root = doc.getElementById('turndown-root'); - } else { - root = input.cloneNode(true); - } - collapseWhitespace({ - element: root, - isBlock: isBlock, - isVoid: isVoid, - isPre: options.preformattedCode ? isPreOrCode : null - }); - - return root - } - - var _htmlParser; - function htmlParser () { - _htmlParser = _htmlParser || new HTMLParser(); - return _htmlParser - } - - function isPreOrCode (node) { - return node.nodeName === 'PRE' || node.nodeName === 'CODE' - } - - function Node (node, options) { - node.isBlock = isBlock(node); - node.isCode = node.nodeName === 'CODE' || node.parentNode.isCode; - node.isBlank = isBlank(node); - node.flankingWhitespace = flankingWhitespace(node, options); - return node - } - - function isBlank (node) { - return ( - !isVoid(node) && - !isMeaningfulWhenBlank(node) && - /^\s*$/i.test(node.textContent) && - !hasVoid(node) && - !hasMeaningfulWhenBlank(node) - ) - } - - function flankingWhitespace (node, options) { - if (node.isBlock || (options.preformattedCode && node.isCode)) { - return { leading: '', trailing: '' } - } - - var edges = edgeWhitespace(node.textContent); - - // abandon leading ASCII WS if left-flanked by ASCII WS - if (edges.leadingAscii && isFlankedByWhitespace('left', node, options)) { - edges.leading = edges.leadingNonAscii; - } - - // abandon trailing ASCII WS if right-flanked by ASCII WS - if (edges.trailingAscii && isFlankedByWhitespace('right', node, options)) { - edges.trailing = edges.trailingNonAscii; - } - - return { leading: edges.leading, trailing: edges.trailing } - } - - function edgeWhitespace (string) { - var m = string.match(/^(([ \t\r\n]*)(\s*))(?:(?=\S)[\s\S]*\S)?((\s*?)([ \t\r\n]*))$/); - return { - leading: m[1], // whole string for whitespace-only strings - leadingAscii: m[2], - leadingNonAscii: m[3], - trailing: m[4], // empty for whitespace-only strings - trailingNonAscii: m[5], - trailingAscii: m[6] - } - } - - function isFlankedByWhitespace (side, node, options) { - var sibling; - var regExp; - var isFlanked; - - if (side === 'left') { - sibling = node.previousSibling; - regExp = / $/; - } else { - sibling = node.nextSibling; - regExp = /^ /; - } - - if (sibling) { - if (sibling.nodeType === 3) { - isFlanked = regExp.test(sibling.nodeValue); - } else if (options.preformattedCode && sibling.nodeName === 'CODE') { - isFlanked = false; - } else if (sibling.nodeType === 1 && !isBlock(sibling)) { - isFlanked = regExp.test(sibling.textContent); - } - } - return isFlanked - } - - var reduce = Array.prototype.reduce; - var escapes = [ - [/\\/g, '\\\\'], - [/\*/g, '\\*'], - [/^-/g, '\\-'], - [/^\+ /g, '\\+ '], - [/^(=+)/g, '\\$1'], - [/^(#{1,6}) /g, '\\$1 '], - [/`/g, '\\`'], - [/^~~~/g, '\\~~~'], - [/\[/g, '\\['], - [/\]/g, '\\]'], - [/^>/g, '\\>'], - [/_/g, '\\_'], - [/^(\d+)\. /g, '$1\\. '] - ]; - - function TurndownService (options) { - if (!(this instanceof TurndownService)) return new TurndownService(options) - - var defaults = { - rules: rules, - headingStyle: 'setext', - hr: '* * *', - bulletListMarker: '*', - codeBlockStyle: 'indented', - fence: '```', - emDelimiter: '_', - strongDelimiter: '**', - linkStyle: 'inlined', - linkReferenceStyle: 'full', - br: ' ', - preformattedCode: false, - blankReplacement: function (content, node) { - return node.isBlock ? '\n\n' : '' - }, - keepReplacement: function (content, node) { - return node.isBlock ? '\n\n' + node.outerHTML + '\n\n' : node.outerHTML - }, - defaultReplacement: function (content, node) { - return node.isBlock ? '\n\n' + content + '\n\n' : content - } - }; - this.options = extend({}, defaults, options); - this.rules = new Rules(this.options); - } - - TurndownService.prototype = { - /** - * The entry point for converting a string or DOM node to Markdown - * @public - * @param {String|HTMLElement} input The string or DOM node to convert - * @returns A Markdown representation of the input - * @type String - */ - - turndown: function (input) { - if (!canConvert(input)) { - throw new TypeError( - input + ' is not a string, or an element/document/fragment node.' - ) - } - - if (input === '') return '' - - var output = process.call(this, new RootNode(input, this.options)); - return postProcess.call(this, output) - }, - - /** - * Add one or more plugins - * @public - * @param {Function|Array} plugin The plugin or array of plugins to add - * @returns The Turndown instance for chaining - * @type Object - */ - - use: function (plugin) { - if (Array.isArray(plugin)) { - for (var i = 0; i < plugin.length; i++) this.use(plugin[i]); - } else if (typeof plugin === 'function') { - plugin(this); - } else { - throw new TypeError('plugin must be a Function or an Array of Functions') - } - return this - }, - - /** - * Adds a rule - * @public - * @param {String} key The unique key of the rule - * @param {Object} rule The rule - * @returns The Turndown instance for chaining - * @type Object - */ - - addRule: function (key, rule) { - this.rules.add(key, rule); - return this - }, - - /** - * Keep a node (as HTML) that matches the filter - * @public - * @param {String|Array|Function} filter The unique key of the rule - * @returns The Turndown instance for chaining - * @type Object - */ - - keep: function (filter) { - this.rules.keep(filter); - return this - }, - - /** - * Remove a node that matches the filter - * @public - * @param {String|Array|Function} filter The unique key of the rule - * @returns The Turndown instance for chaining - * @type Object - */ - - remove: function (filter) { - this.rules.remove(filter); - return this - }, - - /** - * Escapes Markdown syntax - * @public - * @param {String} string The string to escape - * @returns A string with Markdown syntax escaped - * @type String - */ - - escape: function (string) { - return escapes.reduce(function (accumulator, escape) { - return accumulator.replace(escape[0], escape[1]) - }, string) - } - }; - - /** - * Reduces a DOM node down to its Markdown string equivalent - * @private - * @param {HTMLElement} parentNode The node to convert - * @returns A Markdown representation of the node - * @type String - */ - - function process (parentNode) { - var self = this; - return reduce.call(parentNode.childNodes, function (output, node) { - node = new Node(node, self.options); - - var replacement = ''; - if (node.nodeType === 3) { - replacement = node.isCode ? node.nodeValue : self.escape(node.nodeValue); - } else if (node.nodeType === 1) { - replacement = replacementForNode.call(self, node); - } - - return join(output, replacement) - }, '') - } - - /** - * Appends strings as each rule requires and trims the output - * @private - * @param {String} output The conversion output - * @returns A trimmed version of the ouput - * @type String - */ - - function postProcess (output) { - var self = this; - this.rules.forEach(function (rule) { - if (typeof rule.append === 'function') { - output = join(output, rule.append(self.options)); - } - }); - - return output.replace(/^[\t\r\n]+/, '').replace(/[\t\r\n\s]+$/, '') - } - - /** - * Converts an element node to its Markdown equivalent - * @private - * @param {HTMLElement} node The node to convert - * @returns A Markdown representation of the node - * @type String - */ - - function replacementForNode (node) { - var rule = this.rules.forNode(node); - var content = process.call(this, node); - var whitespace = node.flankingWhitespace; - if (whitespace.leading || whitespace.trailing) content = content.trim(); - return ( - whitespace.leading + - rule.replacement(content, node, this.options) + - whitespace.trailing - ) - } - - /** - * Joins replacement to the current output with appropriate number of new lines - * @private - * @param {String} output The current conversion output - * @param {String} replacement The string to append to the output - * @returns Joined output - * @type String - */ - - function join (output, replacement) { - var s1 = trimTrailingNewlines(output); - var s2 = trimLeadingNewlines(replacement); - var nls = Math.max(output.length - s1.length, replacement.length - s2.length); - var separator = '\n\n'.substring(0, nls); - - return s1 + separator + s2 - } - - /** - * Determines whether an input can be converted - * @private - * @param {String|HTMLElement} input Describe this parameter - * @returns Describe what it returns - * @type String|Object|Array|Boolean|Number - */ - - function canConvert (input) { - return ( - input != null && ( - typeof input === 'string' || - (input.nodeType && ( - input.nodeType === 1 || input.nodeType === 9 || input.nodeType === 11 - )) - ) - ) - } - - return TurndownService; - -}()); diff --git a/native-host/host.js b/native-host/host.js index 286490c6..31bb24f7 100755 --- a/native-host/host.js +++ b/native-host/host.js @@ -8,11 +8,56 @@ const TurndownService = require('turndown'); // Native messaging uses stdin/stdout for communication // Messages are length-prefixed (4 bytes, native byte order) followed by JSON -// Log errors to file for debugging -const logFile = path.join(os.homedir(), 'markdown-printer-debug.log'); +// Log to dated files under ~/.markdown-printer/logs/, retaining the last 7. +const LOG_DIR = path.join(os.homedir(), '.markdown-printer', 'logs'); +const LOG_RETENTION_DAYS = 7; + +function ensureLogDir() { + try { + if (!fs.existsSync(LOG_DIR)) fs.mkdirSync(LOG_DIR, { recursive: true }); + } catch (_e) { + /* swallow — logging must never break the host */ + } +} + +function pruneOldLogs() { + try { + if (!fs.existsSync(LOG_DIR)) return; + const entries = fs + .readdirSync(LOG_DIR) + .filter(f => /^host-\d{4}-\d{2}-\d{2}\.log$/.test(f)) + .sort(); + while (entries.length > LOG_RETENTION_DAYS) { + const oldest = entries.shift(); + try { + fs.unlinkSync(path.join(LOG_DIR, oldest)); + } catch (_e) { + /* ignore */ + } + } + } catch (_e) { + /* ignore */ + } +} + +function currentLogPath() { + const day = new Date().toISOString().split('T')[0]; + return path.join(LOG_DIR, `host-${day}.log`); +} + +let prunedThisRun = false; function logError(message) { - const timestamp = new Date().toISOString(); - fs.appendFileSync(logFile, `[${timestamp}] ${message}\n`); + try { + ensureLogDir(); + if (!prunedThisRun) { + pruneOldLogs(); + prunedThisRun = true; + } + const timestamp = new Date().toISOString(); + fs.appendFileSync(currentLogPath(), `[${timestamp}] ${message}\n`); + } catch (_e) { + /* swallow */ + } } function readMessage() { diff --git a/native-host/pnpm-lock.yaml b/native-host/pnpm-lock.yaml index a24bb33d..db91d626 100644 --- a/native-host/pnpm-lock.yaml +++ b/native-host/pnpm-lock.yaml @@ -10,20 +10,21 @@ importers: dependencies: turndown: specifier: ^7.1.2 - version: 7.2.2 + version: 7.2.4 packages: '@mixmark-io/domino@2.2.0': resolution: {integrity: sha512-Y28PR25bHXUg88kCV7nivXrP2Nj2RueZ3/l/jdx6J9f8J4nsEGcgX0Qe6lt7Pa+J79+kPiJU3LguR6O/6zrLOw==} - turndown@7.2.2: - resolution: {integrity: sha512-1F7db8BiExOKxjSMU2b7if62D/XOyQyZbPKq/nUwopfgnHlqXHqQ0lvfUTeUIr1lZJzOPFn43dODyMSIfvWRKQ==} + turndown@7.2.4: + resolution: {integrity: sha512-I8yFsfRzmzK0WV1pNNOA4A7y4RDfFxPRxb3t+e3ui14qSGOxGtiSP6GjeX+Y6CHb7HYaFj7ECUD7VE5kQMZWGQ==} + engines: {node: '>=18', npm: '>=9'} snapshots: '@mixmark-io/domino@2.2.0': {} - turndown@7.2.2: + turndown@7.2.4: dependencies: '@mixmark-io/domino': 2.2.0 diff --git a/package.json b/package.json index 25e603e9..a167d1f2 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "markdown-printer", - "version": "1.1.1", + "version": "1.2.0", "description": "Save web pages as Markdown files with preserved formatting", "scripts": { "build": "node build.js", @@ -9,8 +9,10 @@ "version:patch": "npm version patch && npm run build", "version:minor": "npm version minor && npm run build", "version:major": "npm version major && npm run build", + "pretest": "node build.js", "test": "jest", "test:watch": "jest --watch", + "pretest:coverage": "node build.js", "test:coverage": "jest --coverage", "lint": "eslint .", "lint:fix": "eslint . --fix", @@ -25,7 +27,7 @@ "license": "MIT", "devDependencies": { "@types/jest": "^30.0.0", - "eslint": "^9.37.0", + "eslint": "^10.0.0", "jest": "^30.2.0", "prettier": "^3.6.2", "publish-browser-extension": "^4.0.0" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 46bcf86a..d3f1e012 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -12,17 +12,17 @@ importers: specifier: ^30.0.0 version: 30.0.0 eslint: - specifier: ^9.37.0 - version: 9.39.1 + specifier: ^10.0.0 + version: 10.3.0 jest: specifier: ^30.2.0 - version: 30.2.0(@types/node@24.7.0) + version: 30.4.2(@types/node@25.6.2) prettier: specifier: ^3.6.2 - version: 3.6.2 + version: 3.8.3 publish-browser-extension: specifier: ^4.0.0 - version: 4.0.0 + version: 4.0.5 packages: @@ -30,38 +30,42 @@ packages: resolution: {integrity: sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==} engines: {node: '>=6.9.0'} - '@babel/compat-data@7.28.4': - resolution: {integrity: sha512-YsmSKC29MJwf0gF8Rjjrg5LQCmyh+j/nD8/eP7f+BeoQTKYqs9RoWbjGOdy0+1Ekr68RJZMUOPVQaQisnIo4Rw==} + '@babel/code-frame@7.29.0': + resolution: {integrity: sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==} engines: {node: '>=6.9.0'} - '@babel/core@7.28.4': - resolution: {integrity: sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==} + '@babel/compat-data@7.29.3': + resolution: {integrity: sha512-LIVqM46zQWZhj17qA8wb4nW/ixr2y1Nw+r1etiAWgRM6U1IqP+LNhL1yg440jYZR72jCWcWbLWzIosH+uP1fqg==} engines: {node: '>=6.9.0'} - '@babel/generator@7.28.3': - resolution: {integrity: sha512-3lSpxGgvnmZznmBkCRnVREPUFJv2wrv9iAoFDvADJc0ypmdOxdUtcLeBgBJ6zE0PMeTKnxeQzyk0xTBq4Ep7zw==} + '@babel/core@7.29.0': + resolution: {integrity: sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==} engines: {node: '>=6.9.0'} - '@babel/helper-compilation-targets@7.27.2': - resolution: {integrity: sha512-2+1thGUUWWjLTYTHZWK1n8Yga0ijBz1XAhUXcKy81rd5g6yh7hGqMp45v7cadSbEHc9G3OTv45SyneRN3ps4DQ==} + '@babel/generator@7.29.1': + resolution: {integrity: sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==} + engines: {node: '>=6.9.0'} + + '@babel/helper-compilation-targets@7.28.6': + resolution: {integrity: sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==} engines: {node: '>=6.9.0'} '@babel/helper-globals@7.28.0': resolution: {integrity: sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==} engines: {node: '>=6.9.0'} - '@babel/helper-module-imports@7.27.1': - resolution: {integrity: sha512-0gSFWUPNXNopqtIPQvlD5WgXYI5GY2kP2cCvoT8kczjbfcfuIljTbcWrulD1CIPIX2gt1wghbDy08yE1p+/r3w==} + '@babel/helper-module-imports@7.28.6': + resolution: {integrity: sha512-l5XkZK7r7wa9LucGw9LwZyyCUscb4x37JWTPz7swwFE/0FMQAGpiWUZn8u9DzkSBWEcK25jmvubfpw2dnAMdbw==} engines: {node: '>=6.9.0'} - '@babel/helper-module-transforms@7.28.3': - resolution: {integrity: sha512-gytXUbs8k2sXS9PnQptz5o0QnpLL51SwASIORY6XaBKF88nsOT0Zw9szLqlSGQDP/4TljBAD5y98p2U1fqkdsw==} + '@babel/helper-module-transforms@7.28.6': + resolution: {integrity: sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0 - '@babel/helper-plugin-utils@7.27.1': - resolution: {integrity: sha512-1gn1Up5YXka3YYAHGKpbideQ5Yjf1tDa9qYcgysz+cNCXukyLl6DjPXhD3VRwSb8c0J9tA4b2+rHEZtc6R0tlw==} + '@babel/helper-plugin-utils@7.28.6': + resolution: {integrity: sha512-S9gzZ/bz83GRysI7gAD4wPT/AI3uCnY+9xn+Mx/KPs2JwHJIz1W8PZkg2cqyt3RNOBM8ejcXhV6y8Og7ly/Dug==} engines: {node: '>=6.9.0'} '@babel/helper-string-parser@7.27.1': @@ -72,16 +76,20 @@ packages: resolution: {integrity: sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==} engines: {node: '>=6.9.0'} + '@babel/helper-validator-identifier@7.28.5': + resolution: {integrity: sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==} + engines: {node: '>=6.9.0'} + '@babel/helper-validator-option@7.27.1': resolution: {integrity: sha512-YvjJow9FxbhFFKDSuFnVCe2WxXk1zWc22fFePVNEaWJEu8IrZVlda6N0uHwzZrUM1il7NC9Mlp4MaJYbYd9JSg==} engines: {node: '>=6.9.0'} - '@babel/helpers@7.28.4': - resolution: {integrity: sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==} + '@babel/helpers@7.29.2': + resolution: {integrity: sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==} engines: {node: '>=6.9.0'} - '@babel/parser@7.28.4': - resolution: {integrity: sha512-yZbBqeM6TkpP9du/I2pUZnJsRMGGvOuIrhjzC1AwHwW+6he4mni6Bp/m8ijn0iOuZuPI2BfkCoSRunpyjnrQKg==} + '@babel/parser@7.29.3': + resolution: {integrity: sha512-b3ctpQwp+PROvU/cttc4OYl4MzfJUWy6FZg+PMXfzmt/+39iHVF0sDfqay8TQM3JA2EUOyKcFZt75jWriQijsA==} engines: {node: '>=6.0.0'} hasBin: true @@ -106,8 +114,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-syntax-import-attributes@7.27.1': - resolution: {integrity: sha512-oFT0FrKHgF53f4vOsZGi2Hh3I35PfSmVs4IBFLFj4dnafP+hIWDLg3VyKmUHfLoLHlyxY4C7DGtmHuJgn+IGww==} + '@babel/plugin-syntax-import-attributes@7.28.6': + resolution: {integrity: sha512-jiLC0ma9XkQT3TKJ9uYvlakm66Pamywo+qwL+oL8HJOvc6TWdZXVfhqJr8CCzbSGUAbDOzlGHJC1U+vRfLQDvw==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -122,8 +130,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-syntax-jsx@7.27.1': - resolution: {integrity: sha512-y8YTNIeKoyhGd9O0Jiyzyyqk8gdjnumGTQPsz0xOZOQ2RmkVJeZ1vmmfIvFEKqucBG6axJGBZDE/7iI5suUI/w==} + '@babel/plugin-syntax-jsx@7.28.6': + resolution: {integrity: sha512-wgEmr06G6sIpqr8YDwA2dSRTE3bJ+V0IfpzfSY3Lfgd7YWOaAdlykvJi13ZKBt8cZHfgH1IXN+CL656W3uUa4w==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 @@ -170,38 +178,38 @@ packages: peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/plugin-syntax-typescript@7.27.1': - resolution: {integrity: sha512-xfYCBMxveHrRMnAWl1ZlPXOZjzkN82THFvLhQhFXFt81Z5HnN+EtUkZhv/zcKpmT3fzmWZB0ywiBrbC3vogbwQ==} + '@babel/plugin-syntax-typescript@7.28.6': + resolution: {integrity: sha512-+nDNmQye7nlnuuHDboPbGm00Vqg3oO8niRRL27/4LYHUsHYh0zJ1xWOz0uRwNFmM1Avzk8wZbc6rdiYhomzv/A==} engines: {node: '>=6.9.0'} peerDependencies: '@babel/core': ^7.0.0-0 - '@babel/template@7.27.2': - resolution: {integrity: sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==} + '@babel/template@7.28.6': + resolution: {integrity: sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==} engines: {node: '>=6.9.0'} - '@babel/traverse@7.28.4': - resolution: {integrity: sha512-YEzuboP2qvQavAcjgQNVgsvHIDv6ZpwXvcvjmyySP2DIMuByS/6ioU5G9pYrWHM6T2YDfc7xga9iNzYOs12CFQ==} + '@babel/traverse@7.29.0': + resolution: {integrity: sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==} engines: {node: '>=6.9.0'} - '@babel/types@7.28.4': - resolution: {integrity: sha512-bkFqkLhh3pMBUQQkpVgWDWq/lqzc2678eUyDlTBhRqhCHFguYYGM0Efga7tYk4TogG/3x0EEl66/OQ+WGbWB/Q==} + '@babel/types@7.29.0': + resolution: {integrity: sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==} engines: {node: '>=6.9.0'} '@bcoe/v8-coverage@0.2.3': resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} - '@emnapi/core@1.5.0': - resolution: {integrity: sha512-sbP8GzB1WDzacS8fgNPpHlp6C9VZe+SJP3F90W9rLemaQj2PzIuTEl1qDOYQf58YIpyjViI24y9aPWCjEzY2cg==} + '@emnapi/core@1.10.0': + resolution: {integrity: sha512-yq6OkJ4p82CAfPl0u9mQebQHKPJkY7WrIuk205cTYnYe+k2Z8YBh11FrbRG/H6ihirqcacOgl2BIO8oyMQLeXw==} - '@emnapi/runtime@1.5.0': - resolution: {integrity: sha512-97/BJ3iXHww3djw6hYIfErCZFee7qCtrneuLa20UXFCOTCfBM2cvQHjWJ2EG0s0MtdNwInarqCTz35i4wWXHsQ==} + '@emnapi/runtime@1.10.0': + resolution: {integrity: sha512-ewvYlk86xUoGI0zQRNq/mC+16R1QeDlKQy21Ki3oSYXNgLb45GV1P6A0M+/s6nyCuNDqe5VpaY84BzXGwVbwFA==} - '@emnapi/wasi-threads@1.1.0': - resolution: {integrity: sha512-WI0DdZ8xFSbgMjR1sFsKABJ/C5OnRrjT06JXbZKexJGrDuPTzZdDYfFlsgcCXCyf+suG5QU2e/y1Wo2V/OapLQ==} + '@emnapi/wasi-threads@1.2.1': + resolution: {integrity: sha512-uTII7OYF+/Mes/MrcIOYp5yOtSMLBWSIoLPpcgwipoiKbli6k322tcoFsxoIIxPDqW01SQGAgko4EzZi2BNv2w==} - '@eslint-community/eslint-utils@4.9.0': - resolution: {integrity: sha512-ayVFHdtZ+hsq1t2Dy24wCmGXGe4q9Gu3smhLYALJrr473ZH27MsnSL+LKUlimp4BWJqMDMLmPpx/Q9R3OAlL4g==} + '@eslint-community/eslint-utils@4.9.1': + resolution: {integrity: sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || >=8.0.0 @@ -210,33 +218,25 @@ packages: resolution: {integrity: sha512-EriSTlt5OC9/7SXkRSCAhfSxxoSUgBm33OH+IkwbdpgoqsSsUg7y3uh+IICI/Qg4BBWr3U2i39RpmycbxMq4ew==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} - '@eslint/config-array@0.21.1': - resolution: {integrity: sha512-aw1gNayWpdI/jSYVgzN5pL0cfzU02GT3NBpeT/DXbx1/1x7ZKxFPd9bwrzygx/qiwIQiJ1sw/zD8qY/kRvlGHA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@eslint/config-helpers@0.4.2': - resolution: {integrity: sha512-gBrxN88gOIf3R7ja5K9slwNayVcZgK6SOUORm2uBzTeIEfeVaIhOpCtTox3P6R7o2jLFwLFTLnC7kU/RGcYEgw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/config-array@0.23.5': + resolution: {integrity: sha512-Y3kKLvC1dvTOT+oGlqNQ1XLqK6D1HU2YXPc52NmAlJZbMMWDzGYXMiPRJ8TYD39muD/OTjlZmNJ4ib7dvSrMBA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} - '@eslint/core@0.17.0': - resolution: {integrity: sha512-yL/sLrpmtDaFEiUj1osRP4TI2MDz1AddJL+jZ7KSqvBuliN4xqYY54IfdN8qD8Toa6g1iloph1fxQNkjOxrrpQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/config-helpers@0.5.5': + resolution: {integrity: sha512-eIJYKTCECbP/nsKaaruF6LW967mtbQbsw4JTtSVkUQc9MneSkbrgPJAbKl9nWr0ZeowV8BfsarBmPpBzGelA2w==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} - '@eslint/eslintrc@3.3.1': - resolution: {integrity: sha512-gtF186CXhIl1p4pJNGZw8Yc6RlshoePRvE0X91oPGb3vZ8pM3qOS9W9NGPat9LziaBV7XrJWGylNQXkGcnM3IQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/core@1.2.1': + resolution: {integrity: sha512-MwcE1P+AZ4C6DWlpin/OmOA54mmIZ/+xZuJiQd4SyB29oAJjN30UW9wkKNptW2ctp4cEsvhlLY/CsQ1uoHDloQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} - '@eslint/js@9.39.1': - resolution: {integrity: sha512-S26Stp4zCy88tH94QbBv3XCuzRQiZ9yXofEILmglYTh/Ug/a9/umqvgFtYBAo3Lp0nsI/5/qH1CCrbdK3AP1Tw==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/object-schema@3.0.5': + resolution: {integrity: sha512-vqTaUEgxzm+YDSdElad6PiRoX4t8VGDjCtt05zn4nU810UIx/uNEV7/lZJ6KwFThKZOzOxzXy48da+No7HZaMw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} - '@eslint/object-schema@2.1.7': - resolution: {integrity: sha512-VtAOaymWVfZcmZbp6E2mympDIHvyjXs/12LqWYjVw6qjrfF+VK+fyG33kChz3nnK+SU5/NeHOqrTEHS8sXO3OA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - - '@eslint/plugin-kit@0.4.1': - resolution: {integrity: sha512-43/qtrDUokr7LJqoF2c3+RInu/t4zfrpYdoSDfYyhg52rwLV6TnOvdG4fXm7IkSB3wErkcmJS9iEhjVtOSEjjA==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/plugin-kit@0.7.1': + resolution: {integrity: sha512-rZAP3aVgB9ds9KOeUSL+zZ21hPmo8dh6fnIFwRQj5EAZl9gzR7wxYbYXYysAM8CTqGmUGyp2S4kUdV17MnGuWQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} '@humanfs/core@0.19.1': resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} @@ -262,16 +262,16 @@ packages: resolution: {integrity: sha512-VjeHSlIzpv/NyD3N0YuHfXOPDIixcA1q2ZV98wsMqcYlPmv2n3Yb2lYP9XMElnaFVXg5A7YLTeLu6V84uQDjmQ==} engines: {node: '>=8'} - '@istanbuljs/schema@0.1.3': - resolution: {integrity: sha512-ZXRY4jNvVgSVQ8DL3LTcakaAtXwTVUxE81hslsyD2AtoXW/wVob10HkOJ1X/pAlcI7D+2YoZKg5do8G/w6RYgA==} + '@istanbuljs/schema@0.1.6': + resolution: {integrity: sha512-+Sg6GCR/wy1oSmQDFq4LQDAhm3ETKnorxN+y5nbLULOR3P0c14f2Wurzj3/xqPXtasLFfHd5iRFQ7AJt4KH2cw==} engines: {node: '>=8'} - '@jest/console@30.2.0': - resolution: {integrity: sha512-+O1ifRjkvYIkBqASKWgLxrpEhQAAE7hY77ALLUufSk5717KfOShg6IbqLmdsLMPdUiFvA2kTs0R7YZy+l0IzZQ==} + '@jest/console@30.4.1': + resolution: {integrity: sha512-v3bhyxUh9Hgmo5p6hAOXe14/R3ZxZDOsvHleh4B07z3m/x4/ngPUXEm9XwK4sF4u+f+P2ORb0Ge+MgpaqRMVDA==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - '@jest/core@30.2.0': - resolution: {integrity: sha512-03W6IhuhjqTlpzh/ojut/pDB2LPRygyWX8ExpgHtQA8H/3K7+1vKmcINx5UzeOX1se6YEsBsOHQ1CRzf3fOwTQ==} + '@jest/core@30.4.2': + resolution: {integrity: sha512-TZJA6cPJUFxoWhxaLo8t0VX/MZX2wPWr0uIDvLSHIvN4gu9h02vSzqI2kBADG1ExqQlC+cY09xKMSreivvrChQ==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} peerDependencies: node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 @@ -283,36 +283,48 @@ packages: resolution: {integrity: sha512-n5H8QLDJ47QqbCNn5SuFjCRDrOLEZ0h8vAHCK5RL9Ls7Xa8AQLa/YxAc9UjFqoEDM48muwtBGjtMY5cr0PLDCw==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - '@jest/environment@30.2.0': - resolution: {integrity: sha512-/QPTL7OBJQ5ac09UDRa3EQes4gt1FTEG/8jZ/4v5IVzx+Cv7dLxlVIvfvSVRiiX2drWyXeBjkMSR8hvOWSog5g==} + '@jest/diff-sequences@30.4.0': + resolution: {integrity: sha512-zOpzlfUs45l6u7jm39qr87JCHUDsaeCtvL+kQe/Vn9jSnRB4/5IPXISm0h9I1vZW/o00Kn4UTJ2MOlhnUGwv3g==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/environment@30.4.1': + resolution: {integrity: sha512-AK9yNRqgKxiabqMoe4oW+3/TSSeV8vkdC7BGaxZdU0AFXfOpofTLqdru2GXKZghP3sdgwE9XXpnVwfZ8JnFV4w==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} '@jest/expect-utils@30.2.0': resolution: {integrity: sha512-1JnRfhqpD8HGpOmQp180Fo9Zt69zNtC+9lR+kT7NVL05tNXIi+QC8Csz7lfidMoVLPD3FnOtcmp0CEFnxExGEA==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - '@jest/expect@30.2.0': - resolution: {integrity: sha512-V9yxQK5erfzx99Sf+7LbhBwNWEZ9eZay8qQ9+JSC0TrMR1pMDHLMY+BnVPacWU6Jamrh252/IKo4F1Xn/zfiqA==} + '@jest/expect-utils@30.4.1': + resolution: {integrity: sha512-ZBn5CglH8fBsQsvs4VWNzD4aWfUYks+IdOOQU3MEK71ol/BcVm+P+rtb1KpiFBpSWSCE27uOahyyf1vfqOVbcQ==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/expect@30.4.1': + resolution: {integrity: sha512-ginrj6TMgh2GshLUGCjO94Ptx9HhdZA/I6A9iUfyeLKFtdAjnKzHDgzgP9HYQgbxM1lbXScQ2eUBz2lGeVDPWA==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - '@jest/fake-timers@30.2.0': - resolution: {integrity: sha512-HI3tRLjRxAbBy0VO8dqqm7Hb2mIa8d5bg/NJkyQcOk7V118ObQML8RC5luTF/Zsg4474a+gDvhce7eTnP4GhYw==} + '@jest/fake-timers@30.4.1': + resolution: {integrity: sha512-iW5umdmfPeWzehrVhugFQZqCchSCud5S1l2YT0O9ZhjRR0ExclANDZkiSBwzqtnlOn0J1JXvO+HZ6rkuyOVOgQ==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} '@jest/get-type@30.1.0': resolution: {integrity: sha512-eMbZE2hUnx1WV0pmURZY9XoXPkUYjpc55mb0CrhtdWLtzMQPFvu/rZkTLZFTsdaVQa+Tr4eWAteqcUzoawq/uA==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - '@jest/globals@30.2.0': - resolution: {integrity: sha512-b63wmnKPaK+6ZZfpYhz9K61oybvbI1aMcIs80++JI1O1rR1vaxHUCNqo3ITu6NU0d4V34yZFoHMn/uoKr/Rwfw==} + '@jest/globals@30.4.1': + resolution: {integrity: sha512-ZbuY4cmXC8DkxYjfvT2DbcHWL2T6vmsMhXCDcmTB2T0y0gaezBI77ufq5ZAIdcRkYZ7NEQEDg1xFeKbxUJ5v5Q==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} '@jest/pattern@30.0.1': resolution: {integrity: sha512-gWp7NfQW27LaBQz3TITS8L7ZCQ0TLvtmI//4OwlQRx4rnWxcPNIYjxZpDcN4+UlGxgm3jS5QPz8IPTCkb59wZA==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - '@jest/reporters@30.2.0': - resolution: {integrity: sha512-DRyW6baWPqKMa9CzeiBjHwjd8XeAyco2Vt8XbcLFjiwCOEKOvy82GJ8QQnJE9ofsxCMPjH4MfH8fCWIHHDKpAQ==} + '@jest/pattern@30.4.0': + resolution: {integrity: sha512-RAWn3+f9u8BsHijKJ71uHcFp6vmyEt6VvoWXkl6hKF3qVIuWNmudVjg12DlBPGup/frIl5UcUlH5HfEuvHpEXg==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/reporters@30.4.1': + resolution: {integrity: sha512-/SnkPCzEQpUaBH81kjdEdDdo2WZl5hxw+BmLDGWjRkm8o7XlhjwsU36cqwe5PGBE5WYpBvDzRSdXx9rbGuJtNA==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} peerDependencies: node-notifier: ^8.0.1 || ^9.0.0 || ^10.0.0 @@ -324,30 +336,38 @@ packages: resolution: {integrity: sha512-DmdYgtezMkh3cpU8/1uyXakv3tJRcmcXxBOcO0tbaozPwpmh4YMsnWrQm9ZmZMfa5ocbxzbFk6O4bDPEc/iAnA==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - '@jest/snapshot-utils@30.2.0': - resolution: {integrity: sha512-0aVxM3RH6DaiLcjj/b0KrIBZhSX1373Xci4l3cW5xiUWPctZ59zQ7jj4rqcJQ/Z8JuN/4wX3FpJSa3RssVvCug==} + '@jest/schemas@30.4.1': + resolution: {integrity: sha512-i6b4qw5qnP8c5FEeBJg/uZQ4ddrkN6Ca8qISJh0pr7a5hfn3h3v5x60BEbOC7OYAGZNMs1LfFLwnW2CuK8F57Q==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + '@jest/snapshot-utils@30.4.1': + resolution: {integrity: sha512-ObY4ljvQ95mt6iwKtVLetR/4yXiAgl3H4nJxhztr0MTjrN97TwDYrnCp/kF60Ec9HdhkWTHSu+Hg05aXfngpOA==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} '@jest/source-map@30.0.1': resolution: {integrity: sha512-MIRWMUUR3sdbP36oyNyhbThLHyJ2eEDClPCiHVbrYAe5g3CHRArIVpBw7cdSB5fr+ofSfIb2Tnsw8iEHL0PYQg==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - '@jest/test-result@30.2.0': - resolution: {integrity: sha512-RF+Z+0CCHkARz5HT9mcQCBulb1wgCP3FBvl9VFokMX27acKphwyQsNuWH3c+ojd1LeWBLoTYoxF0zm6S/66mjg==} + '@jest/test-result@30.4.1': + resolution: {integrity: sha512-/ZG7pgEiOmmWkN9TplKbOu4id2N5lh7FHwRwlkgBVAzGdRH+OkkQ8wX/kIxg4zmd3ZQvAL1RwL2yWsvNYYECTw==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - '@jest/test-sequencer@30.2.0': - resolution: {integrity: sha512-wXKgU/lk8fKXMu/l5Hog1R61bL4q5GCdT6OJvdAFz1P+QrpoFuLU68eoKuVc4RbrTtNnTL5FByhWdLgOPSph+Q==} + '@jest/test-sequencer@30.4.1': + resolution: {integrity: sha512-PeYE+4td5rKjoRPxztObrXU+H8hsjZfxKMXOcmrr34JerSyB/ROOxbbicz8B7A5j9R9VayDnVPvBmedqCsFCdw==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - '@jest/transform@30.2.0': - resolution: {integrity: sha512-XsauDV82o5qXbhalKxD7p4TZYYdwcaEXC77PPD2HixEFF+6YGppjrAAQurTl2ECWcEomHBMMNS9AH3kcCFx8jA==} + '@jest/transform@30.4.1': + resolution: {integrity: sha512-Wz0LyktlTvRefoymh+n64hQ84KNXsRGcwdoZ8CSa0Ea+fgYcHZlnk+hDP7v2MS7il2bQ5uTEIxf4/NNfhMN4KQ==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} '@jest/types@30.2.0': resolution: {integrity: sha512-H9xg1/sfVvyfU7o3zMfBEjQ1gcsdeTMgqHoYdN79tuLqfTtuu7WckRA1R5whDwOzxaZAeMKTYWqP+WCAi0CHsg==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + '@jest/types@30.4.1': + resolution: {integrity: sha512-f1x/vJXIfjOlEmejYpbkbgw1gOqpPECwMvMEtBqe47j7H2Hg8h8w3o3ikhSXq3MI15kg+oQ0exWO0uCtTNJLoQ==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + '@jridgewell/gen-mapping@0.3.13': resolution: {integrity: sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==} @@ -375,17 +395,17 @@ packages: resolution: {integrity: sha512-QNqXyfVS2wm9hweSYD2O7F0G06uurj9kZ96TRQE5Y9hU7+tgdZwIkbAKc5Ocy1HxEY2kuDQa6cQ1WRs/O5LFKA==} engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0} - '@sinclair/typebox@0.34.41': - resolution: {integrity: sha512-6gS8pZzSXdyRHTIqoqSVknxolr1kzfy4/CeDnrzsVz8TTIWUbOBr6gnzOmTYJ3eXQNh4IYHIGi5aIL7sOZ2G/g==} + '@sinclair/typebox@0.34.49': + resolution: {integrity: sha512-brySQQs7Jtn0joV8Xh9ZV/hZb9Ozb0pmazDIASBkYKCjXrXU3mpcFahmK/z4YDhGkQvP9mWJbVyahdtU5wQA+A==} '@sinonjs/commons@3.0.1': resolution: {integrity: sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==} - '@sinonjs/fake-timers@13.0.5': - resolution: {integrity: sha512-36/hTbH2uaWuGVERyC6da9YwGWnzUZXuPro/F2LfsdOsLnCojz/iSH8MxUt/FD2S5XBSVPhmArFUXcpCQ2Hkiw==} + '@sinonjs/fake-timers@15.4.0': + resolution: {integrity: sha512-DsG+8/LscQIQg68J6Ef3dv10u6nVyetYn923s3/sus5eaGfTo1of5WMZSLf0UJc9KDuKPilPH0UDJCjvNbDNCA==} - '@tybys/wasm-util@0.10.1': - resolution: {integrity: sha512-9tTaPJLSiejZKx+Bmog4uSubteqTvFrVrURwkmHixBo0G4seD0zUxp98E1DzUBJxLQ3NPwXrGKDiVjwx/DpPsg==} + '@tybys/wasm-util@0.10.2': + resolution: {integrity: sha512-RoBvJ2X0wuKlWFIjrwffGw1IqZHKQqzIchKaadZZfnNpsAYp2mM0h36JtPCjNDAHGgYez/15uMBpfGwchhiMgg==} '@types/babel__core@7.20.5': resolution: {integrity: sha512-qoQprZvz5wQFJwMDqeseRXWv3rqMvhgpbXFfVyWhbx9X47POIA6i/+dXefEmZKoAgOaTdaIgNSMqMIU61yRyzA==} @@ -399,6 +419,9 @@ packages: '@types/babel__traverse@7.28.0': resolution: {integrity: sha512-8PvcXf70gTDZBgt9ptxJ8elBeBjcLOAcOtoO/mPJjtji1+CdGbHgm77om1GrsPxsiE+uXIpNSK64UYaIwQXd4Q==} + '@types/esrecurse@4.3.1': + resolution: {integrity: sha512-xJBAbDifo5hpffDBuHl0Y8ywswbiAp/Wi7Y/GtAgSlZyIABppyurxVueOPE8LUQOxdlgi6Zqce7uoEpqNTeiUw==} + '@types/estree@1.0.8': resolution: {integrity: sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==} @@ -420,17 +443,20 @@ packages: '@types/node@24.7.0': resolution: {integrity: sha512-IbKooQVqUBrlzWTi79E8Fw78l8k1RNtlDDNWsFZs7XonuQSJ8oNYfEeclhprUldXISRMLzBpILuKgPlIxm+/Yw==} + '@types/node@25.6.2': + resolution: {integrity: sha512-sokuT28dxf9JT5Kady1fsXOvI4HVpjZa95NKT5y9PNTIrs2AsobR4GFAA90ZG8M+nxVRLysCXsVj6eGC7Vbrlw==} + '@types/stack-utils@2.0.3': resolution: {integrity: sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==} '@types/yargs-parser@21.0.3': resolution: {integrity: sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==} - '@types/yargs@17.0.33': - resolution: {integrity: sha512-WpxBCKWPLr4xSsHgz511rFJAM+wS28w2zEO1QDNY5zM/S8ok70NNfztH0xwhqKyaK0OHCbN98LDAZuy1ctxDkA==} + '@types/yargs@17.0.35': + resolution: {integrity: sha512-qUHkeCyQFxMXg79wQfTtfndEC+N9ZZg76HJftDJp+qH2tV7Gj4OJi7l+PiWwJ+pWtW8GwSmqsDj/oymhrTWXjg==} - '@ungap/structured-clone@1.3.0': - resolution: {integrity: sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==} + '@ungap/structured-clone@1.3.1': + resolution: {integrity: sha512-mUFwbeTqrVgDQxFveS+df2yfap6iuP20NAKAsBt5jDEoOTDew+zwLAOilHCeQJOVSvmgCX4ogqIrA0mnyr08yQ==} '@unrs/resolver-binding-android-arm-eabi@1.11.1': resolution: {integrity: sha512-ppLRUgHVaGRWUx0R0Ut06Mjo9gBaBkg3v/8AxusGLhsIotbBLuRk51rAzqLC8gq6NyyAojEXglNjzf6R948DNw==} @@ -471,41 +497,49 @@ packages: resolution: {integrity: sha512-34gw7PjDGB9JgePJEmhEqBhWvCiiWCuXsL9hYphDF7crW7UgI05gyBAi6MF58uGcMOiOqSJ2ybEeCvHcq0BCmQ==} cpu: [arm64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-arm64-musl@1.11.1': resolution: {integrity: sha512-RyMIx6Uf53hhOtJDIamSbTskA99sPHS96wxVE/bJtePJJtpdKGXO1wY90oRdXuYOGOTuqjT8ACccMc4K6QmT3w==} cpu: [arm64] os: [linux] + libc: [musl] '@unrs/resolver-binding-linux-ppc64-gnu@1.11.1': resolution: {integrity: sha512-D8Vae74A4/a+mZH0FbOkFJL9DSK2R6TFPC9M+jCWYia/q2einCubX10pecpDiTmkJVUH+y8K3BZClycD8nCShA==} cpu: [ppc64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-riscv64-gnu@1.11.1': resolution: {integrity: sha512-frxL4OrzOWVVsOc96+V3aqTIQl1O2TjgExV4EKgRY09AJ9leZpEg8Ak9phadbuX0BA4k8U5qtvMSQQGGmaJqcQ==} cpu: [riscv64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-riscv64-musl@1.11.1': resolution: {integrity: sha512-mJ5vuDaIZ+l/acv01sHoXfpnyrNKOk/3aDoEdLO/Xtn9HuZlDD6jKxHlkN8ZhWyLJsRBxfv9GYM2utQ1SChKew==} cpu: [riscv64] os: [linux] + libc: [musl] '@unrs/resolver-binding-linux-s390x-gnu@1.11.1': resolution: {integrity: sha512-kELo8ebBVtb9sA7rMe1Cph4QHreByhaZ2QEADd9NzIQsYNQpt9UkM9iqr2lhGr5afh885d/cB5QeTXSbZHTYPg==} cpu: [s390x] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-x64-gnu@1.11.1': resolution: {integrity: sha512-C3ZAHugKgovV5YvAMsxhq0gtXuwESUKc5MhEtjBpLoHPLYM+iuwSj3lflFwK3DPm68660rZ7G8BMcwSro7hD5w==} cpu: [x64] os: [linux] + libc: [glibc] '@unrs/resolver-binding-linux-x64-musl@1.11.1': resolution: {integrity: sha512-rV0YSoyhK2nZ4vEswT/QwqzqQXw5I6CjoaYMOX0TqBlWhojUf8P94mvI7nuJTeaCkkds3QE4+zS8Ko+GdXuZtA==} cpu: [x64] os: [linux] + libc: [musl] '@unrs/resolver-binding-wasm32-wasi@1.11.1': resolution: {integrity: sha512-5u4RkfxJm+Ng7IWgkzi3qrFOvLvQYnPBmjmZQ8+szTK/b31fQCnleNl1GgEt7nIsZRIf5PLhPwT0WM+q45x/UQ==} @@ -532,20 +566,20 @@ packages: peerDependencies: acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 - acorn@8.15.0: - resolution: {integrity: sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==} + acorn@8.16.0: + resolution: {integrity: sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==} engines: {node: '>=0.4.0'} hasBin: true - ajv@6.12.6: - resolution: {integrity: sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==} + ajv@6.14.0: + resolution: {integrity: sha512-IWrosm/yrn43eiKqkfkHis7QioDleaXQHdDVPKg0FSwwd/DuvyX79TZnFOnYpB7dcsFAMmtFztZuXPDvSePkFw==} ansi-escapes@4.3.2: resolution: {integrity: sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==} engines: {node: '>=8'} - ansi-escapes@7.2.0: - resolution: {integrity: sha512-g6LhBsl+GBPRWGWsBtutpzBYuIIdBkLEvad5C/va/74Db018+5TZiyA26cZJAr3Rft5lprVqOIPxf5Vid6tqAw==} + ansi-escapes@7.3.0: + resolution: {integrity: sha512-BvU8nYgGQBxcmMuEeUEmNTvrMVjJNSH7RgW24vXexN4Ven6qCvy4TntnvlnwnMLTVlcRQQdbRY8NKnaIoeWDNg==} engines: {node: '>=18'} ansi-regex@5.0.1: @@ -575,11 +609,8 @@ packages: argparse@1.0.10: resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} - argparse@2.0.1: - resolution: {integrity: sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==} - - babel-jest@30.2.0: - resolution: {integrity: sha512-0YiBEOxWqKkSQWL9nNGGEgndoeL0ZpWrbLMNL5u/Kaxrli3Eaxlt3ZtIDktEvXt4L/R9r3ODr2zKwGM/2BjxVw==} + babel-jest@30.4.1: + resolution: {integrity: sha512-fATAbM8piYxkiXQp3RBXmZHxZVNJZAVXXfyeyCN2Tida3+qJ8ea9UxhiJ2y4fLO90ZImKt6k9FlcH2+rLkJGhw==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} peerDependencies: '@babel/core': ^7.11.0 || ^8.0.0-0 @@ -588,8 +619,8 @@ packages: resolution: {integrity: sha512-D8Z6Qm8jCvVXtIRkBnqNHX0zJ37rQcFJ9u8WOS6tkYOsRdHBzypCstaxWiu5ZIlqQtviRYbgnRLSoCEvjqcqbA==} engines: {node: '>=12'} - babel-plugin-jest-hoist@30.2.0: - resolution: {integrity: sha512-ftzhzSGMUnOzcCXd6WHdBGMyuwy15Wnn0iyyWGKgBDLxf9/s5ABuraCSpBX2uG0jUg4rqJnxsLc5+oYBqoxVaA==} + babel-plugin-jest-hoist@30.4.0: + resolution: {integrity: sha512-9EdtWM/sSfXLOGLwSn+GS6pIXyBnL07/8gyJlwFXjWy4DxMOyItqyUT29d4lQiS380EZwYlX7/At4PgBS+m2aA==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} babel-preset-current-node-syntax@1.2.0: @@ -597,8 +628,8 @@ packages: peerDependencies: '@babel/core': ^7.0.0 || ^8.0.0-0 - babel-preset-jest@30.2.0: - resolution: {integrity: sha512-US4Z3NOieAQumwFnYdUWKvUKh8+YSnS/gB3t6YBiz0bskpu7Pine8pPCheNxlPEW4wnUkma2a94YuW2q3guvCQ==} + babel-preset-jest@30.4.0: + resolution: {integrity: sha512-lBY4jxsNmCnSiu7kquw8ZC9F4+XLMOKypT3RnNHPvU2Kpd4W0xaPuLr5ZkRyOsvLYAY4yaW1ZwTW4xB7NIiZzg==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} peerDependencies: '@babel/core': ^7.11.0 || ^8.0.0-beta.1 @@ -606,28 +637,40 @@ packages: balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} - baseline-browser-mapping@2.8.12: - resolution: {integrity: sha512-vAPMQdnyKCBtkmQA6FMCBvU9qFIppS3nzyXnEM+Lo2IAhG4Mpjv9cCxMudhgV3YdNNJv6TNqXy97dfRVL2LmaQ==} + balanced-match@4.0.4: + resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} + engines: {node: 18 || 20 || >=22} + + baseline-browser-mapping@2.10.27: + resolution: {integrity: sha512-zEs/ufmZoUd7WftKpKyXaT6RFxpQ5Qm9xytKRHvJfxFV9DFJkZph9RvJ1LcOUi0Z1ZVijMte65JbILeV+8QQEA==} + engines: {node: '>=6.0.0'} hasBin: true brace-expansion@1.1.12: resolution: {integrity: sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==} - brace-expansion@2.0.2: - resolution: {integrity: sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==} + brace-expansion@2.1.0: + resolution: {integrity: sha512-TN1kCZAgdgweJhWWpgKYrQaMNHcDULHkWwQIspdtjV4Y5aurRdZpjAqn6yX3FPqTA9ngHCc4hJxMAMgGfve85w==} + + brace-expansion@5.0.6: + resolution: {integrity: sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==} + engines: {node: 18 || 20 || >=22} braces@3.0.3: resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==} engines: {node: '>=8'} - browserslist@4.26.3: - resolution: {integrity: sha512-lAUU+02RFBuCKQPj/P6NgjlbCnLBMp4UtgTx7vNHd3XSIJF87s9a5rA3aH2yw3GS9DqZAUbOtZdCCiZeVRqt0w==} + browserslist@4.28.2: + resolution: {integrity: sha512-48xSriZYYg+8qXna9kwqjIVzuQxi+KYWp2+5nCYnYKPTr0LvD89Jqk2Or5ogxz0NUMfIjhh2lIUX/LyX9B4oIg==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} hasBin: true bser@2.1.1: resolution: {integrity: sha512-gQxTNE/GAfIIrmHLUE3oJyp5FO6HRBfhjnw4/wMmA63ZGDJnWBmgY/lyQBpnDUkGmAhbSe39tx2d/iTOAfglwQ==} + buffer-equal-constant-time@1.0.1: + resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==} + buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} @@ -647,8 +690,8 @@ packages: resolution: {integrity: sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==} engines: {node: '>=10'} - caniuse-lite@1.0.30001748: - resolution: {integrity: sha512-5P5UgAr0+aBmNiplks08JLw+AW/XG/SurlgZLgB1dDLfAw7EfRGxIwzPHxdSCGY/BTKDqIVyJL87cCN6s0ZR0w==} + caniuse-lite@1.0.30001792: + resolution: {integrity: sha512-hVLMUZFgR4JJ6ACt1uEESvQN1/dBVqPAKY0hgrV70eN3391K6juAfTjKZLKvOMsx8PxA7gsY1/tLMMTcfFLLpw==} chalk@4.1.2: resolution: {integrity: sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==} @@ -662,16 +705,20 @@ packages: resolution: {integrity: sha512-Wdy2Igu8OcBpI2pZePZ5oWjPC38tmDVx5WKUXKwlLYkA0ozo85sLsLvkBbBn/sZaSCMFOGZJ14fvW9t5/d7kdA==} engines: {node: '>=8'} - cjs-module-lexer@2.1.0: - resolution: {integrity: sha512-UX0OwmYRYQQetfrLEZeewIFFI+wSTofC+pMBLNuH3RUuu/xzG1oz84UCEDOSoQlN3fZ4+AzmV50ZYvGqkMh9yA==} + ci-info@4.4.0: + resolution: {integrity: sha512-77PSwercCZU2Fc4sX94eF8k8Pxte6JAwL4/ICZLFjJLqegs7kCuAsqqj/70NQF6TvDpgFjkubQB2FW2ZZddvQg==} + engines: {node: '>=8'} + + cjs-module-lexer@2.2.0: + resolution: {integrity: sha512-4bHTS2YuzUvtoLjdy+98ykbNB5jS0+07EvFNXerqZQJ89F7DI6ET7OQo/HJuW6K0aVsKA9hj9/RVb2kQVOrPDQ==} cli-cursor@5.0.0: resolution: {integrity: sha512-aCj4O5wKyszjMmDT4tZj93kxyydN/K5zPWSCe6/0AV/AA1pqe5ZBIw0a2ZfPQV7lL5/yb5HsUreJ6UFAF1tEQw==} engines: {node: '>=18'} - cli-truncate@4.0.0: - resolution: {integrity: sha512-nPdaFdQ0h/GEigbPClz11D0v/ZJEwxmeVZGeMo3Z5StPtUTkA9o1lD6QwoirYiSDzbcwn2XcjwmCp68W1IS4TA==} - engines: {node: '>=18'} + cli-truncate@5.2.0: + resolution: {integrity: sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw==} + engines: {node: '>=20'} cliui@8.0.1: resolution: {integrity: sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==} @@ -681,8 +728,8 @@ packages: resolution: {integrity: sha512-QVb0dM5HvG+uaxitm8wONl7jltx8dqhfU33DcqtOZcLSVIKSDDLDi7+0LbAKiyI8hD9u42m2YxXSkMGWThaecQ==} engines: {iojs: '>= 1.0.0', node: '>= 0.12.0'} - collect-v8-coverage@1.0.2: - resolution: {integrity: sha512-lHl4d5/ONEbLlJvaJNtsF/Lz+WvB07u2ycqTYbdrq7UypDXailES4valYb2eWiJFxZlVmpGekfqoxQhzyFdT4Q==} + collect-v8-coverage@1.0.3: + resolution: {integrity: sha512-1L5aqIkwPfiodaMgQunkF1zRhNqifHBmtbbbxcr6yVxxBnliw4TDOW6NxpO8DJLgJ16OT+Y4ztZqP6p/FtXnAw==} color-convert@2.0.1: resolution: {integrity: sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==} @@ -691,9 +738,6 @@ packages: color-name@1.1.4: resolution: {integrity: sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==} - colorette@2.0.20: - resolution: {integrity: sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==} - concat-map@0.0.1: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} @@ -717,8 +761,8 @@ packages: supports-color: optional: true - dedent@1.7.0: - resolution: {integrity: sha512-HGFtf8yhuhGhqO07SV79tRp+br4MnbdjeVxotpn1QBl30pcLLCQjX5b2295ll0fv8RKDKsmWYrl05usHM9CewQ==} + dedent@1.7.2: + resolution: {integrity: sha512-WzMx3mW98SN+zn3hgemf4OzdmyNhhhKz5Ay0pUfQiMQ3e1g+xmTJWp/pKdwKVXhdSkAEGIIzqeuWrL3mV/AXbA==} peerDependencies: babel-plugin-macros: ^3.1.0 peerDependenciesMeta: @@ -739,15 +783,18 @@ packages: resolution: {integrity: sha512-TLz+x/vEXm/Y7P7wn1EJFNLxYpUD4TgMosxY6fAVJUnJMbupHBOncxyWUG9OpTaH9EBD7uFI5LfEgmMOc54DsA==} engines: {node: '>=8'} - dotenv@17.2.3: - resolution: {integrity: sha512-JVUnt+DUIzu87TABbhPmNfVdBDt18BLOWjMUFJMSi/Qqg7NTYtabbvSNJGOJ7afbRuv9D/lngizHtP7QyLQ+9w==} + dotenv@17.4.2: + resolution: {integrity: sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==} engines: {node: '>=12'} eastasianwidth@0.2.0: resolution: {integrity: sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==} - electron-to-chromium@1.5.232: - resolution: {integrity: sha512-ENirSe7wf8WzyPCibqKUG1Cg43cPaxH4wRR7AJsX7MCABCHBIOFqvaYODSLKUuZdraxUTHRE/0A2Aq8BYKEHOg==} + ecdsa-sig-formatter@1.0.11: + resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==} + + electron-to-chromium@1.5.352: + resolution: {integrity: sha512-9wHk8x6dyuimoe18EdiDPWKExNdxYqo4fn4FwOVVper6RxT3cmpBwBkWWfSOCYJjQdIco/nPhJhNLmn4Ufg1Yg==} emittery@0.13.1: resolution: {integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==} @@ -781,21 +828,21 @@ packages: resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==} engines: {node: '>=10'} - eslint-scope@8.4.0: - resolution: {integrity: sha512-sNXOfKCn74rt8RICKMvJS7XKV/Xk9kA7DyJr8mJik3S7Cwgy3qlkkmyS2uQB3jiJg6VNdZd/pDBJu0nvG2NlTg==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + eslint-scope@9.1.2: + resolution: {integrity: sha512-xS90H51cKw0jltxmvmHy2Iai1LIqrfbw57b79w/J7MfvDfkIkFZ+kj6zC3BjtUwh150HsSSdxXZcsuv72miDFQ==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} eslint-visitor-keys@3.4.3: resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - eslint-visitor-keys@4.2.1: - resolution: {integrity: sha512-Uhdk5sfqcee/9H/rCOJikYz67o0a2Tw2hGRPOG2Y1R2dg7brRe1uG0yaNQDHu+TO/uQPF/5eCapvYSmHUjt7JQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + eslint-visitor-keys@5.0.1: + resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} - eslint@9.39.1: - resolution: {integrity: sha512-BhHmn2yNOFA9H9JmmIVKJmd288g9hrVRDkdoIgRCRuSySRUHH7r/DI6aAXW9T1WwUuY3DFgrcaqB+deURBLR5g==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + eslint@10.3.0: + resolution: {integrity: sha512-XbEXaRva5cF0ZQB8w6MluHA0kZZfV2DuCMJ3ozyEOHLwDpZX2Lmm/7Pp0xdJmI0GL1W05VH5VwIFHEm1Vcw2gw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} hasBin: true peerDependencies: jiti: '*' @@ -803,17 +850,17 @@ packages: jiti: optional: true - espree@10.4.0: - resolution: {integrity: sha512-j6PAQ2uUr79PZhBjP5C5fhl8e39FmRnOjsD5lGnWrFU8i2G776tBK7+nP8KuQUTTyAZUwfQqXAgrVH5MbH9CYQ==} - engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + espree@11.2.0: + resolution: {integrity: sha512-7p3DrVEIopW1B1avAGLuCSh1jubc01H2JHc8B4qqGblmg5gI9yumBgACjWo4JlIc04ufug4xJ3SQI8HkS/Rgzw==} + engines: {node: ^20.19.0 || ^22.13.0 || >=24} esprima@4.0.1: resolution: {integrity: sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==} engines: {node: '>=4'} hasBin: true - esquery@1.6.0: - resolution: {integrity: sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==} + esquery@1.7.0: + resolution: {integrity: sha512-Ap6G0WQwcU/LHsvLwON1fAQX9Zp0A2Y6Y/cJBl9r/JbW90Zyg4/zbG6zzKa2OTALELarYHmKu0GhpM5EO+7T0g==} engines: {node: '>=0.10'} esrecurse@4.3.0: @@ -828,8 +875,8 @@ packages: resolution: {integrity: sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==} engines: {node: '>=0.10.0'} - eventemitter3@5.0.1: - resolution: {integrity: sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==} + eventemitter3@5.0.4: + resolution: {integrity: sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==} execa@5.1.1: resolution: {integrity: sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==} @@ -843,6 +890,10 @@ packages: resolution: {integrity: sha512-u/feCi0GPsI+988gU2FLcsHyAHTU0MX1Wg68NhAnN7z/+C5wqG+CY8J53N9ioe8RXgaoz0nBR/TYMf3AycUuPw==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + expect@30.4.1: + resolution: {integrity: sha512-PMARsyh/JtqC20HoGqlFcIlQAyqUtW4PlI1rup1uhYJtKuwAjbvWi3GQMAn+STdHum/dk8xrKfUM1+5SAwpolA==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} @@ -875,8 +926,8 @@ packages: resolution: {integrity: sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==} engines: {node: '>=16'} - flatted@3.3.3: - resolution: {integrity: sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==} + flatted@3.4.1: + resolution: {integrity: sha512-IxfVbRFVlV8V/yRaGzk0UVIcsKKHMSfYw66T/u4nTwlWteQePsxe//LjudR1AMX4tZW3WFCh3Zqa/sjlqpbURQ==} foreground-child@3.3.1: resolution: {integrity: sha512-gIXjKqtFuWEgzFRJA9WCQeSJLZDjgJUOMCMzxtvFq/37KojM1BFGufqsCy0r4qSQmYLsZYMeyRqzIWOMup03sw==} @@ -906,8 +957,8 @@ packages: resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==} engines: {node: 6.* || 8.* || >= 10.*} - get-east-asian-width@1.4.0: - resolution: {integrity: sha512-QZjmEOC+IT1uk6Rx0sX22V6uHWVwbdbxf1faPqJ1QhLdGgsRGCZoyaQBm/piRdJy/D2um6hM1UP7ZEeQ4EkP+Q==} + get-east-asian-width@1.5.0: + resolution: {integrity: sha512-CQ+bEO+Tva/qlmw24dCejulK5pMzVnUOFOijVogd3KQs07HnRIgp8TGipvCCRT06xeYEbpbgwaCxglFyiuIcmA==} engines: {node: '>=18'} get-package-type@0.1.0: @@ -922,17 +973,14 @@ packages: resolution: {integrity: sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==} engines: {node: '>=10.13.0'} - glob@10.4.5: - resolution: {integrity: sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==} + glob@10.5.0: + resolution: {integrity: sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me hasBin: true glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} - deprecated: Glob versions prior to v9 are no longer supported - - globals@14.0.0: - resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} - engines: {node: '>=18'} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me graceful-fs@4.2.11: resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==} @@ -952,10 +1000,6 @@ packages: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} - import-fresh@3.3.1: - resolution: {integrity: sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==} - engines: {node: '>=6'} - import-local@3.2.0: resolution: {integrity: sha512-2SPlun1JUPWoM6t3F0dw0FkCF/jWY8kttcY4f599GLTSjh2OCuuhdTkJQsEcZzBqbXZGKMK2OqW1oZsjtf/gQA==} engines: {node: '>=8'} @@ -983,10 +1027,6 @@ packages: resolution: {integrity: sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==} engines: {node: '>=8'} - is-fullwidth-code-point@4.0.0: - resolution: {integrity: sha512-O4L094N2/dZ7xqVdrXhh9r1KODPJpFms8B5sGdJLPy664AgvXsreZUyCQQNItZRDlYug4xStLjNp/sz3HvBowQ==} - engines: {node: '>=12'} - is-fullwidth-code-point@5.1.0: resolution: {integrity: sha512-5XHYaSyiqADb4RnZ1Bdad6cPp8Toise4TzEjcOYDHZkTCbKgiUl7WTUCpNWHuxmDt91wnsZBc9xinNzopv3JMQ==} engines: {node: '>=18'} @@ -1033,16 +1073,16 @@ packages: jackspeak@3.4.3: resolution: {integrity: sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw==} - jest-changed-files@30.2.0: - resolution: {integrity: sha512-L8lR1ChrRnSdfeOvTrwZMlnWV8G/LLjQ0nG9MBclwWZidA2N5FviRki0Bvh20WRMOX31/JYvzdqTJrk5oBdydQ==} + jest-changed-files@30.4.1: + resolution: {integrity: sha512-IuctmYrxi21iOSOaIXpJWalHyPAsVv0GeBHKDn8C1CA4W5htHn7INL+wdnL4Bo0+olEndvAFkmb++tIQJG+vvg==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - jest-circus@30.2.0: - resolution: {integrity: sha512-Fh0096NC3ZkFx05EP2OXCxJAREVxj1BcW/i6EWqqymcgYKWjyyDpral3fMxVcHXg6oZM7iULer9wGRFvfpl+Tg==} + jest-circus@30.4.2: + resolution: {integrity: sha512-rvHH7VlY6LgbJXJTQ87GW62g1FntOtbhh0zT+v04kC+pgL6aBKyYINXxWukCpj3dcIBMw5/XUbtDS9dU9JTXeQ==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - jest-cli@30.2.0: - resolution: {integrity: sha512-Os9ukIvADX/A9sLt6Zse3+nmHtHaE6hqOsjQtNiugFTbKRHYIYtZXNGNK9NChseXy7djFPjndX1tL0sCTlfpAA==} + jest-cli@30.4.2: + resolution: {integrity: sha512-jfA2ocvVHMXS2QijrJ0d31ektP+d/W0T5RpcTX2Pq+3sVqHlsXVCM2+FmwpL+bdY8OfHpIg9xMxLF17Zg0U49Q==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} hasBin: true peerDependencies: @@ -1051,8 +1091,8 @@ packages: node-notifier: optional: true - jest-config@30.2.0: - resolution: {integrity: sha512-g4WkyzFQVWHtu6uqGmQR4CQxz/CH3yDSlhzXMWzNjDx843gYjReZnMRanjRCq5XZFuQrGDxgUaiYWE8BRfVckA==} + jest-config@30.4.2: + resolution: {integrity: sha512-rNHAShJQqQwFNoL0hbf3BphSBOWnpOUAKvidLS/AjNVLPfoj5mSf4jQMfW3cYOs6hXeZC7nF7mDHaBnbxELOzg==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} peerDependencies: '@types/node': '*' @@ -1070,38 +1110,54 @@ packages: resolution: {integrity: sha512-dQHFo3Pt4/NLlG5z4PxZ/3yZTZ1C7s9hveiOj+GCN+uT109NC2QgsoVZsVOAvbJ3RgKkvyLGXZV9+piDpWbm6A==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - jest-docblock@30.2.0: - resolution: {integrity: sha512-tR/FFgZKS1CXluOQzZvNH3+0z9jXr3ldGSD8bhyuxvlVUwbeLOGynkunvlTMxchC5urrKndYiwCFC0DLVjpOCA==} + jest-diff@30.4.1: + resolution: {integrity: sha512-CRpFK0RtLriVDGcPPAnR6HMVI8bSR2jnUIgralhauzYQZIb4RH9AtEInTuQr65LmmGggGcRT6HIASxwqsVsmlA==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - jest-each@30.2.0: - resolution: {integrity: sha512-lpWlJlM7bCUf1mfmuqTA8+j2lNURW9eNafOy99knBM01i5CQeY5UH1vZjgT9071nDJac1M4XsbyI44oNOdhlDQ==} + jest-docblock@30.4.0: + resolution: {integrity: sha512-ZPMabUZCx5MpbZ2eBYSvZ0J8fvo3dR9oM+eeUpb3aKNQFuS2tu3Duw1TNlMoP8k3WQgKGJuhcMFvwcVuq6T7oA==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - jest-environment-node@30.2.0: - resolution: {integrity: sha512-ElU8v92QJ9UrYsKrxDIKCxu6PfNj4Hdcktcn0JX12zqNdqWHB0N+hwOnnBBXvjLd2vApZtuLUGs1QSY+MsXoNA==} + jest-each@30.4.1: + resolution: {integrity: sha512-/8MJbH6fuj48TstjrMf+u/pd06Qezz5xOXvZA6442heNOWr8bdeoGZX2d9fCn028CoMgYmroH9//zky5GfyYmA==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - jest-haste-map@30.2.0: - resolution: {integrity: sha512-sQA/jCb9kNt+neM0anSj6eZhLZUIhQgwDt7cPGjumgLM4rXsfb9kpnlacmvZz3Q5tb80nS+oG/if+NBKrHC+Xw==} + jest-environment-node@30.4.1: + resolution: {integrity: sha512-4FZYVOk85hz2AyT6BbarKy9u37g6DbrDyCdFhsnDdXqyrueYQvB+0zO4f/kqLCRD0BsPRXPMNJeQwihKZV8naw==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - jest-leak-detector@30.2.0: - resolution: {integrity: sha512-M6jKAjyzjHG0SrQgwhgZGy9hFazcudwCNovY/9HPIicmNSBuockPSedAP9vlPK6ONFJ1zfyH/M2/YYJxOz5cdQ==} + jest-haste-map@30.4.1: + resolution: {integrity: sha512-rFrcONd8jeFsyw+Z9CrScJgglRf2+NFmNam8dKu7n+SoHqNYT47mn0DdEcVUZJpvh7Iz6/si7f7yUH7GJHVgnw==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-leak-detector@30.4.1: + resolution: {integrity: sha512-IpmyiioeHxiWDhesHnUFmOxcTzwCwKpgACgWajtAP+nYQXiY7DakTxB6Bx9JFiRMljr0AX1PvnQdaU1KFoz6NQ==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} jest-matcher-utils@30.2.0: resolution: {integrity: sha512-dQ94Nq4dbzmUWkQ0ANAWS9tBRfqCrn0bV9AMYdOi/MHW726xn7eQmMeRTpX2ViC00bpNaWXq+7o4lIQ3AX13Hg==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + jest-matcher-utils@30.4.1: + resolution: {integrity: sha512-zvYfX5CaeEkFrrLS9suWe9rvJrm9J1Iv3ua8kIBv9GEPzcnsfBf0bob37la7s67fs0nlBC3EuvkOLnXQKxtx4A==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + jest-message-util@30.2.0: resolution: {integrity: sha512-y4DKFLZ2y6DxTWD4cDe07RglV88ZiNEdlRfGtqahfbIjfsw1nMCPx49Uev4IA/hWn3sDKyAnSPwoYSsAEdcimw==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + jest-message-util@30.4.1: + resolution: {integrity: sha512-kwCKIvq0MCW1HzLoGola9Te6JUdzgV0loyKJ3Qghrkz9i5/RRIHsL95BMQc2HBBhlBKC4j22K9p11TGHH8RBpQ==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + jest-mock@30.2.0: resolution: {integrity: sha512-JNNNl2rj4b5ICpmAcq+WbLH83XswjPbjH4T7yvGzfAGCPh1rw+xVNbtk+FnRslvt9lkCcdn9i1oAoKUuFsOxRw==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + jest-mock@30.4.1: + resolution: {integrity: sha512-/i8SVb8/NSB7RfNi8gfqu8gxLV23KaL5EpAttyb9iz8qWRIqXRLflycz/32wXsYkOnaUlx8NAKnJYtpsmXUmfw==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + jest-pnp-resolver@1.2.3: resolution: {integrity: sha512-+3NpwQEnRoIBtx4fyhblQDPgJI0H1IEIkX7ShLUjPGA7TtUTvI1oiKi3SR4oBR0hQhQR80l4WAe5RrXBwWMA8w==} engines: {node: '>=6'} @@ -1115,44 +1171,52 @@ packages: resolution: {integrity: sha512-jHEQgBXAgc+Gh4g0p3bCevgRCVRkB4VB70zhoAE48gxeSr1hfUOsM/C2WoJgVL7Eyg//hudYENbm3Ne+/dRVVA==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - jest-resolve-dependencies@30.2.0: - resolution: {integrity: sha512-xTOIGug/0RmIe3mmCqCT95yO0vj6JURrn1TKWlNbhiAefJRWINNPgwVkrVgt/YaerPzY3iItufd80v3lOrFJ2w==} + jest-regex-util@30.4.0: + resolution: {integrity: sha512-mWlvLviKIgIQ8VCuM1xRdD0TWp3zlzionlmDBjuXVBs+VkmXq6FgW9T4Emr7oGz/Rk6feDCGyiugolcQEyp3mg==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-resolve-dependencies@30.4.2: + resolution: {integrity: sha512-gDiVh1I+GxYzz9oXlyw+1wv6VOYX1WYxMOfjsA3iGKePV2oxmbHhwxfkALxNxYy1ciw6APWwkW2zZONwP97aEQ==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - jest-resolve@30.2.0: - resolution: {integrity: sha512-TCrHSxPlx3tBY3hWNtRQKbtgLhsXa1WmbJEqBlTBrGafd5fiQFByy2GNCEoGR+Tns8d15GaL9cxEzKOO3GEb2A==} + jest-resolve@30.4.1: + resolution: {integrity: sha512-Zry8Yq/yJcNAZ7dJ5F2heic8AheXvbFZ7XI5V+h28nrYZ7Qoyy4dItq8OodjnYD270mvX+ZudmrNV9cysqhW5Q==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - jest-runner@30.2.0: - resolution: {integrity: sha512-PqvZ2B2XEyPEbclp+gV6KO/F1FIFSbIwewRgmROCMBo/aZ6J1w8Qypoj2pEOcg3G2HzLlaP6VUtvwCI8dM3oqQ==} + jest-runner@30.4.2: + resolution: {integrity: sha512-2dw0PslVYXxffXGpLo+Ejad+KcI1Qkjn7f4X4619gf21oCUmL+SPfjqIa/losUem3yEOvfNZe/F1HWUcNpODcg==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - jest-runtime@30.2.0: - resolution: {integrity: sha512-p1+GVX/PJqTucvsmERPMgCPvQJpFt4hFbM+VN3n8TMo47decMUcJbt+rgzwrEme0MQUA/R+1de2axftTHkKckg==} + jest-runtime@30.4.2: + resolution: {integrity: sha512-3/5e8iPz2k/VLqlr8DgTftYyLUv8Su3FkCAO2/Od81UsUTpSxOrS6O5x5KkoQwyUjmpYyDJKeyAvg2T2nvpNkQ==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - jest-snapshot@30.2.0: - resolution: {integrity: sha512-5WEtTy2jXPFypadKNpbNkZ72puZCa6UjSr/7djeecHWOu7iYhSXSnHScT8wBz3Rn8Ena5d5RYRcsyKIeqG1IyA==} + jest-snapshot@30.4.1: + resolution: {integrity: sha512-tEOkkfOMppUyeiHwjZswOQ3lcnoTnws/q5FnGIaeIh/jmoU0ZlgMYRR8sTlTj+nNGCoJ0RDq6SfxGxCsyMTPmw==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} jest-util@30.2.0: resolution: {integrity: sha512-QKNsM0o3Xe6ISQU869e+DhG+4CK/48aHYdJZGlFQVTjnbvgpcKyxpzk29fGiO7i/J8VENZ+d2iGnSsvmuHywlA==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - jest-validate@30.2.0: - resolution: {integrity: sha512-FBGWi7dP2hpdi8nBoWxSsLvBFewKAg0+uSQwBaof4Y4DPgBabXgpSYC5/lR7VmnIlSpASmCi/ntRWPbv7089Pw==} + jest-util@30.4.1: + resolution: {integrity: sha512-vjQb1sACEiv13DKJMDToJpzVW0joCsIQrmbg0fi7CyOOt+g9jTuQl2A216pWRBYhOVt53XbL/2LbMKg1BECWOw==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + jest-validate@30.4.1: + resolution: {integrity: sha512-PDWi4SOwLnwqNDfHZjOcsEFyZ4fc/2W2gVL3DEoyqnB6jCQMLRtfBong8s6omIw3lI0HWOus12xfnFmQtjW3fw==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - jest-watcher@30.2.0: - resolution: {integrity: sha512-PYxa28dxJ9g777pGm/7PrbnMeA0Jr7osHP9bS7eJy9DuAjMgdGtxgf0uKMyoIsTWAkIbUW5hSDdJ3urmgXBqxg==} + jest-watcher@30.4.1: + resolution: {integrity: sha512-/l9UonmvCwjHH7d2h3iAwIloLc1H0S8mJZ/LNK3i86hqwPAz8otUJjP9MfYtz9Tt77Su5FD2xGjZn8d31IZHlw==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - jest-worker@30.2.0: - resolution: {integrity: sha512-0Q4Uk8WF7BUwqXHuAjc23vmopWJw5WH7w2tqBoUOZpOjW/ZnR44GXXd1r82RvnmI2GZge3ivrYXk/BE2+VtW2g==} + jest-worker@30.4.1: + resolution: {integrity: sha512-SHynN/q/QD++iNyvMdy+WMmbCGk8jIsNcRxycXbWubSOhvo6T+j2afcfUSl+3hYsiBebOTo0cT7c2H7CXugu1g==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - jest@30.2.0: - resolution: {integrity: sha512-F26gjC0yWN8uAA5m5Ss8ZQf5nDHWGlN/xWZIh8S5SRbsEKBovwZhxGd6LJlbZYxBgCYOtreSUyb8hpXyGC5O4A==} + jest@30.4.2: + resolution: {integrity: sha512-Yi1jqNC/Oq0N4hBgNH/YvBpP1P57QqundgytzYqy3yqAa7NZPNjSoi4SGbRAXDMdBzNE6xBCi5U7RgfrvMEUVQ==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} hasBin: true peerDependencies: @@ -1168,10 +1232,6 @@ packages: resolution: {integrity: sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==} hasBin: true - js-yaml@4.1.1: - resolution: {integrity: sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==} - hasBin: true - jsesc@3.1.0: resolution: {integrity: sha512-/sM3dO2FOzXjKQhJuo0Q173wf2KOo8t4I8vHy6lF9poUp7bKT0/NHE8fPX23PwfhnykfqnC2xRxOnVw5XuGIaA==} engines: {node: '>=6'} @@ -1194,6 +1254,16 @@ packages: engines: {node: '>=6'} hasBin: true + jsonwebtoken@9.0.3: + resolution: {integrity: sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==} + engines: {node: '>=12', npm: '>=6'} + + jwa@2.0.1: + resolution: {integrity: sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==} + + jws@4.0.1: + resolution: {integrity: sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==} + keyv@4.5.4: resolution: {integrity: sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==} @@ -1208,9 +1278,9 @@ packages: lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} - listr2@8.3.3: - resolution: {integrity: sha512-LWzX2KsqcB1wqQ4AHgYb4RsDXauQiqhjLk+6hjbaeHG4zpjjVAB6wC/gz6X0l+Du1cN3pUB5ZlrvTbhGSNnUQQ==} - engines: {node: '>=18.0.0'} + listr2@10.2.1: + resolution: {integrity: sha512-7I5knELsJKTUjXG+A6BkKAiGkW1i25fNa/xlUl9hFtk15WbE9jndA89xu5FzQKrY5llajE1hfZZFMILXkDHk/Q==} + engines: {node: '>=22.13.0'} locate-path@5.0.0: resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==} @@ -1220,8 +1290,26 @@ packages: resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==} engines: {node: '>=10'} - lodash.merge@4.6.2: - resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} + lodash.includes@4.3.0: + resolution: {integrity: sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==} + + lodash.isboolean@3.0.3: + resolution: {integrity: sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==} + + lodash.isinteger@4.0.4: + resolution: {integrity: sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==} + + lodash.isnumber@3.0.3: + resolution: {integrity: sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==} + + lodash.isplainobject@4.0.6: + resolution: {integrity: sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==} + + lodash.isstring@4.0.1: + resolution: {integrity: sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==} + + lodash.once@4.1.1: + resolution: {integrity: sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==} log-update@6.1.0: resolution: {integrity: sha512-9ie8ItPR6tjY5uYJh8K/Zrv/RMZ5VOlOWvtZdEHYSTFKZfIBPQa9tOAEeAWhd+AnIneLJ22w5fjOYtoutpWq5w==} @@ -1255,15 +1343,19 @@ packages: resolution: {integrity: sha512-VP79XUPxV2CigYP3jWwAUFSku2aKqBH7uTAapFWCBqutsbmDo96KY5o8uh6U+/YSIn5OxJnXp73beVkpqMIGhA==} engines: {node: '>=18'} - minimatch@3.1.2: - resolution: {integrity: sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==} + minimatch@10.2.5: + resolution: {integrity: sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==} + engines: {node: 18 || 20 || >=22} + + minimatch@3.1.5: + resolution: {integrity: sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==} - minimatch@9.0.5: - resolution: {integrity: sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==} + minimatch@9.0.9: + resolution: {integrity: sha512-OBwBN9AL4dqmETlpS2zasx+vTeWclWzkblfZk7KTA5j3jeOONz/tRCnZomUyvNg83wL5Zv9Ss6HMJXAgL8R2Yg==} engines: {node: '>=16 || 14 >=14.17'} - minipass@7.1.2: - resolution: {integrity: sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw==} + minipass@7.1.3: + resolution: {integrity: sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==} engines: {node: '>=16 || 14 >=14.17'} ms@2.1.3: @@ -1283,8 +1375,8 @@ packages: node-int64@0.4.0: resolution: {integrity: sha512-O5lz91xSOeoXP6DulyHfllpq+Eg00MWitZIbtPfoSEvqIHdl5gfcY6hYzDWnj0qD5tz52PI08u9qUvSVeUBeHw==} - node-releases@2.0.23: - resolution: {integrity: sha512-cCmFDMSm26S6tQSDpBCg/NR8NENrVPhAJSf+XbxBG4rPFaaonlEoE9wHQmun+cls499TQGSb7ZyPBRlzgKfpeg==} + node-releases@2.0.38: + resolution: {integrity: sha512-3qT/88Y3FbH/Kx4szpQQ4HzUbVrHPKTLVpVocKiLfoYvw9XSGOX2FmD2d6DrXbVYyAQTF2HeF6My8jmzx7/CRw==} normalize-path@3.0.0: resolution: {integrity: sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==} @@ -1335,10 +1427,6 @@ packages: package-json-from-dist@1.0.1: resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==} - parent-module@1.0.1: - resolution: {integrity: sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==} - engines: {node: '>=6'} - parse-json@5.2.0: resolution: {integrity: sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==} engines: {node: '>=8'} @@ -1366,10 +1454,18 @@ packages: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} + picomatch@2.3.2: + resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==} + engines: {node: '>=8.6'} + picomatch@4.0.3: resolution: {integrity: sha512-5gTmgEY/sqK6gFXLIsQNH19lWb4ebPDLA4SdLP7dsWkIXHWlG66oPuVvXSGFPppYZz8ZDZq0dYYrbHfBCVUb1Q==} engines: {node: '>=12'} + picomatch@4.0.4: + resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} + engines: {node: '>=12'} + pirates@4.0.7: resolution: {integrity: sha512-TfySrs/5nm8fQJDcBDuUng3VOUKsd7S+zqvbOTiGXHfxX4wK31ard+hoNuvkicM/2YFzlpDgABOevKSsB4G/FA==} engines: {node: '>= 6'} @@ -1382,8 +1478,8 @@ packages: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} - prettier@3.6.2: - resolution: {integrity: sha512-I7AIg5boAr5R0FFtJ6rCfD+LFsWHp81dolrFD8S79U9tb8Az2nGrJncnMSnys+bpQJfRUzqs9hnA81OAA3hCuQ==} + prettier@3.8.3: + resolution: {integrity: sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==} engines: {node: '>=14'} hasBin: true @@ -1391,8 +1487,13 @@ packages: resolution: {integrity: sha512-9uBdv/B4EefsuAL+pWqueZyZS2Ba+LxfFeQ9DN14HU4bN8bhaxKdkpjpB6fs9+pSjIBu+FXQHImEg8j/Lw0+vA==} engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} - publish-browser-extension@4.0.0: - resolution: {integrity: sha512-2bsHf2m+ivNyDa67YdVhcxpF3wjWzVTqG5JWQYwi6by47XyA7jf43A9S7+UBULM/d2f82fPUGnRuecMK1CgEPA==} + pretty-format@30.4.1: + resolution: {integrity: sha512-K6KiKMHTL4jjX4u3Kir2EW07nRfcqVTXIImx50wbjHQTcZPgg+gjVeNTIT3l3L1Rd4UefxfogquC9J37SoFyyw==} + engines: {node: ^18.14.0 || ^20.0.0 || ^22.0.0 || >=24.0.0} + + publish-browser-extension@4.0.5: + resolution: {integrity: sha512-EePAn3VIHJS/jqCuvs1NgPgoecCT8+RsES76hbgYe2Ze1dyvB0tX60C1PCrV8Z8fv56mW3E59s9Gd/GwWiw7dw==} + engines: {node: '>=18.0.0'} hasBin: true punycode@2.3.1: @@ -1405,6 +1506,9 @@ packages: react-is@18.3.1: resolution: {integrity: sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==} + react-is@19.2.6: + resolution: {integrity: sha512-XjBR15BhXuylgWGuslhDKqlSayuqvqBX91BP8pauG8kd1zY8kotkNWbXksTCNRarse4kuGbe2kIY05ARtwNIvw==} + require-directory@2.1.1: resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==} engines: {node: '>=0.10.0'} @@ -1413,10 +1517,6 @@ packages: resolution: {integrity: sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==} engines: {node: '>=8'} - resolve-from@4.0.0: - resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==} - engines: {node: '>=4'} - resolve-from@5.0.0: resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==} engines: {node: '>=8'} @@ -1428,12 +1528,15 @@ packages: rfdc@1.4.1: resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} + safe-buffer@5.2.1: + resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} + semver@6.3.1: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} hasBin: true - semver@7.7.2: - resolution: {integrity: sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==} + semver@7.7.4: + resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==} engines: {node: '>=10'} hasBin: true @@ -1456,14 +1559,14 @@ packages: resolution: {integrity: sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==} engines: {node: '>=8'} - slice-ansi@5.0.0: - resolution: {integrity: sha512-FC+lgizVPfie0kkhqUScwRu1O/lF6NOgJmlCgK+/LYxDCTk8sGelYaHDhFcDN+Sn3Cv+3VSa4Byeo+IMCzpMgQ==} - engines: {node: '>=12'} - slice-ansi@7.1.2: resolution: {integrity: sha512-iOBWFgUX7caIZiuutICxVgX1SdxwAVFFKwt1EvMYYec/NWO5meOJ6K5uQxhrYBdQJne4KxiqZc+KptFOWFSI9w==} engines: {node: '>=18'} + slice-ansi@8.0.0: + resolution: {integrity: sha512-stxByr12oeeOyY2BlviTNQlYV5xOj47GirPr4yA1hE9JCtxfQN0+tVbkxwCtYDQWhEKWFHsEK48ORg5jrouCAg==} + engines: {node: '>=20'} + source-map-support@0.5.13: resolution: {integrity: sha512-SHSKFHadjVA5oR4PPqhtAVdcBWwRYVd6g6cAXnIbRiIwc2EhPrTuKUBdSLvlEKyIP3GCf89fltvcZiP9MMFA1w==} @@ -1494,12 +1597,16 @@ packages: resolution: {integrity: sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ==} engines: {node: '>=18'} + string-width@8.2.1: + resolution: {integrity: sha512-IIaP0g3iy9Cyy18w3M9YcaDudujEAVHKt3a3QJg1+sr/oX96TbaGUubG0hJyCjCBThFH+tFpcIyoUHUn1ogaLA==} + engines: {node: '>=20'} + strip-ansi@6.0.1: resolution: {integrity: sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==} engines: {node: '>=8'} - strip-ansi@7.1.2: - resolution: {integrity: sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==} + strip-ansi@7.2.0: + resolution: {integrity: sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w==} engines: {node: '>=12'} strip-bom@4.0.0: @@ -1522,8 +1629,8 @@ packages: resolution: {integrity: sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==} engines: {node: '>=10'} - synckit@0.11.11: - resolution: {integrity: sha512-MeQTA1r0litLUf0Rp/iisCaL8761lKAZHaimlbGK4j0HysC4PLfqygQj9srcs0m2RdtDYnF8UuYyKpbjHYp7Jw==} + synckit@0.11.12: + resolution: {integrity: sha512-Bh7QjT8/SuKUIfObSXNHNSK6WHo6J1tHCqJsuaFDP7gP0fkzSfTxI8y85JrppZ0h8l0maIgc2tfuZQ6/t3GtnQ==} engines: {node: ^14.18.0 || >=16.0.0} test-exclude@6.0.0: @@ -1552,17 +1659,20 @@ packages: resolution: {integrity: sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==} engines: {node: '>=10'} - ufo@1.6.1: - resolution: {integrity: sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==} + ufo@1.6.4: + resolution: {integrity: sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==} undici-types@7.14.0: resolution: {integrity: sha512-QQiYxHuyZ9gQUIrmPo3IA+hUl4KYk8uSA7cHrcKd/l3p1OTpZcM0Tbp9x7FAtXdAYhlasd60ncPpgu6ihG6TOA==} + undici-types@7.19.2: + resolution: {integrity: sha512-qYVnV5OEm2AW8cJMCpdV20CDyaN3g0AjDlOGf1OW4iaDEx8MwdtChUp4zu4H0VP3nDRF/8RKWH+IPp9uW0YGZg==} + unrs-resolver@1.11.1: resolution: {integrity: sha512-bSjt9pjaEBnNiGgc9rUiHGKv5l4/TGzDmYw3RhnkJGtLhbnnA/5qJj7x3dNDCRx/PJxu774LlH8lCOlB4hEfKg==} - update-browserslist-db@1.1.3: - resolution: {integrity: sha512-UxhIZQ+QInVdunkDAaiazvvT/+fXL5Osr0JZlJulepYu6Jd7qJtDZjlur0emRlT71EN3ScPoE7gvsuIKKNavKw==} + update-browserslist-db@1.2.3: + resolution: {integrity: sha512-Js0m9cx+qOgDxo0eMiFGEueWztz+d4+M3rGlmKPT+T4IS/jP4ylw3Nwpu6cpTTP8R1MAC1kF4VbdLt3ARf209w==} hasBin: true peerDependencies: browserslist: '>= 4.21.0' @@ -1586,6 +1696,10 @@ packages: resolution: {integrity: sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==} engines: {node: '>=0.10.0'} + wrap-ansi@10.0.0: + resolution: {integrity: sha512-SGcvg80f0wUy2/fXES19feHMz8E0JoXv2uNgHOu4Dgi2OrCy1lqwFYEJz1BLbDI0exjPMe/ZdzZ/YpGECBG/aQ==} + engines: {node: '>=20'} + wrap-ansi@7.0.0: resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==} engines: {node: '>=10'} @@ -1624,8 +1738,8 @@ packages: resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==} engines: {node: '>=10'} - zod@4.1.12: - resolution: {integrity: sha512-JInaHOamG8pt5+Ey8kGmdcAcg3OL9reK8ltczgHTAwNhMys/6ThXHityHxVV2p3fkw/c+MAvBHFVYHFZDmjMCQ==} + zod@4.4.3: + resolution: {integrity: sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==} snapshots: @@ -1635,19 +1749,25 @@ snapshots: js-tokens: 4.0.0 picocolors: 1.1.1 - '@babel/compat-data@7.28.4': {} + '@babel/code-frame@7.29.0': + dependencies: + '@babel/helper-validator-identifier': 7.28.5 + js-tokens: 4.0.0 + picocolors: 1.1.1 + + '@babel/compat-data@7.29.3': {} - '@babel/core@7.28.4': + '@babel/core@7.29.0': dependencies: - '@babel/code-frame': 7.27.1 - '@babel/generator': 7.28.3 - '@babel/helper-compilation-targets': 7.27.2 - '@babel/helper-module-transforms': 7.28.3(@babel/core@7.28.4) - '@babel/helpers': 7.28.4 - '@babel/parser': 7.28.4 - '@babel/template': 7.27.2 - '@babel/traverse': 7.28.4 - '@babel/types': 7.28.4 + '@babel/code-frame': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/helper-compilation-targets': 7.28.6 + '@babel/helper-module-transforms': 7.28.6(@babel/core@7.29.0) + '@babel/helpers': 7.29.2 + '@babel/parser': 7.29.3 + '@babel/template': 7.28.6 + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 '@jridgewell/remapping': 2.3.5 convert-source-map: 2.0.0 debug: 4.4.3 @@ -1657,227 +1777,213 @@ snapshots: transitivePeerDependencies: - supports-color - '@babel/generator@7.28.3': + '@babel/generator@7.29.1': dependencies: - '@babel/parser': 7.28.4 - '@babel/types': 7.28.4 + '@babel/parser': 7.29.3 + '@babel/types': 7.29.0 '@jridgewell/gen-mapping': 0.3.13 '@jridgewell/trace-mapping': 0.3.31 jsesc: 3.1.0 - '@babel/helper-compilation-targets@7.27.2': + '@babel/helper-compilation-targets@7.28.6': dependencies: - '@babel/compat-data': 7.28.4 + '@babel/compat-data': 7.29.3 '@babel/helper-validator-option': 7.27.1 - browserslist: 4.26.3 + browserslist: 4.28.2 lru-cache: 5.1.1 semver: 6.3.1 '@babel/helper-globals@7.28.0': {} - '@babel/helper-module-imports@7.27.1': + '@babel/helper-module-imports@7.28.6': dependencies: - '@babel/traverse': 7.28.4 - '@babel/types': 7.28.4 + '@babel/traverse': 7.29.0 + '@babel/types': 7.29.0 transitivePeerDependencies: - supports-color - '@babel/helper-module-transforms@7.28.3(@babel/core@7.28.4)': + '@babel/helper-module-transforms@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.4 - '@babel/helper-module-imports': 7.27.1 - '@babel/helper-validator-identifier': 7.27.1 - '@babel/traverse': 7.28.4 + '@babel/core': 7.29.0 + '@babel/helper-module-imports': 7.28.6 + '@babel/helper-validator-identifier': 7.28.5 + '@babel/traverse': 7.29.0 transitivePeerDependencies: - supports-color - '@babel/helper-plugin-utils@7.27.1': {} + '@babel/helper-plugin-utils@7.28.6': {} '@babel/helper-string-parser@7.27.1': {} '@babel/helper-validator-identifier@7.27.1': {} + '@babel/helper-validator-identifier@7.28.5': {} + '@babel/helper-validator-option@7.27.1': {} - '@babel/helpers@7.28.4': + '@babel/helpers@7.29.2': dependencies: - '@babel/template': 7.27.2 - '@babel/types': 7.28.4 + '@babel/template': 7.28.6 + '@babel/types': 7.29.0 - '@babel/parser@7.28.4': + '@babel/parser@7.29.3': dependencies: - '@babel/types': 7.28.4 + '@babel/types': 7.29.0 - '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.28.4)': + '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.4 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.28.4)': + '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.4 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.28.4)': + '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.4 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.28.4)': + '@babel/plugin-syntax-class-static-block@7.14.5(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.4 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-import-attributes@7.27.1(@babel/core@7.28.4)': + '@babel/plugin-syntax-import-attributes@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.4 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.28.4)': + '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.4 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.28.4)': + '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.4 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-jsx@7.27.1(@babel/core@7.28.4)': + '@babel/plugin-syntax-jsx@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.4 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.28.4)': + '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.4 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.28.4)': + '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.4 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.28.4)': + '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.4 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.28.4)': + '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.4 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.28.4)': + '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.4 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.28.4)': + '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.4 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.28.4)': + '@babel/plugin-syntax-private-property-in-object@7.14.5(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.4 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.28.4)': + '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.4 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/plugin-syntax-typescript@7.27.1(@babel/core@7.28.4)': + '@babel/plugin-syntax-typescript@7.28.6(@babel/core@7.29.0)': dependencies: - '@babel/core': 7.28.4 - '@babel/helper-plugin-utils': 7.27.1 + '@babel/core': 7.29.0 + '@babel/helper-plugin-utils': 7.28.6 - '@babel/template@7.27.2': + '@babel/template@7.28.6': dependencies: - '@babel/code-frame': 7.27.1 - '@babel/parser': 7.28.4 - '@babel/types': 7.28.4 + '@babel/code-frame': 7.29.0 + '@babel/parser': 7.29.3 + '@babel/types': 7.29.0 - '@babel/traverse@7.28.4': + '@babel/traverse@7.29.0': dependencies: - '@babel/code-frame': 7.27.1 - '@babel/generator': 7.28.3 + '@babel/code-frame': 7.29.0 + '@babel/generator': 7.29.1 '@babel/helper-globals': 7.28.0 - '@babel/parser': 7.28.4 - '@babel/template': 7.27.2 - '@babel/types': 7.28.4 + '@babel/parser': 7.29.3 + '@babel/template': 7.28.6 + '@babel/types': 7.29.0 debug: 4.4.3 transitivePeerDependencies: - supports-color - '@babel/types@7.28.4': + '@babel/types@7.29.0': dependencies: '@babel/helper-string-parser': 7.27.1 - '@babel/helper-validator-identifier': 7.27.1 + '@babel/helper-validator-identifier': 7.28.5 '@bcoe/v8-coverage@0.2.3': {} - '@emnapi/core@1.5.0': + '@emnapi/core@1.10.0': dependencies: - '@emnapi/wasi-threads': 1.1.0 + '@emnapi/wasi-threads': 1.2.1 tslib: 2.8.1 optional: true - '@emnapi/runtime@1.5.0': + '@emnapi/runtime@1.10.0': dependencies: tslib: 2.8.1 optional: true - '@emnapi/wasi-threads@1.1.0': + '@emnapi/wasi-threads@1.2.1': dependencies: tslib: 2.8.1 optional: true - '@eslint-community/eslint-utils@4.9.0(eslint@9.39.1)': + '@eslint-community/eslint-utils@4.9.1(eslint@10.3.0)': dependencies: - eslint: 9.39.1 + eslint: 10.3.0 eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.12.2': {} - '@eslint/config-array@0.21.1': + '@eslint/config-array@0.23.5': dependencies: - '@eslint/object-schema': 2.1.7 + '@eslint/object-schema': 3.0.5 debug: 4.4.3 - minimatch: 3.1.2 + minimatch: 10.2.5 transitivePeerDependencies: - supports-color - '@eslint/config-helpers@0.4.2': + '@eslint/config-helpers@0.5.5': dependencies: - '@eslint/core': 0.17.0 + '@eslint/core': 1.2.1 - '@eslint/core@0.17.0': + '@eslint/core@1.2.1': dependencies: '@types/json-schema': 7.0.15 - '@eslint/eslintrc@3.3.1': - dependencies: - ajv: 6.12.6 - debug: 4.4.3 - espree: 10.4.0 - globals: 14.0.0 - ignore: 5.3.2 - import-fresh: 3.3.1 - js-yaml: 4.1.1 - minimatch: 3.1.2 - strip-json-comments: 3.1.1 - transitivePeerDependencies: - - supports-color - - '@eslint/js@9.39.1': {} + '@eslint/object-schema@3.0.5': {} - '@eslint/object-schema@2.1.7': {} - - '@eslint/plugin-kit@0.4.1': + '@eslint/plugin-kit@0.7.1': dependencies: - '@eslint/core': 0.17.0 + '@eslint/core': 1.2.1 levn: 0.4.1 '@humanfs/core@0.19.1': {} @@ -1895,7 +2001,7 @@ snapshots: dependencies: string-width: 5.1.2 string-width-cjs: string-width@4.2.3 - strip-ansi: 7.1.2 + strip-ansi: 7.2.0 strip-ansi-cjs: strip-ansi@6.0.1 wrap-ansi: 8.1.0 wrap-ansi-cjs: wrap-ansi@7.0.0 @@ -1908,46 +2014,46 @@ snapshots: js-yaml: 3.14.2 resolve-from: 5.0.0 - '@istanbuljs/schema@0.1.3': {} + '@istanbuljs/schema@0.1.6': {} - '@jest/console@30.2.0': + '@jest/console@30.4.1': dependencies: - '@jest/types': 30.2.0 - '@types/node': 24.7.0 + '@jest/types': 30.4.1 + '@types/node': 25.6.2 chalk: 4.1.2 - jest-message-util: 30.2.0 - jest-util: 30.2.0 + jest-message-util: 30.4.1 + jest-util: 30.4.1 slash: 3.0.0 - '@jest/core@30.2.0': + '@jest/core@30.4.2': dependencies: - '@jest/console': 30.2.0 - '@jest/pattern': 30.0.1 - '@jest/reporters': 30.2.0 - '@jest/test-result': 30.2.0 - '@jest/transform': 30.2.0 - '@jest/types': 30.2.0 - '@types/node': 24.7.0 + '@jest/console': 30.4.1 + '@jest/pattern': 30.4.0 + '@jest/reporters': 30.4.1 + '@jest/test-result': 30.4.1 + '@jest/transform': 30.4.1 + '@jest/types': 30.4.1 + '@types/node': 25.6.2 ansi-escapes: 4.3.2 chalk: 4.1.2 - ci-info: 4.3.1 + ci-info: 4.4.0 exit-x: 0.2.2 + fast-json-stable-stringify: 2.1.0 graceful-fs: 4.2.11 - jest-changed-files: 30.2.0 - jest-config: 30.2.0(@types/node@24.7.0) - jest-haste-map: 30.2.0 - jest-message-util: 30.2.0 - jest-regex-util: 30.0.1 - jest-resolve: 30.2.0 - jest-resolve-dependencies: 30.2.0 - jest-runner: 30.2.0 - jest-runtime: 30.2.0 - jest-snapshot: 30.2.0 - jest-util: 30.2.0 - jest-validate: 30.2.0 - jest-watcher: 30.2.0 - micromatch: 4.0.8 - pretty-format: 30.2.0 + jest-changed-files: 30.4.1 + jest-config: 30.4.2(@types/node@25.6.2) + jest-haste-map: 30.4.1 + jest-message-util: 30.4.1 + jest-regex-util: 30.4.0 + jest-resolve: 30.4.1 + jest-resolve-dependencies: 30.4.2 + jest-runner: 30.4.2 + jest-runtime: 30.4.2 + jest-snapshot: 30.4.1 + jest-util: 30.4.1 + jest-validate: 30.4.1 + jest-watcher: 30.4.1 + pretty-format: 30.4.1 slash: 3.0.0 transitivePeerDependencies: - babel-plugin-macros @@ -1957,41 +2063,47 @@ snapshots: '@jest/diff-sequences@30.0.1': {} - '@jest/environment@30.2.0': + '@jest/diff-sequences@30.4.0': {} + + '@jest/environment@30.4.1': dependencies: - '@jest/fake-timers': 30.2.0 - '@jest/types': 30.2.0 - '@types/node': 24.7.0 - jest-mock: 30.2.0 + '@jest/fake-timers': 30.4.1 + '@jest/types': 30.4.1 + '@types/node': 25.6.2 + jest-mock: 30.4.1 '@jest/expect-utils@30.2.0': dependencies: '@jest/get-type': 30.1.0 - '@jest/expect@30.2.0': + '@jest/expect-utils@30.4.1': dependencies: - expect: 30.2.0 - jest-snapshot: 30.2.0 + '@jest/get-type': 30.1.0 + + '@jest/expect@30.4.1': + dependencies: + expect: 30.4.1 + jest-snapshot: 30.4.1 transitivePeerDependencies: - supports-color - '@jest/fake-timers@30.2.0': + '@jest/fake-timers@30.4.1': dependencies: - '@jest/types': 30.2.0 - '@sinonjs/fake-timers': 13.0.5 - '@types/node': 24.7.0 - jest-message-util: 30.2.0 - jest-mock: 30.2.0 - jest-util: 30.2.0 + '@jest/types': 30.4.1 + '@sinonjs/fake-timers': 15.4.0 + '@types/node': 25.6.2 + jest-message-util: 30.4.1 + jest-mock: 30.4.1 + jest-util: 30.4.1 '@jest/get-type@30.1.0': {} - '@jest/globals@30.2.0': + '@jest/globals@30.4.1': dependencies: - '@jest/environment': 30.2.0 - '@jest/expect': 30.2.0 - '@jest/types': 30.2.0 - jest-mock: 30.2.0 + '@jest/environment': 30.4.1 + '@jest/expect': 30.4.1 + '@jest/types': 30.4.1 + jest-mock: 30.4.1 transitivePeerDependencies: - supports-color @@ -2000,28 +2112,33 @@ snapshots: '@types/node': 24.7.0 jest-regex-util: 30.0.1 - '@jest/reporters@30.2.0': + '@jest/pattern@30.4.0': + dependencies: + '@types/node': 25.6.2 + jest-regex-util: 30.4.0 + + '@jest/reporters@30.4.1': dependencies: '@bcoe/v8-coverage': 0.2.3 - '@jest/console': 30.2.0 - '@jest/test-result': 30.2.0 - '@jest/transform': 30.2.0 - '@jest/types': 30.2.0 + '@jest/console': 30.4.1 + '@jest/test-result': 30.4.1 + '@jest/transform': 30.4.1 + '@jest/types': 30.4.1 '@jridgewell/trace-mapping': 0.3.31 - '@types/node': 24.7.0 + '@types/node': 25.6.2 chalk: 4.1.2 - collect-v8-coverage: 1.0.2 + collect-v8-coverage: 1.0.3 exit-x: 0.2.2 - glob: 10.4.5 + glob: 10.5.0 graceful-fs: 4.2.11 istanbul-lib-coverage: 3.2.2 istanbul-lib-instrument: 6.0.3 istanbul-lib-report: 3.0.1 istanbul-lib-source-maps: 5.0.6 istanbul-reports: 3.2.0 - jest-message-util: 30.2.0 - jest-util: 30.2.0 - jest-worker: 30.2.0 + jest-message-util: 30.4.1 + jest-util: 30.4.1 + jest-worker: 30.4.1 slash: 3.0.0 string-length: 4.0.2 v8-to-istanbul: 9.3.0 @@ -2030,11 +2147,15 @@ snapshots: '@jest/schemas@30.0.5': dependencies: - '@sinclair/typebox': 0.34.41 + '@sinclair/typebox': 0.34.49 - '@jest/snapshot-utils@30.2.0': + '@jest/schemas@30.4.1': dependencies: - '@jest/types': 30.2.0 + '@sinclair/typebox': 0.34.49 + + '@jest/snapshot-utils@30.4.1': + dependencies: + '@jest/types': 30.4.1 chalk: 4.1.2 graceful-fs: 4.2.11 natural-compare: 1.4.0 @@ -2045,34 +2166,33 @@ snapshots: callsites: 3.1.0 graceful-fs: 4.2.11 - '@jest/test-result@30.2.0': + '@jest/test-result@30.4.1': dependencies: - '@jest/console': 30.2.0 - '@jest/types': 30.2.0 + '@jest/console': 30.4.1 + '@jest/types': 30.4.1 '@types/istanbul-lib-coverage': 2.0.6 - collect-v8-coverage: 1.0.2 + collect-v8-coverage: 1.0.3 - '@jest/test-sequencer@30.2.0': + '@jest/test-sequencer@30.4.1': dependencies: - '@jest/test-result': 30.2.0 + '@jest/test-result': 30.4.1 graceful-fs: 4.2.11 - jest-haste-map: 30.2.0 + jest-haste-map: 30.4.1 slash: 3.0.0 - '@jest/transform@30.2.0': + '@jest/transform@30.4.1': dependencies: - '@babel/core': 7.28.4 - '@jest/types': 30.2.0 + '@babel/core': 7.29.0 + '@jest/types': 30.4.1 '@jridgewell/trace-mapping': 0.3.31 babel-plugin-istanbul: 7.0.1 chalk: 4.1.2 convert-source-map: 2.0.0 fast-json-stable-stringify: 2.1.0 graceful-fs: 4.2.11 - jest-haste-map: 30.2.0 - jest-regex-util: 30.0.1 - jest-util: 30.2.0 - micromatch: 4.0.8 + jest-haste-map: 30.4.1 + jest-regex-util: 30.4.0 + jest-util: 30.4.1 pirates: 4.0.7 slash: 3.0.0 write-file-atomic: 5.0.1 @@ -2086,7 +2206,17 @@ snapshots: '@types/istanbul-lib-coverage': 2.0.6 '@types/istanbul-reports': 3.0.4 '@types/node': 24.7.0 - '@types/yargs': 17.0.33 + '@types/yargs': 17.0.35 + chalk: 4.1.2 + + '@jest/types@30.4.1': + dependencies: + '@jest/pattern': 30.4.0 + '@jest/schemas': 30.4.1 + '@types/istanbul-lib-coverage': 2.0.6 + '@types/istanbul-reports': 3.0.4 + '@types/node': 25.6.2 + '@types/yargs': 17.0.35 chalk: 4.1.2 '@jridgewell/gen-mapping@0.3.13': @@ -2110,9 +2240,9 @@ snapshots: '@napi-rs/wasm-runtime@0.2.12': dependencies: - '@emnapi/core': 1.5.0 - '@emnapi/runtime': 1.5.0 - '@tybys/wasm-util': 0.10.1 + '@emnapi/core': 1.10.0 + '@emnapi/runtime': 1.10.0 + '@tybys/wasm-util': 0.10.2 optional: true '@pkgjs/parseargs@0.11.0': @@ -2120,41 +2250,43 @@ snapshots: '@pkgr/core@0.2.9': {} - '@sinclair/typebox@0.34.41': {} + '@sinclair/typebox@0.34.49': {} '@sinonjs/commons@3.0.1': dependencies: type-detect: 4.0.8 - '@sinonjs/fake-timers@13.0.5': + '@sinonjs/fake-timers@15.4.0': dependencies: '@sinonjs/commons': 3.0.1 - '@tybys/wasm-util@0.10.1': + '@tybys/wasm-util@0.10.2': dependencies: tslib: 2.8.1 optional: true '@types/babel__core@7.20.5': dependencies: - '@babel/parser': 7.28.4 - '@babel/types': 7.28.4 + '@babel/parser': 7.29.3 + '@babel/types': 7.29.0 '@types/babel__generator': 7.27.0 '@types/babel__template': 7.4.4 '@types/babel__traverse': 7.28.0 '@types/babel__generator@7.27.0': dependencies: - '@babel/types': 7.28.4 + '@babel/types': 7.29.0 '@types/babel__template@7.4.4': dependencies: - '@babel/parser': 7.28.4 - '@babel/types': 7.28.4 + '@babel/parser': 7.29.3 + '@babel/types': 7.29.0 '@types/babel__traverse@7.28.0': dependencies: - '@babel/types': 7.28.4 + '@babel/types': 7.29.0 + + '@types/esrecurse@4.3.1': {} '@types/estree@1.0.8': {} @@ -2179,15 +2311,19 @@ snapshots: dependencies: undici-types: 7.14.0 + '@types/node@25.6.2': + dependencies: + undici-types: 7.19.2 + '@types/stack-utils@2.0.3': {} '@types/yargs-parser@21.0.3': {} - '@types/yargs@17.0.33': + '@types/yargs@17.0.35': dependencies: '@types/yargs-parser': 21.0.3 - '@ungap/structured-clone@1.3.0': {} + '@ungap/structured-clone@1.3.1': {} '@unrs/resolver-binding-android-arm-eabi@1.11.1': optional: true @@ -2248,13 +2384,13 @@ snapshots: '@unrs/resolver-binding-win32-x64-msvc@1.11.1': optional: true - acorn-jsx@5.3.2(acorn@8.15.0): + acorn-jsx@5.3.2(acorn@8.16.0): dependencies: - acorn: 8.15.0 + acorn: 8.16.0 - acorn@8.15.0: {} + acorn@8.16.0: {} - ajv@6.12.6: + ajv@6.14.0: dependencies: fast-deep-equal: 3.1.3 fast-json-stable-stringify: 2.1.0 @@ -2265,7 +2401,7 @@ snapshots: dependencies: type-fest: 0.21.3 - ansi-escapes@7.2.0: + ansi-escapes@7.3.0: dependencies: environment: 1.1.0 @@ -2284,21 +2420,19 @@ snapshots: anymatch@3.1.3: dependencies: normalize-path: 3.0.0 - picomatch: 2.3.1 + picomatch: 2.3.2 argparse@1.0.10: dependencies: sprintf-js: 1.0.3 - argparse@2.0.1: {} - - babel-jest@30.2.0(@babel/core@7.28.4): + babel-jest@30.4.1(@babel/core@7.29.0): dependencies: - '@babel/core': 7.28.4 - '@jest/transform': 30.2.0 + '@babel/core': 7.29.0 + '@jest/transform': 30.4.1 '@types/babel__core': 7.20.5 babel-plugin-istanbul: 7.0.1 - babel-preset-jest: 30.2.0(@babel/core@7.28.4) + babel-preset-jest: 30.4.0(@babel/core@7.29.0) chalk: 4.1.2 graceful-fs: 4.2.11 slash: 3.0.0 @@ -2307,72 +2441,80 @@ snapshots: babel-plugin-istanbul@7.0.1: dependencies: - '@babel/helper-plugin-utils': 7.27.1 + '@babel/helper-plugin-utils': 7.28.6 '@istanbuljs/load-nyc-config': 1.1.0 - '@istanbuljs/schema': 0.1.3 + '@istanbuljs/schema': 0.1.6 istanbul-lib-instrument: 6.0.3 test-exclude: 6.0.0 transitivePeerDependencies: - supports-color - babel-plugin-jest-hoist@30.2.0: + babel-plugin-jest-hoist@30.4.0: dependencies: '@types/babel__core': 7.20.5 - babel-preset-current-node-syntax@1.2.0(@babel/core@7.28.4): - dependencies: - '@babel/core': 7.28.4 - '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.28.4) - '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.28.4) - '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.28.4) - '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.28.4) - '@babel/plugin-syntax-import-attributes': 7.27.1(@babel/core@7.28.4) - '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.28.4) - '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.28.4) - '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.28.4) - '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.28.4) - '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.28.4) - '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.28.4) - '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.28.4) - '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.28.4) - '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.28.4) - '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.28.4) - - babel-preset-jest@30.2.0(@babel/core@7.28.4): - dependencies: - '@babel/core': 7.28.4 - babel-plugin-jest-hoist: 30.2.0 - babel-preset-current-node-syntax: 1.2.0(@babel/core@7.28.4) + babel-preset-current-node-syntax@1.2.0(@babel/core@7.29.0): + dependencies: + '@babel/core': 7.29.0 + '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.29.0) + '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.29.0) + '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.29.0) + '@babel/plugin-syntax-class-static-block': 7.14.5(@babel/core@7.29.0) + '@babel/plugin-syntax-import-attributes': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.29.0) + '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.29.0) + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.29.0) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.29.0) + '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.29.0) + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.29.0) + '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.29.0) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.29.0) + '@babel/plugin-syntax-private-property-in-object': 7.14.5(@babel/core@7.29.0) + '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.29.0) + + babel-preset-jest@30.4.0(@babel/core@7.29.0): + dependencies: + '@babel/core': 7.29.0 + babel-plugin-jest-hoist: 30.4.0 + babel-preset-current-node-syntax: 1.2.0(@babel/core@7.29.0) balanced-match@1.0.2: {} - baseline-browser-mapping@2.8.12: {} + balanced-match@4.0.4: {} + + baseline-browser-mapping@2.10.27: {} brace-expansion@1.1.12: dependencies: balanced-match: 1.0.2 concat-map: 0.0.1 - brace-expansion@2.0.2: + brace-expansion@2.1.0: dependencies: balanced-match: 1.0.2 + brace-expansion@5.0.6: + dependencies: + balanced-match: 4.0.4 + braces@3.0.3: dependencies: fill-range: 7.1.1 - browserslist@4.26.3: + browserslist@4.28.2: dependencies: - baseline-browser-mapping: 2.8.12 - caniuse-lite: 1.0.30001748 - electron-to-chromium: 1.5.232 - node-releases: 2.0.23 - update-browserslist-db: 1.1.3(browserslist@4.26.3) + baseline-browser-mapping: 2.10.27 + caniuse-lite: 1.0.30001792 + electron-to-chromium: 1.5.352 + node-releases: 2.0.38 + update-browserslist-db: 1.2.3(browserslist@4.28.2) bser@2.1.1: dependencies: node-int64: 0.4.0 + buffer-equal-constant-time@1.0.1: {} + buffer-from@1.1.2: {} cac@6.7.14: {} @@ -2383,7 +2525,7 @@ snapshots: camelcase@6.3.0: {} - caniuse-lite@1.0.30001748: {} + caniuse-lite@1.0.30001792: {} chalk@4.1.2: dependencies: @@ -2394,16 +2536,18 @@ snapshots: ci-info@4.3.1: {} - cjs-module-lexer@2.1.0: {} + ci-info@4.4.0: {} + + cjs-module-lexer@2.2.0: {} cli-cursor@5.0.0: dependencies: restore-cursor: 5.1.0 - cli-truncate@4.0.0: + cli-truncate@5.2.0: dependencies: - slice-ansi: 5.0.0 - string-width: 7.2.0 + slice-ansi: 8.0.0 + string-width: 8.2.1 cliui@8.0.1: dependencies: @@ -2413,7 +2557,7 @@ snapshots: co@4.6.0: {} - collect-v8-coverage@1.0.2: {} + collect-v8-coverage@1.0.3: {} color-convert@2.0.1: dependencies: @@ -2421,8 +2565,6 @@ snapshots: color-name@1.1.4: {} - colorette@2.0.20: {} - concat-map@0.0.1: {} consola@3.4.2: {} @@ -2439,7 +2581,7 @@ snapshots: dependencies: ms: 2.1.3 - dedent@1.7.0: {} + dedent@1.7.2: {} deep-is@0.1.4: {} @@ -2449,11 +2591,15 @@ snapshots: detect-newline@3.1.0: {} - dotenv@17.2.3: {} + dotenv@17.4.2: {} eastasianwidth@0.2.0: {} - electron-to-chromium@1.5.232: {} + ecdsa-sig-formatter@1.0.11: + dependencies: + safe-buffer: 5.2.1 + + electron-to-chromium@1.5.352: {} emittery@0.13.1: {} @@ -2475,38 +2621,37 @@ snapshots: escape-string-regexp@4.0.0: {} - eslint-scope@8.4.0: + eslint-scope@9.1.2: dependencies: + '@types/esrecurse': 4.3.1 + '@types/estree': 1.0.8 esrecurse: 4.3.0 estraverse: 5.3.0 eslint-visitor-keys@3.4.3: {} - eslint-visitor-keys@4.2.1: {} + eslint-visitor-keys@5.0.1: {} - eslint@9.39.1: + eslint@10.3.0: dependencies: - '@eslint-community/eslint-utils': 4.9.0(eslint@9.39.1) + '@eslint-community/eslint-utils': 4.9.1(eslint@10.3.0) '@eslint-community/regexpp': 4.12.2 - '@eslint/config-array': 0.21.1 - '@eslint/config-helpers': 0.4.2 - '@eslint/core': 0.17.0 - '@eslint/eslintrc': 3.3.1 - '@eslint/js': 9.39.1 - '@eslint/plugin-kit': 0.4.1 + '@eslint/config-array': 0.23.5 + '@eslint/config-helpers': 0.5.5 + '@eslint/core': 1.2.1 + '@eslint/plugin-kit': 0.7.1 '@humanfs/node': 0.16.7 '@humanwhocodes/module-importer': 1.0.1 '@humanwhocodes/retry': 0.4.3 '@types/estree': 1.0.8 - ajv: 6.12.6 - chalk: 4.1.2 + ajv: 6.14.0 cross-spawn: 7.0.6 debug: 4.4.3 escape-string-regexp: 4.0.0 - eslint-scope: 8.4.0 - eslint-visitor-keys: 4.2.1 - espree: 10.4.0 - esquery: 1.6.0 + eslint-scope: 9.1.2 + eslint-visitor-keys: 5.0.1 + espree: 11.2.0 + esquery: 1.7.0 esutils: 2.0.3 fast-deep-equal: 3.1.3 file-entry-cache: 8.0.0 @@ -2516,22 +2661,21 @@ snapshots: imurmurhash: 0.1.4 is-glob: 4.0.3 json-stable-stringify-without-jsonify: 1.0.1 - lodash.merge: 4.6.2 - minimatch: 3.1.2 + minimatch: 10.2.5 natural-compare: 1.4.0 optionator: 0.9.4 transitivePeerDependencies: - supports-color - espree@10.4.0: + espree@11.2.0: dependencies: - acorn: 8.15.0 - acorn-jsx: 5.3.2(acorn@8.15.0) - eslint-visitor-keys: 4.2.1 + acorn: 8.16.0 + acorn-jsx: 5.3.2(acorn@8.16.0) + eslint-visitor-keys: 5.0.1 esprima@4.0.1: {} - esquery@1.6.0: + esquery@1.7.0: dependencies: estraverse: 5.3.0 @@ -2543,7 +2687,7 @@ snapshots: esutils@2.0.3: {} - eventemitter3@5.0.1: {} + eventemitter3@5.0.4: {} execa@5.1.1: dependencies: @@ -2568,6 +2712,15 @@ snapshots: jest-mock: 30.2.0 jest-util: 30.2.0 + expect@30.4.1: + dependencies: + '@jest/expect-utils': 30.4.1 + '@jest/get-type': 30.1.0 + jest-matcher-utils: 30.4.1 + jest-message-util: 30.4.1 + jest-mock: 30.4.1 + jest-util: 30.4.1 + fast-deep-equal@3.1.3: {} fast-json-stable-stringify@2.1.0: {} @@ -2598,10 +2751,10 @@ snapshots: flat-cache@4.0.1: dependencies: - flatted: 3.3.3 + flatted: 3.4.1 keyv: 4.5.4 - flatted@3.3.3: {} + flatted@3.4.1: {} foreground-child@3.3.1: dependencies: @@ -2621,7 +2774,7 @@ snapshots: get-caller-file@2.0.5: {} - get-east-asian-width@1.4.0: {} + get-east-asian-width@1.5.0: {} get-package-type@0.1.0: {} @@ -2631,12 +2784,12 @@ snapshots: dependencies: is-glob: 4.0.3 - glob@10.4.5: + glob@10.5.0: dependencies: foreground-child: 3.3.1 jackspeak: 3.4.3 - minimatch: 9.0.5 - minipass: 7.1.2 + minimatch: 9.0.9 + minipass: 7.1.3 package-json-from-dist: 1.0.1 path-scurry: 1.11.1 @@ -2645,12 +2798,10 @@ snapshots: fs.realpath: 1.0.0 inflight: 1.0.6 inherits: 2.0.4 - minimatch: 3.1.2 + minimatch: 3.1.5 once: 1.4.0 path-is-absolute: 1.0.1 - globals@14.0.0: {} - graceful-fs@4.2.11: {} has-flag@4.0.0: {} @@ -2661,11 +2812,6 @@ snapshots: ignore@5.3.2: {} - import-fresh@3.3.1: - dependencies: - parent-module: 1.0.1 - resolve-from: 4.0.0 - import-local@3.2.0: dependencies: pkg-dir: 4.2.0 @@ -2686,11 +2832,9 @@ snapshots: is-fullwidth-code-point@3.0.0: {} - is-fullwidth-code-point@4.0.0: {} - is-fullwidth-code-point@5.1.0: dependencies: - get-east-asian-width: 1.4.0 + get-east-asian-width: 1.5.0 is-generator-fn@2.1.0: {} @@ -2708,11 +2852,11 @@ snapshots: istanbul-lib-instrument@6.0.3: dependencies: - '@babel/core': 7.28.4 - '@babel/parser': 7.28.4 - '@istanbuljs/schema': 0.1.3 + '@babel/core': 7.29.0 + '@babel/parser': 7.29.3 + '@istanbuljs/schema': 0.1.6 istanbul-lib-coverage: 3.2.2 - semver: 7.7.2 + semver: 7.7.4 transitivePeerDependencies: - supports-color @@ -2741,31 +2885,31 @@ snapshots: optionalDependencies: '@pkgjs/parseargs': 0.11.0 - jest-changed-files@30.2.0: + jest-changed-files@30.4.1: dependencies: execa: 5.1.1 - jest-util: 30.2.0 + jest-util: 30.4.1 p-limit: 3.1.0 - jest-circus@30.2.0: + jest-circus@30.4.2: dependencies: - '@jest/environment': 30.2.0 - '@jest/expect': 30.2.0 - '@jest/test-result': 30.2.0 - '@jest/types': 30.2.0 - '@types/node': 24.7.0 + '@jest/environment': 30.4.1 + '@jest/expect': 30.4.1 + '@jest/test-result': 30.4.1 + '@jest/types': 30.4.1 + '@types/node': 25.6.2 chalk: 4.1.2 co: 4.6.0 - dedent: 1.7.0 + dedent: 1.7.2 is-generator-fn: 2.1.0 - jest-each: 30.2.0 - jest-matcher-utils: 30.2.0 - jest-message-util: 30.2.0 - jest-runtime: 30.2.0 - jest-snapshot: 30.2.0 - jest-util: 30.2.0 + jest-each: 30.4.1 + jest-matcher-utils: 30.4.1 + jest-message-util: 30.4.1 + jest-runtime: 30.4.2 + jest-snapshot: 30.4.1 + jest-util: 30.4.1 p-limit: 3.1.0 - pretty-format: 30.2.0 + pretty-format: 30.4.1 pure-rand: 7.0.1 slash: 3.0.0 stack-utils: 2.0.6 @@ -2773,17 +2917,17 @@ snapshots: - babel-plugin-macros - supports-color - jest-cli@30.2.0(@types/node@24.7.0): + jest-cli@30.4.2(@types/node@25.6.2): dependencies: - '@jest/core': 30.2.0 - '@jest/test-result': 30.2.0 - '@jest/types': 30.2.0 + '@jest/core': 30.4.2 + '@jest/test-result': 30.4.1 + '@jest/types': 30.4.1 chalk: 4.1.2 exit-x: 0.2.2 import-local: 3.2.0 - jest-config: 30.2.0(@types/node@24.7.0) - jest-util: 30.2.0 - jest-validate: 30.2.0 + jest-config: 30.4.2(@types/node@25.6.2) + jest-util: 30.4.1 + jest-validate: 30.4.1 yargs: 17.7.2 transitivePeerDependencies: - '@types/node' @@ -2792,34 +2936,33 @@ snapshots: - supports-color - ts-node - jest-config@30.2.0(@types/node@24.7.0): + jest-config@30.4.2(@types/node@25.6.2): dependencies: - '@babel/core': 7.28.4 + '@babel/core': 7.29.0 '@jest/get-type': 30.1.0 - '@jest/pattern': 30.0.1 - '@jest/test-sequencer': 30.2.0 - '@jest/types': 30.2.0 - babel-jest: 30.2.0(@babel/core@7.28.4) + '@jest/pattern': 30.4.0 + '@jest/test-sequencer': 30.4.1 + '@jest/types': 30.4.1 + babel-jest: 30.4.1(@babel/core@7.29.0) chalk: 4.1.2 - ci-info: 4.3.1 + ci-info: 4.4.0 deepmerge: 4.3.1 - glob: 10.4.5 + glob: 10.5.0 graceful-fs: 4.2.11 - jest-circus: 30.2.0 - jest-docblock: 30.2.0 - jest-environment-node: 30.2.0 - jest-regex-util: 30.0.1 - jest-resolve: 30.2.0 - jest-runner: 30.2.0 - jest-util: 30.2.0 - jest-validate: 30.2.0 - micromatch: 4.0.8 + jest-circus: 30.4.2 + jest-docblock: 30.4.0 + jest-environment-node: 30.4.1 + jest-regex-util: 30.4.0 + jest-resolve: 30.4.1 + jest-runner: 30.4.2 + jest-util: 30.4.1 + jest-validate: 30.4.1 parse-json: 5.2.0 - pretty-format: 30.2.0 + pretty-format: 30.4.1 slash: 3.0.0 strip-json-comments: 3.1.1 optionalDependencies: - '@types/node': 24.7.0 + '@types/node': 25.6.2 transitivePeerDependencies: - babel-plugin-macros - supports-color @@ -2831,47 +2974,54 @@ snapshots: chalk: 4.1.2 pretty-format: 30.2.0 - jest-docblock@30.2.0: + jest-diff@30.4.1: + dependencies: + '@jest/diff-sequences': 30.4.0 + '@jest/get-type': 30.1.0 + chalk: 4.1.2 + pretty-format: 30.4.1 + + jest-docblock@30.4.0: dependencies: detect-newline: 3.1.0 - jest-each@30.2.0: + jest-each@30.4.1: dependencies: '@jest/get-type': 30.1.0 - '@jest/types': 30.2.0 + '@jest/types': 30.4.1 chalk: 4.1.2 - jest-util: 30.2.0 - pretty-format: 30.2.0 + jest-util: 30.4.1 + pretty-format: 30.4.1 - jest-environment-node@30.2.0: + jest-environment-node@30.4.1: dependencies: - '@jest/environment': 30.2.0 - '@jest/fake-timers': 30.2.0 - '@jest/types': 30.2.0 - '@types/node': 24.7.0 - jest-mock: 30.2.0 - jest-util: 30.2.0 - jest-validate: 30.2.0 + '@jest/environment': 30.4.1 + '@jest/fake-timers': 30.4.1 + '@jest/types': 30.4.1 + '@types/node': 25.6.2 + jest-mock: 30.4.1 + jest-util: 30.4.1 + jest-validate: 30.4.1 - jest-haste-map@30.2.0: + jest-haste-map@30.4.1: dependencies: - '@jest/types': 30.2.0 - '@types/node': 24.7.0 + '@jest/types': 30.4.1 + '@types/node': 25.6.2 anymatch: 3.1.3 fb-watchman: 2.0.2 graceful-fs: 4.2.11 - jest-regex-util: 30.0.1 - jest-util: 30.2.0 - jest-worker: 30.2.0 - micromatch: 4.0.8 + jest-regex-util: 30.4.0 + jest-util: 30.4.1 + jest-worker: 30.4.1 + picomatch: 4.0.4 walker: 1.0.8 optionalDependencies: fsevents: 2.3.3 - jest-leak-detector@30.2.0: + jest-leak-detector@30.4.1: dependencies: '@jest/get-type': 30.1.0 - pretty-format: 30.2.0 + pretty-format: 30.4.1 jest-matcher-utils@30.2.0: dependencies: @@ -2880,6 +3030,13 @@ snapshots: jest-diff: 30.2.0 pretty-format: 30.2.0 + jest-matcher-utils@30.4.1: + dependencies: + '@jest/get-type': 30.1.0 + chalk: 4.1.2 + jest-diff: 30.4.1 + pretty-format: 30.4.1 + jest-message-util@30.2.0: dependencies: '@babel/code-frame': 7.27.1 @@ -2892,113 +3049,134 @@ snapshots: slash: 3.0.0 stack-utils: 2.0.6 + jest-message-util@30.4.1: + dependencies: + '@babel/code-frame': 7.29.0 + '@jest/types': 30.4.1 + '@types/stack-utils': 2.0.3 + chalk: 4.1.2 + graceful-fs: 4.2.11 + jest-util: 30.4.1 + picomatch: 4.0.4 + pretty-format: 30.4.1 + slash: 3.0.0 + stack-utils: 2.0.6 + jest-mock@30.2.0: dependencies: '@jest/types': 30.2.0 '@types/node': 24.7.0 jest-util: 30.2.0 - jest-pnp-resolver@1.2.3(jest-resolve@30.2.0): + jest-mock@30.4.1: + dependencies: + '@jest/types': 30.4.1 + '@types/node': 25.6.2 + jest-util: 30.4.1 + + jest-pnp-resolver@1.2.3(jest-resolve@30.4.1): optionalDependencies: - jest-resolve: 30.2.0 + jest-resolve: 30.4.1 jest-regex-util@30.0.1: {} - jest-resolve-dependencies@30.2.0: + jest-regex-util@30.4.0: {} + + jest-resolve-dependencies@30.4.2: dependencies: - jest-regex-util: 30.0.1 - jest-snapshot: 30.2.0 + jest-regex-util: 30.4.0 + jest-snapshot: 30.4.1 transitivePeerDependencies: - supports-color - jest-resolve@30.2.0: + jest-resolve@30.4.1: dependencies: chalk: 4.1.2 graceful-fs: 4.2.11 - jest-haste-map: 30.2.0 - jest-pnp-resolver: 1.2.3(jest-resolve@30.2.0) - jest-util: 30.2.0 - jest-validate: 30.2.0 + jest-haste-map: 30.4.1 + jest-pnp-resolver: 1.2.3(jest-resolve@30.4.1) + jest-util: 30.4.1 + jest-validate: 30.4.1 slash: 3.0.0 unrs-resolver: 1.11.1 - jest-runner@30.2.0: + jest-runner@30.4.2: dependencies: - '@jest/console': 30.2.0 - '@jest/environment': 30.2.0 - '@jest/test-result': 30.2.0 - '@jest/transform': 30.2.0 - '@jest/types': 30.2.0 - '@types/node': 24.7.0 + '@jest/console': 30.4.1 + '@jest/environment': 30.4.1 + '@jest/test-result': 30.4.1 + '@jest/transform': 30.4.1 + '@jest/types': 30.4.1 + '@types/node': 25.6.2 chalk: 4.1.2 emittery: 0.13.1 exit-x: 0.2.2 graceful-fs: 4.2.11 - jest-docblock: 30.2.0 - jest-environment-node: 30.2.0 - jest-haste-map: 30.2.0 - jest-leak-detector: 30.2.0 - jest-message-util: 30.2.0 - jest-resolve: 30.2.0 - jest-runtime: 30.2.0 - jest-util: 30.2.0 - jest-watcher: 30.2.0 - jest-worker: 30.2.0 + jest-docblock: 30.4.0 + jest-environment-node: 30.4.1 + jest-haste-map: 30.4.1 + jest-leak-detector: 30.4.1 + jest-message-util: 30.4.1 + jest-resolve: 30.4.1 + jest-runtime: 30.4.2 + jest-util: 30.4.1 + jest-watcher: 30.4.1 + jest-worker: 30.4.1 p-limit: 3.1.0 source-map-support: 0.5.13 transitivePeerDependencies: - supports-color - jest-runtime@30.2.0: + jest-runtime@30.4.2: dependencies: - '@jest/environment': 30.2.0 - '@jest/fake-timers': 30.2.0 - '@jest/globals': 30.2.0 + '@jest/environment': 30.4.1 + '@jest/fake-timers': 30.4.1 + '@jest/globals': 30.4.1 '@jest/source-map': 30.0.1 - '@jest/test-result': 30.2.0 - '@jest/transform': 30.2.0 - '@jest/types': 30.2.0 - '@types/node': 24.7.0 + '@jest/test-result': 30.4.1 + '@jest/transform': 30.4.1 + '@jest/types': 30.4.1 + '@types/node': 25.6.2 chalk: 4.1.2 - cjs-module-lexer: 2.1.0 - collect-v8-coverage: 1.0.2 - glob: 10.4.5 + cjs-module-lexer: 2.2.0 + collect-v8-coverage: 1.0.3 + glob: 10.5.0 graceful-fs: 4.2.11 - jest-haste-map: 30.2.0 - jest-message-util: 30.2.0 - jest-mock: 30.2.0 - jest-regex-util: 30.0.1 - jest-resolve: 30.2.0 - jest-snapshot: 30.2.0 - jest-util: 30.2.0 + jest-haste-map: 30.4.1 + jest-message-util: 30.4.1 + jest-mock: 30.4.1 + jest-regex-util: 30.4.0 + jest-resolve: 30.4.1 + jest-snapshot: 30.4.1 + jest-util: 30.4.1 slash: 3.0.0 strip-bom: 4.0.0 transitivePeerDependencies: - supports-color - jest-snapshot@30.2.0: + jest-snapshot@30.4.1: dependencies: - '@babel/core': 7.28.4 - '@babel/generator': 7.28.3 - '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.28.4) - '@babel/plugin-syntax-typescript': 7.27.1(@babel/core@7.28.4) - '@babel/types': 7.28.4 - '@jest/expect-utils': 30.2.0 + '@babel/core': 7.29.0 + '@babel/generator': 7.29.1 + '@babel/plugin-syntax-jsx': 7.28.6(@babel/core@7.29.0) + '@babel/plugin-syntax-typescript': 7.28.6(@babel/core@7.29.0) + '@babel/types': 7.29.0 + '@jest/expect-utils': 30.4.1 '@jest/get-type': 30.1.0 - '@jest/snapshot-utils': 30.2.0 - '@jest/transform': 30.2.0 - '@jest/types': 30.2.0 - babel-preset-current-node-syntax: 1.2.0(@babel/core@7.28.4) + '@jest/snapshot-utils': 30.4.1 + '@jest/transform': 30.4.1 + '@jest/types': 30.4.1 + babel-preset-current-node-syntax: 1.2.0(@babel/core@7.29.0) chalk: 4.1.2 - expect: 30.2.0 + expect: 30.4.1 graceful-fs: 4.2.11 - jest-diff: 30.2.0 - jest-matcher-utils: 30.2.0 - jest-message-util: 30.2.0 - jest-util: 30.2.0 - pretty-format: 30.2.0 - semver: 7.7.2 - synckit: 0.11.11 + jest-diff: 30.4.1 + jest-matcher-utils: 30.4.1 + jest-message-util: 30.4.1 + jest-util: 30.4.1 + pretty-format: 30.4.1 + semver: 7.7.4 + synckit: 0.11.12 transitivePeerDependencies: - supports-color @@ -3011,40 +3189,49 @@ snapshots: graceful-fs: 4.2.11 picomatch: 4.0.3 - jest-validate@30.2.0: + jest-util@30.4.1: + dependencies: + '@jest/types': 30.4.1 + '@types/node': 25.6.2 + chalk: 4.1.2 + ci-info: 4.4.0 + graceful-fs: 4.2.11 + picomatch: 4.0.4 + + jest-validate@30.4.1: dependencies: '@jest/get-type': 30.1.0 - '@jest/types': 30.2.0 + '@jest/types': 30.4.1 camelcase: 6.3.0 chalk: 4.1.2 leven: 3.1.0 - pretty-format: 30.2.0 + pretty-format: 30.4.1 - jest-watcher@30.2.0: + jest-watcher@30.4.1: dependencies: - '@jest/test-result': 30.2.0 - '@jest/types': 30.2.0 - '@types/node': 24.7.0 + '@jest/test-result': 30.4.1 + '@jest/types': 30.4.1 + '@types/node': 25.6.2 ansi-escapes: 4.3.2 chalk: 4.1.2 emittery: 0.13.1 - jest-util: 30.2.0 + jest-util: 30.4.1 string-length: 4.0.2 - jest-worker@30.2.0: + jest-worker@30.4.1: dependencies: - '@types/node': 24.7.0 - '@ungap/structured-clone': 1.3.0 - jest-util: 30.2.0 + '@types/node': 25.6.2 + '@ungap/structured-clone': 1.3.1 + jest-util: 30.4.1 merge-stream: 2.0.0 supports-color: 8.1.1 - jest@30.2.0(@types/node@24.7.0): + jest@30.4.2(@types/node@25.6.2): dependencies: - '@jest/core': 30.2.0 - '@jest/types': 30.2.0 + '@jest/core': 30.4.2 + '@jest/types': 30.4.1 import-local: 3.2.0 - jest-cli: 30.2.0(@types/node@24.7.0) + jest-cli: 30.4.2(@types/node@25.6.2) transitivePeerDependencies: - '@types/node' - babel-plugin-macros @@ -3059,10 +3246,6 @@ snapshots: argparse: 1.0.10 esprima: 4.0.1 - js-yaml@4.1.1: - dependencies: - argparse: 2.0.1 - jsesc@3.1.0: {} json-buffer@3.0.1: {} @@ -3075,6 +3258,30 @@ snapshots: json5@2.2.3: {} + jsonwebtoken@9.0.3: + dependencies: + jws: 4.0.1 + lodash.includes: 4.3.0 + lodash.isboolean: 3.0.3 + lodash.isinteger: 4.0.4 + lodash.isnumber: 3.0.3 + lodash.isplainobject: 4.0.6 + lodash.isstring: 4.0.1 + lodash.once: 4.1.1 + ms: 2.1.3 + semver: 7.7.4 + + jwa@2.0.1: + dependencies: + buffer-equal-constant-time: 1.0.1 + ecdsa-sig-formatter: 1.0.11 + safe-buffer: 5.2.1 + + jws@4.0.1: + dependencies: + jwa: 2.0.1 + safe-buffer: 5.2.1 + keyv@4.5.4: dependencies: json-buffer: 3.0.1 @@ -3088,14 +3295,13 @@ snapshots: lines-and-columns@1.2.4: {} - listr2@8.3.3: + listr2@10.2.1: dependencies: - cli-truncate: 4.0.0 - colorette: 2.0.20 - eventemitter3: 5.0.1 + cli-truncate: 5.2.0 + eventemitter3: 5.0.4 log-update: 6.1.0 rfdc: 1.4.1 - wrap-ansi: 9.0.2 + wrap-ansi: 10.0.0 locate-path@5.0.0: dependencies: @@ -3105,14 +3311,26 @@ snapshots: dependencies: p-locate: 5.0.0 - lodash.merge@4.6.2: {} + lodash.includes@4.3.0: {} + + lodash.isboolean@3.0.3: {} + + lodash.isinteger@4.0.4: {} + + lodash.isnumber@3.0.3: {} + + lodash.isplainobject@4.0.6: {} + + lodash.isstring@4.0.1: {} + + lodash.once@4.1.1: {} log-update@6.1.0: dependencies: - ansi-escapes: 7.2.0 + ansi-escapes: 7.3.0 cli-cursor: 5.0.0 slice-ansi: 7.1.2 - strip-ansi: 7.1.2 + strip-ansi: 7.2.0 wrap-ansi: 9.0.2 lru-cache@10.4.3: {} @@ -3123,7 +3341,7 @@ snapshots: make-dir@4.0.0: dependencies: - semver: 7.7.2 + semver: 7.7.4 makeerror@1.0.12: dependencies: @@ -3140,15 +3358,19 @@ snapshots: mimic-function@5.0.1: {} - minimatch@3.1.2: + minimatch@10.2.5: + dependencies: + brace-expansion: 5.0.6 + + minimatch@3.1.5: dependencies: brace-expansion: 1.1.12 - minimatch@9.0.5: + minimatch@9.0.9: dependencies: - brace-expansion: 2.0.2 + brace-expansion: 2.1.0 - minipass@7.1.2: {} + minipass@7.1.3: {} ms@2.1.3: {} @@ -3160,7 +3382,7 @@ snapshots: node-int64@0.4.0: {} - node-releases@2.0.23: {} + node-releases@2.0.38: {} normalize-path@3.0.0: {} @@ -3172,7 +3394,7 @@ snapshots: dependencies: destr: 2.0.5 node-fetch-native: 1.6.7 - ufo: 1.6.1 + ufo: 1.6.4 once@1.4.0: dependencies: @@ -3215,13 +3437,9 @@ snapshots: package-json-from-dist@1.0.1: {} - parent-module@1.0.1: - dependencies: - callsites: 3.1.0 - parse-json@5.2.0: dependencies: - '@babel/code-frame': 7.27.1 + '@babel/code-frame': 7.29.0 error-ex: 1.3.4 json-parse-even-better-errors: 2.3.1 lines-and-columns: 1.2.4 @@ -3235,14 +3453,18 @@ snapshots: path-scurry@1.11.1: dependencies: lru-cache: 10.4.3 - minipass: 7.1.2 + minipass: 7.1.3 picocolors@1.1.1: {} picomatch@2.3.1: {} + picomatch@2.3.2: {} + picomatch@4.0.3: {} + picomatch@4.0.4: {} + pirates@4.0.7: {} pkg-dir@4.2.0: @@ -3251,7 +3473,7 @@ snapshots: prelude-ls@1.2.1: {} - prettier@3.6.2: {} + prettier@3.8.3: {} pretty-format@30.2.0: dependencies: @@ -3259,16 +3481,24 @@ snapshots: ansi-styles: 5.2.0 react-is: 18.3.1 - publish-browser-extension@4.0.0: + pretty-format@30.4.1: + dependencies: + '@jest/schemas': 30.4.1 + ansi-styles: 5.2.0 + react-is-18: react-is@18.3.1 + react-is-19: react-is@19.2.6 + + publish-browser-extension@4.0.5: dependencies: cac: 6.7.14 consola: 3.4.2 - dotenv: 17.2.3 + dotenv: 17.4.2 form-data-encoder: 4.1.0 formdata-node: 6.0.3 - listr2: 8.3.3 + jsonwebtoken: 9.0.3 + listr2: 10.2.1 ofetch: 1.5.1 - zod: 4.1.12 + zod: 4.4.3 punycode@2.3.1: {} @@ -3276,14 +3506,14 @@ snapshots: react-is@18.3.1: {} + react-is@19.2.6: {} + require-directory@2.1.1: {} resolve-cwd@3.0.0: dependencies: resolve-from: 5.0.0 - resolve-from@4.0.0: {} - resolve-from@5.0.0: {} restore-cursor@5.1.0: @@ -3293,9 +3523,11 @@ snapshots: rfdc@1.4.1: {} + safe-buffer@5.2.1: {} + semver@6.3.1: {} - semver@7.7.2: {} + semver@7.7.4: {} shebang-command@2.0.0: dependencies: @@ -3309,12 +3541,12 @@ snapshots: slash@3.0.0: {} - slice-ansi@5.0.0: + slice-ansi@7.1.2: dependencies: ansi-styles: 6.2.3 - is-fullwidth-code-point: 4.0.0 + is-fullwidth-code-point: 5.1.0 - slice-ansi@7.1.2: + slice-ansi@8.0.0: dependencies: ansi-styles: 6.2.3 is-fullwidth-code-point: 5.1.0 @@ -3347,19 +3579,24 @@ snapshots: dependencies: eastasianwidth: 0.2.0 emoji-regex: 9.2.2 - strip-ansi: 7.1.2 + strip-ansi: 7.2.0 string-width@7.2.0: dependencies: emoji-regex: 10.6.0 - get-east-asian-width: 1.4.0 - strip-ansi: 7.1.2 + get-east-asian-width: 1.5.0 + strip-ansi: 7.2.0 + + string-width@8.2.1: + dependencies: + get-east-asian-width: 1.5.0 + strip-ansi: 7.2.0 strip-ansi@6.0.1: dependencies: ansi-regex: 5.0.1 - strip-ansi@7.1.2: + strip-ansi@7.2.0: dependencies: ansi-regex: 6.2.2 @@ -3377,15 +3614,15 @@ snapshots: dependencies: has-flag: 4.0.0 - synckit@0.11.11: + synckit@0.11.12: dependencies: '@pkgr/core': 0.2.9 test-exclude@6.0.0: dependencies: - '@istanbuljs/schema': 0.1.3 + '@istanbuljs/schema': 0.1.6 glob: 7.2.3 - minimatch: 3.1.2 + minimatch: 3.1.5 tmpl@1.0.5: {} @@ -3404,10 +3641,12 @@ snapshots: type-fest@0.21.3: {} - ufo@1.6.1: {} + ufo@1.6.4: {} undici-types@7.14.0: {} + undici-types@7.19.2: {} + unrs-resolver@1.11.1: dependencies: napi-postinstall: 0.3.4 @@ -3432,9 +3671,9 @@ snapshots: '@unrs/resolver-binding-win32-ia32-msvc': 1.11.1 '@unrs/resolver-binding-win32-x64-msvc': 1.11.1 - update-browserslist-db@1.1.3(browserslist@4.26.3): + update-browserslist-db@1.2.3(browserslist@4.28.2): dependencies: - browserslist: 4.26.3 + browserslist: 4.28.2 escalade: 3.2.0 picocolors: 1.1.1 @@ -3458,6 +3697,12 @@ snapshots: word-wrap@1.2.5: {} + wrap-ansi@10.0.0: + dependencies: + ansi-styles: 6.2.3 + string-width: 8.2.1 + strip-ansi: 7.2.0 + wrap-ansi@7.0.0: dependencies: ansi-styles: 4.3.0 @@ -3468,13 +3713,13 @@ snapshots: dependencies: ansi-styles: 6.2.3 string-width: 5.1.2 - strip-ansi: 7.1.2 + strip-ansi: 7.2.0 wrap-ansi@9.0.2: dependencies: ansi-styles: 6.2.3 string-width: 7.2.0 - strip-ansi: 7.1.2 + strip-ansi: 7.2.0 wrappy@1.0.2: {} @@ -3501,4 +3746,4 @@ snapshots: yocto-queue@0.1.0: {} - zod@4.1.12: {} + zod@4.4.3: {} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml new file mode 100644 index 00000000..ab4c8b45 --- /dev/null +++ b/pnpm-workspace.yaml @@ -0,0 +1,6 @@ +# pnpm 11+ replaced `onlyBuiltDependencies` (in package.json) with +# `allowBuilds` here. eslint 10 pulls in unrs-resolver, which has a +# postinstall step to build a native binary; CI fails with +# ERR_PNPM_IGNORED_BUILDS unless we explicitly allow it. +allowBuilds: + unrs-resolver: true diff --git a/src/background.js b/src/background.js index faa854de..08859896 100644 --- a/src/background.js +++ b/src/background.js @@ -1,8 +1,39 @@ // Cross-browser compatibility const browserAPI = typeof browser !== 'undefined' ? browser : chrome; -// Create context menu on installation -browserAPI.runtime.onInstalled.addListener(() => { +// Chrome MV3 service workers expose importScripts; Firefox loads these via +// the manifest's background.scripts array instead. Order matters: +// log-buffer.js → logger.js (logger consumes mdpLogBuffer). +if (typeof importScripts === 'function' && typeof mdpLog === 'undefined') { + try { + importScripts('log-buffer.js', 'logger.js'); + } catch (_e) { + /* logger optional; ignore */ + } +} + +// Safe accessors — logger may be unavailable in pathological cases. +const log = (typeof mdpLog !== 'undefined' && mdpLog) || { + error: (...a) => console.error(...a), + warn: (...a) => console.warn(...a), + info: (...a) => console.info(...a), +}; + +// Initialize stats on first install; create context menu on every install/update. +browserAPI.runtime.onInstalled.addListener(async details => { + try { + if (details.reason === 'install') { + const existing = await browserAPI.storage.local.get('mdpStats'); + if (!existing.mdpStats) { + await browserAPI.storage.local.set({ + mdpStats: { installedAt: new Date().toISOString(), saveCount: 0 }, + }); + } + } + } catch (e) { + log.error('onInstalled: failed to init stats', e); + } + browserAPI.contextMenus.create({ id: 'saveAsMarkdown', title: browserAPI.i18n.getMessage('contextMenuTitle'), @@ -10,6 +41,19 @@ browserAPI.runtime.onInstalled.addListener(() => { }); }); +async function bumpSaveCount() { + try { + const { mdpStats = {} } = await browserAPI.storage.local.get('mdpStats'); + mdpStats.saveCount = (mdpStats.saveCount || 0) + 1; + if (!mdpStats.installedAt) { + mdpStats.installedAt = new Date().toISOString(); + } + await browserAPI.storage.local.set({ mdpStats }); + } catch (e) { + log.error('bumpSaveCount failed', e); + } +} + // Handle context menu click browserAPI.contextMenus.onClicked.addListener((info, tab) => { if (info.menuItemId === 'saveAsMarkdown') { @@ -126,8 +170,9 @@ async function savePageAsMarkdown(tabId) { saveAs: true, }); } + await bumpSaveCount(); } catch (error) { - console.error('Error saving markdown:', error); + log.error('Error saving markdown:', error); throw error; } } diff --git a/extension-chrome/icon128.png b/src/icon128.png similarity index 100% rename from extension-chrome/icon128.png rename to src/icon128.png diff --git a/extension-chrome/icon16.png b/src/icon16.png similarity index 100% rename from extension-chrome/icon16.png rename to src/icon16.png diff --git a/extension-chrome/icon48.png b/src/icon48.png similarity index 100% rename from extension-chrome/icon48.png rename to src/icon48.png diff --git a/src/log-buffer.js b/src/log-buffer.js new file mode 100644 index 00000000..6a98d3f7 --- /dev/null +++ b/src/log-buffer.js @@ -0,0 +1,41 @@ +// Pure ring-buffer helpers used by logger.js. Kept dependency-free so they +// can be unit-tested in Node without a browser or storage stub. +(function (root, factory) { + const mod = factory(); + if (typeof module !== 'undefined' && module.exports) { + module.exports = mod; + } else { + root.mdpLogBuffer = mod; + } +})(typeof self !== 'undefined' ? self : globalThis, function () { + function stringifyArg(arg) { + if (arg instanceof Error) { + return `${arg.name}: ${arg.message}${arg.stack ? `\n${arg.stack}` : ''}`; + } + if (typeof arg === 'object' && arg !== null) { + try { + return JSON.stringify(arg); + } catch (_e) { + return String(arg); + } + } + return String(arg); + } + + function formatArgs(args) { + return args.map(stringifyArg).join(' '); + } + + // Returns a new array with `entry` appended and the oldest entries trimmed + // so the result is at most `max` long. Pure: does not mutate `log`. + function appendBounded(log, entry, max) { + const base = Array.isArray(log) ? log : []; + const next = base.concat([entry]); + if (next.length <= max) { + return next; + } + return next.slice(next.length - max); + } + + return { stringifyArg, formatArgs, appendBounded }; +}); diff --git a/src/logger.js b/src/logger.js new file mode 100644 index 00000000..0c7ed07d --- /dev/null +++ b/src/logger.js @@ -0,0 +1,93 @@ +// Markdown Printer diagnostic logger. +// Ring-buffered structured log in chrome.storage.local. Works in service +// workers, event pages, and popup windows. Never throws — logging must +// never break the extension. +// +// Depends on mdpLogBuffer (src/log-buffer.js) being loaded first. +(function () { + const browserAPI = typeof browser !== 'undefined' ? browser : chrome; + const STORAGE_KEY = 'mdpDiagnosticLog'; + const MAX_ENTRIES = 200; + + // Resolve buffer helpers. In tests, log-buffer is required via CommonJS; + // in the extension, it attaches to the global before this script runs. + const buf = + (typeof mdpLogBuffer !== 'undefined' && mdpLogBuffer) || + (typeof self !== 'undefined' && self.mdpLogBuffer) || + null; + + async function append(level, args) { + if (!buf) { + return; + } + try { + const entry = { + t: new Date().toISOString(), + level, + msg: buf.formatArgs(args), + }; + const stored = await browserAPI.storage.local.get(STORAGE_KEY); + const next = buf.appendBounded(stored[STORAGE_KEY], entry, MAX_ENTRIES); + await browserAPI.storage.local.set({ [STORAGE_KEY]: next }); + } catch (_e) { + // Swallow — logger must never break the caller. + } + } + + function wrap(level, fallback) { + return function (...args) { + try { + fallback.apply(console, args); + } catch (_e) { + /* noop */ + } + append(level, args); + }; + } + + const mdpLog = { + error: wrap('error', console.error), + warn: wrap('warn', console.warn), + info: wrap('info', console.info || console.log), + }; + + async function mdpGetDiagnostics() { + let log = []; + let stats = {}; + try { + const stored = await browserAPI.storage.local.get([STORAGE_KEY, 'mdpStats']); + log = stored[STORAGE_KEY] || []; + stats = stored.mdpStats || {}; + } catch (_e) { + /* noop */ + } + const manifest = browserAPI.runtime.getManifest(); + const ua = typeof navigator !== 'undefined' ? navigator.userAgent : 'n/a'; + const lang = typeof navigator !== 'undefined' ? navigator.language : 'n/a'; + return { + extension: { name: manifest.name, version: manifest.version }, + browser: ua, + language: lang, + stats, + log, + capturedAt: new Date().toISOString(), + }; + } + + function installGlobalHandlers(scope) { + if (!scope || typeof scope.addEventListener !== 'function') { + return; + } + scope.addEventListener('error', event => { + mdpLog.error('uncaught', event.error || event.message || event); + }); + scope.addEventListener('unhandledrejection', event => { + mdpLog.error('unhandledrejection', event.reason); + }); + } + + const g = typeof self !== 'undefined' ? self : globalThis; + g.mdpLog = mdpLog; + g.mdpGetDiagnostics = mdpGetDiagnostics; + installGlobalHandlers(g); +})(); diff --git a/src/popup.html b/src/popup.html new file mode 100644 index 00000000..8a96145a --- /dev/null +++ b/src/popup.html @@ -0,0 +1,176 @@ + + + + + + + +

+ + + +

+ + + + +
+ +
+ + + + + + + + diff --git a/src/popup.js b/src/popup.js new file mode 100644 index 00000000..02f6ba7e --- /dev/null +++ b/src/popup.js @@ -0,0 +1,184 @@ +// Cross-browser compatibility +const browserAPI = typeof browser !== 'undefined' ? browser : chrome; + +// Policy + URLs come from src/rating-policy.js (loaded as a separate