Import Instagram archive to a Bluesky account
9
fork

Configure Feed

Select the types of activity you want to include in your feed.

Merge pull request #33 from straiforos/split-mixed-media-and-image-max-posts

Split mixed media and image max posts

authored by

Marco Maroni and committed by
GitHub
9297eedd 7014630c

+4979 -645
+1
.env.dist
··· 10 10 TEST_VIDEO_MODE=0 11 11 TEST_IMAGE_MODE=0 12 12 TEST_IMAGES_MODE=0 # 5 images in a post (only 4 should upload) 13 + TEST_MIXED_MEDIA_MODE=0 # 5 images in a post (uploads two posts, one with 4 and a second with 1) 13 14 # Logging level 14 15 LOG_LEVEL=info
+42
.github/workflows/ci.yml
··· 1 + name: CI 2 + 3 + on: 4 + push: 5 + pull_request: 6 + 7 + jobs: 8 + lint: 9 + runs-on: ubuntu-latest 10 + 11 + steps: 12 + - uses: actions/checkout@v4 13 + 14 + - name: Use Node.js 15 + uses: actions/setup-node@v4 16 + with: 17 + node-version: '20.x' 18 + cache: 'npm' 19 + 20 + - name: Install dependencies 21 + run: npm ci 22 + 23 + - name: Run ESLint 24 + run: npm run lint 25 + 26 + test: 27 + runs-on: ubuntu-latest 28 + 29 + steps: 30 + - uses: actions/checkout@v4 31 + 32 + - name: Use Node.js 33 + uses: actions/setup-node@v4 34 + with: 35 + node-version: '20.x' 36 + cache: 'npm' 37 + 38 + - name: Install dependencies 39 + run: npm ci 40 + 41 + - name: Run tests 42 + run: npm test
+3 -1
.gitignore
··· 20 20 # Manual network exports from dev tools 21 21 *.har 22 22 23 - dist 23 + dist 24 + 25 + coverage
+11 -7
README.md
··· 12 12 13 13 - Imports photos and videos from Instagram posts 14 14 - Preserves original post dates and captions 15 - - Supports importing up to 4 images per post (Bluesky limit) 15 + - Supports importing up to 4 images per post, splitting a post to incude all images and videos due to bluesky limits. 16 16 - Test modes for verifying video and image imports 17 17 - Simulation mode to estimate import time 18 18 - Configurable date ranges for selective imports ··· 54 54 ARCHIVE_FOLDER=c:/download/instagram-username-2025-XX-XX-hash 55 55 56 56 # Optional settings 57 - SIMULATE=1 # Set to 1 to simulate import without posting 58 - TEST_VIDEO_MODE=0 # Set to 1 to test video imports 59 - TEST_IMAGE_MODE=0 # Set to 1 to test image imports 60 - TEST_IMAGES_MODE=0 # Set to 1 to test post with many images. (5 yet only 4 will be allowed due to limit.) 61 - LOG_LEVEL=debug # Set logging verbosity (debug, info, warn, error) 57 + SIMULATE=1 # Set to 1 to simulate import without posting 58 + TEST_VIDEO_MODE=0 # Set to 1 to test video imports 59 + TEST_IMAGE_MODE=0 # Set to 1 to test image imports 60 + TEST_IMAGES_MODE=1 # 5 images in a post (all 4 should upload, plus a second post with 1) 61 + TEST_MIXED_MEDIA_MODE=0 # many images and videos, single post split into 5, with a total of 10 media uploaded. 62 62 MIN_DATE=2020-01-01 # Only import posts after this date 63 63 MAX_DATE=2025-01-01 # Only import posts before this date 64 + LOG_LEVEL=debug # Set logging verbosity (debug, info, warn, error) 64 65 ``` 65 66 66 67 ## Running the Import ··· 72 73 73 74 ### Test Modes 74 75 75 - The project includes two test modes to verify imports: 76 + The project includes four test modes to verify imports: 76 77 77 78 - `TEST_VIDEO_MODE`: Tests video import functionality 78 79 - `TEST_IMAGE_MODE`: Tests image import functionality 80 + - `TEST_IMAGES_MODE`: Tests max image per post split functionality. 81 + - `TEST_MIXED_MEDIA_MODE`: Tests posts with video and image formats splitting content to match Bluesky's limitations. 79 82 80 83 Enable these by setting the corresponding environment variable to 1. Note: You cannot enable both modes simultaneously. 81 84 ··· 92 95 ## Limitations 93 96 94 97 - Maximum 4 images per post (Bluesky platform limit) 98 + - Splits posts adding a postfix `(Part 1/4)` ensuring no data loss. 95 99 - Maximum video size of 100MB 96 100 - Rate limiting enforced between posts 97 101 - Stories, and likes can not be imported.
+123
eslint.config.ts
··· 1 + import eslint from '@eslint/js'; 2 + import tseslint from '@typescript-eslint/eslint-plugin'; 3 + import tsParser from '@typescript-eslint/parser'; 4 + import eslintConfigImport from 'eslint-plugin-import'; 5 + import eslintPluginJest from 'eslint-plugin-jest'; 6 + import globals from 'globals'; 7 + 8 + export default [ 9 + { 10 + ignores: ['dist/**', 'node_modules/**'] 11 + }, 12 + { 13 + files: ['**/*.ts'], 14 + ignores: ['**/*.test.ts'], 15 + languageOptions: { 16 + parser: tsParser, 17 + parserOptions: { 18 + ecmaVersion: 'latest', 19 + sourceType: 'module', 20 + project: ['./tsconfig.json', './tsconfig.root.json'] 21 + }, 22 + globals: { 23 + ...globals.node, 24 + ...globals.es2021 25 + } 26 + }, 27 + plugins: { 28 + '@typescript-eslint': tseslint, 29 + 'import': eslintConfigImport 30 + }, 31 + rules: { 32 + ...eslint.configs.recommended.rules, 33 + ...tseslint.configs.recommended.rules, 34 + 'no-unused-vars': 'off', 35 + '@typescript-eslint/no-unused-vars': ['warn'], 36 + '@typescript-eslint/no-explicit-any': 'off', 37 + '@typescript-eslint/no-empty-interface': 'warn', 38 + '@typescript-eslint/no-empty-object-type': 'warn', 39 + 'import/order': [ 40 + 'error', 41 + { 42 + 'groups': [ 43 + 'builtin', 44 + 'external', 45 + 'internal', 46 + ['parent', 'sibling'], 47 + 'index', 48 + 'object', 49 + 'type' 50 + ], 51 + 'newlines-between': 'always', 52 + 'alphabetize': { 53 + 'order': 'asc', 54 + 'caseInsensitive': true 55 + } 56 + } 57 + ] 58 + }, 59 + settings: { 60 + 'import/resolver': { 61 + node: { 62 + extensions: ['.js', '.jsx', '.ts', '.tsx'] 63 + } 64 + } 65 + } 66 + }, 67 + { 68 + files: ['**/*.test.ts'], 69 + languageOptions: { 70 + parser: tsParser, 71 + parserOptions: { 72 + ecmaVersion: 'latest', 73 + sourceType: 'module', 74 + project: './tsconfig.jest.json' 75 + }, 76 + globals: { 77 + ...globals.node, 78 + ...globals.es2021, 79 + ...eslintPluginJest.environments.globals.globals 80 + } 81 + }, 82 + plugins: { 83 + '@typescript-eslint': tseslint, 84 + 'import': eslintConfigImport, 85 + 'jest': eslintPluginJest 86 + }, 87 + rules: { 88 + ...eslint.configs.recommended.rules, 89 + ...tseslint.configs.recommended.rules, 90 + ...eslintPluginJest.configs['flat/recommended'].rules, 91 + 'no-unused-vars': 'off', 92 + '@typescript-eslint/no-unused-vars': ['warn'], 93 + '@typescript-eslint/no-explicit-any': 'off', 94 + '@typescript-eslint/no-empty-interface': 'warn', 95 + 'import/order': [ 96 + 'error', 97 + { 98 + 'groups': [ 99 + 'builtin', 100 + 'external', 101 + 'internal', 102 + ['parent', 'sibling'], 103 + 'index', 104 + 'object', 105 + 'type' 106 + ], 107 + 'newlines-between': 'always', 108 + 'alphabetize': { 109 + 'order': 'asc', 110 + 'caseInsensitive': true 111 + } 112 + } 113 + ] 114 + }, 115 + settings: { 116 + 'import/resolver': { 117 + node: { 118 + extensions: ['.js', '.jsx', '.ts', '.tsx'] 119 + } 120 + } 121 + } 122 + } 123 + ];
+9 -1
jest.config.ts
··· 10 10 tsconfig: 'tsconfig.jest.json' 11 11 } 12 12 ] 13 - } 13 + }, 14 + collectCoverage: true, 15 + coverageDirectory: 'coverage', 16 + coverageReporters: ['text', 'lcov'], 17 + collectCoverageFrom: [ 18 + 'src/**/*.{js,jsx,ts,tsx}', 19 + '!src/**/*.d.ts', 20 + '!src/**/*.test.{js,jsx,ts,tsx}' 21 + ] 14 22 };
+2975 -27
package-lock.json
··· 22 22 "sharp": "^0.33.5" 23 23 }, 24 24 "devDependencies": { 25 + "@eslint/js": "^9.21.0", 25 26 "@types/jest": "^29.5.14", 26 27 "@types/node": "^22.10.10", 28 + "@typescript-eslint/eslint-plugin": "^8.26.0", 29 + "@typescript-eslint/parser": "^8.26.0", 30 + "eslint": "^9.21.0", 31 + "eslint-plugin-import": "^2.31.0", 32 + "eslint-plugin-jest": "^28.11.0", 33 + "globals": "^16.0.0", 34 + "jiti": "^2.4.2", 27 35 "ts-jest": "^29.2.5", 28 36 "ts-node": "^10.9.2", 29 37 "tsconfig-paths": "^4.2.0", ··· 606 614 "node": ">=6.9.0" 607 615 } 608 616 }, 617 + "node_modules/@babel/traverse/node_modules/globals": { 618 + "version": "11.12.0", 619 + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", 620 + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", 621 + "dev": true, 622 + "license": "MIT", 623 + "peer": true, 624 + "engines": { 625 + "node": ">=4" 626 + } 627 + }, 609 628 "node_modules/@babel/types": { 610 629 "version": "7.26.8", 611 630 "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.26.8.tgz", ··· 659 678 "tslib": "^2.4.0" 660 679 } 661 680 }, 681 + "node_modules/@eslint-community/eslint-utils": { 682 + "version": "4.4.1", 683 + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.1.tgz", 684 + "integrity": "sha512-s3O3waFUrMV8P/XaF/+ZTp1X9XBZW1a4B97ZnjQF2KYWaFD2A8KyFBsrsfSjEmjn3RGWAIuvlneuZm3CUK3jbA==", 685 + "dev": true, 686 + "license": "MIT", 687 + "dependencies": { 688 + "eslint-visitor-keys": "^3.4.3" 689 + }, 690 + "engines": { 691 + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" 692 + }, 693 + "funding": { 694 + "url": "https://opencollective.com/eslint" 695 + }, 696 + "peerDependencies": { 697 + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" 698 + } 699 + }, 700 + "node_modules/@eslint-community/regexpp": { 701 + "version": "4.12.1", 702 + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.12.1.tgz", 703 + "integrity": "sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==", 704 + "dev": true, 705 + "license": "MIT", 706 + "engines": { 707 + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" 708 + } 709 + }, 710 + "node_modules/@eslint/config-array": { 711 + "version": "0.19.2", 712 + "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.19.2.tgz", 713 + "integrity": "sha512-GNKqxfHG2ySmJOBSHg7LxeUx4xpuCoFjacmlCoYWEbaPXLwvfIjixRI12xCQZeULksQb23uiA8F40w5TojpV7w==", 714 + "dev": true, 715 + "license": "Apache-2.0", 716 + "dependencies": { 717 + "@eslint/object-schema": "^2.1.6", 718 + "debug": "^4.3.1", 719 + "minimatch": "^3.1.2" 720 + }, 721 + "engines": { 722 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 723 + } 724 + }, 725 + "node_modules/@eslint/core": { 726 + "version": "0.12.0", 727 + "resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.12.0.tgz", 728 + "integrity": "sha512-cmrR6pytBuSMTaBweKoGMwu3EiHiEC+DoyupPmlZ0HxBJBtIxwe+j/E4XPIKNx+Q74c8lXKPwYawBf5glsTkHg==", 729 + "dev": true, 730 + "license": "Apache-2.0", 731 + "dependencies": { 732 + "@types/json-schema": "^7.0.15" 733 + }, 734 + "engines": { 735 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 736 + } 737 + }, 738 + "node_modules/@eslint/eslintrc": { 739 + "version": "3.3.0", 740 + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.3.0.tgz", 741 + "integrity": "sha512-yaVPAiNAalnCZedKLdR21GOGILMLKPyqSLWaAjQFvYA2i/ciDi8ArYVr69Anohb6cH2Ukhqti4aFnYyPm8wdwQ==", 742 + "dev": true, 743 + "license": "MIT", 744 + "dependencies": { 745 + "ajv": "^6.12.4", 746 + "debug": "^4.3.2", 747 + "espree": "^10.0.1", 748 + "globals": "^14.0.0", 749 + "ignore": "^5.2.0", 750 + "import-fresh": "^3.2.1", 751 + "js-yaml": "^4.1.0", 752 + "minimatch": "^3.1.2", 753 + "strip-json-comments": "^3.1.1" 754 + }, 755 + "engines": { 756 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 757 + }, 758 + "funding": { 759 + "url": "https://opencollective.com/eslint" 760 + } 761 + }, 762 + "node_modules/@eslint/eslintrc/node_modules/argparse": { 763 + "version": "2.0.1", 764 + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", 765 + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", 766 + "dev": true, 767 + "license": "Python-2.0" 768 + }, 769 + "node_modules/@eslint/eslintrc/node_modules/globals": { 770 + "version": "14.0.0", 771 + "resolved": "https://registry.npmjs.org/globals/-/globals-14.0.0.tgz", 772 + "integrity": "sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==", 773 + "dev": true, 774 + "license": "MIT", 775 + "engines": { 776 + "node": ">=18" 777 + }, 778 + "funding": { 779 + "url": "https://github.com/sponsors/sindresorhus" 780 + } 781 + }, 782 + "node_modules/@eslint/eslintrc/node_modules/js-yaml": { 783 + "version": "4.1.0", 784 + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", 785 + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", 786 + "dev": true, 787 + "license": "MIT", 788 + "dependencies": { 789 + "argparse": "^2.0.1" 790 + }, 791 + "bin": { 792 + "js-yaml": "bin/js-yaml.js" 793 + } 794 + }, 795 + "node_modules/@eslint/js": { 796 + "version": "9.21.0", 797 + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.21.0.tgz", 798 + "integrity": "sha512-BqStZ3HX8Yz6LvsF5ByXYrtigrV5AXADWLAGc7PH/1SxOb7/FIYYMszZZWiUou/GB9P2lXWk2SV4d+Z8h0nknw==", 799 + "dev": true, 800 + "license": "MIT", 801 + "engines": { 802 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 803 + } 804 + }, 805 + "node_modules/@eslint/object-schema": { 806 + "version": "2.1.6", 807 + "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.6.tgz", 808 + "integrity": "sha512-RBMg5FRL0I0gs51M/guSAj5/e14VQ4tpZnQNWwuDT66P14I43ItmPfIZRhO9fUVIPOAQXU47atlywZ/czoqFPA==", 809 + "dev": true, 810 + "license": "Apache-2.0", 811 + "engines": { 812 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 813 + } 814 + }, 815 + "node_modules/@eslint/plugin-kit": { 816 + "version": "0.2.7", 817 + "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.7.tgz", 818 + "integrity": "sha512-JubJ5B2pJ4k4yGxaNLdbjrnk9d/iDz6/q8wOilpIowd6PJPgaxCuHBnBszq7Ce2TyMrywm5r4PnKm6V3iiZF+g==", 819 + "dev": true, 820 + "license": "Apache-2.0", 821 + "dependencies": { 822 + "@eslint/core": "^0.12.0", 823 + "levn": "^0.4.1" 824 + }, 825 + "engines": { 826 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 827 + } 828 + }, 662 829 "node_modules/@ffprobe-installer/darwin-arm64": { 663 830 "version": "5.0.1", 664 831 "resolved": "https://registry.npmjs.org/@ffprobe-installer/darwin-arm64/-/darwin-arm64-5.0.1.tgz", ··· 779 946 "win32" 780 947 ] 781 948 }, 949 + "node_modules/@humanfs/core": { 950 + "version": "0.19.1", 951 + "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.1.tgz", 952 + "integrity": "sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==", 953 + "dev": true, 954 + "license": "Apache-2.0", 955 + "engines": { 956 + "node": ">=18.18.0" 957 + } 958 + }, 959 + "node_modules/@humanfs/node": { 960 + "version": "0.16.6", 961 + "resolved": "https://registry.npmjs.org/@humanfs/node/-/node-0.16.6.tgz", 962 + "integrity": "sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==", 963 + "dev": true, 964 + "license": "Apache-2.0", 965 + "dependencies": { 966 + "@humanfs/core": "^0.19.1", 967 + "@humanwhocodes/retry": "^0.3.0" 968 + }, 969 + "engines": { 970 + "node": ">=18.18.0" 971 + } 972 + }, 973 + "node_modules/@humanfs/node/node_modules/@humanwhocodes/retry": { 974 + "version": "0.3.1", 975 + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.1.tgz", 976 + "integrity": "sha512-JBxkERygn7Bv/GbN5Rv8Ul6LVknS+5Bp6RgDC/O8gEBU/yeH5Ui5C/OlWrTb6qct7LjjfT6Re2NxB0ln0yYybA==", 977 + "dev": true, 978 + "license": "Apache-2.0", 979 + "engines": { 980 + "node": ">=18.18" 981 + }, 982 + "funding": { 983 + "type": "github", 984 + "url": "https://github.com/sponsors/nzakas" 985 + } 986 + }, 987 + "node_modules/@humanwhocodes/module-importer": { 988 + "version": "1.0.1", 989 + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", 990 + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", 991 + "dev": true, 992 + "license": "Apache-2.0", 993 + "engines": { 994 + "node": ">=12.22" 995 + }, 996 + "funding": { 997 + "type": "github", 998 + "url": "https://github.com/sponsors/nzakas" 999 + } 1000 + }, 1001 + "node_modules/@humanwhocodes/retry": { 1002 + "version": "0.4.2", 1003 + "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.4.2.tgz", 1004 + "integrity": "sha512-xeO57FpIu4p1Ri3Jq/EXq4ClRm86dVF2z/+kvFnyqVYRavTZmaFaUBbWCOuuTh0o/g7DSsk6kc2vrS4Vl5oPOQ==", 1005 + "dev": true, 1006 + "license": "Apache-2.0", 1007 + "engines": { 1008 + "node": ">=18.18" 1009 + }, 1010 + "funding": { 1011 + "type": "github", 1012 + "url": "https://github.com/sponsors/nzakas" 1013 + } 1014 + }, 782 1015 "node_modules/@img/sharp-darwin-arm64": { 783 1016 "version": "0.33.5", 784 1017 "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz", ··· 1513 1746 "integrity": "sha512-eMk0b9ReBbV23xXU693TAIrLyeO5iTgBZGSJfpqriG8UkYvr/hC9u9pyMlAakDNHWmbhMZCDs6KQO0jzKD8OTw==", 1514 1747 "license": "MIT" 1515 1748 }, 1749 + "node_modules/@nodelib/fs.scandir": { 1750 + "version": "2.1.5", 1751 + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", 1752 + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", 1753 + "dev": true, 1754 + "license": "MIT", 1755 + "dependencies": { 1756 + "@nodelib/fs.stat": "2.0.5", 1757 + "run-parallel": "^1.1.9" 1758 + }, 1759 + "engines": { 1760 + "node": ">= 8" 1761 + } 1762 + }, 1763 + "node_modules/@nodelib/fs.stat": { 1764 + "version": "2.0.5", 1765 + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", 1766 + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", 1767 + "dev": true, 1768 + "license": "MIT", 1769 + "engines": { 1770 + "node": ">= 8" 1771 + } 1772 + }, 1773 + "node_modules/@nodelib/fs.walk": { 1774 + "version": "1.2.8", 1775 + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", 1776 + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", 1777 + "dev": true, 1778 + "license": "MIT", 1779 + "dependencies": { 1780 + "@nodelib/fs.scandir": "2.1.5", 1781 + "fastq": "^1.6.0" 1782 + }, 1783 + "engines": { 1784 + "node": ">= 8" 1785 + } 1786 + }, 1787 + "node_modules/@rtsao/scc": { 1788 + "version": "1.1.0", 1789 + "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", 1790 + "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", 1791 + "dev": true, 1792 + "license": "MIT" 1793 + }, 1516 1794 "node_modules/@sinclair/typebox": { 1517 1795 "version": "0.27.8", 1518 1796 "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.8.tgz", ··· 1608 1886 "@babel/types": "^7.20.7" 1609 1887 } 1610 1888 }, 1889 + "node_modules/@types/estree": { 1890 + "version": "1.0.6", 1891 + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.6.tgz", 1892 + "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", 1893 + "dev": true, 1894 + "license": "MIT" 1895 + }, 1611 1896 "node_modules/@types/gensync": { 1612 1897 "version": "1.0.4", 1613 1898 "resolved": "https://registry.npmjs.org/@types/gensync/-/gensync-1.0.4.tgz", ··· 1659 1944 "pretty-format": "^29.0.0" 1660 1945 } 1661 1946 }, 1947 + "node_modules/@types/json-schema": { 1948 + "version": "7.0.15", 1949 + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", 1950 + "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", 1951 + "dev": true, 1952 + "license": "MIT" 1953 + }, 1954 + "node_modules/@types/json5": { 1955 + "version": "0.0.29", 1956 + "resolved": "https://registry.npmjs.org/@types/json5/-/json5-0.0.29.tgz", 1957 + "integrity": "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==", 1958 + "dev": true, 1959 + "license": "MIT" 1960 + }, 1662 1961 "node_modules/@types/node": { 1663 1962 "version": "22.10.10", 1664 1963 "resolved": "https://registry.npmjs.org/@types/node/-/node-22.10.10.tgz", ··· 1690 1989 "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", 1691 1990 "dev": true 1692 1991 }, 1992 + "node_modules/@typescript-eslint/eslint-plugin": { 1993 + "version": "8.26.0", 1994 + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.26.0.tgz", 1995 + "integrity": "sha512-cLr1J6pe56zjKYajK6SSSre6nl1Gj6xDp1TY0trpgPzjVbgDwd09v2Ws37LABxzkicmUjhEeg/fAUjPJJB1v5Q==", 1996 + "dev": true, 1997 + "license": "MIT", 1998 + "dependencies": { 1999 + "@eslint-community/regexpp": "^4.10.0", 2000 + "@typescript-eslint/scope-manager": "8.26.0", 2001 + "@typescript-eslint/type-utils": "8.26.0", 2002 + "@typescript-eslint/utils": "8.26.0", 2003 + "@typescript-eslint/visitor-keys": "8.26.0", 2004 + "graphemer": "^1.4.0", 2005 + "ignore": "^5.3.1", 2006 + "natural-compare": "^1.4.0", 2007 + "ts-api-utils": "^2.0.1" 2008 + }, 2009 + "engines": { 2010 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 2011 + }, 2012 + "funding": { 2013 + "type": "opencollective", 2014 + "url": "https://opencollective.com/typescript-eslint" 2015 + }, 2016 + "peerDependencies": { 2017 + "@typescript-eslint/parser": "^8.0.0 || ^8.0.0-alpha.0", 2018 + "eslint": "^8.57.0 || ^9.0.0", 2019 + "typescript": ">=4.8.4 <5.9.0" 2020 + } 2021 + }, 2022 + "node_modules/@typescript-eslint/parser": { 2023 + "version": "8.26.0", 2024 + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.26.0.tgz", 2025 + "integrity": "sha512-mNtXP9LTVBy14ZF3o7JG69gRPBK/2QWtQd0j0oH26HcY/foyJJau6pNUez7QrM5UHnSvwlQcJXKsk0I99B9pOA==", 2026 + "dev": true, 2027 + "license": "MIT", 2028 + "dependencies": { 2029 + "@typescript-eslint/scope-manager": "8.26.0", 2030 + "@typescript-eslint/types": "8.26.0", 2031 + "@typescript-eslint/typescript-estree": "8.26.0", 2032 + "@typescript-eslint/visitor-keys": "8.26.0", 2033 + "debug": "^4.3.4" 2034 + }, 2035 + "engines": { 2036 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 2037 + }, 2038 + "funding": { 2039 + "type": "opencollective", 2040 + "url": "https://opencollective.com/typescript-eslint" 2041 + }, 2042 + "peerDependencies": { 2043 + "eslint": "^8.57.0 || ^9.0.0", 2044 + "typescript": ">=4.8.4 <5.9.0" 2045 + } 2046 + }, 2047 + "node_modules/@typescript-eslint/scope-manager": { 2048 + "version": "8.26.0", 2049 + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.26.0.tgz", 2050 + "integrity": "sha512-E0ntLvsfPqnPwng8b8y4OGuzh/iIOm2z8U3S9zic2TeMLW61u5IH2Q1wu0oSTkfrSzwbDJIB/Lm8O3//8BWMPA==", 2051 + "dev": true, 2052 + "license": "MIT", 2053 + "dependencies": { 2054 + "@typescript-eslint/types": "8.26.0", 2055 + "@typescript-eslint/visitor-keys": "8.26.0" 2056 + }, 2057 + "engines": { 2058 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 2059 + }, 2060 + "funding": { 2061 + "type": "opencollective", 2062 + "url": "https://opencollective.com/typescript-eslint" 2063 + } 2064 + }, 2065 + "node_modules/@typescript-eslint/type-utils": { 2066 + "version": "8.26.0", 2067 + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.26.0.tgz", 2068 + "integrity": "sha512-ruk0RNChLKz3zKGn2LwXuVoeBcUMh+jaqzN461uMMdxy5H9epZqIBtYj7UiPXRuOpaALXGbmRuZQhmwHhaS04Q==", 2069 + "dev": true, 2070 + "license": "MIT", 2071 + "dependencies": { 2072 + "@typescript-eslint/typescript-estree": "8.26.0", 2073 + "@typescript-eslint/utils": "8.26.0", 2074 + "debug": "^4.3.4", 2075 + "ts-api-utils": "^2.0.1" 2076 + }, 2077 + "engines": { 2078 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 2079 + }, 2080 + "funding": { 2081 + "type": "opencollective", 2082 + "url": "https://opencollective.com/typescript-eslint" 2083 + }, 2084 + "peerDependencies": { 2085 + "eslint": "^8.57.0 || ^9.0.0", 2086 + "typescript": ">=4.8.4 <5.9.0" 2087 + } 2088 + }, 2089 + "node_modules/@typescript-eslint/types": { 2090 + "version": "8.26.0", 2091 + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.26.0.tgz", 2092 + "integrity": "sha512-89B1eP3tnpr9A8L6PZlSjBvnJhWXtYfZhECqlBl1D9Lme9mHO6iWlsprBtVenQvY1HMhax1mWOjhtL3fh/u+pA==", 2093 + "dev": true, 2094 + "license": "MIT", 2095 + "engines": { 2096 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 2097 + }, 2098 + "funding": { 2099 + "type": "opencollective", 2100 + "url": "https://opencollective.com/typescript-eslint" 2101 + } 2102 + }, 2103 + "node_modules/@typescript-eslint/typescript-estree": { 2104 + "version": "8.26.0", 2105 + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.26.0.tgz", 2106 + "integrity": "sha512-tiJ1Hvy/V/oMVRTbEOIeemA2XoylimlDQ03CgPPNaHYZbpsc78Hmngnt+WXZfJX1pjQ711V7g0H7cSJThGYfPQ==", 2107 + "dev": true, 2108 + "license": "MIT", 2109 + "dependencies": { 2110 + "@typescript-eslint/types": "8.26.0", 2111 + "@typescript-eslint/visitor-keys": "8.26.0", 2112 + "debug": "^4.3.4", 2113 + "fast-glob": "^3.3.2", 2114 + "is-glob": "^4.0.3", 2115 + "minimatch": "^9.0.4", 2116 + "semver": "^7.6.0", 2117 + "ts-api-utils": "^2.0.1" 2118 + }, 2119 + "engines": { 2120 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 2121 + }, 2122 + "funding": { 2123 + "type": "opencollective", 2124 + "url": "https://opencollective.com/typescript-eslint" 2125 + }, 2126 + "peerDependencies": { 2127 + "typescript": ">=4.8.4 <5.9.0" 2128 + } 2129 + }, 2130 + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { 2131 + "version": "2.0.1", 2132 + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", 2133 + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", 2134 + "dev": true, 2135 + "license": "MIT", 2136 + "dependencies": { 2137 + "balanced-match": "^1.0.0" 2138 + } 2139 + }, 2140 + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { 2141 + "version": "9.0.5", 2142 + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", 2143 + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", 2144 + "dev": true, 2145 + "license": "ISC", 2146 + "dependencies": { 2147 + "brace-expansion": "^2.0.1" 2148 + }, 2149 + "engines": { 2150 + "node": ">=16 || 14 >=14.17" 2151 + }, 2152 + "funding": { 2153 + "url": "https://github.com/sponsors/isaacs" 2154 + } 2155 + }, 2156 + "node_modules/@typescript-eslint/utils": { 2157 + "version": "8.26.0", 2158 + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.26.0.tgz", 2159 + "integrity": "sha512-2L2tU3FVwhvU14LndnQCA2frYC8JnPDVKyQtWFPf8IYFMt/ykEN1bPolNhNbCVgOmdzTlWdusCTKA/9nKrf8Ig==", 2160 + "dev": true, 2161 + "license": "MIT", 2162 + "dependencies": { 2163 + "@eslint-community/eslint-utils": "^4.4.0", 2164 + "@typescript-eslint/scope-manager": "8.26.0", 2165 + "@typescript-eslint/types": "8.26.0", 2166 + "@typescript-eslint/typescript-estree": "8.26.0" 2167 + }, 2168 + "engines": { 2169 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 2170 + }, 2171 + "funding": { 2172 + "type": "opencollective", 2173 + "url": "https://opencollective.com/typescript-eslint" 2174 + }, 2175 + "peerDependencies": { 2176 + "eslint": "^8.57.0 || ^9.0.0", 2177 + "typescript": ">=4.8.4 <5.9.0" 2178 + } 2179 + }, 2180 + "node_modules/@typescript-eslint/visitor-keys": { 2181 + "version": "8.26.0", 2182 + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.26.0.tgz", 2183 + "integrity": "sha512-2z8JQJWAzPdDd51dRQ/oqIJxe99/hoLIqmf8RMCAJQtYDc535W/Jt2+RTP4bP0aKeBG1F65yjIZuczOXCmbWwg==", 2184 + "dev": true, 2185 + "license": "MIT", 2186 + "dependencies": { 2187 + "@typescript-eslint/types": "8.26.0", 2188 + "eslint-visitor-keys": "^4.2.0" 2189 + }, 2190 + "engines": { 2191 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 2192 + }, 2193 + "funding": { 2194 + "type": "opencollective", 2195 + "url": "https://opencollective.com/typescript-eslint" 2196 + } 2197 + }, 2198 + "node_modules/@typescript-eslint/visitor-keys/node_modules/eslint-visitor-keys": { 2199 + "version": "4.2.0", 2200 + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", 2201 + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", 2202 + "dev": true, 2203 + "license": "Apache-2.0", 2204 + "engines": { 2205 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 2206 + }, 2207 + "funding": { 2208 + "url": "https://opencollective.com/eslint" 2209 + } 2210 + }, 1693 2211 "node_modules/acorn": { 1694 2212 "version": "8.14.0", 1695 2213 "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.0.tgz", ··· 1702 2220 "node": ">=0.4.0" 1703 2221 } 1704 2222 }, 2223 + "node_modules/acorn-jsx": { 2224 + "version": "5.3.2", 2225 + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", 2226 + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", 2227 + "dev": true, 2228 + "license": "MIT", 2229 + "peerDependencies": { 2230 + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" 2231 + } 2232 + }, 1705 2233 "node_modules/acorn-walk": { 1706 2234 "version": "8.3.4", 1707 2235 "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-8.3.4.tgz", ··· 1712 2240 }, 1713 2241 "engines": { 1714 2242 "node": ">=0.4.0" 2243 + } 2244 + }, 2245 + "node_modules/ajv": { 2246 + "version": "6.12.6", 2247 + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", 2248 + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", 2249 + "dev": true, 2250 + "license": "MIT", 2251 + "dependencies": { 2252 + "fast-deep-equal": "^3.1.1", 2253 + "fast-json-stable-stringify": "^2.0.0", 2254 + "json-schema-traverse": "^0.4.1", 2255 + "uri-js": "^4.2.2" 2256 + }, 2257 + "funding": { 2258 + "type": "github", 2259 + "url": "https://github.com/sponsors/epoberezkin" 1715 2260 } 1716 2261 }, 1717 2262 "node_modules/ansi-escapes": { ··· 1785 2330 "sprintf-js": "~1.0.2" 1786 2331 } 1787 2332 }, 2333 + "node_modules/array-buffer-byte-length": { 2334 + "version": "1.0.2", 2335 + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", 2336 + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", 2337 + "dev": true, 2338 + "license": "MIT", 2339 + "dependencies": { 2340 + "call-bound": "^1.0.3", 2341 + "is-array-buffer": "^3.0.5" 2342 + }, 2343 + "engines": { 2344 + "node": ">= 0.4" 2345 + }, 2346 + "funding": { 2347 + "url": "https://github.com/sponsors/ljharb" 2348 + } 2349 + }, 2350 + "node_modules/array-includes": { 2351 + "version": "3.1.8", 2352 + "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.8.tgz", 2353 + "integrity": "sha512-itaWrbYbqpGXkGhZPGUulwnhVf5Hpy1xiCFsGqyIGglbBxmG5vSjxQen3/WGOjPpNEv1RtBLKxbmVXm8HpJStQ==", 2354 + "dev": true, 2355 + "license": "MIT", 2356 + "dependencies": { 2357 + "call-bind": "^1.0.7", 2358 + "define-properties": "^1.2.1", 2359 + "es-abstract": "^1.23.2", 2360 + "es-object-atoms": "^1.0.0", 2361 + "get-intrinsic": "^1.2.4", 2362 + "is-string": "^1.0.7" 2363 + }, 2364 + "engines": { 2365 + "node": ">= 0.4" 2366 + }, 2367 + "funding": { 2368 + "url": "https://github.com/sponsors/ljharb" 2369 + } 2370 + }, 2371 + "node_modules/array.prototype.findlastindex": { 2372 + "version": "1.2.5", 2373 + "resolved": "https://registry.npmjs.org/array.prototype.findlastindex/-/array.prototype.findlastindex-1.2.5.tgz", 2374 + "integrity": "sha512-zfETvRFA8o7EiNn++N5f/kaCw221hrpGsDmcpndVupkPzEc1Wuf3VgC0qby1BbHs7f5DVYjgtEU2LLh5bqeGfQ==", 2375 + "dev": true, 2376 + "license": "MIT", 2377 + "dependencies": { 2378 + "call-bind": "^1.0.7", 2379 + "define-properties": "^1.2.1", 2380 + "es-abstract": "^1.23.2", 2381 + "es-errors": "^1.3.0", 2382 + "es-object-atoms": "^1.0.0", 2383 + "es-shim-unscopables": "^1.0.2" 2384 + }, 2385 + "engines": { 2386 + "node": ">= 0.4" 2387 + }, 2388 + "funding": { 2389 + "url": "https://github.com/sponsors/ljharb" 2390 + } 2391 + }, 2392 + "node_modules/array.prototype.flat": { 2393 + "version": "1.3.3", 2394 + "resolved": "https://registry.npmjs.org/array.prototype.flat/-/array.prototype.flat-1.3.3.tgz", 2395 + "integrity": "sha512-rwG/ja1neyLqCuGZ5YYrznA62D4mZXg0i1cIskIUKSiqF3Cje9/wXAls9B9s1Wa2fomMsIv8czB8jZcPmxCXFg==", 2396 + "dev": true, 2397 + "license": "MIT", 2398 + "dependencies": { 2399 + "call-bind": "^1.0.8", 2400 + "define-properties": "^1.2.1", 2401 + "es-abstract": "^1.23.5", 2402 + "es-shim-unscopables": "^1.0.2" 2403 + }, 2404 + "engines": { 2405 + "node": ">= 0.4" 2406 + }, 2407 + "funding": { 2408 + "url": "https://github.com/sponsors/ljharb" 2409 + } 2410 + }, 2411 + "node_modules/array.prototype.flatmap": { 2412 + "version": "1.3.3", 2413 + "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.3.tgz", 2414 + "integrity": "sha512-Y7Wt51eKJSyi80hFrJCePGGNo5ktJCslFuboqJsbf57CCPcm5zztluPlc4/aD8sWsKvlwatezpV4U1efk8kpjg==", 2415 + "dev": true, 2416 + "license": "MIT", 2417 + "dependencies": { 2418 + "call-bind": "^1.0.8", 2419 + "define-properties": "^1.2.1", 2420 + "es-abstract": "^1.23.5", 2421 + "es-shim-unscopables": "^1.0.2" 2422 + }, 2423 + "engines": { 2424 + "node": ">= 0.4" 2425 + }, 2426 + "funding": { 2427 + "url": "https://github.com/sponsors/ljharb" 2428 + } 2429 + }, 2430 + "node_modules/arraybuffer.prototype.slice": { 2431 + "version": "1.0.4", 2432 + "resolved": "https://registry.npmjs.org/arraybuffer.prototype.slice/-/arraybuffer.prototype.slice-1.0.4.tgz", 2433 + "integrity": "sha512-BNoCY6SXXPQ7gF2opIP4GBE+Xw7U+pHMYKuzjgCN3GwiaIR09UUeKfheyIry77QtrCBlC0KK0q5/TER/tYh3PQ==", 2434 + "dev": true, 2435 + "license": "MIT", 2436 + "dependencies": { 2437 + "array-buffer-byte-length": "^1.0.1", 2438 + "call-bind": "^1.0.8", 2439 + "define-properties": "^1.2.1", 2440 + "es-abstract": "^1.23.5", 2441 + "es-errors": "^1.3.0", 2442 + "get-intrinsic": "^1.2.6", 2443 + "is-array-buffer": "^3.0.4" 2444 + }, 2445 + "engines": { 2446 + "node": ">= 0.4" 2447 + }, 2448 + "funding": { 2449 + "url": "https://github.com/sponsors/ljharb" 2450 + } 2451 + }, 1788 2452 "node_modules/async": { 1789 2453 "version": "0.2.10", 1790 2454 "resolved": "https://registry.npmjs.org/async/-/async-0.2.10.tgz", 1791 2455 "integrity": "sha512-eAkdoKxU6/LkKDBzLpT+t6Ff5EtfSF4wx1WfJiPEEV7WNLnDaRXk0oVysiEPm262roaachGexwUv94WhSgN5TQ==" 2456 + }, 2457 + "node_modules/async-function": { 2458 + "version": "1.0.0", 2459 + "resolved": "https://registry.npmjs.org/async-function/-/async-function-1.0.0.tgz", 2460 + "integrity": "sha512-hsU18Ae8CDTR6Kgu9DYf0EbCr/a5iGL0rytQDobUcdpYOKokk8LEjVphnXkDkgpi0wYVsqrXuP0bZxJaTqdgoA==", 2461 + "dev": true, 2462 + "license": "MIT", 2463 + "engines": { 2464 + "node": ">= 0.4" 2465 + } 1792 2466 }, 1793 2467 "node_modules/atomic-sleep": { 1794 2468 "version": "1.0.0", ··· 1797 2471 "license": "MIT", 1798 2472 "engines": { 1799 2473 "node": ">=8.0.0" 2474 + } 2475 + }, 2476 + "node_modules/available-typed-arrays": { 2477 + "version": "1.0.7", 2478 + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", 2479 + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", 2480 + "dev": true, 2481 + "license": "MIT", 2482 + "dependencies": { 2483 + "possible-typed-array-names": "^1.0.0" 2484 + }, 2485 + "engines": { 2486 + "node": ">= 0.4" 2487 + }, 2488 + "funding": { 2489 + "url": "https://github.com/sponsors/ljharb" 1800 2490 } 1801 2491 }, 1802 2492 "node_modules/await-lock": { ··· 2037 2727 } 2038 2728 } 2039 2729 }, 2730 + "node_modules/call-bind": { 2731 + "version": "1.0.8", 2732 + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", 2733 + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", 2734 + "dev": true, 2735 + "license": "MIT", 2736 + "dependencies": { 2737 + "call-bind-apply-helpers": "^1.0.0", 2738 + "es-define-property": "^1.0.0", 2739 + "get-intrinsic": "^1.2.4", 2740 + "set-function-length": "^1.2.2" 2741 + }, 2742 + "engines": { 2743 + "node": ">= 0.4" 2744 + }, 2745 + "funding": { 2746 + "url": "https://github.com/sponsors/ljharb" 2747 + } 2748 + }, 2749 + "node_modules/call-bind-apply-helpers": { 2750 + "version": "1.0.2", 2751 + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", 2752 + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", 2753 + "dev": true, 2754 + "license": "MIT", 2755 + "dependencies": { 2756 + "es-errors": "^1.3.0", 2757 + "function-bind": "^1.1.2" 2758 + }, 2759 + "engines": { 2760 + "node": ">= 0.4" 2761 + } 2762 + }, 2763 + "node_modules/call-bound": { 2764 + "version": "1.0.4", 2765 + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", 2766 + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", 2767 + "dev": true, 2768 + "license": "MIT", 2769 + "dependencies": { 2770 + "call-bind-apply-helpers": "^1.0.2", 2771 + "get-intrinsic": "^1.3.0" 2772 + }, 2773 + "engines": { 2774 + "node": ">= 0.4" 2775 + }, 2776 + "funding": { 2777 + "url": "https://github.com/sponsors/ljharb" 2778 + } 2779 + }, 2040 2780 "node_modules/callsites": { 2041 2781 "version": "3.1.0", 2042 2782 "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", 2043 2783 "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", 2044 2784 "dev": true, 2045 - "peer": true, 2046 2785 "engines": { 2047 2786 "node": ">=6" 2048 2787 } ··· 2252 2991 "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", 2253 2992 "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", 2254 2993 "dev": true, 2255 - "peer": true, 2256 2994 "dependencies": { 2257 2995 "path-key": "^3.1.0", 2258 2996 "shebang-command": "^2.0.0", ··· 2267 3005 "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", 2268 3006 "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", 2269 3007 "dev": true, 2270 - "peer": true, 2271 3008 "dependencies": { 2272 3009 "isexe": "^2.0.0" 2273 3010 }, ··· 2278 3015 "node": ">= 8" 2279 3016 } 2280 3017 }, 3018 + "node_modules/data-view-buffer": { 3019 + "version": "1.0.2", 3020 + "resolved": "https://registry.npmjs.org/data-view-buffer/-/data-view-buffer-1.0.2.tgz", 3021 + "integrity": "sha512-EmKO5V3OLXh1rtK2wgXRansaK1/mtVdTUEiEI0W8RkvgT05kfxaH29PliLnpLP73yYO6142Q72QNa8Wx/A5CqQ==", 3022 + "dev": true, 3023 + "license": "MIT", 3024 + "dependencies": { 3025 + "call-bound": "^1.0.3", 3026 + "es-errors": "^1.3.0", 3027 + "is-data-view": "^1.0.2" 3028 + }, 3029 + "engines": { 3030 + "node": ">= 0.4" 3031 + }, 3032 + "funding": { 3033 + "url": "https://github.com/sponsors/ljharb" 3034 + } 3035 + }, 3036 + "node_modules/data-view-byte-length": { 3037 + "version": "1.0.2", 3038 + "resolved": "https://registry.npmjs.org/data-view-byte-length/-/data-view-byte-length-1.0.2.tgz", 3039 + "integrity": "sha512-tuhGbE6CfTM9+5ANGf+oQb72Ky/0+s3xKUpHvShfiz2RxMFgFPjsXuRLBVMtvMs15awe45SRb83D6wH4ew6wlQ==", 3040 + "dev": true, 3041 + "license": "MIT", 3042 + "dependencies": { 3043 + "call-bound": "^1.0.3", 3044 + "es-errors": "^1.3.0", 3045 + "is-data-view": "^1.0.2" 3046 + }, 3047 + "engines": { 3048 + "node": ">= 0.4" 3049 + }, 3050 + "funding": { 3051 + "url": "https://github.com/sponsors/inspect-js" 3052 + } 3053 + }, 3054 + "node_modules/data-view-byte-offset": { 3055 + "version": "1.0.1", 3056 + "resolved": "https://registry.npmjs.org/data-view-byte-offset/-/data-view-byte-offset-1.0.1.tgz", 3057 + "integrity": "sha512-BS8PfmtDGnrgYdOonGZQdLZslWIeCGFP9tpan0hi1Co2Zr2NKADsvGYA8XxuG/4UWgJ6Cjtv+YJnB6MM69QGlQ==", 3058 + "dev": true, 3059 + "license": "MIT", 3060 + "dependencies": { 3061 + "call-bound": "^1.0.2", 3062 + "es-errors": "^1.3.0", 3063 + "is-data-view": "^1.0.1" 3064 + }, 3065 + "engines": { 3066 + "node": ">= 0.4" 3067 + }, 3068 + "funding": { 3069 + "url": "https://github.com/sponsors/ljharb" 3070 + } 3071 + }, 2281 3072 "node_modules/dateformat": { 2282 3073 "version": "4.6.3", 2283 3074 "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz", ··· 2292 3083 "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", 2293 3084 "integrity": "sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA==", 2294 3085 "dev": true, 2295 - "peer": true, 2296 3086 "dependencies": { 2297 3087 "ms": "^2.1.3" 2298 3088 }, ··· 2320 3110 } 2321 3111 } 2322 3112 }, 3113 + "node_modules/deep-is": { 3114 + "version": "0.1.4", 3115 + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", 3116 + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", 3117 + "dev": true, 3118 + "license": "MIT" 3119 + }, 2323 3120 "node_modules/deepmerge": { 2324 3121 "version": "4.3.1", 2325 3122 "resolved": "https://registry.npmjs.org/deepmerge/-/deepmerge-4.3.1.tgz", ··· 2330 3127 "node": ">=0.10.0" 2331 3128 } 2332 3129 }, 3130 + "node_modules/define-data-property": { 3131 + "version": "1.1.4", 3132 + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", 3133 + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", 3134 + "dev": true, 3135 + "license": "MIT", 3136 + "dependencies": { 3137 + "es-define-property": "^1.0.0", 3138 + "es-errors": "^1.3.0", 3139 + "gopd": "^1.0.1" 3140 + }, 3141 + "engines": { 3142 + "node": ">= 0.4" 3143 + }, 3144 + "funding": { 3145 + "url": "https://github.com/sponsors/ljharb" 3146 + } 3147 + }, 3148 + "node_modules/define-properties": { 3149 + "version": "1.2.1", 3150 + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", 3151 + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", 3152 + "dev": true, 3153 + "license": "MIT", 3154 + "dependencies": { 3155 + "define-data-property": "^1.0.1", 3156 + "has-property-descriptors": "^1.0.0", 3157 + "object-keys": "^1.1.1" 3158 + }, 3159 + "engines": { 3160 + "node": ">= 0.4" 3161 + }, 3162 + "funding": { 3163 + "url": "https://github.com/sponsors/ljharb" 3164 + } 3165 + }, 2333 3166 "node_modules/detect-libc": { 2334 3167 "version": "2.0.3", 2335 3168 "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", ··· 2367 3200 "node": "^14.15.0 || ^16.10.0 || >=18.0.0" 2368 3201 } 2369 3202 }, 3203 + "node_modules/doctrine": { 3204 + "version": "2.1.0", 3205 + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz", 3206 + "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==", 3207 + "dev": true, 3208 + "license": "Apache-2.0", 3209 + "dependencies": { 3210 + "esutils": "^2.0.2" 3211 + }, 3212 + "engines": { 3213 + "node": ">=0.10.0" 3214 + } 3215 + }, 2370 3216 "node_modules/dotenv": { 2371 3217 "version": "16.4.7", 2372 3218 "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.7.tgz", ··· 2377 3223 }, 2378 3224 "funding": { 2379 3225 "url": "https://dotenvx.com" 3226 + } 3227 + }, 3228 + "node_modules/dunder-proto": { 3229 + "version": "1.0.1", 3230 + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", 3231 + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", 3232 + "dev": true, 3233 + "license": "MIT", 3234 + "dependencies": { 3235 + "call-bind-apply-helpers": "^1.0.1", 3236 + "es-errors": "^1.3.0", 3237 + "gopd": "^1.2.0" 3238 + }, 3239 + "engines": { 3240 + "node": ">= 0.4" 2380 3241 } 2381 3242 }, 2382 3243 "node_modules/ejs": { ··· 2447 3308 "dev": true, 2448 3309 "peer": true 2449 3310 }, 3311 + "node_modules/es-abstract": { 3312 + "version": "1.23.9", 3313 + "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.23.9.tgz", 3314 + "integrity": "sha512-py07lI0wjxAC/DcfK1S6G7iANonniZwTISvdPzk9hzeH0IZIshbuuFxLIU96OyF89Yb9hiqWn8M/bY83KY5vzA==", 3315 + "dev": true, 3316 + "license": "MIT", 3317 + "dependencies": { 3318 + "array-buffer-byte-length": "^1.0.2", 3319 + "arraybuffer.prototype.slice": "^1.0.4", 3320 + "available-typed-arrays": "^1.0.7", 3321 + "call-bind": "^1.0.8", 3322 + "call-bound": "^1.0.3", 3323 + "data-view-buffer": "^1.0.2", 3324 + "data-view-byte-length": "^1.0.2", 3325 + "data-view-byte-offset": "^1.0.1", 3326 + "es-define-property": "^1.0.1", 3327 + "es-errors": "^1.3.0", 3328 + "es-object-atoms": "^1.0.0", 3329 + "es-set-tostringtag": "^2.1.0", 3330 + "es-to-primitive": "^1.3.0", 3331 + "function.prototype.name": "^1.1.8", 3332 + "get-intrinsic": "^1.2.7", 3333 + "get-proto": "^1.0.0", 3334 + "get-symbol-description": "^1.1.0", 3335 + "globalthis": "^1.0.4", 3336 + "gopd": "^1.2.0", 3337 + "has-property-descriptors": "^1.0.2", 3338 + "has-proto": "^1.2.0", 3339 + "has-symbols": "^1.1.0", 3340 + "hasown": "^2.0.2", 3341 + "internal-slot": "^1.1.0", 3342 + "is-array-buffer": "^3.0.5", 3343 + "is-callable": "^1.2.7", 3344 + "is-data-view": "^1.0.2", 3345 + "is-regex": "^1.2.1", 3346 + "is-shared-array-buffer": "^1.0.4", 3347 + "is-string": "^1.1.1", 3348 + "is-typed-array": "^1.1.15", 3349 + "is-weakref": "^1.1.0", 3350 + "math-intrinsics": "^1.1.0", 3351 + "object-inspect": "^1.13.3", 3352 + "object-keys": "^1.1.1", 3353 + "object.assign": "^4.1.7", 3354 + "own-keys": "^1.0.1", 3355 + "regexp.prototype.flags": "^1.5.3", 3356 + "safe-array-concat": "^1.1.3", 3357 + "safe-push-apply": "^1.0.0", 3358 + "safe-regex-test": "^1.1.0", 3359 + "set-proto": "^1.0.0", 3360 + "string.prototype.trim": "^1.2.10", 3361 + "string.prototype.trimend": "^1.0.9", 3362 + "string.prototype.trimstart": "^1.0.8", 3363 + "typed-array-buffer": "^1.0.3", 3364 + "typed-array-byte-length": "^1.0.3", 3365 + "typed-array-byte-offset": "^1.0.4", 3366 + "typed-array-length": "^1.0.7", 3367 + "unbox-primitive": "^1.1.0", 3368 + "which-typed-array": "^1.1.18" 3369 + }, 3370 + "engines": { 3371 + "node": ">= 0.4" 3372 + }, 3373 + "funding": { 3374 + "url": "https://github.com/sponsors/ljharb" 3375 + } 3376 + }, 3377 + "node_modules/es-define-property": { 3378 + "version": "1.0.1", 3379 + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", 3380 + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", 3381 + "dev": true, 3382 + "license": "MIT", 3383 + "engines": { 3384 + "node": ">= 0.4" 3385 + } 3386 + }, 3387 + "node_modules/es-errors": { 3388 + "version": "1.3.0", 3389 + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", 3390 + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", 3391 + "dev": true, 3392 + "license": "MIT", 3393 + "engines": { 3394 + "node": ">= 0.4" 3395 + } 3396 + }, 3397 + "node_modules/es-object-atoms": { 3398 + "version": "1.1.1", 3399 + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", 3400 + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", 3401 + "dev": true, 3402 + "license": "MIT", 3403 + "dependencies": { 3404 + "es-errors": "^1.3.0" 3405 + }, 3406 + "engines": { 3407 + "node": ">= 0.4" 3408 + } 3409 + }, 3410 + "node_modules/es-set-tostringtag": { 3411 + "version": "2.1.0", 3412 + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", 3413 + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", 3414 + "dev": true, 3415 + "license": "MIT", 3416 + "dependencies": { 3417 + "es-errors": "^1.3.0", 3418 + "get-intrinsic": "^1.2.6", 3419 + "has-tostringtag": "^1.0.2", 3420 + "hasown": "^2.0.2" 3421 + }, 3422 + "engines": { 3423 + "node": ">= 0.4" 3424 + } 3425 + }, 3426 + "node_modules/es-shim-unscopables": { 3427 + "version": "1.1.0", 3428 + "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.1.0.tgz", 3429 + "integrity": "sha512-d9T8ucsEhh8Bi1woXCf+TIKDIROLG5WCkxg8geBCbvk22kzwC5G2OnXVMO6FUsvQlgUUXQ2itephWDLqDzbeCw==", 3430 + "dev": true, 3431 + "license": "MIT", 3432 + "dependencies": { 3433 + "hasown": "^2.0.2" 3434 + }, 3435 + "engines": { 3436 + "node": ">= 0.4" 3437 + } 3438 + }, 3439 + "node_modules/es-to-primitive": { 3440 + "version": "1.3.0", 3441 + "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.3.0.tgz", 3442 + "integrity": "sha512-w+5mJ3GuFL+NjVtJlvydShqE1eN3h3PbI7/5LAsYJP/2qtuMXjfL2LpHSRqo4b4eSF5K/DH1JXKUAHSB2UW50g==", 3443 + "dev": true, 3444 + "license": "MIT", 3445 + "dependencies": { 3446 + "is-callable": "^1.2.7", 3447 + "is-date-object": "^1.0.5", 3448 + "is-symbol": "^1.0.4" 3449 + }, 3450 + "engines": { 3451 + "node": ">= 0.4" 3452 + }, 3453 + "funding": { 3454 + "url": "https://github.com/sponsors/ljharb" 3455 + } 3456 + }, 2450 3457 "node_modules/escalade": { 2451 3458 "version": "3.2.0", 2452 3459 "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", ··· 2466 3473 "node": ">=8" 2467 3474 } 2468 3475 }, 3476 + "node_modules/eslint": { 3477 + "version": "9.21.0", 3478 + "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.21.0.tgz", 3479 + "integrity": "sha512-KjeihdFqTPhOMXTt7StsDxriV4n66ueuF/jfPNC3j/lduHwr/ijDwJMsF+wyMJethgiKi5wniIE243vi07d3pg==", 3480 + "dev": true, 3481 + "license": "MIT", 3482 + "dependencies": { 3483 + "@eslint-community/eslint-utils": "^4.2.0", 3484 + "@eslint-community/regexpp": "^4.12.1", 3485 + "@eslint/config-array": "^0.19.2", 3486 + "@eslint/core": "^0.12.0", 3487 + "@eslint/eslintrc": "^3.3.0", 3488 + "@eslint/js": "9.21.0", 3489 + "@eslint/plugin-kit": "^0.2.7", 3490 + "@humanfs/node": "^0.16.6", 3491 + "@humanwhocodes/module-importer": "^1.0.1", 3492 + "@humanwhocodes/retry": "^0.4.2", 3493 + "@types/estree": "^1.0.6", 3494 + "@types/json-schema": "^7.0.15", 3495 + "ajv": "^6.12.4", 3496 + "chalk": "^4.0.0", 3497 + "cross-spawn": "^7.0.6", 3498 + "debug": "^4.3.2", 3499 + "escape-string-regexp": "^4.0.0", 3500 + "eslint-scope": "^8.2.0", 3501 + "eslint-visitor-keys": "^4.2.0", 3502 + "espree": "^10.3.0", 3503 + "esquery": "^1.5.0", 3504 + "esutils": "^2.0.2", 3505 + "fast-deep-equal": "^3.1.3", 3506 + "file-entry-cache": "^8.0.0", 3507 + "find-up": "^5.0.0", 3508 + "glob-parent": "^6.0.2", 3509 + "ignore": "^5.2.0", 3510 + "imurmurhash": "^0.1.4", 3511 + "is-glob": "^4.0.0", 3512 + "json-stable-stringify-without-jsonify": "^1.0.1", 3513 + "lodash.merge": "^4.6.2", 3514 + "minimatch": "^3.1.2", 3515 + "natural-compare": "^1.4.0", 3516 + "optionator": "^0.9.3" 3517 + }, 3518 + "bin": { 3519 + "eslint": "bin/eslint.js" 3520 + }, 3521 + "engines": { 3522 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 3523 + }, 3524 + "funding": { 3525 + "url": "https://eslint.org/donate" 3526 + }, 3527 + "peerDependencies": { 3528 + "jiti": "*" 3529 + }, 3530 + "peerDependenciesMeta": { 3531 + "jiti": { 3532 + "optional": true 3533 + } 3534 + } 3535 + }, 3536 + "node_modules/eslint-import-resolver-node": { 3537 + "version": "0.3.9", 3538 + "resolved": "https://registry.npmjs.org/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.9.tgz", 3539 + "integrity": "sha512-WFj2isz22JahUv+B788TlO3N6zL3nNJGU8CcZbPZvVEkBPaJdCV4vy5wyghty5ROFbCRnm132v8BScu5/1BQ8g==", 3540 + "dev": true, 3541 + "license": "MIT", 3542 + "dependencies": { 3543 + "debug": "^3.2.7", 3544 + "is-core-module": "^2.13.0", 3545 + "resolve": "^1.22.4" 3546 + } 3547 + }, 3548 + "node_modules/eslint-import-resolver-node/node_modules/debug": { 3549 + "version": "3.2.7", 3550 + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", 3551 + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", 3552 + "dev": true, 3553 + "license": "MIT", 3554 + "dependencies": { 3555 + "ms": "^2.1.1" 3556 + } 3557 + }, 3558 + "node_modules/eslint-module-utils": { 3559 + "version": "2.12.0", 3560 + "resolved": "https://registry.npmjs.org/eslint-module-utils/-/eslint-module-utils-2.12.0.tgz", 3561 + "integrity": "sha512-wALZ0HFoytlyh/1+4wuZ9FJCD/leWHQzzrxJ8+rebyReSLk7LApMyd3WJaLVoN+D5+WIdJyDK1c6JnE65V4Zyg==", 3562 + "dev": true, 3563 + "license": "MIT", 3564 + "dependencies": { 3565 + "debug": "^3.2.7" 3566 + }, 3567 + "engines": { 3568 + "node": ">=4" 3569 + }, 3570 + "peerDependenciesMeta": { 3571 + "eslint": { 3572 + "optional": true 3573 + } 3574 + } 3575 + }, 3576 + "node_modules/eslint-module-utils/node_modules/debug": { 3577 + "version": "3.2.7", 3578 + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", 3579 + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", 3580 + "dev": true, 3581 + "license": "MIT", 3582 + "dependencies": { 3583 + "ms": "^2.1.1" 3584 + } 3585 + }, 3586 + "node_modules/eslint-plugin-import": { 3587 + "version": "2.31.0", 3588 + "resolved": "https://registry.npmjs.org/eslint-plugin-import/-/eslint-plugin-import-2.31.0.tgz", 3589 + "integrity": "sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==", 3590 + "dev": true, 3591 + "license": "MIT", 3592 + "dependencies": { 3593 + "@rtsao/scc": "^1.1.0", 3594 + "array-includes": "^3.1.8", 3595 + "array.prototype.findlastindex": "^1.2.5", 3596 + "array.prototype.flat": "^1.3.2", 3597 + "array.prototype.flatmap": "^1.3.2", 3598 + "debug": "^3.2.7", 3599 + "doctrine": "^2.1.0", 3600 + "eslint-import-resolver-node": "^0.3.9", 3601 + "eslint-module-utils": "^2.12.0", 3602 + "hasown": "^2.0.2", 3603 + "is-core-module": "^2.15.1", 3604 + "is-glob": "^4.0.3", 3605 + "minimatch": "^3.1.2", 3606 + "object.fromentries": "^2.0.8", 3607 + "object.groupby": "^1.0.3", 3608 + "object.values": "^1.2.0", 3609 + "semver": "^6.3.1", 3610 + "string.prototype.trimend": "^1.0.8", 3611 + "tsconfig-paths": "^3.15.0" 3612 + }, 3613 + "engines": { 3614 + "node": ">=4" 3615 + }, 3616 + "peerDependencies": { 3617 + "eslint": "^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9" 3618 + } 3619 + }, 3620 + "node_modules/eslint-plugin-import/node_modules/debug": { 3621 + "version": "3.2.7", 3622 + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", 3623 + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", 3624 + "dev": true, 3625 + "license": "MIT", 3626 + "dependencies": { 3627 + "ms": "^2.1.1" 3628 + } 3629 + }, 3630 + "node_modules/eslint-plugin-import/node_modules/json5": { 3631 + "version": "1.0.2", 3632 + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.2.tgz", 3633 + "integrity": "sha512-g1MWMLBiz8FKi1e4w0UyVL3w+iJceWAFBAaBnnGKOpNa5f8TLktkbre1+s6oICydWAm+HRUGTmI+//xv2hvXYA==", 3634 + "dev": true, 3635 + "license": "MIT", 3636 + "dependencies": { 3637 + "minimist": "^1.2.0" 3638 + }, 3639 + "bin": { 3640 + "json5": "lib/cli.js" 3641 + } 3642 + }, 3643 + "node_modules/eslint-plugin-import/node_modules/semver": { 3644 + "version": "6.3.1", 3645 + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", 3646 + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", 3647 + "dev": true, 3648 + "license": "ISC", 3649 + "bin": { 3650 + "semver": "bin/semver.js" 3651 + } 3652 + }, 3653 + "node_modules/eslint-plugin-import/node_modules/strip-bom": { 3654 + "version": "3.0.0", 3655 + "resolved": "https://registry.npmjs.org/strip-bom/-/strip-bom-3.0.0.tgz", 3656 + "integrity": "sha512-vavAMRXOgBVNF6nyEEmL3DBK19iRpDcoIwW+swQ+CbGiu7lju6t+JklA1MHweoWtadgt4ISVUsXLyDq34ddcwA==", 3657 + "dev": true, 3658 + "license": "MIT", 3659 + "engines": { 3660 + "node": ">=4" 3661 + } 3662 + }, 3663 + "node_modules/eslint-plugin-import/node_modules/tsconfig-paths": { 3664 + "version": "3.15.0", 3665 + "resolved": "https://registry.npmjs.org/tsconfig-paths/-/tsconfig-paths-3.15.0.tgz", 3666 + "integrity": "sha512-2Ac2RgzDe/cn48GvOe3M+o82pEFewD3UPbyoUHHdKasHwJKjds4fLXWf/Ux5kATBKN20oaFGu+jbElp1pos0mg==", 3667 + "dev": true, 3668 + "license": "MIT", 3669 + "dependencies": { 3670 + "@types/json5": "^0.0.29", 3671 + "json5": "^1.0.2", 3672 + "minimist": "^1.2.6", 3673 + "strip-bom": "^3.0.0" 3674 + } 3675 + }, 3676 + "node_modules/eslint-plugin-jest": { 3677 + "version": "28.11.0", 3678 + "resolved": "https://registry.npmjs.org/eslint-plugin-jest/-/eslint-plugin-jest-28.11.0.tgz", 3679 + "integrity": "sha512-QAfipLcNCWLVocVbZW8GimKn5p5iiMcgGbRzz8z/P5q7xw+cNEpYqyzFMtIF/ZgF2HLOyy+dYBut+DoYolvqig==", 3680 + "dev": true, 3681 + "license": "MIT", 3682 + "dependencies": { 3683 + "@typescript-eslint/utils": "^6.0.0 || ^7.0.0 || ^8.0.0" 3684 + }, 3685 + "engines": { 3686 + "node": "^16.10.0 || ^18.12.0 || >=20.0.0" 3687 + }, 3688 + "peerDependencies": { 3689 + "@typescript-eslint/eslint-plugin": "^6.0.0 || ^7.0.0 || ^8.0.0", 3690 + "eslint": "^7.0.0 || ^8.0.0 || ^9.0.0", 3691 + "jest": "*" 3692 + }, 3693 + "peerDependenciesMeta": { 3694 + "@typescript-eslint/eslint-plugin": { 3695 + "optional": true 3696 + }, 3697 + "jest": { 3698 + "optional": true 3699 + } 3700 + } 3701 + }, 3702 + "node_modules/eslint-scope": { 3703 + "version": "8.2.0", 3704 + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.2.0.tgz", 3705 + "integrity": "sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==", 3706 + "dev": true, 3707 + "license": "BSD-2-Clause", 3708 + "dependencies": { 3709 + "esrecurse": "^4.3.0", 3710 + "estraverse": "^5.2.0" 3711 + }, 3712 + "engines": { 3713 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 3714 + }, 3715 + "funding": { 3716 + "url": "https://opencollective.com/eslint" 3717 + } 3718 + }, 3719 + "node_modules/eslint-visitor-keys": { 3720 + "version": "3.4.3", 3721 + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", 3722 + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", 3723 + "dev": true, 3724 + "license": "Apache-2.0", 3725 + "engines": { 3726 + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" 3727 + }, 3728 + "funding": { 3729 + "url": "https://opencollective.com/eslint" 3730 + } 3731 + }, 3732 + "node_modules/eslint/node_modules/escape-string-regexp": { 3733 + "version": "4.0.0", 3734 + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", 3735 + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", 3736 + "dev": true, 3737 + "license": "MIT", 3738 + "engines": { 3739 + "node": ">=10" 3740 + }, 3741 + "funding": { 3742 + "url": "https://github.com/sponsors/sindresorhus" 3743 + } 3744 + }, 3745 + "node_modules/eslint/node_modules/eslint-visitor-keys": { 3746 + "version": "4.2.0", 3747 + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", 3748 + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", 3749 + "dev": true, 3750 + "license": "Apache-2.0", 3751 + "engines": { 3752 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 3753 + }, 3754 + "funding": { 3755 + "url": "https://opencollective.com/eslint" 3756 + } 3757 + }, 3758 + "node_modules/eslint/node_modules/find-up": { 3759 + "version": "5.0.0", 3760 + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", 3761 + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", 3762 + "dev": true, 3763 + "license": "MIT", 3764 + "dependencies": { 3765 + "locate-path": "^6.0.0", 3766 + "path-exists": "^4.0.0" 3767 + }, 3768 + "engines": { 3769 + "node": ">=10" 3770 + }, 3771 + "funding": { 3772 + "url": "https://github.com/sponsors/sindresorhus" 3773 + } 3774 + }, 3775 + "node_modules/eslint/node_modules/locate-path": { 3776 + "version": "6.0.0", 3777 + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", 3778 + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", 3779 + "dev": true, 3780 + "license": "MIT", 3781 + "dependencies": { 3782 + "p-locate": "^5.0.0" 3783 + }, 3784 + "engines": { 3785 + "node": ">=10" 3786 + }, 3787 + "funding": { 3788 + "url": "https://github.com/sponsors/sindresorhus" 3789 + } 3790 + }, 3791 + "node_modules/eslint/node_modules/p-locate": { 3792 + "version": "5.0.0", 3793 + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", 3794 + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", 3795 + "dev": true, 3796 + "license": "MIT", 3797 + "dependencies": { 3798 + "p-limit": "^3.0.2" 3799 + }, 3800 + "engines": { 3801 + "node": ">=10" 3802 + }, 3803 + "funding": { 3804 + "url": "https://github.com/sponsors/sindresorhus" 3805 + } 3806 + }, 3807 + "node_modules/espree": { 3808 + "version": "10.3.0", 3809 + "resolved": "https://registry.npmjs.org/espree/-/espree-10.3.0.tgz", 3810 + "integrity": "sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==", 3811 + "dev": true, 3812 + "license": "BSD-2-Clause", 3813 + "dependencies": { 3814 + "acorn": "^8.14.0", 3815 + "acorn-jsx": "^5.3.2", 3816 + "eslint-visitor-keys": "^4.2.0" 3817 + }, 3818 + "engines": { 3819 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 3820 + }, 3821 + "funding": { 3822 + "url": "https://opencollective.com/eslint" 3823 + } 3824 + }, 3825 + "node_modules/espree/node_modules/eslint-visitor-keys": { 3826 + "version": "4.2.0", 3827 + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-4.2.0.tgz", 3828 + "integrity": "sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==", 3829 + "dev": true, 3830 + "license": "Apache-2.0", 3831 + "engines": { 3832 + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" 3833 + }, 3834 + "funding": { 3835 + "url": "https://opencollective.com/eslint" 3836 + } 3837 + }, 2469 3838 "node_modules/esprima": { 2470 3839 "version": "4.0.1", 2471 3840 "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", ··· 2480 3849 "node": ">=4" 2481 3850 } 2482 3851 }, 3852 + "node_modules/esquery": { 3853 + "version": "1.6.0", 3854 + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.6.0.tgz", 3855 + "integrity": "sha512-ca9pw9fomFcKPvFLXhBKUK90ZvGibiGOvRJNbjljY7s7uq/5YO4BOzcYtJqExdx99rF6aAcnRxHmcUHcz6sQsg==", 3856 + "dev": true, 3857 + "license": "BSD-3-Clause", 3858 + "dependencies": { 3859 + "estraverse": "^5.1.0" 3860 + }, 3861 + "engines": { 3862 + "node": ">=0.10" 3863 + } 3864 + }, 3865 + "node_modules/esrecurse": { 3866 + "version": "4.3.0", 3867 + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", 3868 + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", 3869 + "dev": true, 3870 + "license": "BSD-2-Clause", 3871 + "dependencies": { 3872 + "estraverse": "^5.2.0" 3873 + }, 3874 + "engines": { 3875 + "node": ">=4.0" 3876 + } 3877 + }, 3878 + "node_modules/estraverse": { 3879 + "version": "5.3.0", 3880 + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", 3881 + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", 3882 + "dev": true, 3883 + "license": "BSD-2-Clause", 3884 + "engines": { 3885 + "node": ">=4.0" 3886 + } 3887 + }, 3888 + "node_modules/esutils": { 3889 + "version": "2.0.3", 3890 + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", 3891 + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", 3892 + "dev": true, 3893 + "license": "BSD-2-Clause", 3894 + "engines": { 3895 + "node": ">=0.10.0" 3896 + } 3897 + }, 2483 3898 "node_modules/execa": { 2484 3899 "version": "5.1.1", 2485 3900 "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", ··· 2536 3951 "integrity": "sha512-dl0O9Vhju8IrcLndv2eU4ldt1ftXMqqfgN4H1cpmGV7P6jeB9FwpN9a2c8DPGE1Ys88rNUJVYDHq73CGAGOPfQ==", 2537 3952 "license": "MIT" 2538 3953 }, 3954 + "node_modules/fast-deep-equal": { 3955 + "version": "3.1.3", 3956 + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", 3957 + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", 3958 + "dev": true, 3959 + "license": "MIT" 3960 + }, 3961 + "node_modules/fast-glob": { 3962 + "version": "3.3.3", 3963 + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", 3964 + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", 3965 + "dev": true, 3966 + "license": "MIT", 3967 + "dependencies": { 3968 + "@nodelib/fs.stat": "^2.0.2", 3969 + "@nodelib/fs.walk": "^1.2.3", 3970 + "glob-parent": "^5.1.2", 3971 + "merge2": "^1.3.0", 3972 + "micromatch": "^4.0.8" 3973 + }, 3974 + "engines": { 3975 + "node": ">=8.6.0" 3976 + } 3977 + }, 3978 + "node_modules/fast-glob/node_modules/glob-parent": { 3979 + "version": "5.1.2", 3980 + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", 3981 + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", 3982 + "dev": true, 3983 + "license": "ISC", 3984 + "dependencies": { 3985 + "is-glob": "^4.0.1" 3986 + }, 3987 + "engines": { 3988 + "node": ">= 6" 3989 + } 3990 + }, 2539 3991 "node_modules/fast-json-stable-stringify": { 2540 3992 "version": "2.1.0", 2541 3993 "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", 2542 3994 "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", 2543 3995 "dev": true 3996 + }, 3997 + "node_modules/fast-levenshtein": { 3998 + "version": "2.0.6", 3999 + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", 4000 + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", 4001 + "dev": true, 4002 + "license": "MIT" 2544 4003 }, 2545 4004 "node_modules/fast-redact": { 2546 4005 "version": "3.5.0", ··· 2557 4016 "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", 2558 4017 "license": "MIT" 2559 4018 }, 4019 + "node_modules/fastq": { 4020 + "version": "1.19.1", 4021 + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.19.1.tgz", 4022 + "integrity": "sha512-GwLTyxkCXjXbxqIhTsMI2Nui8huMPtnxg7krajPJAjnEG/iiOS7i+zCtWGZR9G0NBKbXKh6X9m9UIsYX/N6vvQ==", 4023 + "dev": true, 4024 + "license": "ISC", 4025 + "dependencies": { 4026 + "reusify": "^1.0.4" 4027 + } 4028 + }, 2560 4029 "node_modules/fb-watchman": { 2561 4030 "version": "2.0.2", 2562 4031 "resolved": "https://registry.npmjs.org/fb-watchman/-/fb-watchman-2.0.2.tgz", ··· 2567 4036 "bser": "2.1.1" 2568 4037 } 2569 4038 }, 4039 + "node_modules/file-entry-cache": { 4040 + "version": "8.0.0", 4041 + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", 4042 + "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", 4043 + "dev": true, 4044 + "license": "MIT", 4045 + "dependencies": { 4046 + "flat-cache": "^4.0.0" 4047 + }, 4048 + "engines": { 4049 + "node": ">=16.0.0" 4050 + } 4051 + }, 2570 4052 "node_modules/filelist": { 2571 4053 "version": "1.0.4", 2572 4054 "resolved": "https://registry.npmjs.org/filelist/-/filelist-1.0.4.tgz", ··· 2623 4105 "node": ">=8" 2624 4106 } 2625 4107 }, 4108 + "node_modules/flat-cache": { 4109 + "version": "4.0.1", 4110 + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", 4111 + "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", 4112 + "dev": true, 4113 + "license": "MIT", 4114 + "dependencies": { 4115 + "flatted": "^3.2.9", 4116 + "keyv": "^4.5.4" 4117 + }, 4118 + "engines": { 4119 + "node": ">=16" 4120 + } 4121 + }, 4122 + "node_modules/flatted": { 4123 + "version": "3.3.3", 4124 + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.3.tgz", 4125 + "integrity": "sha512-GX+ysw4PBCz0PzosHDepZGANEuFCMLrnRTiEy9McGjmkCQYwRq4A/X786G/fjM/+OjsWSU1ZrY5qyARZmO/uwg==", 4126 + "dev": true, 4127 + "license": "ISC" 4128 + }, 2626 4129 "node_modules/fluent-ffmpeg": { 2627 4130 "version": "2.1.3", 2628 4131 "resolved": "https://registry.npmjs.org/fluent-ffmpeg/-/fluent-ffmpeg-2.1.3.tgz", ··· 2635 4138 "node": ">=18" 2636 4139 } 2637 4140 }, 4141 + "node_modules/for-each": { 4142 + "version": "0.3.5", 4143 + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", 4144 + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", 4145 + "dev": true, 4146 + "license": "MIT", 4147 + "dependencies": { 4148 + "is-callable": "^1.2.7" 4149 + }, 4150 + "engines": { 4151 + "node": ">= 0.4" 4152 + }, 4153 + "funding": { 4154 + "url": "https://github.com/sponsors/ljharb" 4155 + } 4156 + }, 2638 4157 "node_modules/fs.realpath": { 2639 4158 "version": "1.0.0", 2640 4159 "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", ··· 2662 4181 "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", 2663 4182 "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", 2664 4183 "dev": true, 2665 - "peer": true, 4184 + "funding": { 4185 + "url": "https://github.com/sponsors/ljharb" 4186 + } 4187 + }, 4188 + "node_modules/function.prototype.name": { 4189 + "version": "1.1.8", 4190 + "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.8.tgz", 4191 + "integrity": "sha512-e5iwyodOHhbMr/yNrc7fDYG4qlbIvI5gajyzPnb5TCwyhjApznQh1BMFou9b30SevY43gCJKXycoCBjMbsuW0Q==", 4192 + "dev": true, 4193 + "license": "MIT", 4194 + "dependencies": { 4195 + "call-bind": "^1.0.8", 4196 + "call-bound": "^1.0.3", 4197 + "define-properties": "^1.2.1", 4198 + "functions-have-names": "^1.2.3", 4199 + "hasown": "^2.0.2", 4200 + "is-callable": "^1.2.7" 4201 + }, 4202 + "engines": { 4203 + "node": ">= 0.4" 4204 + }, 4205 + "funding": { 4206 + "url": "https://github.com/sponsors/ljharb" 4207 + } 4208 + }, 4209 + "node_modules/functions-have-names": { 4210 + "version": "1.2.3", 4211 + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", 4212 + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", 4213 + "dev": true, 4214 + "license": "MIT", 2666 4215 "funding": { 2667 4216 "url": "https://github.com/sponsors/ljharb" 2668 4217 } ··· 2687 4236 "node": "6.* || 8.* || >= 10.*" 2688 4237 } 2689 4238 }, 4239 + "node_modules/get-intrinsic": { 4240 + "version": "1.3.0", 4241 + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", 4242 + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", 4243 + "dev": true, 4244 + "license": "MIT", 4245 + "dependencies": { 4246 + "call-bind-apply-helpers": "^1.0.2", 4247 + "es-define-property": "^1.0.1", 4248 + "es-errors": "^1.3.0", 4249 + "es-object-atoms": "^1.1.1", 4250 + "function-bind": "^1.1.2", 4251 + "get-proto": "^1.0.1", 4252 + "gopd": "^1.2.0", 4253 + "has-symbols": "^1.1.0", 4254 + "hasown": "^2.0.2", 4255 + "math-intrinsics": "^1.1.0" 4256 + }, 4257 + "engines": { 4258 + "node": ">= 0.4" 4259 + }, 4260 + "funding": { 4261 + "url": "https://github.com/sponsors/ljharb" 4262 + } 4263 + }, 2690 4264 "node_modules/get-package-type": { 2691 4265 "version": "0.1.0", 2692 4266 "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", ··· 2697 4271 "node": ">=8.0.0" 2698 4272 } 2699 4273 }, 4274 + "node_modules/get-proto": { 4275 + "version": "1.0.1", 4276 + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", 4277 + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", 4278 + "dev": true, 4279 + "license": "MIT", 4280 + "dependencies": { 4281 + "dunder-proto": "^1.0.1", 4282 + "es-object-atoms": "^1.0.0" 4283 + }, 4284 + "engines": { 4285 + "node": ">= 0.4" 4286 + } 4287 + }, 2700 4288 "node_modules/get-stream": { 2701 4289 "version": "6.0.1", 2702 4290 "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", ··· 2710 4298 "url": "https://github.com/sponsors/sindresorhus" 2711 4299 } 2712 4300 }, 4301 + "node_modules/get-symbol-description": { 4302 + "version": "1.1.0", 4303 + "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.1.0.tgz", 4304 + "integrity": "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg==", 4305 + "dev": true, 4306 + "license": "MIT", 4307 + "dependencies": { 4308 + "call-bound": "^1.0.3", 4309 + "es-errors": "^1.3.0", 4310 + "get-intrinsic": "^1.2.6" 4311 + }, 4312 + "engines": { 4313 + "node": ">= 0.4" 4314 + }, 4315 + "funding": { 4316 + "url": "https://github.com/sponsors/ljharb" 4317 + } 4318 + }, 2713 4319 "node_modules/glob": { 2714 4320 "version": "7.2.3", 2715 4321 "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", ··· 2732 4338 "url": "https://github.com/sponsors/isaacs" 2733 4339 } 2734 4340 }, 4341 + "node_modules/glob-parent": { 4342 + "version": "6.0.2", 4343 + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", 4344 + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", 4345 + "dev": true, 4346 + "license": "ISC", 4347 + "dependencies": { 4348 + "is-glob": "^4.0.3" 4349 + }, 4350 + "engines": { 4351 + "node": ">=10.13.0" 4352 + } 4353 + }, 2735 4354 "node_modules/globals": { 2736 - "version": "11.12.0", 2737 - "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", 2738 - "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", 4355 + "version": "16.0.0", 4356 + "resolved": "https://registry.npmjs.org/globals/-/globals-16.0.0.tgz", 4357 + "integrity": "sha512-iInW14XItCXET01CQFqudPOWP2jYMl7T+QRQT+UNcR/iQncN/F0UNpgd76iFkBPgNQb4+X3LV9tLJYzwh+Gl3A==", 2739 4358 "dev": true, 2740 - "peer": true, 4359 + "license": "MIT", 2741 4360 "engines": { 2742 - "node": ">=4" 4361 + "node": ">=18" 4362 + }, 4363 + "funding": { 4364 + "url": "https://github.com/sponsors/sindresorhus" 4365 + } 4366 + }, 4367 + "node_modules/globalthis": { 4368 + "version": "1.0.4", 4369 + "resolved": "https://registry.npmjs.org/globalthis/-/globalthis-1.0.4.tgz", 4370 + "integrity": "sha512-DpLKbNU4WylpxJykQujfCcwYWiV/Jhm50Goo0wrVILAv5jOr9d+H+UR3PhSCD2rCCEIg0uc+G+muBTwD54JhDQ==", 4371 + "dev": true, 4372 + "license": "MIT", 4373 + "dependencies": { 4374 + "define-properties": "^1.2.1", 4375 + "gopd": "^1.0.1" 4376 + }, 4377 + "engines": { 4378 + "node": ">= 0.4" 4379 + }, 4380 + "funding": { 4381 + "url": "https://github.com/sponsors/ljharb" 4382 + } 4383 + }, 4384 + "node_modules/gopd": { 4385 + "version": "1.2.0", 4386 + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", 4387 + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", 4388 + "dev": true, 4389 + "license": "MIT", 4390 + "engines": { 4391 + "node": ">= 0.4" 4392 + }, 4393 + "funding": { 4394 + "url": "https://github.com/sponsors/ljharb" 2743 4395 } 2744 4396 }, 2745 4397 "node_modules/graceful-fs": { ··· 2754 4406 "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", 2755 4407 "license": "MIT" 2756 4408 }, 4409 + "node_modules/has-bigints": { 4410 + "version": "1.1.0", 4411 + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", 4412 + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", 4413 + "dev": true, 4414 + "license": "MIT", 4415 + "engines": { 4416 + "node": ">= 0.4" 4417 + }, 4418 + "funding": { 4419 + "url": "https://github.com/sponsors/ljharb" 4420 + } 4421 + }, 2757 4422 "node_modules/has-flag": { 2758 4423 "version": "4.0.0", 2759 4424 "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", ··· 2763 4428 "node": ">=8" 2764 4429 } 2765 4430 }, 4431 + "node_modules/has-property-descriptors": { 4432 + "version": "1.0.2", 4433 + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", 4434 + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", 4435 + "dev": true, 4436 + "license": "MIT", 4437 + "dependencies": { 4438 + "es-define-property": "^1.0.0" 4439 + }, 4440 + "funding": { 4441 + "url": "https://github.com/sponsors/ljharb" 4442 + } 4443 + }, 4444 + "node_modules/has-proto": { 4445 + "version": "1.2.0", 4446 + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.2.0.tgz", 4447 + "integrity": "sha512-KIL7eQPfHQRC8+XluaIw7BHUwwqL19bQn4hzNgdr+1wXoU0KKj6rufu47lhY7KbJR2C6T6+PfyN0Ea7wkSS+qQ==", 4448 + "dev": true, 4449 + "license": "MIT", 4450 + "dependencies": { 4451 + "dunder-proto": "^1.0.0" 4452 + }, 4453 + "engines": { 4454 + "node": ">= 0.4" 4455 + }, 4456 + "funding": { 4457 + "url": "https://github.com/sponsors/ljharb" 4458 + } 4459 + }, 4460 + "node_modules/has-symbols": { 4461 + "version": "1.1.0", 4462 + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", 4463 + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", 4464 + "dev": true, 4465 + "license": "MIT", 4466 + "engines": { 4467 + "node": ">= 0.4" 4468 + }, 4469 + "funding": { 4470 + "url": "https://github.com/sponsors/ljharb" 4471 + } 4472 + }, 4473 + "node_modules/has-tostringtag": { 4474 + "version": "1.0.2", 4475 + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", 4476 + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", 4477 + "dev": true, 4478 + "license": "MIT", 4479 + "dependencies": { 4480 + "has-symbols": "^1.0.3" 4481 + }, 4482 + "engines": { 4483 + "node": ">= 0.4" 4484 + }, 4485 + "funding": { 4486 + "url": "https://github.com/sponsors/ljharb" 4487 + } 4488 + }, 2766 4489 "node_modules/hasown": { 2767 4490 "version": "2.0.2", 2768 4491 "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", 2769 4492 "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", 2770 4493 "dev": true, 2771 - "peer": true, 2772 4494 "dependencies": { 2773 4495 "function-bind": "^1.1.2" 2774 4496 }, ··· 2799 4521 "node": ">=10.17.0" 2800 4522 } 2801 4523 }, 4524 + "node_modules/ignore": { 4525 + "version": "5.3.2", 4526 + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", 4527 + "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", 4528 + "dev": true, 4529 + "license": "MIT", 4530 + "engines": { 4531 + "node": ">= 4" 4532 + } 4533 + }, 4534 + "node_modules/import-fresh": { 4535 + "version": "3.3.1", 4536 + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.1.tgz", 4537 + "integrity": "sha512-TR3KfrTZTYLPB6jUjfx6MF9WcWrHL9su5TObK4ZkYgBdWKPOFoSoQIdEuTuR82pmtxH2spWG9h6etwfr1pLBqQ==", 4538 + "dev": true, 4539 + "license": "MIT", 4540 + "dependencies": { 4541 + "parent-module": "^1.0.0", 4542 + "resolve-from": "^4.0.0" 4543 + }, 4544 + "engines": { 4545 + "node": ">=6" 4546 + }, 4547 + "funding": { 4548 + "url": "https://github.com/sponsors/sindresorhus" 4549 + } 4550 + }, 4551 + "node_modules/import-fresh/node_modules/resolve-from": { 4552 + "version": "4.0.0", 4553 + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", 4554 + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", 4555 + "dev": true, 4556 + "license": "MIT", 4557 + "engines": { 4558 + "node": ">=4" 4559 + } 4560 + }, 2802 4561 "node_modules/import-local": { 2803 4562 "version": "3.2.0", 2804 4563 "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.2.0.tgz", ··· 2824 4583 "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", 2825 4584 "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", 2826 4585 "dev": true, 2827 - "peer": true, 2828 4586 "engines": { 2829 4587 "node": ">=0.8.19" 2830 4588 } ··· 2848 4606 "dev": true, 2849 4607 "peer": true 2850 4608 }, 4609 + "node_modules/internal-slot": { 4610 + "version": "1.1.0", 4611 + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", 4612 + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", 4613 + "dev": true, 4614 + "license": "MIT", 4615 + "dependencies": { 4616 + "es-errors": "^1.3.0", 4617 + "hasown": "^2.0.2", 4618 + "side-channel": "^1.1.0" 4619 + }, 4620 + "engines": { 4621 + "node": ">= 0.4" 4622 + } 4623 + }, 4624 + "node_modules/is-array-buffer": { 4625 + "version": "3.0.5", 4626 + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", 4627 + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", 4628 + "dev": true, 4629 + "license": "MIT", 4630 + "dependencies": { 4631 + "call-bind": "^1.0.8", 4632 + "call-bound": "^1.0.3", 4633 + "get-intrinsic": "^1.2.6" 4634 + }, 4635 + "engines": { 4636 + "node": ">= 0.4" 4637 + }, 4638 + "funding": { 4639 + "url": "https://github.com/sponsors/ljharb" 4640 + } 4641 + }, 2851 4642 "node_modules/is-arrayish": { 2852 4643 "version": "0.3.2", 2853 4644 "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.2.tgz", 2854 4645 "integrity": "sha512-eVRqCvVlZbuw3GrM63ovNSNAeA1K16kaR/LRY/92w0zxQ5/1YzwblUX652i4Xs9RwAGjW9d9y6X88t8OaAJfWQ==", 2855 4646 "license": "MIT" 2856 4647 }, 4648 + "node_modules/is-async-function": { 4649 + "version": "2.1.1", 4650 + "resolved": "https://registry.npmjs.org/is-async-function/-/is-async-function-2.1.1.tgz", 4651 + "integrity": "sha512-9dgM/cZBnNvjzaMYHVoxxfPj2QXt22Ev7SuuPrs+xav0ukGB0S6d4ydZdEiM48kLx5kDV+QBPrpVnFyefL8kkQ==", 4652 + "dev": true, 4653 + "license": "MIT", 4654 + "dependencies": { 4655 + "async-function": "^1.0.0", 4656 + "call-bound": "^1.0.3", 4657 + "get-proto": "^1.0.1", 4658 + "has-tostringtag": "^1.0.2", 4659 + "safe-regex-test": "^1.1.0" 4660 + }, 4661 + "engines": { 4662 + "node": ">= 0.4" 4663 + }, 4664 + "funding": { 4665 + "url": "https://github.com/sponsors/ljharb" 4666 + } 4667 + }, 4668 + "node_modules/is-bigint": { 4669 + "version": "1.1.0", 4670 + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", 4671 + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", 4672 + "dev": true, 4673 + "license": "MIT", 4674 + "dependencies": { 4675 + "has-bigints": "^1.0.2" 4676 + }, 4677 + "engines": { 4678 + "node": ">= 0.4" 4679 + }, 4680 + "funding": { 4681 + "url": "https://github.com/sponsors/ljharb" 4682 + } 4683 + }, 4684 + "node_modules/is-boolean-object": { 4685 + "version": "1.2.2", 4686 + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", 4687 + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", 4688 + "dev": true, 4689 + "license": "MIT", 4690 + "dependencies": { 4691 + "call-bound": "^1.0.3", 4692 + "has-tostringtag": "^1.0.2" 4693 + }, 4694 + "engines": { 4695 + "node": ">= 0.4" 4696 + }, 4697 + "funding": { 4698 + "url": "https://github.com/sponsors/ljharb" 4699 + } 4700 + }, 4701 + "node_modules/is-callable": { 4702 + "version": "1.2.7", 4703 + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", 4704 + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", 4705 + "dev": true, 4706 + "license": "MIT", 4707 + "engines": { 4708 + "node": ">= 0.4" 4709 + }, 4710 + "funding": { 4711 + "url": "https://github.com/sponsors/ljharb" 4712 + } 4713 + }, 2857 4714 "node_modules/is-core-module": { 2858 4715 "version": "2.16.1", 2859 4716 "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", 2860 4717 "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", 2861 4718 "dev": true, 2862 - "peer": true, 2863 4719 "dependencies": { 2864 4720 "hasown": "^2.0.2" 2865 4721 }, ··· 2870 4726 "url": "https://github.com/sponsors/ljharb" 2871 4727 } 2872 4728 }, 4729 + "node_modules/is-data-view": { 4730 + "version": "1.0.2", 4731 + "resolved": "https://registry.npmjs.org/is-data-view/-/is-data-view-1.0.2.tgz", 4732 + "integrity": "sha512-RKtWF8pGmS87i2D6gqQu/l7EYRlVdfzemCJN/P3UOs//x1QE7mfhvzHIApBTRf7axvT6DMGwSwBXYCT0nfB9xw==", 4733 + "dev": true, 4734 + "license": "MIT", 4735 + "dependencies": { 4736 + "call-bound": "^1.0.2", 4737 + "get-intrinsic": "^1.2.6", 4738 + "is-typed-array": "^1.1.13" 4739 + }, 4740 + "engines": { 4741 + "node": ">= 0.4" 4742 + }, 4743 + "funding": { 4744 + "url": "https://github.com/sponsors/ljharb" 4745 + } 4746 + }, 4747 + "node_modules/is-date-object": { 4748 + "version": "1.1.0", 4749 + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", 4750 + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", 4751 + "dev": true, 4752 + "license": "MIT", 4753 + "dependencies": { 4754 + "call-bound": "^1.0.2", 4755 + "has-tostringtag": "^1.0.2" 4756 + }, 4757 + "engines": { 4758 + "node": ">= 0.4" 4759 + }, 4760 + "funding": { 4761 + "url": "https://github.com/sponsors/ljharb" 4762 + } 4763 + }, 4764 + "node_modules/is-extglob": { 4765 + "version": "2.1.1", 4766 + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", 4767 + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", 4768 + "dev": true, 4769 + "license": "MIT", 4770 + "engines": { 4771 + "node": ">=0.10.0" 4772 + } 4773 + }, 4774 + "node_modules/is-finalizationregistry": { 4775 + "version": "1.1.1", 4776 + "resolved": "https://registry.npmjs.org/is-finalizationregistry/-/is-finalizationregistry-1.1.1.tgz", 4777 + "integrity": "sha512-1pC6N8qWJbWoPtEjgcL2xyhQOP491EQjeUo3qTKcmV8YSDDJrOepfG8pcC7h/QgnQHYSv0mJ3Z/ZWxmatVrysg==", 4778 + "dev": true, 4779 + "license": "MIT", 4780 + "dependencies": { 4781 + "call-bound": "^1.0.3" 4782 + }, 4783 + "engines": { 4784 + "node": ">= 0.4" 4785 + }, 4786 + "funding": { 4787 + "url": "https://github.com/sponsors/ljharb" 4788 + } 4789 + }, 2873 4790 "node_modules/is-fullwidth-code-point": { 2874 4791 "version": "3.0.0", 2875 4792 "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", ··· 2890 4807 "node": ">=6" 2891 4808 } 2892 4809 }, 4810 + "node_modules/is-generator-function": { 4811 + "version": "1.1.0", 4812 + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz", 4813 + "integrity": "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==", 4814 + "dev": true, 4815 + "license": "MIT", 4816 + "dependencies": { 4817 + "call-bound": "^1.0.3", 4818 + "get-proto": "^1.0.0", 4819 + "has-tostringtag": "^1.0.2", 4820 + "safe-regex-test": "^1.1.0" 4821 + }, 4822 + "engines": { 4823 + "node": ">= 0.4" 4824 + }, 4825 + "funding": { 4826 + "url": "https://github.com/sponsors/ljharb" 4827 + } 4828 + }, 4829 + "node_modules/is-glob": { 4830 + "version": "4.0.3", 4831 + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", 4832 + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", 4833 + "dev": true, 4834 + "license": "MIT", 4835 + "dependencies": { 4836 + "is-extglob": "^2.1.1" 4837 + }, 4838 + "engines": { 4839 + "node": ">=0.10.0" 4840 + } 4841 + }, 4842 + "node_modules/is-map": { 4843 + "version": "2.0.3", 4844 + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", 4845 + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", 4846 + "dev": true, 4847 + "license": "MIT", 4848 + "engines": { 4849 + "node": ">= 0.4" 4850 + }, 4851 + "funding": { 4852 + "url": "https://github.com/sponsors/ljharb" 4853 + } 4854 + }, 2893 4855 "node_modules/is-number": { 2894 4856 "version": "7.0.0", 2895 4857 "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", ··· 2899 4861 "node": ">=0.12.0" 2900 4862 } 2901 4863 }, 4864 + "node_modules/is-number-object": { 4865 + "version": "1.1.1", 4866 + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", 4867 + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", 4868 + "dev": true, 4869 + "license": "MIT", 4870 + "dependencies": { 4871 + "call-bound": "^1.0.3", 4872 + "has-tostringtag": "^1.0.2" 4873 + }, 4874 + "engines": { 4875 + "node": ">= 0.4" 4876 + }, 4877 + "funding": { 4878 + "url": "https://github.com/sponsors/ljharb" 4879 + } 4880 + }, 4881 + "node_modules/is-regex": { 4882 + "version": "1.2.1", 4883 + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", 4884 + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", 4885 + "dev": true, 4886 + "license": "MIT", 4887 + "dependencies": { 4888 + "call-bound": "^1.0.2", 4889 + "gopd": "^1.2.0", 4890 + "has-tostringtag": "^1.0.2", 4891 + "hasown": "^2.0.2" 4892 + }, 4893 + "engines": { 4894 + "node": ">= 0.4" 4895 + }, 4896 + "funding": { 4897 + "url": "https://github.com/sponsors/ljharb" 4898 + } 4899 + }, 4900 + "node_modules/is-set": { 4901 + "version": "2.0.3", 4902 + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", 4903 + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", 4904 + "dev": true, 4905 + "license": "MIT", 4906 + "engines": { 4907 + "node": ">= 0.4" 4908 + }, 4909 + "funding": { 4910 + "url": "https://github.com/sponsors/ljharb" 4911 + } 4912 + }, 4913 + "node_modules/is-shared-array-buffer": { 4914 + "version": "1.0.4", 4915 + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", 4916 + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", 4917 + "dev": true, 4918 + "license": "MIT", 4919 + "dependencies": { 4920 + "call-bound": "^1.0.3" 4921 + }, 4922 + "engines": { 4923 + "node": ">= 0.4" 4924 + }, 4925 + "funding": { 4926 + "url": "https://github.com/sponsors/ljharb" 4927 + } 4928 + }, 2902 4929 "node_modules/is-stream": { 2903 4930 "version": "2.0.1", 2904 4931 "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", ··· 2912 4939 "url": "https://github.com/sponsors/sindresorhus" 2913 4940 } 2914 4941 }, 4942 + "node_modules/is-string": { 4943 + "version": "1.1.1", 4944 + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", 4945 + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", 4946 + "dev": true, 4947 + "license": "MIT", 4948 + "dependencies": { 4949 + "call-bound": "^1.0.3", 4950 + "has-tostringtag": "^1.0.2" 4951 + }, 4952 + "engines": { 4953 + "node": ">= 0.4" 4954 + }, 4955 + "funding": { 4956 + "url": "https://github.com/sponsors/ljharb" 4957 + } 4958 + }, 4959 + "node_modules/is-symbol": { 4960 + "version": "1.1.1", 4961 + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", 4962 + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", 4963 + "dev": true, 4964 + "license": "MIT", 4965 + "dependencies": { 4966 + "call-bound": "^1.0.2", 4967 + "has-symbols": "^1.1.0", 4968 + "safe-regex-test": "^1.1.0" 4969 + }, 4970 + "engines": { 4971 + "node": ">= 0.4" 4972 + }, 4973 + "funding": { 4974 + "url": "https://github.com/sponsors/ljharb" 4975 + } 4976 + }, 4977 + "node_modules/is-typed-array": { 4978 + "version": "1.1.15", 4979 + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", 4980 + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", 4981 + "dev": true, 4982 + "license": "MIT", 4983 + "dependencies": { 4984 + "which-typed-array": "^1.1.16" 4985 + }, 4986 + "engines": { 4987 + "node": ">= 0.4" 4988 + }, 4989 + "funding": { 4990 + "url": "https://github.com/sponsors/ljharb" 4991 + } 4992 + }, 4993 + "node_modules/is-weakmap": { 4994 + "version": "2.0.2", 4995 + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", 4996 + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", 4997 + "dev": true, 4998 + "license": "MIT", 4999 + "engines": { 5000 + "node": ">= 0.4" 5001 + }, 5002 + "funding": { 5003 + "url": "https://github.com/sponsors/ljharb" 5004 + } 5005 + }, 5006 + "node_modules/is-weakref": { 5007 + "version": "1.1.1", 5008 + "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.1.1.tgz", 5009 + "integrity": "sha512-6i9mGWSlqzNMEqpCp93KwRS1uUOodk2OJ6b+sq7ZPDSy2WuI5NFIxp/254TytR8ftefexkWn5xNiHUNpPOfSew==", 5010 + "dev": true, 5011 + "license": "MIT", 5012 + "dependencies": { 5013 + "call-bound": "^1.0.3" 5014 + }, 5015 + "engines": { 5016 + "node": ">= 0.4" 5017 + }, 5018 + "funding": { 5019 + "url": "https://github.com/sponsors/ljharb" 5020 + } 5021 + }, 5022 + "node_modules/is-weakset": { 5023 + "version": "2.0.4", 5024 + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", 5025 + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", 5026 + "dev": true, 5027 + "license": "MIT", 5028 + "dependencies": { 5029 + "call-bound": "^1.0.3", 5030 + "get-intrinsic": "^1.2.6" 5031 + }, 5032 + "engines": { 5033 + "node": ">= 0.4" 5034 + }, 5035 + "funding": { 5036 + "url": "https://github.com/sponsors/ljharb" 5037 + } 5038 + }, 5039 + "node_modules/isarray": { 5040 + "version": "2.0.5", 5041 + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", 5042 + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", 5043 + "dev": true, 5044 + "license": "MIT" 5045 + }, 2915 5046 "node_modules/isexe": { 2916 5047 "version": "2.0.0", 2917 5048 "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", ··· 3596 5727 "url": "https://github.com/chalk/supports-color?sponsor=1" 3597 5728 } 3598 5729 }, 5730 + "node_modules/jiti": { 5731 + "version": "2.4.2", 5732 + "resolved": "https://registry.npmjs.org/jiti/-/jiti-2.4.2.tgz", 5733 + "integrity": "sha512-rg9zJN+G4n2nfJl5MW3BMygZX56zKPNVEYYqq7adpmMh4Jn2QNEwhvQlFy6jPVdcod7txZtKHWnyZiA3a0zP7A==", 5734 + "dev": true, 5735 + "license": "MIT", 5736 + "bin": { 5737 + "jiti": "lib/jiti-cli.mjs" 5738 + } 5739 + }, 3599 5740 "node_modules/joycon": { 3600 5741 "version": "3.1.1", 3601 5742 "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", ··· 3638 5779 "node": ">=6" 3639 5780 } 3640 5781 }, 5782 + "node_modules/json-buffer": { 5783 + "version": "3.0.1", 5784 + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", 5785 + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", 5786 + "dev": true, 5787 + "license": "MIT" 5788 + }, 3641 5789 "node_modules/json-parse-even-better-errors": { 3642 5790 "version": "2.3.1", 3643 5791 "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz", ··· 3645 5793 "dev": true, 3646 5794 "peer": true 3647 5795 }, 5796 + "node_modules/json-schema-traverse": { 5797 + "version": "0.4.1", 5798 + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", 5799 + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", 5800 + "dev": true, 5801 + "license": "MIT" 5802 + }, 5803 + "node_modules/json-stable-stringify-without-jsonify": { 5804 + "version": "1.0.1", 5805 + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", 5806 + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", 5807 + "dev": true, 5808 + "license": "MIT" 5809 + }, 3648 5810 "node_modules/json5": { 3649 5811 "version": "2.2.3", 3650 5812 "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", ··· 3657 5819 "node": ">=6" 3658 5820 } 3659 5821 }, 5822 + "node_modules/keyv": { 5823 + "version": "4.5.4", 5824 + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", 5825 + "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", 5826 + "dev": true, 5827 + "license": "MIT", 5828 + "dependencies": { 5829 + "json-buffer": "3.0.1" 5830 + } 5831 + }, 3660 5832 "node_modules/kleur": { 3661 5833 "version": "3.0.3", 3662 5834 "resolved": "https://registry.npmjs.org/kleur/-/kleur-3.0.3.tgz", ··· 3677 5849 "node": ">=6" 3678 5850 } 3679 5851 }, 5852 + "node_modules/levn": { 5853 + "version": "0.4.1", 5854 + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", 5855 + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", 5856 + "dev": true, 5857 + "license": "MIT", 5858 + "dependencies": { 5859 + "prelude-ls": "^1.2.1", 5860 + "type-check": "~0.4.0" 5861 + }, 5862 + "engines": { 5863 + "node": ">= 0.8.0" 5864 + } 5865 + }, 3680 5866 "node_modules/lines-and-columns": { 3681 5867 "version": "1.2.4", 3682 5868 "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", ··· 3702 5888 "resolved": "https://registry.npmjs.org/lodash.memoize/-/lodash.memoize-4.1.2.tgz", 3703 5889 "integrity": "sha512-t7j+NzmgnQzTAYXcsHYLgimltOV1MXHtlOWf6GjL9Kj8GK5FInw5JotxvbOs+IvV1/Dzo04/fCGfLVs7aXb4Ag==", 3704 5890 "dev": true 5891 + }, 5892 + "node_modules/lodash.merge": { 5893 + "version": "4.6.2", 5894 + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", 5895 + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", 5896 + "dev": true, 5897 + "license": "MIT" 3705 5898 }, 3706 5899 "node_modules/lru-cache": { 3707 5900 "version": "5.1.1", ··· 3752 5945 "peer": true, 3753 5946 "dependencies": { 3754 5947 "tmpl": "1.0.5" 5948 + } 5949 + }, 5950 + "node_modules/math-intrinsics": { 5951 + "version": "1.1.0", 5952 + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", 5953 + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", 5954 + "dev": true, 5955 + "license": "MIT", 5956 + "engines": { 5957 + "node": ">= 0.4" 3755 5958 } 3756 5959 }, 3757 5960 "node_modules/merge-stream": { ··· 3761 5964 "dev": true, 3762 5965 "peer": true 3763 5966 }, 5967 + "node_modules/merge2": { 5968 + "version": "1.4.1", 5969 + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", 5970 + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", 5971 + "dev": true, 5972 + "license": "MIT", 5973 + "engines": { 5974 + "node": ">= 8" 5975 + } 5976 + }, 3764 5977 "node_modules/micromatch": { 3765 5978 "version": "4.0.8", 3766 5979 "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", ··· 3809 6022 "version": "2.1.3", 3810 6023 "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", 3811 6024 "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", 3812 - "dev": true, 3813 - "peer": true 6025 + "dev": true 3814 6026 }, 3815 6027 "node_modules/multibase": { 3816 6028 "version": "4.0.6", ··· 3851 6063 "version": "1.4.0", 3852 6064 "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", 3853 6065 "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", 3854 - "dev": true, 3855 - "peer": true 6066 + "dev": true 3856 6067 }, 3857 6068 "node_modules/node-int64": { 3858 6069 "version": "0.4.0", ··· 3891 6102 "node": ">=8" 3892 6103 } 3893 6104 }, 6105 + "node_modules/object-inspect": { 6106 + "version": "1.13.4", 6107 + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", 6108 + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", 6109 + "dev": true, 6110 + "license": "MIT", 6111 + "engines": { 6112 + "node": ">= 0.4" 6113 + }, 6114 + "funding": { 6115 + "url": "https://github.com/sponsors/ljharb" 6116 + } 6117 + }, 6118 + "node_modules/object-keys": { 6119 + "version": "1.1.1", 6120 + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", 6121 + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", 6122 + "dev": true, 6123 + "license": "MIT", 6124 + "engines": { 6125 + "node": ">= 0.4" 6126 + } 6127 + }, 6128 + "node_modules/object.assign": { 6129 + "version": "4.1.7", 6130 + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", 6131 + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", 6132 + "dev": true, 6133 + "license": "MIT", 6134 + "dependencies": { 6135 + "call-bind": "^1.0.8", 6136 + "call-bound": "^1.0.3", 6137 + "define-properties": "^1.2.1", 6138 + "es-object-atoms": "^1.0.0", 6139 + "has-symbols": "^1.1.0", 6140 + "object-keys": "^1.1.1" 6141 + }, 6142 + "engines": { 6143 + "node": ">= 0.4" 6144 + }, 6145 + "funding": { 6146 + "url": "https://github.com/sponsors/ljharb" 6147 + } 6148 + }, 6149 + "node_modules/object.fromentries": { 6150 + "version": "2.0.8", 6151 + "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.8.tgz", 6152 + "integrity": "sha512-k6E21FzySsSK5a21KRADBd/NGneRegFO5pLHfdQLpRDETUNJueLXs3WCzyQ3tFRDYgbq3KHGXfTbi2bs8WQ6rQ==", 6153 + "dev": true, 6154 + "license": "MIT", 6155 + "dependencies": { 6156 + "call-bind": "^1.0.7", 6157 + "define-properties": "^1.2.1", 6158 + "es-abstract": "^1.23.2", 6159 + "es-object-atoms": "^1.0.0" 6160 + }, 6161 + "engines": { 6162 + "node": ">= 0.4" 6163 + }, 6164 + "funding": { 6165 + "url": "https://github.com/sponsors/ljharb" 6166 + } 6167 + }, 6168 + "node_modules/object.groupby": { 6169 + "version": "1.0.3", 6170 + "resolved": "https://registry.npmjs.org/object.groupby/-/object.groupby-1.0.3.tgz", 6171 + "integrity": "sha512-+Lhy3TQTuzXI5hevh8sBGqbmurHbbIjAi0Z4S63nthVLmLxfbj4T54a4CfZrXIrt9iP4mVAPYMo/v99taj3wjQ==", 6172 + "dev": true, 6173 + "license": "MIT", 6174 + "dependencies": { 6175 + "call-bind": "^1.0.7", 6176 + "define-properties": "^1.2.1", 6177 + "es-abstract": "^1.23.2" 6178 + }, 6179 + "engines": { 6180 + "node": ">= 0.4" 6181 + } 6182 + }, 6183 + "node_modules/object.values": { 6184 + "version": "1.2.1", 6185 + "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.2.1.tgz", 6186 + "integrity": "sha512-gXah6aZrcUxjWg2zR2MwouP2eHlCBzdV4pygudehaKXSGW4v2AsRQUK+lwwXhii6KFZcunEnmSUoYp5CXibxtA==", 6187 + "dev": true, 6188 + "license": "MIT", 6189 + "dependencies": { 6190 + "call-bind": "^1.0.8", 6191 + "call-bound": "^1.0.3", 6192 + "define-properties": "^1.2.1", 6193 + "es-object-atoms": "^1.0.0" 6194 + }, 6195 + "engines": { 6196 + "node": ">= 0.4" 6197 + }, 6198 + "funding": { 6199 + "url": "https://github.com/sponsors/ljharb" 6200 + } 6201 + }, 3894 6202 "node_modules/on-exit-leak-free": { 3895 6203 "version": "2.1.2", 3896 6204 "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", ··· 3925 6233 "url": "https://github.com/sponsors/sindresorhus" 3926 6234 } 3927 6235 }, 6236 + "node_modules/optionator": { 6237 + "version": "0.9.4", 6238 + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", 6239 + "integrity": "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==", 6240 + "dev": true, 6241 + "license": "MIT", 6242 + "dependencies": { 6243 + "deep-is": "^0.1.3", 6244 + "fast-levenshtein": "^2.0.6", 6245 + "levn": "^0.4.1", 6246 + "prelude-ls": "^1.2.1", 6247 + "type-check": "^0.4.0", 6248 + "word-wrap": "^1.2.5" 6249 + }, 6250 + "engines": { 6251 + "node": ">= 0.8.0" 6252 + } 6253 + }, 6254 + "node_modules/own-keys": { 6255 + "version": "1.0.1", 6256 + "resolved": "https://registry.npmjs.org/own-keys/-/own-keys-1.0.1.tgz", 6257 + "integrity": "sha512-qFOyK5PjiWZd+QQIh+1jhdb9LpxTF0qs7Pm8o5QHYZ0M3vKqSqzsZaEB6oWlxZ+q2sJBMI/Ktgd2N5ZwQoRHfg==", 6258 + "dev": true, 6259 + "license": "MIT", 6260 + "dependencies": { 6261 + "get-intrinsic": "^1.2.6", 6262 + "object-keys": "^1.1.1", 6263 + "safe-push-apply": "^1.0.0" 6264 + }, 6265 + "engines": { 6266 + "node": ">= 0.4" 6267 + }, 6268 + "funding": { 6269 + "url": "https://github.com/sponsors/ljharb" 6270 + } 6271 + }, 3928 6272 "node_modules/p-limit": { 3929 6273 "version": "3.1.0", 3930 6274 "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", 3931 6275 "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", 3932 6276 "dev": true, 3933 - "peer": true, 3934 6277 "dependencies": { 3935 6278 "yocto-queue": "^0.1.0" 3936 6279 }, ··· 3980 6323 "node": ">=6" 3981 6324 } 3982 6325 }, 6326 + "node_modules/parent-module": { 6327 + "version": "1.0.1", 6328 + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", 6329 + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", 6330 + "dev": true, 6331 + "license": "MIT", 6332 + "dependencies": { 6333 + "callsites": "^3.0.0" 6334 + }, 6335 + "engines": { 6336 + "node": ">=6" 6337 + } 6338 + }, 3983 6339 "node_modules/parse-json": { 3984 6340 "version": "5.2.0", 3985 6341 "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", ··· 4004 6360 "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", 4005 6361 "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", 4006 6362 "dev": true, 4007 - "peer": true, 4008 6363 "engines": { 4009 6364 "node": ">=8" 4010 6365 } ··· 4024 6379 "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", 4025 6380 "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", 4026 6381 "dev": true, 4027 - "peer": true, 4028 6382 "engines": { 4029 6383 "node": ">=8" 4030 6384 } ··· 4033 6387 "version": "1.0.7", 4034 6388 "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", 4035 6389 "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", 4036 - "dev": true, 4037 - "peer": true 6390 + "dev": true 4038 6391 }, 4039 6392 "node_modules/picocolors": { 4040 6393 "version": "1.1.1", ··· 4136 6489 }, 4137 6490 "engines": { 4138 6491 "node": ">=8" 6492 + } 6493 + }, 6494 + "node_modules/possible-typed-array-names": { 6495 + "version": "1.1.0", 6496 + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", 6497 + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", 6498 + "dev": true, 6499 + "license": "MIT", 6500 + "engines": { 6501 + "node": ">= 0.4" 6502 + } 6503 + }, 6504 + "node_modules/prelude-ls": { 6505 + "version": "1.2.1", 6506 + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", 6507 + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", 6508 + "dev": true, 6509 + "license": "MIT", 6510 + "engines": { 6511 + "node": ">= 0.8.0" 4139 6512 } 4140 6513 }, 4141 6514 "node_modules/pretty-format": { ··· 4212 6585 "once": "^1.3.1" 4213 6586 } 4214 6587 }, 6588 + "node_modules/punycode": { 6589 + "version": "2.3.1", 6590 + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", 6591 + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==", 6592 + "dev": true, 6593 + "license": "MIT", 6594 + "engines": { 6595 + "node": ">=6" 6596 + } 6597 + }, 4215 6598 "node_modules/pure-rand": { 4216 6599 "version": "6.1.0", 4217 6600 "resolved": "https://registry.npmjs.org/pure-rand/-/pure-rand-6.1.0.tgz", ··· 4229 6612 ], 4230 6613 "peer": true 4231 6614 }, 6615 + "node_modules/queue-microtask": { 6616 + "version": "1.2.3", 6617 + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", 6618 + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", 6619 + "dev": true, 6620 + "funding": [ 6621 + { 6622 + "type": "github", 6623 + "url": "https://github.com/sponsors/feross" 6624 + }, 6625 + { 6626 + "type": "patreon", 6627 + "url": "https://www.patreon.com/feross" 6628 + }, 6629 + { 6630 + "type": "consulting", 6631 + "url": "https://feross.org/support" 6632 + } 6633 + ], 6634 + "license": "MIT" 6635 + }, 4232 6636 "node_modules/quick-format-unescaped": { 4233 6637 "version": "4.0.4", 4234 6638 "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", ··· 4250 6654 "node": ">= 12.13.0" 4251 6655 } 4252 6656 }, 6657 + "node_modules/reflect.getprototypeof": { 6658 + "version": "1.0.10", 6659 + "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", 6660 + "integrity": "sha512-00o4I+DVrefhv+nX0ulyi3biSHCPDe+yLv5o/p6d/UVlirijB8E16FtfwSAi4g3tcqrQ4lRAqQSoFEZJehYEcw==", 6661 + "dev": true, 6662 + "license": "MIT", 6663 + "dependencies": { 6664 + "call-bind": "^1.0.8", 6665 + "define-properties": "^1.2.1", 6666 + "es-abstract": "^1.23.9", 6667 + "es-errors": "^1.3.0", 6668 + "es-object-atoms": "^1.0.0", 6669 + "get-intrinsic": "^1.2.7", 6670 + "get-proto": "^1.0.1", 6671 + "which-builtin-type": "^1.2.1" 6672 + }, 6673 + "engines": { 6674 + "node": ">= 0.4" 6675 + }, 6676 + "funding": { 6677 + "url": "https://github.com/sponsors/ljharb" 6678 + } 6679 + }, 6680 + "node_modules/regexp.prototype.flags": { 6681 + "version": "1.5.4", 6682 + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", 6683 + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", 6684 + "dev": true, 6685 + "license": "MIT", 6686 + "dependencies": { 6687 + "call-bind": "^1.0.8", 6688 + "define-properties": "^1.2.1", 6689 + "es-errors": "^1.3.0", 6690 + "get-proto": "^1.0.1", 6691 + "gopd": "^1.2.0", 6692 + "set-function-name": "^2.0.2" 6693 + }, 6694 + "engines": { 6695 + "node": ">= 0.4" 6696 + }, 6697 + "funding": { 6698 + "url": "https://github.com/sponsors/ljharb" 6699 + } 6700 + }, 4253 6701 "node_modules/require-directory": { 4254 6702 "version": "2.1.1", 4255 6703 "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", ··· 4265 6713 "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", 4266 6714 "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", 4267 6715 "dev": true, 4268 - "peer": true, 4269 6716 "dependencies": { 4270 6717 "is-core-module": "^2.16.0", 4271 6718 "path-parse": "^1.0.7", ··· 4314 6761 "node": ">=10" 4315 6762 } 4316 6763 }, 6764 + "node_modules/reusify": { 6765 + "version": "1.1.0", 6766 + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", 6767 + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", 6768 + "dev": true, 6769 + "license": "MIT", 6770 + "engines": { 6771 + "iojs": ">=1.0.0", 6772 + "node": ">=0.10.0" 6773 + } 6774 + }, 6775 + "node_modules/run-parallel": { 6776 + "version": "1.2.0", 6777 + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", 6778 + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", 6779 + "dev": true, 6780 + "funding": [ 6781 + { 6782 + "type": "github", 6783 + "url": "https://github.com/sponsors/feross" 6784 + }, 6785 + { 6786 + "type": "patreon", 6787 + "url": "https://www.patreon.com/feross" 6788 + }, 6789 + { 6790 + "type": "consulting", 6791 + "url": "https://feross.org/support" 6792 + } 6793 + ], 6794 + "license": "MIT", 6795 + "dependencies": { 6796 + "queue-microtask": "^1.2.2" 6797 + } 6798 + }, 6799 + "node_modules/safe-array-concat": { 6800 + "version": "1.1.3", 6801 + "resolved": "https://registry.npmjs.org/safe-array-concat/-/safe-array-concat-1.1.3.tgz", 6802 + "integrity": "sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==", 6803 + "dev": true, 6804 + "license": "MIT", 6805 + "dependencies": { 6806 + "call-bind": "^1.0.8", 6807 + "call-bound": "^1.0.2", 6808 + "get-intrinsic": "^1.2.6", 6809 + "has-symbols": "^1.1.0", 6810 + "isarray": "^2.0.5" 6811 + }, 6812 + "engines": { 6813 + "node": ">=0.4" 6814 + }, 6815 + "funding": { 6816 + "url": "https://github.com/sponsors/ljharb" 6817 + } 6818 + }, 6819 + "node_modules/safe-push-apply": { 6820 + "version": "1.0.0", 6821 + "resolved": "https://registry.npmjs.org/safe-push-apply/-/safe-push-apply-1.0.0.tgz", 6822 + "integrity": "sha512-iKE9w/Z7xCzUMIZqdBsp6pEQvwuEebH4vdpjcDWnyzaI6yl6O9FHvVpmGelvEHNsoY6wGblkxR6Zty/h00WiSA==", 6823 + "dev": true, 6824 + "license": "MIT", 6825 + "dependencies": { 6826 + "es-errors": "^1.3.0", 6827 + "isarray": "^2.0.5" 6828 + }, 6829 + "engines": { 6830 + "node": ">= 0.4" 6831 + }, 6832 + "funding": { 6833 + "url": "https://github.com/sponsors/ljharb" 6834 + } 6835 + }, 6836 + "node_modules/safe-regex-test": { 6837 + "version": "1.1.0", 6838 + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", 6839 + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", 6840 + "dev": true, 6841 + "license": "MIT", 6842 + "dependencies": { 6843 + "call-bound": "^1.0.2", 6844 + "es-errors": "^1.3.0", 6845 + "is-regex": "^1.2.1" 6846 + }, 6847 + "engines": { 6848 + "node": ">= 0.4" 6849 + }, 6850 + "funding": { 6851 + "url": "https://github.com/sponsors/ljharb" 6852 + } 6853 + }, 4317 6854 "node_modules/safe-stable-stringify": { 4318 6855 "version": "2.5.0", 4319 6856 "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", ··· 4341 6878 "node": ">=10" 4342 6879 } 4343 6880 }, 6881 + "node_modules/set-function-length": { 6882 + "version": "1.2.2", 6883 + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", 6884 + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", 6885 + "dev": true, 6886 + "license": "MIT", 6887 + "dependencies": { 6888 + "define-data-property": "^1.1.4", 6889 + "es-errors": "^1.3.0", 6890 + "function-bind": "^1.1.2", 6891 + "get-intrinsic": "^1.2.4", 6892 + "gopd": "^1.0.1", 6893 + "has-property-descriptors": "^1.0.2" 6894 + }, 6895 + "engines": { 6896 + "node": ">= 0.4" 6897 + } 6898 + }, 6899 + "node_modules/set-function-name": { 6900 + "version": "2.0.2", 6901 + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", 6902 + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", 6903 + "dev": true, 6904 + "license": "MIT", 6905 + "dependencies": { 6906 + "define-data-property": "^1.1.4", 6907 + "es-errors": "^1.3.0", 6908 + "functions-have-names": "^1.2.3", 6909 + "has-property-descriptors": "^1.0.2" 6910 + }, 6911 + "engines": { 6912 + "node": ">= 0.4" 6913 + } 6914 + }, 6915 + "node_modules/set-proto": { 6916 + "version": "1.0.0", 6917 + "resolved": "https://registry.npmjs.org/set-proto/-/set-proto-1.0.0.tgz", 6918 + "integrity": "sha512-RJRdvCo6IAnPdsvP/7m6bsQqNnn1FCBX5ZNtFL98MmFF/4xAIJTIg1YbHW5DC2W5SKZanrC6i4HsJqlajw/dZw==", 6919 + "dev": true, 6920 + "license": "MIT", 6921 + "dependencies": { 6922 + "dunder-proto": "^1.0.1", 6923 + "es-errors": "^1.3.0", 6924 + "es-object-atoms": "^1.0.0" 6925 + }, 6926 + "engines": { 6927 + "node": ">= 0.4" 6928 + } 6929 + }, 4344 6930 "node_modules/sharp": { 4345 6931 "version": "0.33.5", 4346 6932 "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz", ··· 4385 6971 "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", 4386 6972 "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", 4387 6973 "dev": true, 4388 - "peer": true, 4389 6974 "dependencies": { 4390 6975 "shebang-regex": "^3.0.0" 4391 6976 }, ··· 4398 6983 "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", 4399 6984 "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", 4400 6985 "dev": true, 4401 - "peer": true, 4402 6986 "engines": { 4403 6987 "node": ">=8" 4404 6988 } 4405 6989 }, 6990 + "node_modules/side-channel": { 6991 + "version": "1.1.0", 6992 + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", 6993 + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", 6994 + "dev": true, 6995 + "license": "MIT", 6996 + "dependencies": { 6997 + "es-errors": "^1.3.0", 6998 + "object-inspect": "^1.13.3", 6999 + "side-channel-list": "^1.0.0", 7000 + "side-channel-map": "^1.0.1", 7001 + "side-channel-weakmap": "^1.0.2" 7002 + }, 7003 + "engines": { 7004 + "node": ">= 0.4" 7005 + }, 7006 + "funding": { 7007 + "url": "https://github.com/sponsors/ljharb" 7008 + } 7009 + }, 7010 + "node_modules/side-channel-list": { 7011 + "version": "1.0.0", 7012 + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", 7013 + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", 7014 + "dev": true, 7015 + "license": "MIT", 7016 + "dependencies": { 7017 + "es-errors": "^1.3.0", 7018 + "object-inspect": "^1.13.3" 7019 + }, 7020 + "engines": { 7021 + "node": ">= 0.4" 7022 + }, 7023 + "funding": { 7024 + "url": "https://github.com/sponsors/ljharb" 7025 + } 7026 + }, 7027 + "node_modules/side-channel-map": { 7028 + "version": "1.0.1", 7029 + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", 7030 + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", 7031 + "dev": true, 7032 + "license": "MIT", 7033 + "dependencies": { 7034 + "call-bound": "^1.0.2", 7035 + "es-errors": "^1.3.0", 7036 + "get-intrinsic": "^1.2.5", 7037 + "object-inspect": "^1.13.3" 7038 + }, 7039 + "engines": { 7040 + "node": ">= 0.4" 7041 + }, 7042 + "funding": { 7043 + "url": "https://github.com/sponsors/ljharb" 7044 + } 7045 + }, 7046 + "node_modules/side-channel-weakmap": { 7047 + "version": "1.0.2", 7048 + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", 7049 + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", 7050 + "dev": true, 7051 + "license": "MIT", 7052 + "dependencies": { 7053 + "call-bound": "^1.0.2", 7054 + "es-errors": "^1.3.0", 7055 + "get-intrinsic": "^1.2.5", 7056 + "object-inspect": "^1.13.3", 7057 + "side-channel-map": "^1.0.1" 7058 + }, 7059 + "engines": { 7060 + "node": ">= 0.4" 7061 + }, 7062 + "funding": { 7063 + "url": "https://github.com/sponsors/ljharb" 7064 + } 7065 + }, 4406 7066 "node_modules/signal-exit": { 4407 7067 "version": "3.0.7", 4408 7068 "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", ··· 4522 7182 "node": ">=8" 4523 7183 } 4524 7184 }, 7185 + "node_modules/string.prototype.trim": { 7186 + "version": "1.2.10", 7187 + "resolved": "https://registry.npmjs.org/string.prototype.trim/-/string.prototype.trim-1.2.10.tgz", 7188 + "integrity": "sha512-Rs66F0P/1kedk5lyYyH9uBzuiI/kNRmwJAR9quK6VOtIpZ2G+hMZd+HQbbv25MgCA6gEffoMZYxlTod4WcdrKA==", 7189 + "dev": true, 7190 + "license": "MIT", 7191 + "dependencies": { 7192 + "call-bind": "^1.0.8", 7193 + "call-bound": "^1.0.2", 7194 + "define-data-property": "^1.1.4", 7195 + "define-properties": "^1.2.1", 7196 + "es-abstract": "^1.23.5", 7197 + "es-object-atoms": "^1.0.0", 7198 + "has-property-descriptors": "^1.0.2" 7199 + }, 7200 + "engines": { 7201 + "node": ">= 0.4" 7202 + }, 7203 + "funding": { 7204 + "url": "https://github.com/sponsors/ljharb" 7205 + } 7206 + }, 7207 + "node_modules/string.prototype.trimend": { 7208 + "version": "1.0.9", 7209 + "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.9.tgz", 7210 + "integrity": "sha512-G7Ok5C6E/j4SGfyLCloXTrngQIQU3PWtXGst3yM7Bea9FRURf1S42ZHlZZtsNque2FN2PoUhfZXYLNWwEr4dLQ==", 7211 + "dev": true, 7212 + "license": "MIT", 7213 + "dependencies": { 7214 + "call-bind": "^1.0.8", 7215 + "call-bound": "^1.0.2", 7216 + "define-properties": "^1.2.1", 7217 + "es-object-atoms": "^1.0.0" 7218 + }, 7219 + "engines": { 7220 + "node": ">= 0.4" 7221 + }, 7222 + "funding": { 7223 + "url": "https://github.com/sponsors/ljharb" 7224 + } 7225 + }, 7226 + "node_modules/string.prototype.trimstart": { 7227 + "version": "1.0.8", 7228 + "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.8.tgz", 7229 + "integrity": "sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==", 7230 + "dev": true, 7231 + "license": "MIT", 7232 + "dependencies": { 7233 + "call-bind": "^1.0.7", 7234 + "define-properties": "^1.2.1", 7235 + "es-object-atoms": "^1.0.0" 7236 + }, 7237 + "engines": { 7238 + "node": ">= 0.4" 7239 + }, 7240 + "funding": { 7241 + "url": "https://github.com/sponsors/ljharb" 7242 + } 7243 + }, 4525 7244 "node_modules/strip-ansi": { 4526 7245 "version": "6.0.1", 4527 7246 "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", ··· 4584 7303 "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", 4585 7304 "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", 4586 7305 "dev": true, 4587 - "peer": true, 4588 7306 "engines": { 4589 7307 "node": ">= 0.4" 4590 7308 }, ··· 4643 7361 "node": ">=8.0" 4644 7362 } 4645 7363 }, 7364 + "node_modules/ts-api-utils": { 7365 + "version": "2.0.1", 7366 + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-2.0.1.tgz", 7367 + "integrity": "sha512-dnlgjFSVetynI8nzgJ+qF62efpglpWRk8isUEWZGWlJYySCTD6aKvbUDu+zbPeDakk3bg5H4XpitHukgfL1m9w==", 7368 + "dev": true, 7369 + "license": "MIT", 7370 + "engines": { 7371 + "node": ">=18.12" 7372 + }, 7373 + "peerDependencies": { 7374 + "typescript": ">=4.8.4" 7375 + } 7376 + }, 4646 7377 "node_modules/ts-jest": { 4647 7378 "version": "29.2.5", 4648 7379 "resolved": "https://registry.npmjs.org/ts-jest/-/ts-jest-29.2.5.tgz", ··· 4764 7495 "license": "0BSD", 4765 7496 "optional": true 4766 7497 }, 7498 + "node_modules/type-check": { 7499 + "version": "0.4.0", 7500 + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", 7501 + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", 7502 + "dev": true, 7503 + "license": "MIT", 7504 + "dependencies": { 7505 + "prelude-ls": "^1.2.1" 7506 + }, 7507 + "engines": { 7508 + "node": ">= 0.8.0" 7509 + } 7510 + }, 4767 7511 "node_modules/type-detect": { 4768 7512 "version": "4.0.8", 4769 7513 "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz", ··· 4787 7531 "url": "https://github.com/sponsors/sindresorhus" 4788 7532 } 4789 7533 }, 7534 + "node_modules/typed-array-buffer": { 7535 + "version": "1.0.3", 7536 + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", 7537 + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", 7538 + "dev": true, 7539 + "license": "MIT", 7540 + "dependencies": { 7541 + "call-bound": "^1.0.3", 7542 + "es-errors": "^1.3.0", 7543 + "is-typed-array": "^1.1.14" 7544 + }, 7545 + "engines": { 7546 + "node": ">= 0.4" 7547 + } 7548 + }, 7549 + "node_modules/typed-array-byte-length": { 7550 + "version": "1.0.3", 7551 + "resolved": "https://registry.npmjs.org/typed-array-byte-length/-/typed-array-byte-length-1.0.3.tgz", 7552 + "integrity": "sha512-BaXgOuIxz8n8pIq3e7Atg/7s+DpiYrxn4vdot3w9KbnBhcRQq6o3xemQdIfynqSeXeDrF32x+WvfzmOjPiY9lg==", 7553 + "dev": true, 7554 + "license": "MIT", 7555 + "dependencies": { 7556 + "call-bind": "^1.0.8", 7557 + "for-each": "^0.3.3", 7558 + "gopd": "^1.2.0", 7559 + "has-proto": "^1.2.0", 7560 + "is-typed-array": "^1.1.14" 7561 + }, 7562 + "engines": { 7563 + "node": ">= 0.4" 7564 + }, 7565 + "funding": { 7566 + "url": "https://github.com/sponsors/ljharb" 7567 + } 7568 + }, 7569 + "node_modules/typed-array-byte-offset": { 7570 + "version": "1.0.4", 7571 + "resolved": "https://registry.npmjs.org/typed-array-byte-offset/-/typed-array-byte-offset-1.0.4.tgz", 7572 + "integrity": "sha512-bTlAFB/FBYMcuX81gbL4OcpH5PmlFHqlCCpAl8AlEzMz5k53oNDvN8p1PNOWLEmI2x4orp3raOFB51tv9X+MFQ==", 7573 + "dev": true, 7574 + "license": "MIT", 7575 + "dependencies": { 7576 + "available-typed-arrays": "^1.0.7", 7577 + "call-bind": "^1.0.8", 7578 + "for-each": "^0.3.3", 7579 + "gopd": "^1.2.0", 7580 + "has-proto": "^1.2.0", 7581 + "is-typed-array": "^1.1.15", 7582 + "reflect.getprototypeof": "^1.0.9" 7583 + }, 7584 + "engines": { 7585 + "node": ">= 0.4" 7586 + }, 7587 + "funding": { 7588 + "url": "https://github.com/sponsors/ljharb" 7589 + } 7590 + }, 7591 + "node_modules/typed-array-length": { 7592 + "version": "1.0.7", 7593 + "resolved": "https://registry.npmjs.org/typed-array-length/-/typed-array-length-1.0.7.tgz", 7594 + "integrity": "sha512-3KS2b+kL7fsuk/eJZ7EQdnEmQoaho/r6KUef7hxvltNA5DR8NAUM+8wJMbJyZ4G9/7i3v5zPBIMN5aybAh2/Jg==", 7595 + "dev": true, 7596 + "license": "MIT", 7597 + "dependencies": { 7598 + "call-bind": "^1.0.7", 7599 + "for-each": "^0.3.3", 7600 + "gopd": "^1.0.1", 7601 + "is-typed-array": "^1.1.13", 7602 + "possible-typed-array-names": "^1.0.0", 7603 + "reflect.getprototypeof": "^1.0.6" 7604 + }, 7605 + "engines": { 7606 + "node": ">= 0.4" 7607 + }, 7608 + "funding": { 7609 + "url": "https://github.com/sponsors/ljharb" 7610 + } 7611 + }, 4790 7612 "node_modules/typescript": { 4791 7613 "version": "5.7.3", 4792 7614 "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", ··· 4815 7637 "integrity": "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==", 4816 7638 "license": "(Apache-2.0 AND MIT)" 4817 7639 }, 7640 + "node_modules/unbox-primitive": { 7641 + "version": "1.1.0", 7642 + "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", 7643 + "integrity": "sha512-nWJ91DjeOkej/TA8pXQ3myruKpKEYgqvpw9lz4OPHj/NWFNluYrjbz9j01CJ8yKQd2g4jFoOkINCTW2I5LEEyw==", 7644 + "dev": true, 7645 + "license": "MIT", 7646 + "dependencies": { 7647 + "call-bound": "^1.0.3", 7648 + "has-bigints": "^1.0.2", 7649 + "has-symbols": "^1.1.0", 7650 + "which-boxed-primitive": "^1.1.1" 7651 + }, 7652 + "engines": { 7653 + "node": ">= 0.4" 7654 + }, 7655 + "funding": { 7656 + "url": "https://github.com/sponsors/ljharb" 7657 + } 7658 + }, 4818 7659 "node_modules/undici-types": { 4819 7660 "version": "6.20.0", 4820 7661 "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.20.0.tgz", ··· 4853 7694 "browserslist": ">= 4.21.0" 4854 7695 } 4855 7696 }, 7697 + "node_modules/uri-js": { 7698 + "version": "4.4.1", 7699 + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", 7700 + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", 7701 + "dev": true, 7702 + "license": "BSD-2-Clause", 7703 + "dependencies": { 7704 + "punycode": "^2.1.0" 7705 + } 7706 + }, 4856 7707 "node_modules/v8-compile-cache-lib": { 4857 7708 "version": "3.0.1", 4858 7709 "resolved": "https://registry.npmjs.org/v8-compile-cache-lib/-/v8-compile-cache-lib-3.0.1.tgz", ··· 4901 7752 "which": "bin/which" 4902 7753 } 4903 7754 }, 7755 + "node_modules/which-boxed-primitive": { 7756 + "version": "1.1.1", 7757 + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", 7758 + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", 7759 + "dev": true, 7760 + "license": "MIT", 7761 + "dependencies": { 7762 + "is-bigint": "^1.1.0", 7763 + "is-boolean-object": "^1.2.1", 7764 + "is-number-object": "^1.1.1", 7765 + "is-string": "^1.1.1", 7766 + "is-symbol": "^1.1.1" 7767 + }, 7768 + "engines": { 7769 + "node": ">= 0.4" 7770 + }, 7771 + "funding": { 7772 + "url": "https://github.com/sponsors/ljharb" 7773 + } 7774 + }, 7775 + "node_modules/which-builtin-type": { 7776 + "version": "1.2.1", 7777 + "resolved": "https://registry.npmjs.org/which-builtin-type/-/which-builtin-type-1.2.1.tgz", 7778 + "integrity": "sha512-6iBczoX+kDQ7a3+YJBnh3T+KZRxM/iYNPXicqk66/Qfm1b93iu+yOImkg0zHbj5LNOcNv1TEADiZ0xa34B4q6Q==", 7779 + "dev": true, 7780 + "license": "MIT", 7781 + "dependencies": { 7782 + "call-bound": "^1.0.2", 7783 + "function.prototype.name": "^1.1.6", 7784 + "has-tostringtag": "^1.0.2", 7785 + "is-async-function": "^2.0.0", 7786 + "is-date-object": "^1.1.0", 7787 + "is-finalizationregistry": "^1.1.0", 7788 + "is-generator-function": "^1.0.10", 7789 + "is-regex": "^1.2.1", 7790 + "is-weakref": "^1.0.2", 7791 + "isarray": "^2.0.5", 7792 + "which-boxed-primitive": "^1.1.0", 7793 + "which-collection": "^1.0.2", 7794 + "which-typed-array": "^1.1.16" 7795 + }, 7796 + "engines": { 7797 + "node": ">= 0.4" 7798 + }, 7799 + "funding": { 7800 + "url": "https://github.com/sponsors/ljharb" 7801 + } 7802 + }, 7803 + "node_modules/which-collection": { 7804 + "version": "1.0.2", 7805 + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", 7806 + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", 7807 + "dev": true, 7808 + "license": "MIT", 7809 + "dependencies": { 7810 + "is-map": "^2.0.3", 7811 + "is-set": "^2.0.3", 7812 + "is-weakmap": "^2.0.2", 7813 + "is-weakset": "^2.0.3" 7814 + }, 7815 + "engines": { 7816 + "node": ">= 0.4" 7817 + }, 7818 + "funding": { 7819 + "url": "https://github.com/sponsors/ljharb" 7820 + } 7821 + }, 7822 + "node_modules/which-typed-array": { 7823 + "version": "1.1.18", 7824 + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.18.tgz", 7825 + "integrity": "sha512-qEcY+KJYlWyLH9vNbsr6/5j59AXk5ni5aakf8ldzBvGde6Iz4sxZGkJyWSAueTG7QhOvNRYb1lDdFmL5Td0QKA==", 7826 + "dev": true, 7827 + "license": "MIT", 7828 + "dependencies": { 7829 + "available-typed-arrays": "^1.0.7", 7830 + "call-bind": "^1.0.8", 7831 + "call-bound": "^1.0.3", 7832 + "for-each": "^0.3.3", 7833 + "gopd": "^1.2.0", 7834 + "has-tostringtag": "^1.0.2" 7835 + }, 7836 + "engines": { 7837 + "node": ">= 0.4" 7838 + }, 7839 + "funding": { 7840 + "url": "https://github.com/sponsors/ljharb" 7841 + } 7842 + }, 7843 + "node_modules/word-wrap": { 7844 + "version": "1.2.5", 7845 + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", 7846 + "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", 7847 + "dev": true, 7848 + "license": "MIT", 7849 + "engines": { 7850 + "node": ">=0.10.0" 7851 + } 7852 + }, 4904 7853 "node_modules/wrap-ansi": { 4905 7854 "version": "7.0.0", 4906 7855 "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", ··· 4998 7947 "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", 4999 7948 "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", 5000 7949 "dev": true, 5001 - "peer": true, 5002 7950 "engines": { 5003 7951 "node": ">=10" 5004 7952 },
+11 -1
package.json
··· 12 12 "compile": "npx tsc", 13 13 "test": "jest", 14 14 "test:watch": "jest --watch", 15 - "test:coverage": "jest --coverage" 15 + "test:coverage": "jest --coverage", 16 + "lint": "eslint . --ext .ts", 17 + "lint:fix": "eslint . --ext .ts --fix" 16 18 }, 17 19 "dependencies": { 18 20 "@atproto/api": "^0.13.31", ··· 29 31 "sharp": "^0.33.5" 30 32 }, 31 33 "devDependencies": { 34 + "@eslint/js": "^9.21.0", 32 35 "@types/jest": "^29.5.14", 33 36 "@types/node": "^22.10.10", 37 + "@typescript-eslint/eslint-plugin": "^8.26.0", 38 + "@typescript-eslint/parser": "^8.26.0", 39 + "eslint": "^9.21.0", 40 + "eslint-plugin-import": "^2.31.0", 41 + "eslint-plugin-jest": "^28.11.0", 42 + "globals": "^16.0.0", 43 + "jiti": "^2.4.2", 34 44 "ts-jest": "^29.2.5", 35 45 "ts-node": "^10.9.2", 36 46 "tsconfig-paths": "^4.2.0",
-1
src/bluesky/bluesky.test.ts
··· 1 1 import fs from 'fs'; 2 2 3 3 import { BlueskyClient } from './bluesky'; 4 - 5 4 import { ImagesEmbedImpl, VideoEmbedImpl } from './types/index'; 6 5 7 6 const TEST_VIDEO_PATH = './transfer/test_video/AQM8KYlOYHTF5GlP43eMroHUpmnFHJh5CnCJUdRUeqWxG4tNX7D43eM77F152vfi4znTzgkFTTzzM4nHa_v8ugmP4WPRJtjKPZX5pko_17845940218109367.mp4';
+2 -2
src/bluesky/bluesky.ts
··· 4 4 BlobRef 5 5 } from "@atproto/api"; 6 6 7 - import { logger } from "../logger/logger"; 8 - 9 7 import { 10 8 EmbeddedMedia, 11 9 PostRecordImpl 12 10 } from "./types"; 11 + import { logger } from "../logger/logger"; 12 + 13 13 14 14 15 15 export class BlueskyClient {
+1 -1
src/bluesky/types/EmbeddedMedia.ts
··· 1 - import { VideoEmbed } from "./VideoEmbed"; 2 1 import { ImagesEmbed } from "./ImagesEmbed"; 2 + import { VideoEmbed } from "./VideoEmbed"; 3 3 4 4 export type EmbeddedMedia = VideoEmbed | ImagesEmbed;
+1
src/bluesky/types/ImagesEmbed.ts
··· 1 1 import { AppBskyEmbedImages } from "@atproto/api"; 2 + 2 3 import { ImageEmbed } from "./ImageEmbed"; 3 4 4 5 export interface ImagesEmbed extends AppBskyEmbedImages.Main {
+6 -3
src/bluesky/types/PostRecord.ts
··· 1 1 import { AppBskyFeedPost, Facet } from "@atproto/api"; 2 + 2 3 import { EmbeddedMedia } from "./EmbeddedMedia"; 3 4 4 - export interface PostRecord extends Partial<AppBskyFeedPost.Record> {} 5 - 6 - export class PostRecordImpl implements PostRecord { 5 + /** 6 + * Bluesky post 7 + * @see AppBskyFeedPost.Record 8 + */ 9 + export class PostRecordImpl implements Partial<AppBskyFeedPost.Record> { 7 10 readonly $type = "app.bsky.feed.post"; 8 11 [k: string]: unknown; 9 12
+13 -6
src/config.ts
··· 1 + import path from 'path'; 2 + 1 3 import * as dotenv from 'dotenv'; 2 - import path from 'path'; 3 4 4 5 dotenv.config(); 5 6 ··· 11 12 private readonly testVideoMode: boolean; 12 13 private readonly testImageMode: boolean; 13 14 private readonly testImagesMode: boolean; 15 + private readonly testMixedMediaMode: boolean; 14 16 private readonly simulate: boolean; 15 17 private readonly minDate: Date | undefined; 16 18 private readonly maxDate: Date | undefined; ··· 22 24 testVideoMode: boolean; 23 25 testImageMode: boolean; 24 26 testImagesMode: boolean; 27 + testMixedMediaMode: boolean; 25 28 simulate: boolean; 26 29 minDate?: Date; 27 30 maxDate?: Date; ··· 32 35 this.testVideoMode = config.testVideoMode; 33 36 this.testImageMode = config.testImageMode; 34 37 this.testImagesMode = config.testImagesMode; 38 + this.testMixedMediaMode = config.testMixedMediaMode; 35 39 this.simulate = config.simulate; 36 40 this.minDate = config.minDate; 37 41 this.maxDate = config.maxDate; ··· 48 52 testVideoMode: process.env.TEST_VIDEO_MODE === '1', 49 53 testImageMode: process.env.TEST_IMAGE_MODE === '1', 50 54 testImagesMode: process.env.TEST_IMAGES_MODE === '1', 55 + testMixedMediaMode: process.env.TEST_MIXED_MEDIA_MODE === '1', 51 56 simulate: process.env.SIMULATE === '1', 52 57 minDate: process.env.MIN_DATE ? new Date(process.env.MIN_DATE) : undefined, 53 58 maxDate: process.env.MAX_DATE ? new Date(process.env.MAX_DATE) : undefined, 54 - blueskyUsername: process.env.BLUESKY_USERNAME || '', 55 - blueskyPassword: process.env.BLUESKY_PASSWORD || '', 56 - archiveFolder: process.env.ARCHIVE_FOLDER || '' 59 + blueskyUsername: process.env.BLUESKY_USERNAME ?? '', 60 + blueskyPassword: process.env.BLUESKY_PASSWORD ?? '', 61 + archiveFolder: process.env.ARCHIVE_FOLDER ?? '' 57 62 }); 58 63 } 59 64 ··· 61 66 * Checks if any test mode is enabled 62 67 */ 63 68 isTestModeEnabled(): boolean { 64 - return this.testVideoMode || this.testImageMode || this.testImagesMode; 69 + return this.testVideoMode || this.testImageMode || this.testImagesMode || this.testMixedMediaMode; 65 70 } 66 71 67 72 /** ··· 108 113 if (this.testVideoMode) return path.join(rootDir, 'transfer/test_video'); 109 114 if (this.testImageMode) return path.join(rootDir, 'transfer/test_image'); 110 115 if (this.testImagesMode) return path.join(rootDir, 'transfer/test_images'); 116 + if (this.testMixedMediaMode) return path.join(rootDir, 'transfer/test_mixed_media'); 111 117 return this.archiveFolder; 112 118 } 113 119 ··· 119 125 const enabledModes = Object.entries({ 120 126 testVideoMode: this.testVideoMode, 121 127 testImageMode: this.testImageMode, 122 - testImagesMode: this.testImagesMode 128 + testImagesMode: this.testImagesMode, 129 + testMixedMediaMode: this.testMixedMediaMode 123 130 }) 124 131 .filter(([_, enabled]) => enabled) 125 132 .map(([mode]) => mode);
+88
src/image/image.test.ts
··· 1 + import { getImageSize } from "./image"; 2 + import { logger } from "../logger/logger"; 3 + 4 + // Mock the file system 5 + jest.mock("fs", () => ({ 6 + readFileSync: jest.fn(), 7 + })); 8 + 9 + // Mock sharp 10 + jest.mock("sharp", () => { 11 + return function (filePath: string) { 12 + // Mock different behavior based on file path for testing different scenarios 13 + if (filePath && filePath.includes("missing.jpg")) { 14 + throw new Error("Input file is missing"); 15 + } 16 + 17 + if (filePath && filePath.includes("invalid.jpg")) { 18 + return { 19 + metadata: jest.fn().mockResolvedValue({}), 20 + }; 21 + } 22 + 23 + if (filePath && filePath.includes("landscape.jpg")) { 24 + return { 25 + metadata: jest.fn().mockResolvedValue({ width: 1920, height: 1080 }), 26 + }; 27 + } 28 + 29 + if (filePath && filePath.includes("portrait.jpg")) { 30 + return { 31 + metadata: jest.fn().mockResolvedValue({ width: 1080, height: 1920 }), 32 + }; 33 + } 34 + 35 + // Default square image 36 + return { 37 + metadata: jest.fn().mockResolvedValue({ width: 1080, height: 1080 }), 38 + }; 39 + }; 40 + }); 41 + 42 + // Mock the logger 43 + jest.mock("../logger/logger", () => ({ 44 + logger: { 45 + error: jest.fn(), 46 + warn: jest.fn(), 47 + info: jest.fn(), 48 + debug: jest.fn(), 49 + }, 50 + })); 51 + 52 + describe("getImageSize", () => { 53 + test("should return correct dimensions for a square image", async () => { 54 + const result = await getImageSize("/path/to/square.jpg"); 55 + expect(result).toEqual({ width: 1080, height: 1080 }); 56 + }); 57 + 58 + test("should return correct dimensions for a landscape image", async () => { 59 + const result = await getImageSize("/path/to/landscape.jpg"); 60 + expect(result).toEqual({ width: 1920, height: 1080 }); 61 + }); 62 + 63 + test("should return correct dimensions for a portrait image", async () => { 64 + const result = await getImageSize("/path/to/portrait.jpg"); 65 + expect(result).toEqual({ width: 1080, height: 1920 }); 66 + }); 67 + 68 + test("should return null when metadata is missing width or height", async () => { 69 + const result = await getImageSize("/path/to/invalid.jpg"); 70 + expect(result).toBeNull(); 71 + }); 72 + 73 + test("should log error when image processing fails", async () => { 74 + // Call the function with a path that will trigger an error 75 + const result = await getImageSize("/path/to/missing.jpg"); 76 + 77 + // Verify the logger.error was called with the expected message pattern 78 + expect(logger.error).toHaveBeenCalled(); 79 + expect(logger.error).toHaveBeenCalledWith( 80 + expect.stringMatching( 81 + /Failed to get image aspect ratio; image path: \/path\/to\/missing\.jpg, error:/ 82 + ) 83 + ); 84 + 85 + // Verify the function returns null when an error occurs 86 + expect(result).toBeNull(); 87 + }); 88 + });
+3 -2
src/image/image.ts
··· 1 + import byteSize from "byte-size"; 1 2 import sharp from "sharp"; 2 - import byteSize from "byte-size"; 3 + 3 4 import { logger } from "../logger/logger"; 4 - import { Ratio } from "src/media"; 5 + import { Ratio } from "../media"; 5 6 6 7 /** 7 8 * Image lexicon maxSize 1mb
+12 -9
src/instagram-to-bluesky.test.ts
··· 6 6 calculateEstimatedTime, 7 7 uploadMediaAndEmbed, 8 8 } from "../src/instagram-to-bluesky"; 9 - import { BlueskyClient } from "../src/bluesky/bluesky"; 10 - import { logger } from "../src/logger/logger"; 11 - import { InstagramMediaProcessor } from "../src/media/media"; 12 - import { ImagesEmbedImpl, VideoEmbedImpl } from "../src/bluesky/index"; 13 - import type { InstagramExportedPost } from "../src/media/InstagramExportedPost"; 14 - import { ImageMediaProcessResultImpl } from "./media"; 9 + import { BlueskyClient } from "./bluesky/bluesky"; 10 + import { ImagesEmbedImpl, VideoEmbedImpl } from "./bluesky/index"; 11 + import { logger } from "./logger/logger"; 12 + import { InstagramMediaProcessor, ImageMediaProcessResultImpl } from "./media"; 13 + 14 + import type { InstagramExportedPost } from "./media/InstagramExportedPost"; 15 15 16 16 // Mock all dependencies 17 17 jest.mock("fs"); 18 - jest.mock("../src/bluesky/bluesky", () => { 18 + jest.mock("./bluesky/bluesky", () => { 19 19 return { 20 20 BlueskyClient: jest.fn().mockImplementation(() => ({ 21 21 login: jest.fn().mockResolvedValue(undefined), ··· 28 28 })), 29 29 }; 30 30 }); 31 - jest.mock("../src/media/media", () => { 31 + jest.mock("./media", () => { 32 + const actual = jest.requireActual("./media") 32 33 const mockProcess = jest.fn().mockResolvedValue([ 33 34 { 34 35 postDate: new Date(), ··· 65 66 process: mockProcess, 66 67 })), 67 68 decodeUTF8: jest.fn((x) => x), 69 + ImageMediaProcessResultImpl: actual.ImageMediaProcessResultImpl, 70 + VideoMediaProcessResultImpl: actual.VideoMediaProcessResultImpl 68 71 }; 69 72 }); 70 73 jest.mock("../src/logger/logger", () => ({ ··· 579 582 // Mock BlueskyClient for tracking uploads 580 583 const mockBlueskyClient = { 581 584 login: jest.fn().mockResolvedValue(undefined), 582 - uploadMedia: jest.fn().mockImplementation((_, __) => { 585 + uploadMedia: jest.fn().mockImplementation(() => { 583 586 return Promise.resolve({ 584 587 ref: `test-blob-ref-${mockBlueskyClient.uploadMedia.mock.calls.length}`, 585 588 mimeType: "image/jpeg",
+35 -27
src/instagram-to-bluesky.ts
··· 1 - import FS from 'fs'; 2 - import path from 'path'; 1 + import FS from "fs"; 2 + import path from "path"; 3 3 4 - import { BlobRef } from '@atproto/api'; 4 + import { BlobRef } from "@atproto/api"; 5 5 6 - import { BlueskyClient } from './bluesky/bluesky'; 6 + import { BlueskyClient } from "./bluesky/bluesky"; 7 + import { 8 + EmbeddedMedia, 9 + ImageEmbed, 10 + ImageEmbedImpl, 11 + ImagesEmbedImpl, 12 + VideoEmbedImpl, 13 + } from "./bluesky/index"; 14 + import { AppConfig } from "./config"; 15 + import { logger } from "./logger/logger"; 7 16 import { 8 - EmbeddedMedia, ImageEmbed, ImageEmbedImpl, ImagesEmbedImpl, VideoEmbedImpl 9 - } from './bluesky/index'; 10 - import { AppConfig } from './config'; 11 - import { logger } from './logger/logger'; 12 - import { ImageMediaProcessResultImpl, MediaProcessResult, VideoMediaProcessResultImpl } from './media'; 13 - import { InstagramExportedPost } from './media/InstagramExportedPost'; 14 - import { decodeUTF8, InstagramMediaProcessor } from './media/media'; 15 - 17 + ImageMediaProcessResultImpl, 18 + MediaProcessResult, 19 + VideoMediaProcessResultImpl, 20 + decodeUTF8, 21 + InstagramMediaProcessor, 22 + InstagramExportedPost, 23 + } from "./media"; 16 24 17 25 const API_RATE_LIMIT_DELAY = 3000; // https://docs.bsky.app/docs/advanced-guides/rate-limits 18 26 ··· 30 38 31 39 /** 32 40 * Uploads media files to Bluesky and creates appropriate embed objects 33 - * 41 + * 34 42 * This function processes an array of media files of the same type (either all images or all videos) 35 43 * and uploads them to Bluesky's servers. For images, it collects them into a single ImagesEmbed object. 36 44 * For videos, it creates a VideoEmbed object. If mixed media types are provided, only the first type 37 45 * encountered will be processed. 38 - * 46 + * 39 47 * @param postText - The text content of the post to be associated with the media 40 48 * @param embeddedMedia - Array of media objects to be processed and uploaded (should be same type) 41 49 * @param bluesky - The BlueskyClient instance used for uploading media 42 - * 50 + * 43 51 * @returns {Promise<{ 44 52 * importedMediaCount: number, // Number of successfully uploaded media files 45 53 * uploadedMedia: EmbeddedMedia | undefined // The final embed object for the post 46 54 * }>} 47 - * 55 + * 48 56 * @throws Will log but not throw errors from failed media uploads 49 - * 57 + * 50 58 * @example 51 59 * const result = await uploadMedia( 52 60 * "My vacation photos", ··· 69 77 for (const media of embeddedMedia) { 70 78 try { 71 79 if (media.getType() === "image") { 72 - const { mediaBuffer, mimeType, aspectRatio } = media as ImageMediaProcessResultImpl; 80 + const { mediaBuffer, mimeType, aspectRatio } = 81 + media as ImageMediaProcessResultImpl; 73 82 74 83 const blobRef: BlobRef = await bluesky.uploadMedia( 75 84 mediaBuffer!, 76 85 mimeType! 77 86 ); 78 - embeddedImages.push(new ImageEmbedImpl(postText, blobRef, mimeType!, aspectRatio)); 87 + embeddedImages.push( 88 + new ImageEmbedImpl(postText, blobRef, mimeType!, aspectRatio) 89 + ); 79 90 uploadedMedia = new ImagesEmbedImpl(embeddedImages); 80 91 } else if (media.getType() === "video") { 81 92 const { mediaBuffer, mimeType, aspectRatio } = ··· 143 154 // Decide where to fetch post data to process from. 144 155 let postsJsonPath: string; 145 156 if (config.isTestModeEnabled()) { 146 - postsJsonPath = path.join(archivalFolder, 'posts.json'); 157 + postsJsonPath = path.join(archivalFolder, "posts.json"); 147 158 logger.info( 148 159 `--- TEST mode is enabled, using content from ${archivalFolder} ---` 149 160 ); 150 161 } else { 151 162 postsJsonPath = path.join( 152 163 archivalFolder, 153 - 'your_instagram_activity/content/posts_1.json' 164 + "your_instagram_activity/content/posts_1.json" 154 165 ); 155 166 } 156 167 ··· 237 248 ); 238 249 try { 239 250 // Upload all the embedded media 240 - const { uploadedMedia, importedMediaCount } = await uploadMediaAndEmbed( 241 - postText, 242 - embeddedMedia, 243 - bluesky 244 - ); 251 + const { uploadedMedia, importedMediaCount } = 252 + await uploadMediaAndEmbed(postText, embeddedMedia, bluesky); 245 253 // Added uploaded media to the counter. 246 254 importedMedia += importedMediaCount; 247 255 ··· 259 267 importedPosts++; 260 268 } 261 269 } else { 262 - logger.warn('No media uploaded! Check Error logs.'); 270 + logger.warn("No media uploaded! Check Error logs."); 263 271 } 264 272 } catch (error) { 265 273 logger.error(
+1 -1
src/logger/logger.ts
··· 1 - import { pino } from 'pino'; 2 1 import * as dotenv from 'dotenv'; 2 + import { pino } from 'pino'; 3 3 4 4 // Get env varables from .env 5 5 dotenv.config();
+1 -1
src/media/InstagramExportedPost.ts
··· 43 43 backup_uri: string; 44 44 } 45 45 46 - export interface ImageMedia extends BaseMedia {} 46 + export type ImageMedia = BaseMedia 47 47 48 48 export interface VideoMedia extends BaseMedia { 49 49 dubbing_info: any[];
+12 -1
src/media/index.ts
··· 1 1 export * from './MediaProcessResult'; 2 2 export * from './ProcessedPost'; 3 - export * from './media'; 3 + export * from './InstagramExportedPost'; 4 + export * from './processors/InstagramMediaProcessor'; 5 + export * from './processors/InstagramImageProcessor'; 6 + export * from './processors/InstagramVideoProcessor'; 7 + export * from './processors/DefaultMediaProcessorFactory'; 8 + export * from './interfaces/ProcessStrategy'; 9 + export * from './interfaces/MIMEType'; 10 + export * from './interfaces/InstagramPostProcessingStrategy'; 11 + export * from './interfaces/ImageMediaProcessingStrategy'; 12 + export * from './interfaces/VideoMediaProcessingStrategy'; 13 + export * from './interfaces/MediaProcessorFactory'; 14 + export * from './utils';
+1 -1
src/media/interfaces/ImageMediaProcessingStrategy.ts
··· 1 - import { ProcessStrategy } from './ProcessStrategy'; 2 1 import { MIMEType } from './MIMEType'; 2 + import { ProcessStrategy } from './ProcessStrategy'; 3 3 import { MediaProcessResult } from '../MediaProcessResult'; 4 4 5 5 /**
+2 -2
src/media/interfaces/MediaProcessorFactory.ts
··· 1 1 import { ProcessStrategy } from './ProcessStrategy'; 2 + import { Media, ImageMedia, VideoMedia } from '../InstagramExportedPost'; 2 3 import { MediaProcessResult } from '../MediaProcessResult'; 3 - import { Media } from '../InstagramExportedPost'; 4 4 5 5 export interface MediaProcessorFactory { 6 - createProcessor(media: Media | Media[], archiveFolder: string): ProcessStrategy<MediaProcessResult[]>; 6 + createProcessor(media: ImageMedia[] | VideoMedia[], archiveFolder: string): ProcessStrategy<MediaProcessResult[]>; 7 7 8 8 /** 9 9 * returns if any of the media is a video.
+1 -1
src/media/interfaces/VideoMediaProcessingStrategy.ts
··· 1 - import { ProcessStrategy } from './ProcessStrategy'; 2 1 import { MIMEType } from './MIMEType'; 2 + import { ProcessStrategy } from './ProcessStrategy'; 3 3 import { MediaProcessResult } from '../MediaProcessResult'; 4 4 5 5 /**
-471
src/media/media.test.ts
··· 1 - import fs from "fs"; 2 - 3 - import { InstagramImageProcessor, InstagramMediaProcessor, InstagramVideoProcessor, decodeUTF8 } from "./media"; 4 - import { getImageSize } from "../image"; 5 - import { InstagramExportedPost, VideoMedia, ImageMedia } from "./InstagramExportedPost"; 6 - 7 - // Mock the file system 8 - jest.mock("fs", () => ({ 9 - readFileSync: jest.fn(), 10 - })); 11 - 12 - // Mock sharp 13 - jest.mock("sharp", () => { 14 - return function(filePath: string) { 15 - // Mock different behavior based on file path for testing different scenarios 16 - if (filePath && filePath.includes('missing.jpg')) { 17 - throw new Error('Input file is missing'); 18 - } 19 - 20 - if (filePath && filePath.includes('invalid.jpg')) { 21 - return { 22 - metadata: jest.fn().mockResolvedValue({}) 23 - }; 24 - } 25 - 26 - if (filePath && filePath.includes('landscape.jpg')) { 27 - return { 28 - metadata: jest.fn().mockResolvedValue({ width: 1920, height: 1080 }) 29 - }; 30 - } 31 - 32 - if (filePath && filePath.includes('portrait.jpg')) { 33 - return { 34 - metadata: jest.fn().mockResolvedValue({ width: 1080, height: 1920 }) 35 - }; 36 - } 37 - 38 - // Default square image 39 - return { 40 - metadata: jest.fn().mockResolvedValue({ width: 1080, height: 1080 }) 41 - }; 42 - }; 43 - }); 44 - 45 - // Mock fluent-ffmpeg 46 - jest.mock("fluent-ffmpeg", () => { 47 - return { 48 - setFfprobePath: jest.fn(), 49 - ffprobe: (path: string, callback: (err: Error | null, data: any) => void) => { 50 - callback(null, { 51 - streams: [ 52 - { 53 - codec_type: "video", 54 - width: 1920, 55 - height: 1080, 56 - }, 57 - ], 58 - }); 59 - }, 60 - }; 61 - }); 62 - 63 - // Mock @ffprobe-installer/ffprobe 64 - jest.mock("@ffprobe-installer/ffprobe", () => ({ 65 - path: "/mock/ffprobe/path", 66 - })); 67 - 68 - // Mock the logger 69 - jest.mock("../logger/logger", () => ({ 70 - logger: { 71 - error: jest.fn(), 72 - warn: jest.fn(), 73 - info: jest.fn(), 74 - debug: jest.fn(), 75 - }, 76 - })); 77 - 78 - 79 - // TODO breakdown into processors.test.ts or a test suite per processor instance. 80 - describe("Instagram Media Processing", () => { 81 - beforeEach(() => { 82 - jest.clearAllMocks(); 83 - (fs.readFileSync as jest.Mock).mockReturnValue(Buffer.from("test")); 84 - }); 85 - 86 - describe("InstagramMediaProcessor", () => { 87 - const mockArchiveFolder = "/test/archive"; 88 - 89 - test("should process a post with multiple images", async () => { 90 - const mockPost: InstagramExportedPost = { 91 - creation_timestamp: 1234567890, 92 - title: "Test Post", 93 - media: [ 94 - { 95 - uri: "photo1.jpg", 96 - title: "Image 1", 97 - creation_timestamp: 1234567890, 98 - media_metadata: {}, 99 - cross_post_source: { source_app: "Instagram" }, 100 - backup_uri: "backup1.jpg", 101 - }, 102 - { 103 - uri: "photo2.jpg", 104 - title: "Image 2", 105 - creation_timestamp: 1234567890, 106 - media_metadata: {}, 107 - cross_post_source: { source_app: "Instagram" }, 108 - backup_uri: "backup2.jpg", 109 - }, 110 - ] as ImageMedia[], 111 - }; 112 - 113 - const processor = new InstagramMediaProcessor([mockPost], mockArchiveFolder); 114 - const result = await processor.process(); 115 - 116 - expect(result).toHaveLength(1); 117 - expect(result[0].postText).toBe("Test Post"); 118 - expect(Array.isArray(result[0].embeddedMedia)).toBe(true); 119 - expect(result[0].embeddedMedia).toHaveLength(2); 120 - }); 121 - 122 - test("should process a post with a single video", async () => { 123 - const mockPost: InstagramExportedPost = { 124 - creation_timestamp: 1234567890, 125 - title: "Test Video Post", 126 - media: [{ 127 - uri: "video.mp4", 128 - title: "Test Video", 129 - creation_timestamp: 1234567890, 130 - media_metadata: {}, 131 - cross_post_source: { source_app: "Instagram" }, 132 - backup_uri: "backup_video.mp4", 133 - dubbing_info: [], 134 - media_variants: [], 135 - } as VideoMedia], 136 - }; 137 - 138 - const processor = new InstagramMediaProcessor([mockPost], mockArchiveFolder); 139 - const result = await processor.process(); 140 - 141 - expect(result).toHaveLength(1); 142 - expect(result[0].postText).toBe("Test Video Post"); 143 - expect(Array.isArray(result[0].embeddedMedia)).toBe(true); 144 - }); 145 - 146 - test("should truncate post title when it exceeds limit", async () => { 147 - const longTitle = "A".repeat(400); // Create a title longer than POST_TEXT_LIMIT (300) 148 - const mockPost: InstagramExportedPost = { 149 - creation_timestamp: 1234567890, 150 - title: longTitle, 151 - media: [ 152 - { 153 - uri: "photo1.jpg", 154 - title: "Image 1", 155 - creation_timestamp: 1234567890, 156 - media_metadata: {}, 157 - cross_post_source: { source_app: "Instagram" }, 158 - backup_uri: "backup1.jpg", 159 - }, 160 - ] as ImageMedia[], 161 - }; 162 - 163 - const processor = new InstagramMediaProcessor([mockPost], mockArchiveFolder); 164 - const result = await processor.process(); 165 - 166 - expect(result).toHaveLength(1); 167 - expect(result[0].postText.length).toBe(303); // 300 chars + "..." 168 - expect(result[0].postText.endsWith("...")).toBe(true); 169 - }); 170 - 171 - test("should use media title if no post title is available", async () => { 172 - // Create a post without a title property using type casting 173 - const mockPost = { 174 - creation_timestamp: 1458732736, 175 - media: [ 176 - { 177 - uri: "AQM8KYlOYHTF5GlP43eMroHUpmnFHJh5CnCJUdRUeqWxG4tNX7D43eM77F152vfi4znTzgkFTTzzM4nHa_v8ugmP4WPRJtjKPZX5pko_17845940218109367.mp4", 178 - creation_timestamp: 1458732736, 179 - media_metadata: { 180 - video_metadata: { 181 - exif_data: [ 182 - { 183 - latitude: 53.141186112, 184 - longitude: 11.038734576 185 - } 186 - ] 187 - } 188 - }, 189 - title: "No filter needed. 😍🌱 #waterfall #nature", 190 - cross_post_source: { 191 - source_app: "FB" 192 - }, 193 - backup_uri: "backup_video.mp4", 194 - dubbing_info: [], 195 - media_variants: [] 196 - } as VideoMedia], 197 - } as InstagramExportedPost; // Cast to InstagramExportedPost without providing title 198 - 199 - const processor = new InstagramMediaProcessor([mockPost], mockArchiveFolder); 200 - const result = await processor.process(); 201 - 202 - expect(result).toHaveLength(1); 203 - expect(result[0].postText).toBe("No filter needed. 😍🌱 #waterfall #nature"); 204 - expect(Array.isArray(result[0].embeddedMedia)).toBe(true); 205 - }); 206 - 207 - test("should limit media to maximum allowed images", async () => { 208 - const mockPost: InstagramExportedPost = { 209 - creation_timestamp: 1234567890, 210 - title: "Test Post with Many Images", 211 - media: Array(6).fill(null).map((_, index) => ({ 212 - uri: `photo${index + 1}.jpg`, 213 - title: `Image ${index + 1}`, 214 - creation_timestamp: 1234567890, 215 - media_metadata: {}, 216 - cross_post_source: { source_app: "Instagram" }, 217 - backup_uri: `backup${index + 1}.jpg`, 218 - })) as ImageMedia[], // Create 6 images, more than MAX_IMAGES_PER_POST (4) 219 - }; 220 - 221 - const processor = new InstagramMediaProcessor([mockPost], mockArchiveFolder); 222 - const result = await processor.process(); 223 - 224 - expect(result).toHaveLength(1); 225 - expect(result[0].embeddedMedia).toHaveLength(4); // Should be limited to 4 images 226 - }); 227 - }); 228 - 229 - describe("InstagramImageProcessor", () => { 230 - test("should process multiple images", async () => { 231 - const mockImages: ImageMedia[] = [ 232 - { 233 - uri: "photo1.jpg", 234 - title: "Image 1", 235 - creation_timestamp: 1234567890, 236 - media_metadata: {}, 237 - cross_post_source: { source_app: "Instagram" }, 238 - backup_uri: "backup1.jpg", 239 - }, 240 - { 241 - uri: "photo2.jpg", 242 - title: "Image 2", 243 - creation_timestamp: 1234567890, 244 - media_metadata: {}, 245 - cross_post_source: { source_app: "Instagram" }, 246 - backup_uri: "backup2.jpg", 247 - }, 248 - ]; 249 - 250 - const processor = new InstagramImageProcessor(mockImages, "/test/archive"); 251 - const result = await processor.process(); 252 - 253 - expect(result).toHaveLength(2); 254 - expect(result[0].mimeType).toBe("image/jpeg"); 255 - expect(result[1].mimeType).toBe("image/jpeg"); 256 - }); 257 - 258 - test("should handle unsupported image types", async () => { 259 - const mockImages: ImageMedia[] = [{ 260 - uri: "photo.xyz", 261 - title: "Invalid Image", 262 - creation_timestamp: 1234567890, 263 - media_metadata: {}, 264 - cross_post_source: { source_app: "Instagram" }, 265 - backup_uri: "backup_invalid.jpg", 266 - }]; 267 - 268 - const processor = new InstagramImageProcessor(mockImages, "/test/archive"); 269 - const result = await processor.process(); 270 - 271 - expect(result).toHaveLength(1); 272 - expect(result[0].mimeType).toBe(""); 273 - }); 274 - 275 - test("should truncate image caption when it exceeds limit", async () => { 276 - const longCaption = "B".repeat(400); // Create a caption longer than POST_TEXT_LIMIT (300) 277 - const mockImages: ImageMedia[] = [{ 278 - uri: "photo1.jpg", 279 - title: longCaption, 280 - creation_timestamp: 1234567890, 281 - media_metadata: {}, 282 - cross_post_source: { source_app: "Instagram" }, 283 - backup_uri: "backup1.jpg", 284 - }]; 285 - 286 - const processor = new InstagramImageProcessor(mockImages, "/test/archive"); 287 - const result = await processor.process(); 288 - 289 - expect(result).toHaveLength(1); 290 - expect(result[0].mediaText.length).toBe(303); // 300 chars + "..." 291 - expect(result[0].mediaText.endsWith("...")).toBe(true); 292 - }); 293 - 294 - test("should limit to maximum allowed images when processing multiple images", async () => { 295 - const mockImages: ImageMedia[] = Array(6).fill(null).map((_, index) => ({ 296 - uri: `photo${index + 1}.jpg`, 297 - title: `Image ${index + 1}`, 298 - creation_timestamp: 1234567890, 299 - media_metadata: {}, 300 - cross_post_source: { source_app: "Instagram" }, 301 - backup_uri: `backup${index + 1}.jpg`, 302 - })); // Create 6 images, more than MAX_IMAGES_PER_POST (4) 303 - 304 - const processor = new InstagramImageProcessor(mockImages, "/test/archive"); 305 - const result = await processor.process(); 306 - 307 - expect(result).toHaveLength(4); // Should be limited to 4 images 308 - }); 309 - }); 310 - 311 - describe("InstagramVideoProcessor", () => { 312 - test("should process a video", async () => { 313 - const mockVideo: VideoMedia = { 314 - uri: "video.mp4", 315 - title: "Test Video", 316 - creation_timestamp: 1234567890, 317 - media_metadata: {}, 318 - cross_post_source: { source_app: "Instagram" }, 319 - backup_uri: "backup_video.mp4", 320 - dubbing_info: [], 321 - media_variants: [], 322 - }; 323 - 324 - const processor = new InstagramVideoProcessor([mockVideo], "/test/archive"); 325 - const result = await processor.process(); 326 - 327 - expect(result).toHaveLength(1); 328 - expect(result[0].mimeType).toBe("video/mp4"); 329 - expect(result[0].mediaText).toBe("Test Video"); 330 - }); 331 - 332 - test("should handle unsupported video types", async () => { 333 - const mockVideo: VideoMedia = { 334 - uri: "video.xyz", 335 - title: "Invalid Video", 336 - creation_timestamp: 1234567890, 337 - media_metadata: {}, 338 - cross_post_source: { source_app: "Instagram" }, 339 - backup_uri: "backup_invalid.mp4", 340 - dubbing_info: [], 341 - media_variants: [], 342 - }; 343 - 344 - const processor = new InstagramVideoProcessor([mockVideo], "/test/archive"); 345 - const result = await processor.process(); 346 - 347 - expect(result).toHaveLength(1); 348 - expect(result[0].mimeType).toBe(""); 349 - }); 350 - 351 - test("should truncate video title when it exceeds limit", async () => { 352 - const longTitle = "C".repeat(400); // Create a title longer than POST_TEXT_LIMIT (300) 353 - const mockVideo: VideoMedia = { 354 - uri: "video.mp4", 355 - title: longTitle, 356 - creation_timestamp: 1234567890, 357 - media_metadata: {}, 358 - cross_post_source: { source_app: "Instagram" }, 359 - backup_uri: "backup_video.mp4", 360 - dubbing_info: [], 361 - media_variants: [], 362 - }; 363 - 364 - const processor = new InstagramVideoProcessor([mockVideo], "/test/archive"); 365 - const result = await processor.process(); 366 - 367 - expect(result).toHaveLength(1); 368 - expect(result[0].mediaText.length).toBe(303); // 300 chars + "..." 369 - expect(result[0].mediaText.endsWith("...")).toBe(true); 370 - }); 371 - }); 372 - 373 - describe("MediaProcessorFactory", () => { 374 - test("should use DefaultMediaProcessorFactory for image processing", async () => { 375 - const mockPost: InstagramExportedPost = { 376 - creation_timestamp: 1234567890, 377 - title: "Test Post", 378 - media: [ 379 - { 380 - uri: "test.jpg", 381 - title: "Test", 382 - creation_timestamp: 1234567890, 383 - media_metadata: {}, 384 - cross_post_source: { source_app: "Instagram" }, 385 - backup_uri: "backup_test.jpg", 386 - }, 387 - ] as ImageMedia[], 388 - }; 389 - 390 - const processor = new InstagramMediaProcessor([mockPost], "/test/archive"); 391 - const result = await processor.process(); 392 - 393 - expect(result).toHaveLength(1); 394 - expect(result[0].embeddedMedia).toBeDefined(); 395 - expect(Array.isArray(result[0].embeddedMedia)).toBe(true); 396 - }); 397 - 398 - test("should use DefaultMediaProcessorFactory for video processing", async () => { 399 - const mockPost: InstagramExportedPost = { 400 - creation_timestamp: 1234567890, 401 - title: "Test Video Post", 402 - media: [ 403 - { 404 - uri: "test.mp4", 405 - title: "Test Video", 406 - creation_timestamp: 1234567890, 407 - media_metadata: {}, 408 - cross_post_source: { source_app: "Instagram" }, 409 - backup_uri: "backup_test.mp4", 410 - dubbing_info: [], 411 - media_variants: [], 412 - }, 413 - ] as VideoMedia[], 414 - }; 415 - 416 - const processor = new InstagramMediaProcessor([mockPost], "/test/archive"); 417 - const result = await processor.process(); 418 - 419 - expect(result).toHaveLength(1); 420 - expect(result[0].embeddedMedia).toBeDefined(); 421 - expect(Array.isArray(result[0].embeddedMedia)).toBe(true); 422 - }); 423 - }); 424 - }); 425 - 426 - describe("decodeUTF8", () => { 427 - test("should decode Instagram Unicode escape sequences", () => { 428 - const input = "Basil, Eucalyptus, Thyme \u00f0\u009f\u0098\u008d\u00f0\u009f\u008c\u00b1"; 429 - const result = decodeUTF8(input); 430 - expect(result).toBe("Basil, Eucalyptus, Thyme 😍🌱"); 431 - }); 432 - }); 433 - 434 - describe("getImageSize", () => { 435 - test("should return correct dimensions for a square image", async () => { 436 - const result = await getImageSize("/path/to/square.jpg"); 437 - expect(result).toEqual({ width: 1080, height: 1080 }); 438 - }); 439 - 440 - test("should return correct dimensions for a landscape image", async () => { 441 - const result = await getImageSize("/path/to/landscape.jpg"); 442 - expect(result).toEqual({ width: 1920, height: 1080 }); 443 - }); 444 - 445 - test("should return correct dimensions for a portrait image", async () => { 446 - const result = await getImageSize("/path/to/portrait.jpg"); 447 - expect(result).toEqual({ width: 1080, height: 1920 }); 448 - }); 449 - 450 - test("should return null when metadata is missing width or height", async () => { 451 - const result = await getImageSize("/path/to/invalid.jpg"); 452 - expect(result).toBeNull(); 453 - }); 454 - 455 - test("should log error when image processing fails", async () => { 456 - // Import the logger mock 457 - const { logger } = require("../logger/logger"); 458 - 459 - // Call the function with a path that will trigger an error 460 - const result = await getImageSize("/path/to/missing.jpg"); 461 - 462 - // Verify the logger.error was called with the expected message pattern 463 - expect(logger.error).toHaveBeenCalled(); 464 - expect(logger.error).toHaveBeenCalledWith( 465 - expect.stringMatching(/Failed to get image aspect ratio; image path: \/path\/to\/missing\.jpg, error:/) 466 - ); 467 - 468 - // Verify the function returns null when an error occurs 469 - expect(result).toBeNull(); 470 - }); 471 - });
-11
src/media/media.ts
··· 1 - export * from './processors/InstagramMediaProcessor'; 2 - export * from './processors/InstagramImageProcessor'; 3 - export * from './processors/InstagramVideoProcessor'; 4 - export * from './processors/DefaultMediaProcessorFactory'; 5 - export * from './interfaces/ProcessStrategy'; 6 - export * from './interfaces/MIMEType'; 7 - export * from './interfaces/InstagramPostProcessingStrategy'; 8 - export * from './interfaces/ImageMediaProcessingStrategy'; 9 - export * from './interfaces/VideoMediaProcessingStrategy'; 10 - export * from './interfaces/MediaProcessorFactory'; 11 - export * from './utils';
+6 -6
src/media/processors/DefaultMediaProcessorFactory.ts
··· 1 - import { MediaProcessorFactory } from "../interfaces/MediaProcessorFactory"; 2 - import { ProcessStrategy } from "../interfaces/ProcessStrategy"; 3 - import { Media, VideoMedia } from "../InstagramExportedPost"; 1 + import { Media, VideoMedia, ImageMedia } from "../InstagramExportedPost"; 4 2 import { MediaProcessResult } from "../MediaProcessResult"; 5 3 import { InstagramImageProcessor } from "./InstagramImageProcessor"; 6 4 import { InstagramVideoProcessor } from "./InstagramVideoProcessor"; 7 5 import { getMimeType as getVideoMimeType } from "../../video/video"; 6 + import { MediaProcessorFactory } from "../interfaces/MediaProcessorFactory"; 7 + import { ProcessStrategy } from "../interfaces/ProcessStrategy"; 8 8 9 9 /** 10 10 * Processor factory that handles images and video. 11 11 */ 12 12 export class DefaultMediaProcessorFactory implements MediaProcessorFactory { 13 - createProcessor(media: Media | Media[], archiveFolder: string): ProcessStrategy<MediaProcessResult[]> { 14 - if (Array.isArray(media) && !this.hasVideo(media)) { 15 - return new InstagramImageProcessor(media, archiveFolder); 13 + createProcessor(media: ImageMedia[] | VideoMedia[], archiveFolder: string): ProcessStrategy<MediaProcessResult[]> { 14 + if (!this.hasVideo(media)) { 15 + return new InstagramImageProcessor(media as ImageMedia[], archiveFolder); 16 16 } 17 17 return new InstagramVideoProcessor(media as VideoMedia[], archiveFolder); 18 18 }
+138
src/media/processors/Factory.test.ts
··· 1 + import fs from "fs"; 2 + 3 + import { InstagramMediaProcessor } from "../"; 4 + import { 5 + InstagramExportedPost, 6 + VideoMedia, 7 + ImageMedia, 8 + } from "../InstagramExportedPost"; 9 + 10 + // Mock the file system 11 + jest.mock("fs", () => ({ 12 + readFileSync: jest.fn(), 13 + })); 14 + 15 + // Mock sharp 16 + jest.mock("sharp", () => { 17 + return function (filePath: string) { 18 + // Mock different behavior based on file path for testing different scenarios 19 + if (filePath && filePath.includes("missing.jpg")) { 20 + throw new Error("Input file is missing"); 21 + } 22 + 23 + if (filePath && filePath.includes("invalid.jpg")) { 24 + return { 25 + metadata: jest.fn().mockResolvedValue({}), 26 + }; 27 + } 28 + 29 + if (filePath && filePath.includes("landscape.jpg")) { 30 + return { 31 + metadata: jest.fn().mockResolvedValue({ width: 1920, height: 1080 }), 32 + }; 33 + } 34 + 35 + if (filePath && filePath.includes("portrait.jpg")) { 36 + return { 37 + metadata: jest.fn().mockResolvedValue({ width: 1080, height: 1920 }), 38 + }; 39 + } 40 + 41 + // Default square image 42 + return { 43 + metadata: jest.fn().mockResolvedValue({ width: 1080, height: 1080 }), 44 + }; 45 + }; 46 + }); 47 + 48 + // Mock fluent-ffmpeg 49 + jest.mock("fluent-ffmpeg", () => { 50 + return { 51 + setFfprobePath: jest.fn(), 52 + ffprobe: ( 53 + path: string, 54 + callback: (err: Error | null, data: any) => void 55 + ) => { 56 + callback(null, { 57 + streams: [ 58 + { 59 + codec_type: "video", 60 + width: 1920, 61 + height: 1080, 62 + }, 63 + ], 64 + }); 65 + }, 66 + }; 67 + }); 68 + 69 + // Mock @ffprobe-installer/ffprobe 70 + jest.mock("@ffprobe-installer/ffprobe", () => ({ 71 + path: "/mock/ffprobe/path", 72 + })); 73 + 74 + // Mock the logger 75 + jest.mock("../../logger/logger", () => ({ 76 + logger: { 77 + error: jest.fn(), 78 + warn: jest.fn(), 79 + info: jest.fn(), 80 + debug: jest.fn(), 81 + }, 82 + })); 83 + 84 + describe("MediaProcessorFactory", () => { 85 + beforeEach(() => { 86 + jest.clearAllMocks(); 87 + (fs.readFileSync as jest.Mock).mockReturnValue(Buffer.from("test")); 88 + }); 89 + test("should use DefaultMediaProcessorFactory for image processing", async () => { 90 + const mockPost: InstagramExportedPost = { 91 + creation_timestamp: 1234567890, 92 + title: "Test Post", 93 + media: [ 94 + { 95 + uri: "test.jpg", 96 + title: "Test", 97 + creation_timestamp: 1234567890, 98 + media_metadata: {}, 99 + cross_post_source: { source_app: "Instagram" }, 100 + backup_uri: "backup_test.jpg", 101 + }, 102 + ] as ImageMedia[], 103 + }; 104 + 105 + const processor = new InstagramMediaProcessor([mockPost], "/test/archive"); 106 + const result = await processor.process(); 107 + 108 + expect(result).toHaveLength(1); 109 + expect(result[0].embeddedMedia).toBeDefined(); 110 + expect(Array.isArray(result[0].embeddedMedia)).toBe(true); 111 + }); 112 + 113 + test("should use DefaultMediaProcessorFactory for video processing", async () => { 114 + const mockPost: InstagramExportedPost = { 115 + creation_timestamp: 1234567890, 116 + title: "Test Video Post", 117 + media: [ 118 + { 119 + uri: "test.mp4", 120 + title: "Test Video", 121 + creation_timestamp: 1234567890, 122 + media_metadata: {}, 123 + cross_post_source: { source_app: "Instagram" }, 124 + backup_uri: "backup_test.mp4", 125 + dubbing_info: [], 126 + media_variants: [], 127 + }, 128 + ] as VideoMedia[], 129 + }; 130 + 131 + const processor = new InstagramMediaProcessor([mockPost], "/test/archive"); 132 + const result = await processor.process(); 133 + 134 + expect(result).toHaveLength(1); 135 + expect(result[0].embeddedMedia).toBeDefined(); 136 + expect(Array.isArray(result[0].embeddedMedia)).toBe(true); 137 + }); 138 + });
+161
src/media/processors/InstagramImageProcessor.test.ts
··· 1 + import { InstagramImageProcessor } from "../"; 2 + import { ImageMedia } from "../InstagramExportedPost"; 3 + 4 + // Mock the file system 5 + jest.mock("fs", () => ({ 6 + readFileSync: jest.fn(), 7 + })); 8 + 9 + // Mock sharp 10 + jest.mock("sharp", () => { 11 + return function(filePath: string) { 12 + // Mock different behavior based on file path for testing different scenarios 13 + if (filePath && filePath.includes('missing.jpg')) { 14 + throw new Error('Input file is missing'); 15 + } 16 + 17 + if (filePath && filePath.includes('invalid.jpg')) { 18 + return { 19 + metadata: jest.fn().mockResolvedValue({}) 20 + }; 21 + } 22 + 23 + if (filePath && filePath.includes('landscape.jpg')) { 24 + return { 25 + metadata: jest.fn().mockResolvedValue({ width: 1920, height: 1080 }) 26 + }; 27 + } 28 + 29 + if (filePath && filePath.includes('portrait.jpg')) { 30 + return { 31 + metadata: jest.fn().mockResolvedValue({ width: 1080, height: 1920 }) 32 + }; 33 + } 34 + 35 + // Default square image 36 + return { 37 + metadata: jest.fn().mockResolvedValue({ width: 1080, height: 1080 }) 38 + }; 39 + }; 40 + }); 41 + 42 + // Mock fluent-ffmpeg 43 + jest.mock("fluent-ffmpeg", () => { 44 + return { 45 + setFfprobePath: jest.fn(), 46 + ffprobe: (path: string, callback: (err: Error | null, data: any) => void) => { 47 + callback(null, { 48 + streams: [ 49 + { 50 + codec_type: "video", 51 + width: 1920, 52 + height: 1080, 53 + }, 54 + ], 55 + }); 56 + }, 57 + }; 58 + }); 59 + 60 + // Mock @ffprobe-installer/ffprobe 61 + jest.mock("@ffprobe-installer/ffprobe", () => ({ 62 + path: "/mock/ffprobe/path", 63 + })); 64 + 65 + // Mock the logger 66 + jest.mock("../../logger/logger", () => ({ 67 + logger: { 68 + error: jest.fn(), 69 + warn: jest.fn(), 70 + info: jest.fn(), 71 + debug: jest.fn(), 72 + }, 73 + })); 74 + 75 + 76 + describe("InstagramImageProcessor", () => { 77 + test("should process multiple images", async () => { 78 + const mockImages: ImageMedia[] = [ 79 + { 80 + uri: "photo1.jpg", 81 + title: "Image 1", 82 + creation_timestamp: 1234567890, 83 + media_metadata: {}, 84 + cross_post_source: { source_app: "Instagram" }, 85 + backup_uri: "backup1.jpg", 86 + }, 87 + { 88 + uri: "photo2.jpg", 89 + title: "Image 2", 90 + creation_timestamp: 1234567890, 91 + media_metadata: {}, 92 + cross_post_source: { source_app: "Instagram" }, 93 + backup_uri: "backup2.jpg", 94 + }, 95 + ]; 96 + 97 + const processor = new InstagramImageProcessor(mockImages, "/test/archive"); 98 + const result = await processor.process(); 99 + 100 + expect(result).toHaveLength(2); 101 + expect(result[0].mimeType).toBe("image/jpeg"); 102 + expect(result[1].mimeType).toBe("image/jpeg"); 103 + }); 104 + 105 + test("should handle unsupported image types", async () => { 106 + const mockImages: ImageMedia[] = [{ 107 + uri: "photo.xyz", 108 + title: "Invalid Image", 109 + creation_timestamp: 1234567890, 110 + media_metadata: {}, 111 + cross_post_source: { source_app: "Instagram" }, 112 + backup_uri: "backup_invalid.jpg", 113 + }]; 114 + 115 + const processor = new InstagramImageProcessor(mockImages, "/test/archive"); 116 + const result = await processor.process(); 117 + 118 + expect(result).toHaveLength(1); 119 + expect(result[0].mimeType).toBe(""); 120 + }); 121 + 122 + test("should truncate image caption when it exceeds limit", async () => { 123 + const longCaption = "B".repeat(400); // Create a caption longer than POST_TEXT_LIMIT (300) 124 + const mockImages: ImageMedia[] = [{ 125 + uri: "photo1.jpg", 126 + title: longCaption, 127 + creation_timestamp: 1234567890, 128 + media_metadata: {}, 129 + cross_post_source: { source_app: "Instagram" }, 130 + backup_uri: "backup1.jpg", 131 + }]; 132 + 133 + const processor = new InstagramImageProcessor(mockImages, "/test/archive"); 134 + const result = await processor.process(); 135 + 136 + expect(result).toHaveLength(1); 137 + expect(result[0].mediaText.length).toBe(300); // 297 chars + "..." 138 + expect(result[0].mediaText.endsWith("...")).toBe(true); 139 + }); 140 + 141 + test("should limit to maximum allowed images when processing multiple images", async () => { 142 + const mockImages: ImageMedia[] = Array(6).fill(null).map((_, index) => ({ 143 + uri: `photo${index + 1}.jpg`, 144 + title: `Image ${index + 1}`, 145 + creation_timestamp: 1234567890, 146 + media_metadata: {}, 147 + cross_post_source: { source_app: "Instagram" }, 148 + backup_uri: `backup${index + 1}.jpg`, 149 + })); 150 + 151 + const processor = new InstagramImageProcessor(mockImages, "/test/archive"); 152 + const result = await processor.process(); 153 + 154 + // No longer limits images at this level 155 + expect(result).toHaveLength(6); 156 + result.forEach((media, index) => { 157 + expect(media.mimeType).toBe("image/jpeg"); 158 + expect(media.mediaText).toBe(`Image ${index + 1}`); 159 + }); 160 + }); 161 + });
+6 -17
src/media/processors/InstagramImageProcessor.ts
··· 1 + import { getImageMimeType, getImageSize } from "../../image"; 1 2 import { logger } from "../../logger/logger"; 3 + import { ImageMedia, Media } from "../InstagramExportedPost"; 2 4 import { ImageMediaProcessingStrategy } from "../interfaces/ImageMediaProcessingStrategy"; 3 - import { ImageMedia, Media } from "../InstagramExportedPost"; 4 5 import { MediaProcessResult, ImageMediaProcessResultImpl } from "../MediaProcessResult"; 5 - import { getImageMimeType, getImageSize } from "../../image"; 6 6 import { getMediaBuffer } from "../utils"; 7 7 8 - /** 9 - * @link https://docs.bsky.app/docs/advanced-guides/posts#:~:text=Each%20post%20contains%20up%20to,alt%20text%20and%20aspect%20ratio. 10 - * "Each post contains up to four images, and each image can have its own alt text and aspect ratio." 11 - */ 12 - const MAX_IMAGES_PER_POST = 4; 8 + 13 9 const POST_TEXT_LIMIT = 300; 14 10 const POST_TEXT_TRUNCATE_SUFFIX = "..."; 15 11 ··· 21 17 22 18 process(): Promise<MediaProcessResult[]> { 23 19 const processingResults: Promise<MediaProcessResult>[] = []; 24 - // Iterate over each image in the post, 25 - // adding the process to the promise array. 26 - // Limit to MAX_IMAGES_PER_POST 27 - let limitedImages = this.instagramImages; 28 - if (this.instagramImages.length > MAX_IMAGES_PER_POST) { 29 - logger.info(`Limiting images from ${this.instagramImages.length} to ${MAX_IMAGES_PER_POST}`); 30 - limitedImages = this.instagramImages.slice(0, MAX_IMAGES_PER_POST); 31 - } 32 20 33 - for (const media of limitedImages) { 21 + // Process each image in the array (no need to limit here as that's handled by InstagramMediaProcessor) 22 + for (const media of this.instagramImages) { 34 23 const processedMedia = this.processMedia(media, this.archiveFolder); 35 24 processingResults.push(processedMedia); 36 25 } ··· 73 62 let truncatedText = mediaText; 74 63 if (mediaText.length > POST_TEXT_LIMIT) { 75 64 logger.info(`Truncating image caption from ${mediaText.length} to ${POST_TEXT_LIMIT} characters`); 76 - truncatedText = mediaText.substring(0, POST_TEXT_LIMIT) + POST_TEXT_TRUNCATE_SUFFIX; 65 + truncatedText = mediaText.substring(0, POST_TEXT_LIMIT- POST_TEXT_TRUNCATE_SUFFIX.length) + POST_TEXT_TRUNCATE_SUFFIX; 77 66 } 78 67 79 68 return new ImageMediaProcessResultImpl(
+776
src/media/processors/InstagramMediaProcessor.test.ts
··· 1 + import fs from "fs"; 2 + 3 + import { InstagramMediaProcessor } from "./InstagramMediaProcessor"; 4 + import { logger } from "../../logger/logger"; 5 + import { InstagramExportedPost, VideoMedia, ImageMedia } from "../InstagramExportedPost"; 6 + 7 + // Mock the file system 8 + jest.mock("fs", () => ({ 9 + readFileSync: jest.fn(), 10 + })); 11 + 12 + // Mock sharp 13 + jest.mock("sharp", () => { 14 + return function(filePath: string) { 15 + // Mock different behavior based on file path for testing different scenarios 16 + if (filePath && filePath.includes('missing.jpg')) { 17 + throw new Error('Input file is missing'); 18 + } 19 + 20 + if (filePath && filePath.includes('invalid.jpg')) { 21 + return { 22 + metadata: jest.fn().mockResolvedValue({}) 23 + }; 24 + } 25 + 26 + if (filePath && filePath.includes('landscape.jpg')) { 27 + return { 28 + metadata: jest.fn().mockResolvedValue({ width: 1920, height: 1080 }) 29 + }; 30 + } 31 + 32 + if (filePath && filePath.includes('portrait.jpg')) { 33 + return { 34 + metadata: jest.fn().mockResolvedValue({ width: 1080, height: 1920 }) 35 + }; 36 + } 37 + 38 + // Default square image 39 + return { 40 + metadata: jest.fn().mockResolvedValue({ width: 1080, height: 1080 }) 41 + }; 42 + }; 43 + }); 44 + 45 + // Mock fluent-ffmpeg 46 + jest.mock("fluent-ffmpeg", () => { 47 + return { 48 + setFfprobePath: jest.fn(), 49 + ffprobe: (path: string, callback: (err: Error | null, data: any) => void) => { 50 + callback(null, { 51 + streams: [ 52 + { 53 + codec_type: "video", 54 + width: 1920, 55 + height: 1080, 56 + }, 57 + ], 58 + }); 59 + }, 60 + }; 61 + }); 62 + 63 + // Mock @ffprobe-installer/ffprobe 64 + jest.mock("@ffprobe-installer/ffprobe", () => ({ 65 + path: "/mock/ffprobe/path", 66 + })); 67 + 68 + // Mock the logger 69 + jest.mock("../../logger/logger", () => ({ 70 + logger: { 71 + error: jest.fn(), 72 + warn: jest.fn(), 73 + info: jest.fn(), 74 + debug: jest.fn(), 75 + }, 76 + })); 77 + 78 + 79 + describe("InstagramMediaProcessor", () => { 80 + const mockArchiveFolder = "/test/archive"; 81 + 82 + beforeEach(() => { 83 + jest.clearAllMocks(); 84 + (fs.readFileSync as jest.Mock).mockReturnValue(Buffer.from("test")); 85 + }); 86 + 87 + test("should process a post with multiple images", async () => { 88 + const mockPost: InstagramExportedPost = { 89 + creation_timestamp: 1234567890, 90 + title: "Test Post", 91 + media: [ 92 + { 93 + uri: "photo1.jpg", 94 + title: "Image 1", 95 + creation_timestamp: 1234567890, 96 + media_metadata: {}, 97 + cross_post_source: { source_app: "Instagram" }, 98 + backup_uri: "backup1.jpg", 99 + }, 100 + { 101 + uri: "photo2.jpg", 102 + title: "Image 2", 103 + creation_timestamp: 1234567890, 104 + media_metadata: {}, 105 + cross_post_source: { source_app: "Instagram" }, 106 + backup_uri: "backup2.jpg", 107 + }, 108 + ] as ImageMedia[], 109 + }; 110 + 111 + const processor = new InstagramMediaProcessor([mockPost], mockArchiveFolder); 112 + const result = await processor.process(); 113 + 114 + expect(result).toHaveLength(1); 115 + expect(result[0].postText).toBe("Test Post"); 116 + expect(Array.isArray(result[0].embeddedMedia)).toBe(true); 117 + expect(result[0].embeddedMedia).toHaveLength(2); 118 + }); 119 + 120 + test("should process a post with a single video", async () => { 121 + const mockPost: InstagramExportedPost = { 122 + creation_timestamp: 1234567890, 123 + title: "Test Video Post", 124 + media: [{ 125 + uri: "video.mp4", 126 + title: "Test Video", 127 + creation_timestamp: 1234567890, 128 + media_metadata: {}, 129 + cross_post_source: { source_app: "Instagram" }, 130 + backup_uri: "backup_video.mp4", 131 + dubbing_info: [], 132 + media_variants: [], 133 + } as VideoMedia], 134 + }; 135 + 136 + const processor = new InstagramMediaProcessor([mockPost], mockArchiveFolder); 137 + const result = await processor.process(); 138 + 139 + expect(result).toHaveLength(1); 140 + expect(result[0].postText).toBe("Test Video Post"); 141 + expect(Array.isArray(result[0].embeddedMedia)).toBe(true); 142 + }); 143 + 144 + test("should process a post with mixed media (images and videos)", async () => { 145 + const mockPost: InstagramExportedPost = { 146 + creation_timestamp: 1720384531, 147 + title: "What an incredible weekend day trip to celebrate Momma Olga's birthday.", 148 + media: [ 149 + { 150 + uri: "photo1.jpg", 151 + title: "", 152 + creation_timestamp: 1720384529, 153 + media_metadata: { 154 + camera_metadata: { 155 + has_camera_metadata: false 156 + } 157 + }, 158 + cross_post_source: { source_app: "FB" }, 159 + backup_uri: "backup1.jpg", 160 + } as ImageMedia, 161 + { 162 + uri: "photo2.jpg", 163 + title: "", 164 + creation_timestamp: 1720384529, 165 + media_metadata: { 166 + camera_metadata: { 167 + has_camera_metadata: false 168 + } 169 + }, 170 + cross_post_source: { source_app: "FB" }, 171 + backup_uri: "backup2.jpg", 172 + } as ImageMedia, 173 + { 174 + uri: "video1.mp4", 175 + title: "", 176 + creation_timestamp: 1720384529, 177 + media_metadata: { 178 + camera_metadata: { 179 + has_camera_metadata: false 180 + } 181 + }, 182 + cross_post_source: { source_app: "FB" }, 183 + backup_uri: "backup_video1.mp4", 184 + dubbing_info: [], 185 + media_variants: [], 186 + } as VideoMedia, 187 + { 188 + uri: "photo3.jpg", 189 + title: "", 190 + creation_timestamp: 1720384529, 191 + media_metadata: { 192 + camera_metadata: { 193 + has_camera_metadata: false 194 + } 195 + }, 196 + cross_post_source: { source_app: "FB" }, 197 + backup_uri: "backup3.jpg", 198 + } as ImageMedia, 199 + { 200 + uri: "photo4.jpg", 201 + title: "", 202 + creation_timestamp: 1720384529, 203 + media_metadata: { 204 + camera_metadata: { 205 + has_camera_metadata: false 206 + } 207 + }, 208 + cross_post_source: { source_app: "FB" }, 209 + backup_uri: "backup4.jpg", 210 + } as ImageMedia, 211 + { 212 + uri: "video2.mp4", 213 + title: "", 214 + creation_timestamp: 1720384529, 215 + media_metadata: { 216 + camera_metadata: { 217 + has_camera_metadata: false 218 + } 219 + }, 220 + cross_post_source: { source_app: "FB" }, 221 + backup_uri: "backup_video2.mp4", 222 + dubbing_info: [], 223 + media_variants: [], 224 + } as VideoMedia, 225 + { 226 + uri: "photo5.jpg", 227 + title: "", 228 + creation_timestamp: 1720384529, 229 + media_metadata: { 230 + camera_metadata: { 231 + has_camera_metadata: false 232 + } 233 + }, 234 + cross_post_source: { source_app: "FB" }, 235 + backup_uri: "backup5.jpg", 236 + } as ImageMedia, 237 + ], 238 + }; 239 + 240 + const processor = new InstagramMediaProcessor([mockPost], mockArchiveFolder); 241 + const result = await processor.process(); 242 + 243 + // Should create 4 posts total 244 + expect(result).toHaveLength(4); 245 + 246 + // First post should have 4 images 247 + expect(result[0].postText).toBe("What an incredible weekend day trip to celebrate Momma Olga's birthday. (Part 1/4)"); 248 + expect(result[0].embeddedMedia).toHaveLength(4); 249 + result[0].embeddedMedia.forEach(media => { 250 + expect(media.mimeType).toBe("image/jpeg"); 251 + }); 252 + 253 + // Second post should have 1 image 254 + expect(result[1].postText).toBe("What an incredible weekend day trip to celebrate Momma Olga's birthday. (Part 2/4)"); 255 + expect(result[1].embeddedMedia).toHaveLength(1); 256 + expect(result[1].embeddedMedia[0].mimeType).toBe("image/jpeg"); 257 + 258 + // Third post should have first video 259 + expect(result[2].postText).toBe("What an incredible weekend day trip to celebrate Momma Olga's birthday. (Part 3/4)"); 260 + expect(result[2].embeddedMedia).toHaveLength(1); 261 + expect(result[2].embeddedMedia[0].mimeType).toBe("video/mp4"); 262 + 263 + // Fourth post should have second video 264 + expect(result[3].postText).toBe("What an incredible weekend day trip to celebrate Momma Olga's birthday. (Part 4/4)"); 265 + expect(result[3].embeddedMedia).toHaveLength(1); 266 + expect(result[3].embeddedMedia[0].mimeType).toBe("video/mp4"); 267 + }); 268 + 269 + test("should truncate post title when it exceeds limit", async () => { 270 + const longTitle = "A".repeat(400); // Create a title longer than POST_TEXT_LIMIT (300) 271 + const mockPost: InstagramExportedPost = { 272 + creation_timestamp: 1234567890, 273 + title: longTitle, 274 + media: [ 275 + { 276 + uri: "photo1.jpg", 277 + title: "Image 1", 278 + creation_timestamp: 1234567890, 279 + media_metadata: {}, 280 + cross_post_source: { source_app: "Instagram" }, 281 + backup_uri: "backup1.jpg", 282 + }, 283 + ] as ImageMedia[], 284 + }; 285 + 286 + const processor = new InstagramMediaProcessor([mockPost], mockArchiveFolder); 287 + const result = await processor.process(); 288 + 289 + expect(result).toHaveLength(1); 290 + expect(result[0].postText.length).toBe(300); // 297 chars + "..." 291 + expect(result[0].postText.endsWith("...")).toBe(true); 292 + }); 293 + 294 + test("should use media title if no post title is available", async () => { 295 + // Create a post without a title property using type casting 296 + const mockPost = { 297 + creation_timestamp: 1458732736, 298 + media: [ 299 + { 300 + uri: "AQM8KYlOYHTF5GlP43eMroHUpmnFHJh5CnCJUdRUeqWxG4tNX7D43eM77F152vfi4znTzgkFTTzzM4nHa_v8ugmP4WPRJtjKPZX5pko_17845940218109367.mp4", 301 + creation_timestamp: 1458732736, 302 + media_metadata: { 303 + video_metadata: { 304 + exif_data: [ 305 + { 306 + latitude: 53.141186112, 307 + longitude: 11.038734576 308 + } 309 + ] 310 + } 311 + }, 312 + title: "No filter needed. 😍🌱 #waterfall #nature", 313 + cross_post_source: { 314 + source_app: "FB" 315 + }, 316 + backup_uri: "backup_video.mp4", 317 + dubbing_info: [], 318 + media_variants: [] 319 + } as VideoMedia], 320 + } as InstagramExportedPost; // Cast to InstagramExportedPost without providing title 321 + 322 + const processor = new InstagramMediaProcessor([mockPost], mockArchiveFolder); 323 + const result = await processor.process(); 324 + 325 + expect(result).toHaveLength(1); 326 + expect(result[0].postText).toBe("No filter needed. 😍🌱 #waterfall #nature"); 327 + expect(Array.isArray(result[0].embeddedMedia)).toBe(true); 328 + }); 329 + 330 + test("should limit media to maximum allowed images", async () => { 331 + const mockPost: InstagramExportedPost = { 332 + creation_timestamp: 1234567890, 333 + title: "Test Post with Many Images", 334 + media: Array(6).fill(null).map((_, index) => ({ 335 + uri: `photo${index + 1}.jpg`, 336 + title: `Image ${index + 1}`, 337 + creation_timestamp: 1234567890, 338 + media_metadata: {}, 339 + cross_post_source: { source_app: "Instagram" }, 340 + backup_uri: `backup${index + 1}.jpg`, 341 + })) as ImageMedia[], // Create 6 images, more than MAX_IMAGES_PER_POST (4) 342 + }; 343 + 344 + const processor = new InstagramMediaProcessor([mockPost], mockArchiveFolder); 345 + const result = await processor.process(); 346 + 347 + // Should create 2 posts total 348 + expect(result).toHaveLength(2); 349 + 350 + // First post should have 4 images 351 + expect(result[0].postText).toBe("Test Post with Many Images (Part 1/2)"); 352 + expect(result[0].embeddedMedia).toHaveLength(4); 353 + result[0].embeddedMedia.forEach(media => { 354 + expect(media.mimeType).toBe("image/jpeg"); 355 + }); 356 + 357 + // Second post should have 2 images 358 + expect(result[1].postText).toBe("Test Post with Many Images (Part 2/2)"); 359 + expect(result[1].embeddedMedia).toHaveLength(2); 360 + result[1].embeddedMedia.forEach(media => { 361 + expect(media.mimeType).toBe("image/jpeg"); 362 + }); 363 + }); 364 + 365 + test("should split mixed media post into multiple posts based on media limits", async () => { 366 + const mockPost: InstagramExportedPost = { 367 + creation_timestamp: 1720384531, 368 + title: "What an incredible weekend day trip to celebrate Momma Olga's birthday.", 369 + media: [ 370 + { 371 + uri: "photo1.jpg", 372 + title: "", 373 + creation_timestamp: 1720384529, 374 + media_metadata: { 375 + camera_metadata: { 376 + has_camera_metadata: false 377 + } 378 + }, 379 + cross_post_source: { source_app: "FB" }, 380 + backup_uri: "backup1.jpg", 381 + } as ImageMedia, 382 + { 383 + uri: "photo2.jpg", 384 + title: "", 385 + creation_timestamp: 1720384529, 386 + media_metadata: { 387 + camera_metadata: { 388 + has_camera_metadata: false 389 + } 390 + }, 391 + cross_post_source: { source_app: "FB" }, 392 + backup_uri: "backup2.jpg", 393 + } as ImageMedia, 394 + { 395 + uri: "photo3.jpg", 396 + title: "", 397 + creation_timestamp: 1720384529, 398 + media_metadata: { 399 + camera_metadata: { 400 + has_camera_metadata: false 401 + } 402 + }, 403 + cross_post_source: { source_app: "FB" }, 404 + backup_uri: "backup3.jpg", 405 + } as ImageMedia, 406 + { 407 + uri: "photo4.jpg", 408 + title: "", 409 + creation_timestamp: 1720384529, 410 + media_metadata: { 411 + camera_metadata: { 412 + has_camera_metadata: false 413 + } 414 + }, 415 + cross_post_source: { source_app: "FB" }, 416 + backup_uri: "backup4.jpg", 417 + } as ImageMedia, 418 + { 419 + uri: "photo5.jpg", 420 + title: "", 421 + creation_timestamp: 1720384529, 422 + media_metadata: { 423 + camera_metadata: { 424 + has_camera_metadata: false 425 + } 426 + }, 427 + cross_post_source: { source_app: "FB" }, 428 + backup_uri: "backup5.jpg", 429 + } as ImageMedia, 430 + { 431 + uri: "video1.mp4", 432 + title: "", 433 + creation_timestamp: 1720384529, 434 + media_metadata: { 435 + camera_metadata: { 436 + has_camera_metadata: false 437 + } 438 + }, 439 + cross_post_source: { source_app: "FB" }, 440 + backup_uri: "backup_video1.mp4", 441 + dubbing_info: [], 442 + media_variants: [], 443 + } as VideoMedia, 444 + { 445 + uri: "video2.mp4", 446 + title: "", 447 + creation_timestamp: 1720384529, 448 + media_metadata: { 449 + camera_metadata: { 450 + has_camera_metadata: false 451 + } 452 + }, 453 + cross_post_source: { source_app: "FB" }, 454 + backup_uri: "backup_video2.mp4", 455 + dubbing_info: [], 456 + media_variants: [], 457 + } as VideoMedia, 458 + ], 459 + }; 460 + 461 + const processor = new InstagramMediaProcessor([mockPost], mockArchiveFolder); 462 + const result = await processor.process(); 463 + 464 + // Should create 4 posts total 465 + expect(result).toHaveLength(4); 466 + 467 + // First post should have 4 images 468 + expect(result[0].postText).toBe("What an incredible weekend day trip to celebrate Momma Olga's birthday. (Part 1/4)"); 469 + expect(result[0].embeddedMedia).toHaveLength(4); 470 + result[0].embeddedMedia.forEach(media => { 471 + expect(media.mimeType).toBe("image/jpeg"); 472 + }); 473 + 474 + // Second post should have 1 image 475 + expect(result[1].postText).toBe("What an incredible weekend day trip to celebrate Momma Olga's birthday. (Part 2/4)"); 476 + expect(result[1].embeddedMedia).toHaveLength(1); 477 + expect(result[1].embeddedMedia[0].mimeType).toBe("image/jpeg"); 478 + 479 + // Third post should have first video 480 + expect(result[2].postText).toBe("What an incredible weekend day trip to celebrate Momma Olga's birthday. (Part 3/4)"); 481 + expect(result[2].embeddedMedia).toHaveLength(1); 482 + expect(result[2].embeddedMedia[0].mimeType).toBe("video/mp4"); 483 + 484 + // Fourth post should have second video 485 + expect(result[3].postText).toBe("What an incredible weekend day trip to celebrate Momma Olga's birthday. (Part 4/4)"); 486 + expect(result[3].embeddedMedia).toHaveLength(1); 487 + expect(result[3].embeddedMedia[0].mimeType).toBe("video/mp4"); 488 + }); 489 + 490 + test("should ensure split posts have different timestamps to prevent Bluesky duplicates", async () => { 491 + const mockPost: InstagramExportedPost = { 492 + creation_timestamp: 1720384531, 493 + title: "Test post with multiple media", 494 + media: [ 495 + { 496 + uri: "photo1.jpg", 497 + title: "", 498 + creation_timestamp: 1720384529, 499 + media_metadata: { 500 + camera_metadata: { 501 + has_camera_metadata: false 502 + } 503 + }, 504 + cross_post_source: { source_app: "FB" }, 505 + backup_uri: "backup1.jpg", 506 + } as ImageMedia, 507 + { 508 + uri: "photo2.jpg", 509 + title: "", 510 + creation_timestamp: 1720384529, 511 + media_metadata: { 512 + camera_metadata: { 513 + has_camera_metadata: false 514 + } 515 + }, 516 + cross_post_source: { source_app: "FB" }, 517 + backup_uri: "backup2.jpg", 518 + } as ImageMedia, 519 + { 520 + uri: "video1.mp4", 521 + title: "", 522 + creation_timestamp: 1720384529, 523 + media_metadata: { 524 + camera_metadata: { 525 + has_camera_metadata: false 526 + } 527 + }, 528 + cross_post_source: { source_app: "FB" }, 529 + backup_uri: "backup_video1.mp4", 530 + dubbing_info: [], 531 + media_variants: [], 532 + } as VideoMedia, 533 + ], 534 + }; 535 + 536 + const processor = new InstagramMediaProcessor([mockPost], mockArchiveFolder); 537 + const result = await processor.process(); 538 + 539 + // Should create 2 posts (1 for images, 1 for video) 540 + expect(result).toHaveLength(2); 541 + 542 + // Verify timestamps are different and increment by 1 second 543 + const baseTimestamp = result[0].postDate!.getTime(); 544 + for (let i = 1; i < result.length; i++) { 545 + const currentTimestamp = result[i].postDate!.getTime(); 546 + const previousTimestamp = result[i-1].postDate!.getTime(); 547 + 548 + // Each post should be 1 second after the previous 549 + expect(currentTimestamp - previousTimestamp).toBe(1000); 550 + 551 + // Should be incrementally later than base timestamp 552 + expect(currentTimestamp - baseTimestamp).toBe(i * 1000); 553 + } 554 + 555 + // Verify post numbering 556 + expect(result[0].postText).toContain("(Part 1/2)"); 557 + expect(result[1].postText).toContain("(Part 2/2)"); 558 + }); 559 + 560 + test("should log debug messages when splitting media", async () => { 561 + const mockPost: InstagramExportedPost = { 562 + creation_timestamp: 1234567890, 563 + title: "Test Mixed Media Post", 564 + media: [ 565 + { 566 + uri: "photo1.jpg", 567 + title: "Image 1", 568 + creation_timestamp: 1234567890, 569 + media_metadata: {}, 570 + cross_post_source: { source_app: "Instagram" }, 571 + backup_uri: "backup1.jpg", 572 + } as ImageMedia, 573 + { 574 + uri: "video1.mp4", 575 + title: "Video 1", 576 + creation_timestamp: 1234567890, 577 + media_metadata: {}, 578 + cross_post_source: { source_app: "Instagram" }, 579 + backup_uri: "backup_video1.mp4", 580 + dubbing_info: [], 581 + media_variants: [], 582 + } as VideoMedia, 583 + ], 584 + }; 585 + 586 + const processor = new InstagramMediaProcessor([mockPost], mockArchiveFolder); 587 + await processor.process(); 588 + 589 + // Verify overall process start logging 590 + expect(logger.debug).toHaveBeenCalledWith( 591 + "Starting to process 1 Instagram posts" 592 + ); 593 + 594 + // Verify individual post processing start 595 + expect(logger.debug).toHaveBeenCalledWith( 596 + { 597 + title: "Test Mixed Media Post", 598 + timestamp: 1234567890, 599 + mediaCount: 2, 600 + firstMediaUri: "photo1.jpg" 601 + }, 602 + "Processing Instagram post" 603 + ); 604 + 605 + // Verify initial split logging 606 + expect(logger.debug).toHaveBeenCalledWith( 607 + "Starting to split 2 media items by type" 608 + ); 609 + 610 + // Verify split complete logging 611 + expect(logger.debug).toHaveBeenCalledWith( 612 + { 613 + totalMedia: 2, 614 + images: 1, 615 + videos: 1 616 + }, 617 + "Media split complete" 618 + ); 619 + 620 + // Verify post creation start logging 621 + expect(logger.debug).toHaveBeenCalledWith( 622 + { 623 + title: "Test Mixed Media Post", 624 + imageCount: 1, 625 + videoCount: 1, 626 + firstMediaUri: "photo1.jpg" 627 + }, 628 + "Starting to create posts from media" 629 + ); 630 + 631 + // Verify post distribution logging 632 + expect(logger.debug).toHaveBeenCalledWith( 633 + { 634 + title: "Test Mixed Media Post", 635 + imageChunks: 1, 636 + totalPosts: 2, 637 + firstMediaUri: "photo1.jpg" 638 + }, 639 + "Calculated post distribution" 640 + ); 641 + 642 + // Verify image post creation logging 643 + expect(logger.debug).toHaveBeenCalledWith( 644 + { 645 + title: "Test Mixed Media Post", 646 + postNumber: 1, 647 + totalPosts: 2, 648 + type: "image", 649 + imageCount: 1, 650 + postDate: expect.any(String), 651 + mediaUris: ["photo1.jpg"] 652 + }, 653 + "Created image post" 654 + ); 655 + 656 + // Verify video post creation logging 657 + expect(logger.debug).toHaveBeenCalledWith( 658 + { 659 + title: "Test Mixed Media Post", 660 + postNumber: 2, 661 + totalPosts: 2, 662 + type: "video", 663 + postDate: expect.any(String), 664 + mediaUri: "video1.mp4" 665 + }, 666 + "Created video post" 667 + ); 668 + 669 + // Verify individual post completion logging 670 + expect(logger.debug).toHaveBeenCalledWith( 671 + { 672 + title: "Test Mixed Media Post", 673 + resultingPosts: 2, 674 + imageCount: 1, 675 + videoCount: 1, 676 + firstMediaUri: "photo1.jpg" 677 + }, 678 + "Finished processing Instagram post" 679 + ); 680 + 681 + // Verify final summary logging 682 + expect(logger.debug).toHaveBeenCalledWith( 683 + { 684 + totalInputPosts: 1, 685 + totalOutputPosts: 2 686 + }, 687 + "Completed processing all Instagram posts" 688 + ); 689 + }); 690 + 691 + test("should log debug messages for single image post", async () => { 692 + const mockPost: InstagramExportedPost = { 693 + creation_timestamp: 1234567890, 694 + title: "Test Single Image Post", 695 + media: [ 696 + { 697 + uri: "photo1.jpg", 698 + title: "Image 1", 699 + creation_timestamp: 1234567890, 700 + media_metadata: {}, 701 + cross_post_source: { source_app: "Instagram" }, 702 + backup_uri: "backup1.jpg", 703 + } as ImageMedia, 704 + ], 705 + }; 706 + 707 + const processor = new InstagramMediaProcessor([mockPost], mockArchiveFolder); 708 + await processor.process(); 709 + 710 + // Verify overall process start logging 711 + expect(logger.debug).toHaveBeenCalledWith( 712 + "Starting to process 1 Instagram posts" 713 + ); 714 + 715 + // Verify individual post processing start 716 + expect(logger.debug).toHaveBeenCalledWith( 717 + { 718 + title: "Test Single Image Post", 719 + timestamp: 1234567890, 720 + mediaCount: 1, 721 + firstMediaUri: "photo1.jpg" 722 + }, 723 + "Processing Instagram post" 724 + ); 725 + 726 + // Verify initial split logging 727 + expect(logger.debug).toHaveBeenCalledWith( 728 + "Starting to split 1 media items by type" 729 + ); 730 + 731 + // Verify split complete logging 732 + expect(logger.debug).toHaveBeenCalledWith( 733 + { 734 + totalMedia: 1, 735 + images: 1, 736 + videos: 0 737 + }, 738 + "Media split complete" 739 + ); 740 + 741 + // Verify single post creation logging 742 + expect(logger.debug).toHaveBeenCalledWith( 743 + { 744 + title: "Test Single Image Post", 745 + postNumber: 1, 746 + totalPosts: 1, 747 + type: "image", 748 + imageCount: 1, 749 + postDate: expect.any(String), 750 + mediaUris: ["photo1.jpg"] 751 + }, 752 + "Created image post" 753 + ); 754 + 755 + // Verify individual post completion logging 756 + expect(logger.debug).toHaveBeenCalledWith( 757 + { 758 + title: "Test Single Image Post", 759 + resultingPosts: 1, 760 + imageCount: 1, 761 + videoCount: 0, 762 + firstMediaUri: "photo1.jpg" 763 + }, 764 + "Finished processing Instagram post" 765 + ); 766 + 767 + // Verify final summary logging 768 + expect(logger.debug).toHaveBeenCalledWith( 769 + { 770 + totalInputPosts: 1, 771 + totalOutputPosts: 1 772 + }, 773 + "Completed processing all Instagram posts" 774 + ); 775 + }); 776 + });
+198 -38
src/media/processors/InstagramMediaProcessor.ts
··· 1 1 import { logger } from "../../logger/logger"; 2 - import { ProcessedPost, ProcessedPostImpl } from "../ProcessedPost"; 3 - import { InstagramExportedPost, Media } from "../InstagramExportedPost"; 2 + import { InstagramExportedPost, Media, ImageMedia, VideoMedia } from "../InstagramExportedPost"; 3 + import { DefaultMediaProcessorFactory } from "./DefaultMediaProcessorFactory"; 4 4 import { InstagramPostProcessingStrategy } from "../interfaces/InstagramPostProcessingStrategy"; 5 5 import { MediaProcessorFactory } from "../interfaces/MediaProcessorFactory"; 6 - import { DefaultMediaProcessorFactory } from "./DefaultMediaProcessorFactory"; 6 + import { ProcessedPost, ProcessedPostImpl } from "../ProcessedPost"; 7 7 8 + /** 9 + * @link https://docs.bsky.app/docs/advanced-guides/posts#:~:text=Each%20post%20contains%20up%20to,alt%20text%20and%20aspect%20ratio. 10 + * "Each post contains up to four images, and each image can have its own alt text and aspect ratio." 11 + */ 8 12 const MAX_IMAGES_PER_POST = 4; 9 13 const POST_TEXT_LIMIT = 300; 10 14 const POST_TEXT_TRUNCATE_SUFFIX = "..."; ··· 20 24 this.mediaProcessorFactory = mediaProcessorFactory || new DefaultMediaProcessorFactory(); 21 25 } 22 26 27 + private isVideoMedia(media: Media): media is VideoMedia { 28 + return 'dubbing_info' in media; 29 + } 30 + 31 + private splitMediaByType(media: Media[]): { images: ImageMedia[], videos: VideoMedia[] } { 32 + logger.debug(`Starting to split ${media.length} media items by type`); 33 + 34 + const result = media.reduce((acc, curr) => { 35 + if (this.isVideoMedia(curr)) { 36 + acc.videos.push(curr); 37 + } else { 38 + acc.images.push(curr as ImageMedia); 39 + } 40 + return acc; 41 + }, { images: [] as ImageMedia[], videos: [] as VideoMedia[] }); 42 + 43 + logger.debug({ 44 + totalMedia: media.length, 45 + images: result.images.length, 46 + videos: result.videos.length 47 + }, 'Media split complete'); 48 + 49 + return result; 50 + } 51 + 52 + private async createPostsFromMedia( 53 + originalPost: InstagramExportedPost, 54 + images: ImageMedia[], 55 + videos: VideoMedia[] 56 + ): Promise<ProcessedPost[]> { 57 + const postTitle = originalPost.title || originalPost.media[0]?.title || 'Untitled post'; 58 + logger.debug({ 59 + title: postTitle, 60 + imageCount: images.length, 61 + videoCount: videos.length, 62 + firstMediaUri: originalPost.media[0]?.uri 63 + }, 'Starting to create posts from media'); 64 + 65 + const posts: ProcessedPost[] = []; 66 + const timestamp = originalPost.creation_timestamp || originalPost.media[0].creation_timestamp; 67 + const basePostDate = new Date(timestamp * 1000); 68 + 69 + // Split images into chunks of MAX_IMAGES_PER_POST 70 + const imageChunks: ImageMedia[][] = []; 71 + for (let i = 0; i < images.length; i += MAX_IMAGES_PER_POST) { 72 + imageChunks.push(images.slice(i, i + MAX_IMAGES_PER_POST)); 73 + } 74 + 75 + // Calculate total number of posts 76 + const totalPosts = imageChunks.length + videos.length; 77 + logger.debug({ 78 + title: postTitle, 79 + imageChunks: imageChunks.length, 80 + totalPosts, 81 + firstMediaUri: originalPost.media[0]?.uri 82 + }, 'Calculated post distribution'); 83 + 84 + let currentPostNumber = 1; 85 + 86 + // Create posts for image chunks 87 + for (const imageChunk of imageChunks) { 88 + let title = originalPost.title ?? originalPost.media[0].title ?? ""; 89 + 90 + // Calculate the suffix that will be added 91 + const suffix = totalPosts > 1 ? ` (Part ${currentPostNumber}/${totalPosts})` : ""; 92 + 93 + // If we need to truncate, we need to account for the length of the suffix 94 + if (title.length + suffix.length > POST_TEXT_LIMIT) { 95 + const maxTitleLength = POST_TEXT_LIMIT - suffix.length - POST_TEXT_TRUNCATE_SUFFIX.length; 96 + title = title.substring(0, maxTitleLength) + POST_TEXT_TRUNCATE_SUFFIX; 97 + } 98 + 99 + // Add the suffix after truncation 100 + if (totalPosts > 1) { 101 + title += suffix; 102 + } 103 + 104 + // Add a small time offset for each post (1 second) 105 + const postDate = new Date(basePostDate.getTime() + (currentPostNumber - 1) * 1000); 106 + const post = new ProcessedPostImpl(postDate, title); 107 + const mediaProcessor = this.mediaProcessorFactory.createProcessor( 108 + imageChunk as ImageMedia[], 109 + this.archiveFolder 110 + ); 111 + 112 + // Process media for this post 113 + post.embeddedMedia = await mediaProcessor.process(); 114 + posts.push(post); 115 + 116 + logger.debug({ 117 + title: postTitle, 118 + postNumber: currentPostNumber, 119 + totalPosts, 120 + type: 'image', 121 + imageCount: imageChunk.length, 122 + postDate: postDate.toISOString(), 123 + mediaUris: imageChunk.map(img => img.uri) 124 + }, 'Created image post'); 125 + 126 + currentPostNumber++; 127 + } 128 + 129 + // Create individual posts for each video 130 + for (const video of videos) { 131 + let title = originalPost.title ?? video.title ?? ""; 132 + 133 + // Calculate the suffix that will be added 134 + const suffix = totalPosts > 1 ? ` (Part ${currentPostNumber}/${totalPosts})` : ""; 135 + 136 + // If we need to truncate, we need to account for the length of the suffix 137 + if (title.length + suffix.length > POST_TEXT_LIMIT) { 138 + const maxTitleLength = POST_TEXT_LIMIT - suffix.length - POST_TEXT_TRUNCATE_SUFFIX.length; 139 + title = title.substring(0, maxTitleLength) + POST_TEXT_TRUNCATE_SUFFIX; 140 + } 141 + 142 + // Add the suffix after truncation 143 + if (totalPosts > 1) { 144 + title += suffix; 145 + } 146 + 147 + // Add a small time offset for each post (1 second) 148 + const postDate = new Date(basePostDate.getTime() + (currentPostNumber - 1) * 1000); 149 + const post = new ProcessedPostImpl(postDate, title); 150 + const mediaProcessor = this.mediaProcessorFactory.createProcessor( 151 + [video] as VideoMedia[], 152 + this.archiveFolder 153 + ); 154 + 155 + // Process media for this post 156 + post.embeddedMedia = await mediaProcessor.process(); 157 + posts.push(post); 158 + 159 + logger.debug({ 160 + title: postTitle, 161 + postNumber: currentPostNumber, 162 + totalPosts, 163 + type: 'video', 164 + postDate: postDate.toISOString(), 165 + mediaUri: video.uri 166 + }, 'Created video post'); 167 + 168 + currentPostNumber++; 169 + } 170 + 171 + logger.debug({ 172 + title: postTitle, 173 + totalPostsCreated: posts.length, 174 + firstMediaUri: originalPost.media[0]?.uri 175 + }, 'Finished creating all posts for media'); 176 + 177 + return posts; 178 + } 179 + 23 180 /** 24 181 * Processes Instagram posts and their associated media into a format 25 182 * that can be easily mapped to Bluesky's requirements. 26 183 * 27 - * This method iterates over each Instagram post, processes the media 28 - * (either images or videos), and returns a Promise that resolves to 29 - * an array of ProcessedPost objects once all media processing is complete. 184 + * This method splits posts with mixed media into separate posts: 185 + * - Images are grouped into posts of up to 4 images 186 + * - Each video gets its own post 187 + * - Posts are numbered when split (e.g. "Title (1/4)") 30 188 * 31 189 * @returns {Promise<ProcessedPost[]>} A promise that resolves to an array of ProcessedPost objects. 32 190 */ 33 - public process(): Promise<ProcessedPost[]> { 34 - const processingPosts: Promise<ProcessedPost>[] = []; 191 + public async process(): Promise<ProcessedPost[]> { 192 + const allProcessedPosts: ProcessedPost[] = []; 193 + 194 + logger.debug(`Starting to process ${this.instagramPosts.length} Instagram posts`); 35 195 36 196 for (const post of this.instagramPosts) { 37 - const timestamp = post.creation_timestamp || post.media[0].creation_timestamp; 38 - const postDate = new Date(timestamp * 1000); 197 + const postTitle = post.title || post.media[0]?.title || 'Untitled post'; 198 + // Log the start of processing for this specific post 199 + logger.debug({ 200 + title: postTitle, 201 + timestamp: post.creation_timestamp, 202 + mediaCount: Array.isArray(post.media) ? post.media.length : 1, 203 + firstMediaUri: post.media[0]?.uri 204 + }, 'Processing Instagram post'); 205 + 206 + // Ensure media is always an array 207 + const mediaArray = Array.isArray(post.media) ? post.media : [post.media]; 39 208 40 - // Truncate post title if it exceeds the limit 41 - let title = post.title ?? post.media[0].title; 42 - if (title && title.length > POST_TEXT_LIMIT) { 43 - logger.info(`Truncating post title from ${title.length} to ${POST_TEXT_LIMIT} characters`); 44 - title = title.substring(0, POST_TEXT_LIMIT) + POST_TEXT_TRUNCATE_SUFFIX; 45 - } 46 - 47 - const processingPost = new ProcessedPostImpl(postDate, title); 209 + // Split media by type 210 + const { images, videos } = this.splitMediaByType(mediaArray); 48 211 49 - // Limit media to MAX_IMAGES_PER_POST 50 - let limitedMedia = post.media; 51 - if (Array.isArray(post.media) && post.media.length > MAX_IMAGES_PER_POST) { 52 - logger.info(`Limiting post media from ${post.media.length} to ${MAX_IMAGES_PER_POST} items`); 53 - limitedMedia = post.media.slice(0, MAX_IMAGES_PER_POST); 54 - } 55 - 56 - // Get appropriate strategy from factory 57 - const mediaProcessor = this.mediaProcessorFactory.createProcessor( 58 - limitedMedia, 59 - this.archiveFolder 60 - ); 61 - 62 - // Process using the strategy 63 - const processingMedia = mediaProcessor.process().then(processedMedia => { 64 - processingPost.embeddedMedia = processedMedia; 65 - return processingPost; 66 - }); 212 + // Create posts based on the split media 213 + const posts = await this.createPostsFromMedia(post, images, videos); 214 + allProcessedPosts.push(...posts); 67 215 68 - processingPosts.push(processingMedia); 216 + // Log completion of this post's processing 217 + logger.debug({ 218 + title: postTitle, 219 + resultingPosts: posts.length, 220 + imageCount: images.length, 221 + videoCount: videos.length, 222 + firstMediaUri: post.media[0]?.uri 223 + }, 'Finished processing Instagram post'); 69 224 } 70 225 71 - return Promise.all(processingPosts); 226 + logger.debug({ 227 + totalInputPosts: this.instagramPosts.length, 228 + totalOutputPosts: allProcessedPosts.length 229 + }, 'Completed processing all Instagram posts'); 230 + 231 + return allProcessedPosts; 72 232 } 73 233 }
+145
src/media/processors/InstagramVideoProcessor.test.ts
··· 1 + import fs from "fs"; 2 + 3 + import { InstagramVideoProcessor } from ".."; 4 + import { VideoMedia } from "../InstagramExportedPost"; 5 + 6 + // Mock the file system 7 + jest.mock("fs", () => ({ 8 + readFileSync: jest.fn(), 9 + })); 10 + 11 + // Mock sharp 12 + jest.mock("sharp", () => { 13 + return function (filePath: string) { 14 + // Mock different behavior based on file path for testing different scenarios 15 + if (filePath && filePath.includes("missing.jpg")) { 16 + throw new Error("Input file is missing"); 17 + } 18 + 19 + if (filePath && filePath.includes("invalid.jpg")) { 20 + return { 21 + metadata: jest.fn().mockResolvedValue({}), 22 + }; 23 + } 24 + 25 + if (filePath && filePath.includes("landscape.jpg")) { 26 + return { 27 + metadata: jest.fn().mockResolvedValue({ width: 1920, height: 1080 }), 28 + }; 29 + } 30 + 31 + if (filePath && filePath.includes("portrait.jpg")) { 32 + return { 33 + metadata: jest.fn().mockResolvedValue({ width: 1080, height: 1920 }), 34 + }; 35 + } 36 + 37 + // Default square image 38 + return { 39 + metadata: jest.fn().mockResolvedValue({ width: 1080, height: 1080 }), 40 + }; 41 + }; 42 + }); 43 + 44 + // Mock fluent-ffmpeg 45 + jest.mock("fluent-ffmpeg", () => { 46 + return { 47 + setFfprobePath: jest.fn(), 48 + ffprobe: ( 49 + path: string, 50 + callback: (err: Error | null, data: any) => void 51 + ) => { 52 + callback(null, { 53 + streams: [ 54 + { 55 + codec_type: "video", 56 + width: 1920, 57 + height: 1080, 58 + }, 59 + ], 60 + }); 61 + }, 62 + }; 63 + }); 64 + 65 + // Mock @ffprobe-installer/ffprobe 66 + jest.mock("@ffprobe-installer/ffprobe", () => ({ 67 + path: "/mock/ffprobe/path", 68 + })); 69 + 70 + // Mock the logger 71 + jest.mock("../../logger/logger", () => ({ 72 + logger: { 73 + error: jest.fn(), 74 + warn: jest.fn(), 75 + info: jest.fn(), 76 + debug: jest.fn(), 77 + }, 78 + })); 79 + 80 + describe("InstagramVideoProcessor", () => { 81 + beforeEach(() => { 82 + jest.clearAllMocks(); 83 + (fs.readFileSync as jest.Mock).mockReturnValue(Buffer.from("test")); 84 + }); 85 + 86 + test("should process a video", async () => { 87 + const mockVideo: VideoMedia = { 88 + uri: "video.mp4", 89 + title: "Test Video", 90 + creation_timestamp: 1234567890, 91 + media_metadata: {}, 92 + cross_post_source: { source_app: "Instagram" }, 93 + backup_uri: "backup_video.mp4", 94 + dubbing_info: [], 95 + media_variants: [], 96 + }; 97 + 98 + const processor = new InstagramVideoProcessor([mockVideo], "/test/archive"); 99 + const result = await processor.process(); 100 + 101 + expect(result).toHaveLength(1); 102 + expect(result[0].mimeType).toBe("video/mp4"); 103 + expect(result[0].mediaText).toBe("Test Video"); 104 + }); 105 + 106 + test("should handle unsupported video types", async () => { 107 + const mockVideo: VideoMedia = { 108 + uri: "video.xyz", 109 + title: "Invalid Video", 110 + creation_timestamp: 1234567890, 111 + media_metadata: {}, 112 + cross_post_source: { source_app: "Instagram" }, 113 + backup_uri: "backup_invalid.mp4", 114 + dubbing_info: [], 115 + media_variants: [], 116 + }; 117 + 118 + const processor = new InstagramVideoProcessor([mockVideo], "/test/archive"); 119 + const result = await processor.process(); 120 + 121 + expect(result).toHaveLength(1); 122 + expect(result[0].mimeType).toBe(""); 123 + }); 124 + 125 + test("should truncate video title when it exceeds limit", async () => { 126 + const longTitle = "C".repeat(400); // Create a title longer than POST_TEXT_LIMIT (300) 127 + const mockVideo: VideoMedia = { 128 + uri: "video.mp4", 129 + title: longTitle, 130 + creation_timestamp: 1234567890, 131 + media_metadata: {}, 132 + cross_post_source: { source_app: "Instagram" }, 133 + backup_uri: "backup_video.mp4", 134 + dubbing_info: [], 135 + media_variants: [], 136 + }; 137 + 138 + const processor = new InstagramVideoProcessor([mockVideo], "/test/archive"); 139 + const result = await processor.process(); 140 + 141 + expect(result).toHaveLength(1); 142 + expect(result[0].mediaText.length).toBe(303); // 300 chars + "..." 143 + expect(result[0].mediaText.endsWith("...")).toBe(true); 144 + }); 145 + });
+2 -2
src/media/processors/InstagramVideoProcessor.ts
··· 1 1 import { logger } from "../../logger/logger"; 2 - import { VideoMediaProcessingStrategy } from "../interfaces/VideoMediaProcessingStrategy"; 2 + import { getVideoDimensions, getMimeType as getVideoMimeType, validateVideo } from "../../video/video"; 3 3 import { VideoMedia } from "../InstagramExportedPost"; 4 + import { VideoMediaProcessingStrategy } from "../interfaces/VideoMediaProcessingStrategy"; 4 5 import { MediaProcessResult, VideoMediaProcessResultImpl } from "../MediaProcessResult"; 5 - import { getVideoDimensions, getMimeType as getVideoMimeType, validateVideo } from "../../video/video"; 6 6 import { getMediaBuffer } from "../utils"; 7 7 8 8 const POST_TEXT_LIMIT = 300;
+10
src/media/utils.test.ts
··· 1 + import { decodeUTF8 } from "./utils"; 2 + 3 + describe("decodeUTF8", () => { 4 + test("should decode Instagram Unicode escape sequences", () => { 5 + const input = 6 + "Basil, Eucalyptus, Thyme \u00f0\u009f\u0098\u008d\u00f0\u009f\u008c\u00b1"; 7 + const result = decodeUTF8(input); 8 + expect(result).toBe("Basil, Eucalyptus, Thyme 😍🌱"); 9 + }); 10 + });
+2 -1
src/media/utils.ts
··· 1 1 import FS from "fs"; 2 + 3 + import { Media } from "./InstagramExportedPost"; 2 4 import { logger } from "../logger/logger"; 3 - import { Media } from "./InstagramExportedPost"; 4 5 5 6 /** 6 7 * Decode JSON Data into an Object.
+2 -1
src/video/video.test.ts
··· 1 + import ffmpeg from 'fluent-ffmpeg'; 2 + 1 3 import { validateVideo, getVideoDimensions } from './video'; 2 - import ffmpeg from 'fluent-ffmpeg'; 3 4 4 5 // Mock ffmpeg 5 6 jest.mock('fluent-ffmpeg', () => {
+2 -1
src/video/video.ts
··· 1 + import ffprobe from "@ffprobe-installer/ffprobe"; 1 2 import ffmpeg from "fluent-ffmpeg"; 2 - import ffprobe from "@ffprobe-installer/ffprobe"; 3 + 3 4 import { logger } from "../logger/logger"; 4 5 import { Ratio } from "../media"; 5 6 // Configure ffmpeg to use ffprobe
transfer/test_mixed_media/media/posts/202407/449873973_1204391817570870_1300270201787719920_n_18016994726159185.jpg

This is a binary file and will not be displayed.

transfer/test_mixed_media/media/posts/202407/450090938_1457101201604063_7774123883513094186_n_17892332634027265.jpg

This is a binary file and will not be displayed.

transfer/test_mixed_media/media/posts/202407/450098780_1665263484252935_8355389668902118342_n_18079198510496264.jpg

This is a binary file and will not be displayed.

transfer/test_mixed_media/media/posts/202407/450103288_491061823396353_3794282564229224546_n_18040222816930131.jpg

This is a binary file and will not be displayed.

transfer/test_mixed_media/media/posts/202407/450124567_850539960325047_1014258811537594829_n_17926160567808540.webp

This is a binary file and will not be displayed.

transfer/test_mixed_media/media/posts/202407/450384793_3757102844577409_7210819267977332845_n_18030137863939663.jpg

This is a binary file and will not be displayed.

transfer/test_mixed_media/media/posts/202407/450386863_2125611097838429_8053143681447061885_n_18064275982571079.jpg

This is a binary file and will not be displayed.

transfer/test_mixed_media/media/posts/202407/AQNVWvxnmXorzrSI0J0eosdRGdRUnJ57Bnf2vKtZQymssIG7TF3Lj9IUeX3CPTLjOBbPKmQ45twXxXFJ6t4VMp_17995561811639023.mp4

This is a binary file and will not be displayed.

transfer/test_mixed_media/media/posts/202407/AQNf9UEV2xsOnDpeX9XC1nhKWFDTpknsIjhce1DvwiqgOQMM2LBtQb2WZM1G04ahrdYkJlkqjmACABV4tAGSycJ_18008063708546486.mp4

This is a binary file and will not be displayed.

transfer/test_mixed_media/media/posts/202407/AQOWGsXWS2dp_tAxcY2DOAqT1BrX_NAdDEqRo0__OIFqZD6yT3QwYttrPi5KZE255dYkwRx1QgrGDE4YbZoxND7_18233190331273141.mp4

This is a binary file and will not be displayed.

+164
transfer/test_mixed_media/posts.json
··· 1 + [ { 2 + "media": [ 3 + { 4 + "uri": "media/posts/202407/450124567_850539960325047_1014258811537594829_n_17926160567808540.webp", 5 + "creation_timestamp": 1720384529, 6 + "media_metadata": { 7 + "camera_metadata": { 8 + "has_camera_metadata": false 9 + } 10 + }, 11 + "title": "", 12 + "cross_post_source": { 13 + "source_app": "FB" 14 + }, 15 + "backup_uri": "https://interncache-ldc.fbcdn.net/v/t51.29350-15/450124567_850539960325047_1014258811537594829_n.webp?stp=dst-jpg_e35_tt6&ccb=1-7&_nc_sid=18de74&efg=eyJ1cmxnZW4iOiJwaHBfdXJsZ2VuX2NsaWVudC9tZXRhX3VuaXZlcnNlL2VudGl0eS9pbnN0YWdyYW0vYmVzdF9pbWFnZSJ9&_nc_zt=23&_nc_gid=At21s-bxBp9qaCqtNlpqmQH&oh=00_AYC3h7V2vj_WxEU8cILUYqlu-1OWp6-Mfl4fpGGA4r7Zyg&oe=67AD75C8" 16 + }, 17 + { 18 + "uri": "media/posts/202407/450384793_3757102844577409_7210819267977332845_n_18030137863939663.jpg", 19 + "creation_timestamp": 1720384529, 20 + "media_metadata": { 21 + "camera_metadata": { 22 + "has_camera_metadata": false 23 + } 24 + }, 25 + "title": "", 26 + "cross_post_source": { 27 + "source_app": "FB" 28 + }, 29 + "backup_uri": "https://interncache-ldc.fbcdn.net/v/t51.29350-15/450384793_3757102844577409_7210819267977332845_n.jpg?stp=dst-jpg_e35_tt6&ccb=1-7&_nc_sid=18de74&efg=eyJ1cmxnZW4iOiJwaHBfdXJsZ2VuX2NsaWVudC9tZXRhX3VuaXZlcnNlL2VudGl0eS9pbnN0YWdyYW0vYmVzdF9pbWFnZSJ9&_nc_zt=23&_nc_gid=At21s-bxBp9qaCqtNlpqmQH&oh=00_AYA2W6Y05DojX0yG14var-pEjvM_JNfVQkWXSXzEN2pudg&oe=67AD7784" 30 + }, 31 + { 32 + "uri": "media/posts/202407/449873973_1204391817570870_1300270201787719920_n_18016994726159185.jpg", 33 + "creation_timestamp": 1720384529, 34 + "media_metadata": { 35 + "camera_metadata": { 36 + "has_camera_metadata": false 37 + } 38 + }, 39 + "title": "", 40 + "cross_post_source": { 41 + "source_app": "FB" 42 + }, 43 + "backup_uri": "https://interncache-ldc.fbcdn.net/v/t51.29350-15/449873973_1204391817570870_1300270201787719920_n.jpg?stp=dst-jpg_e35_tt6&ccb=1-7&_nc_sid=18de74&efg=eyJ1cmxnZW4iOiJwaHBfdXJsZ2VuX2NsaWVudC9tZXRhX3VuaXZlcnNlL2VudGl0eS9pbnN0YWdyYW0vYmVzdF9pbWFnZSJ9&_nc_zt=23&_nc_gid=At21s-bxBp9qaCqtNlpqmQH&oh=00_AYDZ3SL6EljwbgS14K_VBIultlrr3UTQijPyL0QztC-eCA&oe=67AD458F" 44 + }, 45 + { 46 + "uri": "media/posts/202407/450090938_1457101201604063_7774123883513094186_n_17892332634027265.jpg", 47 + "creation_timestamp": 1720384529, 48 + "media_metadata": { 49 + "camera_metadata": { 50 + "has_camera_metadata": false 51 + } 52 + }, 53 + "title": "", 54 + "cross_post_source": { 55 + "source_app": "FB" 56 + }, 57 + "backup_uri": "https://interncache-ldc.fbcdn.net/v/t51.29350-15/450090938_1457101201604063_7774123883513094186_n.jpg?stp=dst-jpg_e35_tt6&ccb=1-7&_nc_sid=18de74&efg=eyJ1cmxnZW4iOiJwaHBfdXJsZ2VuX2NsaWVudC9tZXRhX3VuaXZlcnNlL2VudGl0eS9pbnN0YWdyYW0vYmVzdF9pbWFnZSJ9&_nc_zt=23&_nc_gid=At21s-bxBp9qaCqtNlpqmQH&oh=00_AYBYShzWgogdpcaSZ2-Ofg4hEV5BW2p_Hc9VJLpQnkvZpA&oe=67AD50C8" 58 + }, 59 + { 60 + "uri": "media/posts/202407/450103288_491061823396353_3794282564229224546_n_18040222816930131.jpg", 61 + "creation_timestamp": 1720384529, 62 + "media_metadata": { 63 + "camera_metadata": { 64 + "has_camera_metadata": false 65 + } 66 + }, 67 + "title": "", 68 + "cross_post_source": { 69 + "source_app": "FB" 70 + }, 71 + "backup_uri": "https://interncache-ldc.fbcdn.net/v/t51.29350-15/450103288_491061823396353_3794282564229224546_n.jpg?stp=dst-jpg_e35_tt6&ccb=1-7&_nc_sid=18de74&efg=eyJ1cmxnZW4iOiJwaHBfdXJsZ2VuX2NsaWVudC9tZXRhX3VuaXZlcnNlL2VudGl0eS9pbnN0YWdyYW0vYmVzdF9pbWFnZSJ9&_nc_zt=23&_nc_gid=At21s-bxBp9qaCqtNlpqmQH&oh=00_AYCOdBK8UghG9aijfw_KydgQwACdRlMOgT2udj2MDo2UpQ&oe=67AD61FD" 72 + }, 73 + { 74 + "uri": "media/posts/202407/AQNVWvxnmXorzrSI0J0eosdRGdRUnJ57Bnf2vKtZQymssIG7TF3Lj9IUeX3CPTLjOBbPKmQ45twXxXFJ6t4VMp_17995561811639023.mp4", 75 + "creation_timestamp": 1720384529, 76 + "media_metadata": { 77 + "camera_metadata": { 78 + "has_camera_metadata": false 79 + } 80 + }, 81 + "title": "", 82 + "cross_post_source": { 83 + "source_app": "FB" 84 + }, 85 + "dubbing_info": [ 86 + 87 + ], 88 + "media_variants": [ 89 + 90 + ], 91 + "backup_uri": "https://interncache-ldc.fbcdn.net/o1/v/t16/f2/m69/AQNVWvxnm-XorzrSI0J0eosdRGdRUnJ57Bnf2vKtZQymssIG7TF3Lj9IUeX3-CPTLjOBbPKmQ45twXxXFJ6t4VMp?efg=eyJ1cmxnZW4iOiJvaWxfY3BwX3VybGdlbi9PaWxWVFNVcmwiLCJ2ZW5jb2RlX3RhZyI6InZ0c192b2RfdXJsZ2VuLmNhcm91c2VsX2l0ZW0udW5rbm93bi1DOS43MjAuZGFzaF9iYXNlbGluZV8xX3YxIn0&vs=943936170818949_83658459&_nc_vs=HBksFQIYOnBhc3N0aHJvdWdoX2V2ZXJzdG9yZS9HS21IMlJxWHpaZHp2Qm9FQUNUWHJYZ281aTh3YmtZTEFBQUYVAALIAQAVAhg6cGFzc3Rocm91Z2hfZXZlcnN0b3JlL0dNMkUxQnBnVGJYN1U4VUJBSTdQQ29EenRaMEtia1lMQUFBRhUCAsgBACgAGAAbAYgHdXNlX29pbAExFQAAJuaPvO7Xmes%2FFQIoAkMzLBc%2F9G6XjU%2FfOxgSZGFzaF9iYXNlbGluZV8xX3YxEQB17gcA&ccb=9-4&oh=00_AYDv0yCACPligcyEsXHlOvSCRJLx6Sw-hhpVcUjI7ujdNg&oe=67A965C8&_nc_sid=1d576d" 92 + }, 93 + { 94 + "uri": "media/posts/202407/450098780_1665263484252935_8355389668902118342_n_18079198510496264.jpg", 95 + "creation_timestamp": 1720384529, 96 + "media_metadata": { 97 + "camera_metadata": { 98 + "has_camera_metadata": false 99 + } 100 + }, 101 + "title": "", 102 + "cross_post_source": { 103 + "source_app": "FB" 104 + }, 105 + "backup_uri": "https://interncache-ldc.fbcdn.net/v/t51.29350-15/450098780_1665263484252935_8355389668902118342_n.jpg?stp=dst-jpg_e35_tt6&ccb=1-7&_nc_sid=18de74&efg=eyJ1cmxnZW4iOiJwaHBfdXJsZ2VuX2NsaWVudC9tZXRhX3VuaXZlcnNlL2VudGl0eS9pbnN0YWdyYW0vYmVzdF9pbWFnZSJ9&_nc_zt=23&_nc_gid=At21s-bxBp9qaCqtNlpqmQH&oh=00_AYCeaMVOEXFnbMaLBwXHvF40WzKm8psBN7dGyom7Q-3qhA&oe=67AD4D66" 106 + }, 107 + { 108 + "uri": "media/posts/202407/AQOWGsXWS2dp_tAxcY2DOAqT1BrX_NAdDEqRo0__OIFqZD6yT3QwYttrPi5KZE255dYkwRx1QgrGDE4YbZoxND7_18233190331273141.mp4", 109 + "creation_timestamp": 1720384529, 110 + "media_metadata": { 111 + "camera_metadata": { 112 + "has_camera_metadata": false 113 + } 114 + }, 115 + "title": "", 116 + "cross_post_source": { 117 + "source_app": "FB" 118 + }, 119 + "dubbing_info": [ 120 + 121 + ], 122 + "media_variants": [ 123 + 124 + ], 125 + "backup_uri": "https://interncache-ldc.fbcdn.net/o1/v/t16/f2/m69/AQOWGsXWS2dp_tAxcY2DOAqT1BrX_NAdDEqRo0__OIFqZD6yT3QwYttrPi5KZE255dYkwRx1QgrGDE4YbZoxND7-?efg=eyJ1cmxnZW4iOiJvaWxfY3BwX3VybGdlbi9PaWxWVFNVcmwiLCJ2ZW5jb2RlX3RhZyI6InZ0c192b2RfdXJsZ2VuLmNhcm91c2VsX2l0ZW0udW5rbm93bi1DOS43MjAuZGFzaF9iYXNlbGluZV8xX3YxIn0&vs=835407334819922_1041113555&_nc_vs=HBksFQIYOnBhc3N0aHJvdWdoX2V2ZXJzdG9yZS9HQml6MlJybzRFRGk2WjRHQUhPRDVTbWcxemhsYmtZTEFBQUYVAALIAQAVAhg6cGFzc3Rocm91Z2hfZXZlcnN0b3JlL0dCQzF6UnFMSmpjN1NtWUJBSGozdl90YVh0RmJia1lMQUFBRhUCAsgBACgAGAAbAYgHdXNlX29pbAExFQAAJsS3gLrxwbU%2FFQIoAkMzLBdANcHKwIMSbxgSZGFzaF9iYXNlbGluZV8xX3YxEQB17gcA&ccb=9-4&oh=00_AYAkTmzdaP3Se5uFcj743XTT7PdHIyORVe0wkvqldEQQMg&oe=67A960F0&_nc_sid=1d576d" 126 + }, 127 + { 128 + "uri": "media/posts/202407/450386863_2125611097838429_8053143681447061885_n_18064275982571079.jpg", 129 + "creation_timestamp": 1720384529, 130 + "media_metadata": { 131 + "camera_metadata": { 132 + "has_camera_metadata": false 133 + } 134 + }, 135 + "title": "", 136 + "cross_post_source": { 137 + "source_app": "FB" 138 + }, 139 + "backup_uri": "https://interncache-ldc.fbcdn.net/v/t51.29350-15/450386863_2125611097838429_8053143681447061885_n.jpg?stp=dst-jpg_e35_tt6&ccb=1-7&_nc_sid=18de74&efg=eyJ1cmxnZW4iOiJwaHBfdXJsZ2VuX2NsaWVudC9tZXRhX3VuaXZlcnNlL2VudGl0eS9pbnN0YWdyYW0vYmVzdF9pbWFnZSJ9&_nc_zt=23&_nc_gid=At21s-bxBp9qaCqtNlpqmQH&oh=00_AYCfUJgmGskU-nLNIAwDvWVAZfLd2S7eMdbj85_O28TTKg&oe=67AD41C9" 140 + }, 141 + { 142 + "uri": "media/posts/202407/AQNf9UEV2xsOnDpeX9XC1nhKWFDTpknsIjhce1DvwiqgOQMM2LBtQb2WZM1G04ahrdYkJlkqjmACABV4tAGSycJ_18008063708546486.mp4", 143 + "creation_timestamp": 1720384529, 144 + "media_metadata": { 145 + "camera_metadata": { 146 + "has_camera_metadata": false 147 + } 148 + }, 149 + "title": "", 150 + "cross_post_source": { 151 + "source_app": "FB" 152 + }, 153 + "dubbing_info": [ 154 + 155 + ], 156 + "media_variants": [ 157 + 158 + ], 159 + "backup_uri": "https://interncache-ldc.fbcdn.net/o1/v/t16/f2/m69/AQNf-9UEV2xsOnDpeX9XC1nhKWFDTpknsIjhce1DvwiqgOQMM2LBtQb2WZM1G04ahrdYkJlkqjmACABV4tAGSycJ?efg=eyJ1cmxnZW4iOiJvaWxfY3BwX3VybGdlbi9PaWxWVFNVcmwiLCJ2ZW5jb2RlX3RhZyI6InZ0c192b2RfdXJsZ2VuLmNhcm91c2VsX2l0ZW0udW5rbm93bi1DOS43MjAuZGFzaF9iYXNlbGluZV8xX3YxIn0&vs=994522832060125_1361240656&_nc_vs=HBksFQIYOnBhc3N0aHJvdWdoX2V2ZXJzdG9yZS9HR0VmMlJyQkMtaTNKS1lOQUV6WVBCbDNhUDBwYmtZTEFBQUYVAALIAQAVAhg6cGFzc3Rocm91Z2hfZXZlcnN0b3JlL0dNcEQxQnItUjdTRkYwNEZBRlZITE1LQ3RRQjlia1lMQUFBRhUCAsgBACgAGAAbAYgHdXNlX29pbAExFQAAJubvx43dy6xAFQIoAkMzLBdAHUGJN0vGqBgSZGFzaF9iYXNlbGluZV8xX3YxEQB17gcA&ccb=9-4&oh=00_AYB3p0WxwiC7anVKohkB8zenurZNxspxhWEBGdx8NARyTg&oe=67A97CE5&_nc_sid=1d576d" 160 + } 161 + ], 162 + "title": "What an incredible weekend day trip to celebrate Momma Olga's birthday.", 163 + "creation_timestamp": 1720384531 164 + }]
+2 -2
tsconfig.json
··· 20 20 "require": ["tsconfig-paths/register"] 21 21 }, 22 22 "include": ["src/**/*"], 23 - "exclude": ["node_modules", "**/*test.ts"] 24 - } 23 + "exclude": ["node_modules", "**/*test.ts", "tsconfig"] 24 + }
+11
tsconfig.root.json
··· 1 + { 2 + "compilerOptions": { 3 + "target": "esnext", 4 + "module": "NodeNext", 5 + "moduleResolution": "nodenext", 6 + "allowSyntheticDefaultImports": true, 7 + "esModuleInterop": true, 8 + "strict": true 9 + }, 10 + "include": ["eslint.config.ts", "jest.config.ts"] 11 + }