diff --git a/example/app.json b/example/app.json index a082b0ae..67063cc1 100644 --- a/example/app.json +++ b/example/app.json @@ -37,15 +37,30 @@ "widgets": [ { "id": "weather", - "displayName": "Weather Widget", - "description": "Shows current weather conditions", + "displayName": { + "pl": "Widget pogody", + "en": "Weather Widget" + }, + "description": { + "pl": "Pokazuje aktualne warunki pogodowe", + "en": "Shows current weather conditions" + }, "supportedFamilies": ["systemSmall", "systemMedium", "systemLarge"], - "initialStatePath": "./widgets/ios/ios-weather-initial.tsx" + "initialStatePath": { + "en": "./widgets/ios/ios-weather-initial.tsx", + "pl": "./widgets/ios/ios-weather-initial.tsx" + } }, { "id": "portfolio", - "displayName": "Portfolio Widget", - "description": "Shows portfolio performance with a chart and live updates", + "displayName": { + "pl": "Widget portfela", + "en": "Portfolio Widget" + }, + "description": { + "pl": "Pokazuje wyniki portfela z wykresem i aktualizacjami na żywo", + "en": "Shows portfolio performance with a chart and live updates" + }, "supportedFamilies": ["systemSmall", "systemMedium", "systemLarge"], "initialStatePath": "./widgets/ios/ios-portfolio-initial.tsx", "serverUpdate": { @@ -60,8 +75,14 @@ "widgets": [ { "id": "voltra", - "displayName": "Voltra Widget", - "description": "Voltra logo widget", + "displayName": { + "pl": "Widget Voltra", + "en": "Voltra Widget" + }, + "description": { + "pl": "Widget z logo Voltra", + "en": "Voltra logo widget" + }, "minCellWidth": 2, "minCellHeight": 2, "targetCellWidth": 2, @@ -73,8 +94,14 @@ }, { "id": "interactive_todos", - "displayName": "Interactive Todos Widget", - "description": "Testing interactive widgets with checkboxes, switches, and buttons", + "displayName": { + "pl": "Interaktywna lista zadań", + "en": "Interactive Todos Widget" + }, + "description": { + "pl": "Test interaktywnych widgetów z polami wyboru, przełącznikami i przyciskami", + "en": "Testing interactive widgets with checkboxes, switches, and buttons" + }, "targetCellWidth": 2, "targetCellHeight": 2, "resizeMode": "horizontal|vertical", @@ -83,8 +110,14 @@ }, { "id": "image_preloading", - "displayName": "Image Preloading Widget", - "description": "Test image preloading on Android", + "displayName": { + "pl": "Widget wstępnego ładowania obrazów", + "en": "Image Preloading Widget" + }, + "description": { + "pl": "Test wstępnego ładowania obrazów na Androidzie", + "en": "Test image preloading on Android" + }, "targetCellWidth": 2, "targetCellHeight": 2, "resizeMode": "horizontal|vertical", @@ -92,8 +125,14 @@ }, { "id": "image_fallback", - "displayName": "Image Fallback Widget", - "description": "Test image fallback with backgroundColor from styles", + "displayName": { + "pl": "Widget zapasowego obrazu", + "en": "Image Fallback Widget" + }, + "description": { + "pl": "Test zapasowego obrazu z kolorem tła ze stylów", + "en": "Test image fallback with backgroundColor from styles" + }, "targetCellWidth": 2, "targetCellHeight": 2, "resizeMode": "horizontal|vertical", @@ -102,8 +141,14 @@ }, { "id": "chart_widget", - "displayName": "Chart Widget", - "description": "Test Chart component", + "displayName": { + "pl": "Widget wykresu", + "en": "Chart Widget" + }, + "description": { + "pl": "Test komponentu Chart", + "en": "Test Chart component" + }, "targetCellWidth": 3, "targetCellHeight": 3, "resizeMode": "horizontal|vertical", @@ -112,8 +157,14 @@ }, { "id": "portfolio", - "displayName": "Portfolio Widget", - "description": "Shows portfolio performance with a chart and live updates", + "displayName": { + "pl": "Widget portfela", + "en": "Portfolio Widget" + }, + "description": { + "pl": "Pokazuje wyniki portfela z wykresem i aktualizacjami na żywo", + "en": "Shows portfolio performance with a chart and live updates" + }, "targetCellWidth": 2, "targetCellHeight": 2, "resizeMode": "horizontal|vertical", @@ -127,8 +178,14 @@ }, { "id": "material_colors", - "displayName": "Material Colors Widget", - "description": "Compare client-side and server-side rendering with Android dynamic colors", + "displayName": { + "pl": "Widget kolorów Material", + "en": "Material Colors Widget" + }, + "description": { + "pl": "Porównanie renderowania po stronie klienta i serwera z dynamicznymi kolorami Androida", + "en": "Compare client-side and server-side rendering with Android dynamic colors" + }, "targetCellWidth": 2, "targetCellHeight": 2, "resizeMode": "horizontal|vertical", diff --git a/package-lock.json b/package-lock.json index e13b3b80..0b03ffcc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -78,8 +78,6 @@ }, "example/node_modules/@babel/code-frame": { "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", - "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", "license": "MIT", "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", @@ -92,8 +90,6 @@ }, "example/node_modules/@expo/config": { "version": "55.0.11", - "resolved": "https://registry.npmjs.org/@expo/config/-/config-55.0.11.tgz", - "integrity": "sha512-14AkSmR1gOIUhCsPJ0cAo5ZduMNsPQsmFV9jBNZn1xC5Zb3D8x5eqvUie5QzWaUwdcyrq79uYJ2bTCiC6+nD0Q==", "license": "MIT", "dependencies": { "@expo/config-plugins": "~55.0.7", @@ -111,8 +107,6 @@ }, "example/node_modules/@expo/config-plugins": { "version": "55.0.7", - "resolved": "https://registry.npmjs.org/@expo/config-plugins/-/config-plugins-55.0.7.tgz", - "integrity": "sha512-XZUoDWrsHEkH3yasnDSJABM/UxP5a1ixzRwU/M+BToyn/f0nTrSJJe/Ay/FpxkI4JSNz2n0e06I23b2bleXKVA==", "license": "MIT", "dependencies": { "@expo/config-types": "^55.0.5", @@ -132,14 +126,10 @@ }, "example/node_modules/@expo/config-types": { "version": "55.0.5", - "resolved": "https://registry.npmjs.org/@expo/config-types/-/config-types-55.0.5.tgz", - "integrity": "sha512-sCmSUZG4mZ/ySXvfyyBdhjivz8Q539X1NondwDdYG7s3SBsk+wsgPJzYsqgAG/P9+l0xWjUD2F+kQ1cAJ6NNLg==", "license": "MIT" }, "example/node_modules/@expo/env": { "version": "2.1.1", - "resolved": "https://registry.npmjs.org/@expo/env/-/env-2.1.1.tgz", - "integrity": "sha512-rVvHC4I6xlPcg+mAO09ydUi2Wjv1ZytpLmHOSzvXzBAz9mMrJggqCe4s4dubjJvi/Ino/xQCLhbaLCnTtLpikg==", "license": "MIT", "dependencies": { "chalk": "^4.0.0", @@ -152,8 +142,6 @@ }, "example/node_modules/@expo/fingerprint": { "version": "0.16.6", - "resolved": "https://registry.npmjs.org/@expo/fingerprint/-/fingerprint-0.16.6.tgz", - "integrity": "sha512-nRITNbnu3RKSHPvKVehrSU4KG2VY9V8nvULOHBw98ukHCAU4bGrU5APvcblOkX3JAap+xEHsg/mZvqlvkLInmQ==", "license": "MIT", "dependencies": { "@expo/env": "^2.0.11", @@ -174,8 +162,6 @@ }, "example/node_modules/@expo/image-utils": { "version": "0.8.12", - "resolved": "https://registry.npmjs.org/@expo/image-utils/-/image-utils-0.8.12.tgz", - "integrity": "sha512-3KguH7kyKqq7pNwLb9j6BBdD/bjmNwXZG/HPWT6GWIXbwrvAJt2JNyYTP5agWJ8jbbuys1yuCzmkX+TU6rmI7A==", "license": "MIT", "dependencies": { "@expo/spawn-async": "^1.7.2", @@ -189,8 +175,6 @@ }, "example/node_modules/@expo/json-file": { "version": "10.0.12", - "resolved": "https://registry.npmjs.org/@expo/json-file/-/json-file-10.0.12.tgz", - "integrity": "sha512-inbDycp1rMAelAofg7h/mMzIe+Owx6F7pur3XdQ3EPTy00tme+4P6FWgHKUcjN8dBSrnbRNpSyh5/shzHyVCyQ==", "license": "MIT", "dependencies": { "@babel/code-frame": "^7.20.0", @@ -199,8 +183,6 @@ }, "example/node_modules/@expo/plist": { "version": "0.5.2", - "resolved": "https://registry.npmjs.org/@expo/plist/-/plist-0.5.2.tgz", - "integrity": "sha512-o4xdVdBpe4aTl3sPMZ2u3fJH4iG1I768EIRk1xRZP+GaFI93MaR3JvoFibYqxeTmLQ1p1kNEVqylfUjezxx45g==", "license": "MIT", "dependencies": { "@xmldom/xmldom": "^0.8.8", @@ -210,8 +192,6 @@ }, "example/node_modules/@expo/prebuild-config": { "version": "55.0.11", - "resolved": "https://registry.npmjs.org/@expo/prebuild-config/-/prebuild-config-55.0.11.tgz", - "integrity": "sha512-PqjbTTHXS0dnZMH4X5/0rnLxKfQqyN1s/5lmxITn+U6WDUNibatUepfjwV+5C2jU4hv5z2haqX6e9hQ0zUtDMA==", "license": "MIT", "dependencies": { "@expo/config": "~55.0.11", @@ -231,14 +211,10 @@ }, "example/node_modules/@expo/schema-utils": { "version": "55.0.2", - "resolved": "https://registry.npmjs.org/@expo/schema-utils/-/schema-utils-55.0.2.tgz", - "integrity": "sha512-QZ5WKbJOWkCrMq0/kfhV9ry8te/OaS34YgLVpG8u9y2gix96TlpRTbxM/YATjNcUR2s4fiQmPCOxkGtog4i37g==", "license": "MIT" }, "example/node_modules/@expo/vector-icons": { "version": "15.1.1", - "resolved": "https://registry.npmjs.org/@expo/vector-icons/-/vector-icons-15.1.1.tgz", - "integrity": "sha512-Iu2VkcoI5vygbtYngm7jb4ifxElNVXQYdDrYkT7UCEIiKLeWnQY0wf2ZhHZ+Wro6Sc5TaumpKUOqDRpLi5rkvw==", "license": "MIT", "peerDependencies": { "expo-font": ">=14.0.4", @@ -248,8 +224,6 @@ }, "example/node_modules/@react-native/babel-plugin-codegen": { "version": "0.83.4", - "resolved": "https://registry.npmjs.org/@react-native/babel-plugin-codegen/-/babel-plugin-codegen-0.83.4.tgz", - "integrity": "sha512-UFsK+c1rvT84XZfzpmwKePsc5nTr5LK7hh18TI0DooNlVcztDbMDsQZpDnhO/gmk7aTbWEqO5AB3HJ7tvGp+Jg==", "license": "MIT", "dependencies": { "@babel/traverse": "^7.25.3", @@ -261,8 +235,6 @@ }, "example/node_modules/@react-native/babel-preset": { "version": "0.83.4", - "resolved": "https://registry.npmjs.org/@react-native/babel-preset/-/babel-preset-0.83.4.tgz", - "integrity": "sha512-SXPFn3Jp4gOzlBDnDOKPzMfxQPKJMYJs05EmEeFB/6km46xZ9l+2YKXwAwxfNhHnmwNf98U/bnVndU95I0TMCw==", "license": "MIT", "dependencies": { "@babel/core": "^7.25.2", @@ -320,8 +292,6 @@ }, "example/node_modules/@react-native/babel-preset/node_modules/babel-plugin-syntax-hermes-parser": { "version": "0.32.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-hermes-parser/-/babel-plugin-syntax-hermes-parser-0.32.0.tgz", - "integrity": "sha512-m5HthL++AbyeEA2FcdwOLfVFvWYECOBObLHNqdR8ceY4TsEdn4LdX2oTvbB2QJSSElE2AWA/b2MXZ/PF/CqLZg==", "license": "MIT", "dependencies": { "hermes-parser": "0.32.0" @@ -329,14 +299,10 @@ }, "example/node_modules/@react-native/babel-preset/node_modules/hermes-estree": { "version": "0.32.0", - "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.32.0.tgz", - "integrity": "sha512-KWn3BqnlDOl97Xe1Yviur6NbgIZ+IP+UVSpshlZWkq+EtoHg6/cwiDj/osP9PCEgFE15KBm1O55JRwbMEm5ejQ==", "license": "MIT" }, "example/node_modules/@react-native/babel-preset/node_modules/hermes-parser": { "version": "0.32.0", - "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.32.0.tgz", - "integrity": "sha512-g4nBOWFpuiTqjR3LZdRxKUkij9iyveWeuks7INEsMX741f3r9xxrOe8TeQfUxtda0eXmiIFiMQzoeSQEno33Hw==", "license": "MIT", "dependencies": { "hermes-estree": "0.32.0" @@ -344,8 +310,6 @@ }, "example/node_modules/@react-native/codegen": { "version": "0.83.4", - "resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.83.4.tgz", - "integrity": "sha512-CJ7XutzIqJPz3Lp/5TOiRWlU/JAjTboMT1BHNLSXjYHXwTmgHM3iGEbpCOtBMjWvsojRTJyRO/G3ghInIIXEYg==", "license": "MIT", "dependencies": { "@babel/core": "^7.25.2", @@ -365,14 +329,10 @@ }, "example/node_modules/@react-native/codegen/node_modules/balanced-match": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", - "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "license": "MIT" }, "example/node_modules/@react-native/codegen/node_modules/brace-expansion": { "version": "1.1.13", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.13.tgz", - "integrity": "sha512-9ZLprWS6EENmhEOpjCYW2c8VkmOvckIJZfkr7rBW6dObmfgJ/L1GpSYW5Hpo9lDz4D1+n0Ckz8rU7FwHDQiG/w==", "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", @@ -381,9 +341,6 @@ }, "example/node_modules/@react-native/codegen/node_modules/glob": { "version": "7.2.3", - "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", - "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", - "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", @@ -402,14 +359,10 @@ }, "example/node_modules/@react-native/codegen/node_modules/hermes-estree": { "version": "0.32.0", - "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.32.0.tgz", - "integrity": "sha512-KWn3BqnlDOl97Xe1Yviur6NbgIZ+IP+UVSpshlZWkq+EtoHg6/cwiDj/osP9PCEgFE15KBm1O55JRwbMEm5ejQ==", "license": "MIT" }, "example/node_modules/@react-native/codegen/node_modules/hermes-parser": { "version": "0.32.0", - "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.32.0.tgz", - "integrity": "sha512-g4nBOWFpuiTqjR3LZdRxKUkij9iyveWeuks7INEsMX741f3r9xxrOe8TeQfUxtda0eXmiIFiMQzoeSQEno33Hw==", "license": "MIT", "dependencies": { "hermes-estree": "0.32.0" @@ -417,8 +370,6 @@ }, "example/node_modules/@react-native/codegen/node_modules/minimatch": { "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" @@ -429,8 +380,6 @@ }, "example/node_modules/@react-native/debugger-frontend": { "version": "0.83.4", - "resolved": "https://registry.npmjs.org/@react-native/debugger-frontend/-/debugger-frontend-0.83.4.tgz", - "integrity": "sha512-mCE2s/S7SEjax3gZb6LFAraAI3x13gRVWJWqT0HIm71e4ITObENNTDuMw4mvZ/wr4Gz2wv4FcBH5/Nla9LXOcg==", "license": "BSD-3-Clause", "engines": { "node": ">= 20.19.4" @@ -438,8 +387,6 @@ }, "example/node_modules/@react-native/debugger-shell": { "version": "0.83.4", - "resolved": "https://registry.npmjs.org/@react-native/debugger-shell/-/debugger-shell-0.83.4.tgz", - "integrity": "sha512-FtAnrvXqy1xeZ+onwilvxEeeBsvBlhtfrHVIC2R/BOJAK9TbKEtFfjio0wsn3DQIm+UZq48DSa+p9jJZ2aJUww==", "license": "MIT", "dependencies": { "cross-spawn": "^7.0.6", @@ -451,8 +398,6 @@ }, "example/node_modules/@react-native/dev-middleware": { "version": "0.83.4", - "resolved": "https://registry.npmjs.org/@react-native/dev-middleware/-/dev-middleware-0.83.4.tgz", - "integrity": "sha512-3s9nXZc/kj986nI2RPqxiIJeTS3o7pvZDxbHu7GE9WVIGX9YucA1l/tEiXd7BAm3TBFOfefDOT08xD46wH+R3Q==", "license": "MIT", "dependencies": { "@isaacs/ttlcache": "^1.4.1", @@ -474,8 +419,6 @@ }, "example/node_modules/@react-native/dev-middleware/node_modules/ws": { "version": "7.5.10", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", - "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", "license": "MIT", "engines": { "node": ">=8.3.0" @@ -495,14 +438,10 @@ }, "example/node_modules/@react-native/normalize-colors": { "version": "0.83.4", - "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.83.4.tgz", - "integrity": "sha512-9ezxaHjxqTkTOLg62SGg7YhFaE+fxa/jlrWP0nwf7eGFHlGOiTAaRR2KUfiN3K05e+EMbEhgcH/c7bgaXeGyJw==", "license": "MIT" }, "example/node_modules/babel-plugin-react-compiler": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/babel-plugin-react-compiler/-/babel-plugin-react-compiler-1.0.0.tgz", - "integrity": "sha512-Ixm8tFfoKKIPYdCCKYTsqv+Fd4IJ0DQqMyEimo+pxUOMUR9cVPlwTrFt9Avu+3cb6Zp3mAzl+t1MrG2fxxKsxw==", "license": "MIT", "dependencies": { "@babel/types": "^7.26.0" @@ -510,14 +449,10 @@ }, "example/node_modules/babel-plugin-react-native-web": { "version": "0.21.2", - "resolved": "https://registry.npmjs.org/babel-plugin-react-native-web/-/babel-plugin-react-native-web-0.21.2.tgz", - "integrity": "sha512-SPD0J6qjJn8231i0HZhlAGH6NORe+QvRSQM2mwQEzJ2Fb3E4ruWTiiicPlHjmeWShDXLcvoorOCXjeR7k/lyWA==", "license": "MIT" }, "example/node_modules/babel-plugin-syntax-hermes-parser": { "version": "0.32.1", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-hermes-parser/-/babel-plugin-syntax-hermes-parser-0.32.1.tgz", - "integrity": "sha512-HgErPZTghW76Rkq9uqn5ESeiD97FbqpZ1V170T1RG2RDp+7pJVQV2pQJs7y5YzN0/gcT6GM5ci9apRnIwuyPdQ==", "license": "MIT", "dependencies": { "hermes-parser": "0.32.1" @@ -525,8 +460,6 @@ }, "example/node_modules/balanced-match": { "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", "license": "MIT", "engines": { "node": "18 || 20 || >=22" @@ -534,8 +467,6 @@ }, "example/node_modules/brace-expansion": { "version": "5.0.5", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", - "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", "license": "MIT", "dependencies": { "balanced-match": "^4.0.2" @@ -546,8 +477,6 @@ }, "example/node_modules/commander": { "version": "7.2.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", - "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", "license": "MIT", "engines": { "node": ">= 10" @@ -555,8 +484,6 @@ }, "example/node_modules/detect-libc": { "version": "2.1.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", - "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", "license": "Apache-2.0", "engines": { "node": ">=8" @@ -564,8 +491,6 @@ }, "example/node_modules/expo": { "version": "55.0.9", - "resolved": "https://registry.npmjs.org/expo/-/expo-55.0.9.tgz", - "integrity": "sha512-bYDhqr2v2UtTf/9s493bUVRtxsYqXF4KXkaS3sSW827DmgxNJv0NuWKWwfqFdDxKvDELd488J5X9l9ogqUrwOA==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.20.0", @@ -618,8 +543,6 @@ }, "example/node_modules/expo-application": { "version": "55.0.10", - "resolved": "https://registry.npmjs.org/expo-application/-/expo-application-55.0.10.tgz", - "integrity": "sha512-5ccf+S6hsQz+doi907TOJxKzV5AKgAgw004z4FoDWSoGhfab0LUPg6uyvOspuU4cbNvqw8EAy08hZbVO8nKc9Q==", "license": "MIT", "peerDependencies": { "expo": "*" @@ -627,8 +550,6 @@ }, "example/node_modules/expo-blur": { "version": "55.0.10", - "resolved": "https://registry.npmjs.org/expo-blur/-/expo-blur-55.0.10.tgz", - "integrity": "sha512-W6iMVArvCKYqhhZW55/V2yA3t9WcZU718Jg7vyIX+XtpDMYirEu3vqesnodfFvwTS+K1JHNLN+PAjQPy38+Rlg==", "license": "MIT", "peerDependencies": { "expo": "*", @@ -638,8 +559,6 @@ }, "example/node_modules/expo-build-properties": { "version": "55.0.10", - "resolved": "https://registry.npmjs.org/expo-build-properties/-/expo-build-properties-55.0.10.tgz", - "integrity": "sha512-9+JCQuIQaEi27XDIFTR2Y4vok0FidWux2UKrKWD5E38/0fozbZcVUzS14FgMF1F0lalmXMgebH/vjRpbYWJIMA==", "license": "MIT", "dependencies": { "@expo/schema-utils": "^55.0.2", @@ -652,8 +571,6 @@ }, "example/node_modules/expo-constants": { "version": "55.0.9", - "resolved": "https://registry.npmjs.org/expo-constants/-/expo-constants-55.0.9.tgz", - "integrity": "sha512-iBiXjZeuU5S/8docQeNzsVvtDy4w0zlmXBpFEi1ypwugceEpdQQab65TVRbusXAcwpNVxCPMpNlDssYp0Pli2g==", "license": "MIT", "dependencies": { "@expo/config": "~55.0.10", @@ -666,8 +583,6 @@ }, "example/node_modules/expo-font": { "version": "55.0.4", - "resolved": "https://registry.npmjs.org/expo-font/-/expo-font-55.0.4.tgz", - "integrity": "sha512-ZKeGTFffPygvY5dM/9ATM2p7QDkhsaHopH7wFAWgP2lKzqUMS9B/RxCvw5CaObr9Ro7x9YptyeRKX2HmgmMfrg==", "license": "MIT", "dependencies": { "fontfaceobserver": "^2.1.0" @@ -680,8 +595,6 @@ }, "example/node_modules/expo-haptics": { "version": "55.0.9", - "resolved": "https://registry.npmjs.org/expo-haptics/-/expo-haptics-55.0.9.tgz", - "integrity": "sha512-KCRyHr/uu4syXmoq3aIQ6ahuaX6FGtlPkWGlLlHJ836WF3nG+5+oCaCQiI7qMTpml+Tp/V/zP4ZaowM2KHgLNA==", "license": "MIT", "peerDependencies": { "expo": "*" @@ -689,8 +602,6 @@ }, "example/node_modules/expo-image": { "version": "55.0.6", - "resolved": "https://registry.npmjs.org/expo-image/-/expo-image-55.0.6.tgz", - "integrity": "sha512-TKuu0uBmgTZlhd91Glv+V4vSBMlfl0bdQxfl97oKKZUo3OBC13l3eLik7v3VNLJN7PZbiwOAiXkZkqSOBx/Xsw==", "license": "MIT", "dependencies": { "sf-symbols-typescript": "^2.2.0" @@ -709,8 +620,6 @@ }, "example/node_modules/expo-linking": { "version": "55.0.9", - "resolved": "https://registry.npmjs.org/expo-linking/-/expo-linking-55.0.9.tgz", - "integrity": "sha512-QWEefQZUu7PuJzye19Hr6msqpO4VB4TiY4T/6AkISJzZnoZGxWg16s3JTZS7D/b3VMm8VQfhw9I5NF/7f8EPcA==", "license": "MIT", "dependencies": { "expo-constants": "~55.0.9", @@ -723,8 +632,6 @@ }, "example/node_modules/expo-modules-autolinking": { "version": "55.0.12", - "resolved": "https://registry.npmjs.org/expo-modules-autolinking/-/expo-modules-autolinking-55.0.12.tgz", - "integrity": "sha512-nZOPjpl4v5YInNftJpX10bYxDNNq2HM+hWTfr3FPE1/i0lES/cnvaB8v4XKpDTuAUdBwkGYadTfNwNG9k/Ftgw==", "license": "MIT", "dependencies": { "@expo/require-utils": "^55.0.3", @@ -738,8 +645,6 @@ }, "example/node_modules/expo-notifications": { "version": "55.0.14", - "resolved": "https://registry.npmjs.org/expo-notifications/-/expo-notifications-55.0.14.tgz", - "integrity": "sha512-fwWTd0OK82Yj2MLJJK0cIgaRtAu8OUcjGuucdtsp/dOsErqBsGQQWpotoEXoRPrDrCL4sHwSvg9QzGdeouJ/jQ==", "license": "MIT", "dependencies": { "@expo/image-utils": "^0.8.12", @@ -756,8 +661,6 @@ }, "example/node_modules/expo-router": { "version": "55.0.8", - "resolved": "https://registry.npmjs.org/expo-router/-/expo-router-55.0.8.tgz", - "integrity": "sha512-SG51cnmH84Htxa+vXJPw4xl10rDCrWkC/3m38Sn51Bg+9N2nPPJMhCYifAcR9ZYK6mlb2BPG1GiHVjZw78DSxQ==", "license": "MIT", "dependencies": { "@expo/metro-runtime": "^55.0.7", @@ -831,8 +734,6 @@ }, "example/node_modules/expo-router/node_modules/@expo/metro-runtime": { "version": "55.0.7", - "resolved": "https://registry.npmjs.org/@expo/metro-runtime/-/metro-runtime-55.0.7.tgz", - "integrity": "sha512-fV+DYvJ+A3fKEwkpJiXUhrpsWy4HjjbdapwJi/QmnGLFKYrzGvGqsWG+xf3mmUDwP413t6GL9162bnyMReYOaA==", "license": "MIT", "dependencies": { "@expo/log-box": "55.0.8", @@ -855,8 +756,6 @@ }, "example/node_modules/expo-router/node_modules/expo-glass-effect": { "version": "55.0.8", - "resolved": "https://registry.npmjs.org/expo-glass-effect/-/expo-glass-effect-55.0.8.tgz", - "integrity": "sha512-IvUjHb/4t6r2H/LXDjcQ4uDoHrmO2cLOvEb9leLavQ4HX5+P4LRtQrMDMlkWAn5Wo5DkLcG8+1CrQU2nqgogTA==", "license": "MIT", "peerDependencies": { "expo": "*", @@ -866,8 +765,6 @@ }, "example/node_modules/expo-sensors": { "version": "55.0.9", - "resolved": "https://registry.npmjs.org/expo-sensors/-/expo-sensors-55.0.9.tgz", - "integrity": "sha512-PK6hjL2yf4YvumEd+7QXI0mWdyMPiL7MmZnpaDWheZ/g11dsxobKyHWfehjfRUnmnwZD8P6lnQZ2bBvB50ZjjA==", "license": "MIT", "dependencies": { "invariant": "^2.2.4" @@ -879,8 +776,6 @@ }, "example/node_modules/expo-splash-screen": { "version": "55.0.13", - "resolved": "https://registry.npmjs.org/expo-splash-screen/-/expo-splash-screen-55.0.13.tgz", - "integrity": "sha512-dEainzjUZbqdmcQjO7tIqoh432jloxOGzHJHErGIMxg1QlahKj0e5D/4CY1Xd6qIOs1rRBlG63mPxx7iGBWbHQ==", "license": "MIT", "dependencies": { "@expo/prebuild-config": "^55.0.11" @@ -891,8 +786,6 @@ }, "example/node_modules/expo-status-bar": { "version": "55.0.4", - "resolved": "https://registry.npmjs.org/expo-status-bar/-/expo-status-bar-55.0.4.tgz", - "integrity": "sha512-BPDjUXKqv1F9j2YNGLRZfkBEZXIEEpqj+t81y4c+4fdSN3Pos7goIHXgcl2ozbKQLgKRZQyNZQtbUgh5UjHYUQ==", "license": "MIT", "dependencies": { "react-native-is-edge-to-edge": "^1.2.1" @@ -904,8 +797,6 @@ }, "example/node_modules/expo-system-ui": { "version": "55.0.11", - "resolved": "https://registry.npmjs.org/expo-system-ui/-/expo-system-ui-55.0.11.tgz", - "integrity": "sha512-ZxcR9/BtL+fkKp1KPPLLc8xgvbqRcyYg+Drf3XjonY/bHIHiyZY1RLnVLxY1wSpFEly5pJkS9erspFok1ES5dQ==", "license": "MIT", "dependencies": { "@react-native/normalize-colors": "0.83.4", @@ -924,8 +815,6 @@ }, "example/node_modules/expo-web-browser": { "version": "55.0.10", - "resolved": "https://registry.npmjs.org/expo-web-browser/-/expo-web-browser-55.0.10.tgz", - "integrity": "sha512-2d6qVrg/nt0JvW5uAqOMDG/xITIXFe1Prkq1ri+I3PrC0QmV5cMYNSagU9ykfC8S7YKWxF1qO7Qsih9fxNa9dw==", "license": "MIT", "peerDependencies": { "expo": "*", @@ -934,8 +823,6 @@ }, "example/node_modules/expo/node_modules/@expo/cli": { "version": "55.0.19", - "resolved": "https://registry.npmjs.org/@expo/cli/-/cli-55.0.19.tgz", - "integrity": "sha512-PPNWwPXHcLDFgNNmkLmlLm3fLiNTxr7sbhNx4mXdjo0U/2Wg3rWaCeg1yMx49llOpDLZEWJpyAwPvTBqWc8glw==", "license": "MIT", "dependencies": { "@expo/code-signing-certificates": "^0.0.6", @@ -1015,8 +902,6 @@ }, "example/node_modules/expo/node_modules/@expo/cli/node_modules/@expo/router-server": { "version": "55.0.11", - "resolved": "https://registry.npmjs.org/@expo/router-server/-/router-server-55.0.11.tgz", - "integrity": "sha512-Kd8J1OOlFR00DZxn+1KfiQiXZtRut6cj8+ynqHJa7dtt/lTL4tGkYistqmVhpKJ6w886eRY5WivKy7o0ZBFkJA==", "license": "MIT", "dependencies": { "debug": "^4.3.4" @@ -1049,8 +934,6 @@ }, "example/node_modules/expo/node_modules/@expo/devtools": { "version": "55.0.2", - "resolved": "https://registry.npmjs.org/@expo/devtools/-/devtools-55.0.2.tgz", - "integrity": "sha512-4VsFn9MUriocyuhyA+ycJP3TJhUsOFHDc270l9h3LhNpXMf6wvIdGcA0QzXkZtORXmlDybWXRP2KT1k36HcQkA==", "license": "MIT", "dependencies": { "chalk": "^4.1.2" @@ -1070,8 +953,6 @@ }, "example/node_modules/expo/node_modules/@expo/metro-config": { "version": "55.0.11", - "resolved": "https://registry.npmjs.org/@expo/metro-config/-/metro-config-55.0.11.tgz", - "integrity": "sha512-qGxq7RwWpj0zNvZO/e5aizKrOKYYBrVPShSbxPOVB1EXcexxTPTxnOe4pYFg/gKkLIJe0t3jSSF8IDWlGdaaOg==", "license": "MIT", "dependencies": { "@babel/code-frame": "^7.20.0", @@ -1105,8 +986,6 @@ }, "example/node_modules/expo/node_modules/babel-preset-expo": { "version": "55.0.13", - "resolved": "https://registry.npmjs.org/babel-preset-expo/-/babel-preset-expo-55.0.13.tgz", - "integrity": "sha512-7m3Hpi6R1M+3u2LEU15OV59ATtbqz6kFvL6y9TaZTeOGLV28MFULawCQw3BtO/qMYUPz0vkH1OdbCuG7E2cTbg==", "license": "MIT", "dependencies": { "@babel/generator": "^7.20.5", @@ -1153,8 +1032,6 @@ }, "example/node_modules/expo/node_modules/expo-asset": { "version": "55.0.10", - "resolved": "https://registry.npmjs.org/expo-asset/-/expo-asset-55.0.10.tgz", - "integrity": "sha512-wxjNBKIaDyachq7oJgVlWVFzZ6SnNpJFJhkkcymXoTPt5O3XmDM+a6fT91xQQawCXTyZuCc1sNxKMetEofeYkg==", "license": "MIT", "dependencies": { "@expo/image-utils": "^0.8.12", @@ -1168,8 +1045,6 @@ }, "example/node_modules/expo/node_modules/expo-file-system": { "version": "55.0.12", - "resolved": "https://registry.npmjs.org/expo-file-system/-/expo-file-system-55.0.12.tgz", - "integrity": "sha512-MFN/3L3gm174nxP2HqKQsSsPbjAj92wuidKFGSbl3Lt6oJTS09EbTwszX5BhYeeVSprcsw8pnlxYSmhkSqGEFw==", "license": "MIT", "peerDependencies": { "expo": "*", @@ -1178,8 +1053,6 @@ }, "example/node_modules/expo/node_modules/expo-keep-awake": { "version": "55.0.4", - "resolved": "https://registry.npmjs.org/expo-keep-awake/-/expo-keep-awake-55.0.4.tgz", - "integrity": "sha512-vwfdMtMS5Fxaon8gC0AiE70SpxTsHJ+rjeoVJl8kdfdbxczF7OIaVmfjFJ5Gfigd/WZiLqxhfZk34VAkXF4PNg==", "license": "MIT", "peerDependencies": { "expo": "*", @@ -1188,8 +1061,6 @@ }, "example/node_modules/expo/node_modules/expo-modules-core": { "version": "55.0.18", - "resolved": "https://registry.npmjs.org/expo-modules-core/-/expo-modules-core-55.0.18.tgz", - "integrity": "sha512-Qwr3qCCZd/aMtenUo6KmPaFy/uFeNz0rLfRxv0tNsWFF27XS2wjDwb87A7lD2ii8iJhjYEHVetRvFkcDxCw8Lw==", "license": "MIT", "dependencies": { "invariant": "^2.2.4" @@ -1201,8 +1072,6 @@ }, "example/node_modules/glob": { "version": "13.0.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", - "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", "license": "BlueOak-1.0.0", "dependencies": { "minimatch": "^10.2.2", @@ -1218,14 +1087,10 @@ }, "example/node_modules/hermes-estree": { "version": "0.32.1", - "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.32.1.tgz", - "integrity": "sha512-ne5hkuDxheNBAikDjqvCZCwihnz0vVu9YsBzAEO1puiyFR4F1+PAz/SiPHSsNTuOveCYGRMX8Xbx4LOubeC0Qg==", "license": "MIT" }, "example/node_modules/hermes-parser": { "version": "0.32.1", - "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.32.1.tgz", - "integrity": "sha512-175dz634X/W5AiwrpLdoMl/MOb17poLHyIqgyExlE8D9zQ1OPnoORnGMB5ltRKnpvQzBjMYvT2rN/sHeIfZW5Q==", "license": "MIT", "dependencies": { "hermes-estree": "0.32.1" @@ -1233,8 +1098,6 @@ }, "example/node_modules/lan-network": { "version": "0.2.0", - "resolved": "https://registry.npmjs.org/lan-network/-/lan-network-0.2.0.tgz", - "integrity": "sha512-EZgbsXMrGS+oK+Ta12mCjzBFse+SIewGdwrSTr5g+MSymnjpox2x05ceI20PQejJOFvOgzcXrfDk/SdY7dSCtw==", "license": "MIT", "bin": { "lan-network": "dist/lan-network-cli.js" @@ -1242,8 +1105,6 @@ }, "example/node_modules/lightningcss": { "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss/-/lightningcss-1.32.0.tgz", - "integrity": "sha512-NXYBzinNrblfraPGyrbPoD19C1h9lfI/1mzgWYvXUTe414Gz/X1FD2XBZSZM7rRTrMA8JL3OtAaGifrIKhQ5yQ==", "license": "MPL-2.0", "dependencies": { "detect-libc": "^2.0.3" @@ -1271,8 +1132,6 @@ }, "example/node_modules/lightningcss-darwin-arm64": { "version": "1.32.0", - "resolved": "https://registry.npmjs.org/lightningcss-darwin-arm64/-/lightningcss-darwin-arm64-1.32.0.tgz", - "integrity": "sha512-RzeG9Ju5bag2Bv1/lwlVJvBE3q6TtXskdZLLCyfg5pt+HLz9BqlICO7LZM7VHNTTn/5PRhHFBSjk5lc4cmscPQ==", "cpu": [ "arm64" ], @@ -1471,8 +1330,6 @@ }, "example/node_modules/lru-cache": { "version": "11.2.7", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", - "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==", "license": "BlueOak-1.0.0", "engines": { "node": "20 || >=22" @@ -1480,14 +1337,10 @@ }, "example/node_modules/memoize-one": { "version": "6.0.0", - "resolved": "https://registry.npmjs.org/memoize-one/-/memoize-one-6.0.0.tgz", - "integrity": "sha512-rkpe71W0N0c0Xz6QD0eJETuWAJGnJ9afsl1srmwPrI+yBCkge5EycXXbYRyvL29zZVUWQCY7InPRCv3GDXuZNw==", "license": "MIT" }, "example/node_modules/minimatch": { "version": "10.2.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", - "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", "license": "BlueOak-1.0.0", "dependencies": { "brace-expansion": "^5.0.5" @@ -1501,8 +1354,6 @@ }, "example/node_modules/path-scurry": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", - "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", "license": "BlueOak-1.0.0", "dependencies": { "lru-cache": "^11.0.0", @@ -1517,8 +1368,6 @@ }, "example/node_modules/picomatch": { "version": "4.0.4", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", - "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", "license": "MIT", "engines": { "node": ">=12" @@ -1529,8 +1378,6 @@ }, "example/node_modules/react-native-gesture-handler": { "version": "2.30.1", - "resolved": "https://registry.npmjs.org/react-native-gesture-handler/-/react-native-gesture-handler-2.30.1.tgz", - "integrity": "sha512-xIUBDo5ktmJs++0fZlavQNvDEE4PsihWhSeJsJtoz4Q6p0MiTM9TgrTgfEgzRR36qGPytFoeq+ShLrVwGdpUdA==", "license": "MIT", "dependencies": { "@egjs/hammerjs": "^2.0.17", @@ -1544,8 +1391,6 @@ }, "example/node_modules/react-native-reanimated": { "version": "4.2.1", - "resolved": "https://registry.npmjs.org/react-native-reanimated/-/react-native-reanimated-4.2.1.tgz", - "integrity": "sha512-/NcHnZMyOvsD/wYXug/YqSKw90P9edN0kEPL5lP4PFf1aQ4F1V7MKe/E0tvfkXKIajy3Qocp5EiEnlcrK/+BZg==", "license": "MIT", "dependencies": { "react-native-is-edge-to-edge": "1.2.1", @@ -1559,8 +1404,6 @@ }, "example/node_modules/react-native-reanimated/node_modules/react-native-is-edge-to-edge": { "version": "1.2.1", - "resolved": "https://registry.npmjs.org/react-native-is-edge-to-edge/-/react-native-is-edge-to-edge-1.2.1.tgz", - "integrity": "sha512-FLbPWl/MyYQWz+KwqOZsSyj2JmLKglHatd3xLZWskXOpRaio4LfEDEz8E/A6uD8QoTHW6Aobw1jbEwK7KMgR7Q==", "license": "MIT", "peerDependencies": { "react": "*", @@ -1569,8 +1412,6 @@ }, "example/node_modules/react-native-reanimated/node_modules/semver": { "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -1581,8 +1422,6 @@ }, "example/node_modules/react-native-safe-area-context": { "version": "5.6.2", - "resolved": "https://registry.npmjs.org/react-native-safe-area-context/-/react-native-safe-area-context-5.6.2.tgz", - "integrity": "sha512-4XGqMNj5qjUTYywJqpdWZ9IG8jgkS3h06sfVjfw5yZQZfWnRFXczi0GnYyFyCc2EBps/qFmoCH8fez//WumdVg==", "license": "MIT", "peerDependencies": { "react": "*", @@ -1591,8 +1430,6 @@ }, "example/node_modules/react-native-screens": { "version": "4.23.0", - "resolved": "https://registry.npmjs.org/react-native-screens/-/react-native-screens-4.23.0.tgz", - "integrity": "sha512-XhO3aK0UeLpBn4kLecd+J+EDeRRJlI/Ro9Fze06vo1q163VeYtzfU9QS09/VyDFMWR1qxDC1iazCArTPSFFiPw==", "license": "MIT", "dependencies": { "react-freeze": "^1.0.0", @@ -1605,8 +1442,6 @@ }, "example/node_modules/react-native-web": { "version": "0.21.2", - "resolved": "https://registry.npmjs.org/react-native-web/-/react-native-web-0.21.2.tgz", - "integrity": "sha512-SO2t9/17zM4iEnFvlu2DA9jqNbzNhoUP+AItkoCOyFmDMOhUnBBznBDCYN92fGdfAkfQlWzPoez6+zLxFNsZEg==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.18.6", @@ -1625,14 +1460,10 @@ }, "example/node_modules/react-native-web/node_modules/@react-native/normalize-colors": { "version": "0.74.89", - "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.74.89.tgz", - "integrity": "sha512-qoMMXddVKVhZ8PA1AbUCk83trpd6N+1nF2A6k1i6LsQObyS92fELuk8kU/lQs6M7BsMHwqyLCpQJ1uFgNvIQXg==", "license": "MIT" }, "example/node_modules/react-native-webview": { "version": "13.16.0", - "resolved": "https://registry.npmjs.org/react-native-webview/-/react-native-webview-13.16.0.tgz", - "integrity": "sha512-Nh13xKZWW35C0dbOskD7OX01nQQavOzHbCw9XoZmar4eXCo7AvrYJ0jlUfRVVIJzqINxHlpECYLdmAdFsl9xDA==", "license": "MIT", "dependencies": { "escape-string-regexp": "^4.0.0", @@ -1655,8 +1486,6 @@ }, "example/node_modules/typescript": { "version": "5.9.3", - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", - "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", "dev": true, "license": "Apache-2.0", "bin": { @@ -1837,8 +1666,6 @@ }, "node_modules/@babel/generator": { "version": "7.29.1", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", - "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", "license": "MIT", "dependencies": { "@babel/parser": "^7.29.0", @@ -1946,8 +1773,6 @@ }, "node_modules/@babel/helper-globals": { "version": "7.28.0", - "resolved": "https://registry.npmjs.org/@babel/helper-globals/-/helper-globals-7.28.0.tgz", - "integrity": "sha512-+W6cISkXFa1jXsDEdYA8HeevQT/FULhxzR99pxphltZcVaugps53THCeiWA8SguxxpSp3gKPiuYfSWopkLQ4hw==", "license": "MIT", "engines": { "node": ">=6.9.0" @@ -2164,8 +1989,6 @@ }, "node_modules/@babel/parser": { "version": "7.29.2", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", - "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", "license": "MIT", "dependencies": { "@babel/types": "^7.29.0" @@ -2656,8 +2479,6 @@ }, "node_modules/@babel/plugin-transform-classes": { "version": "7.28.4", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.28.4.tgz", - "integrity": "sha512-cFOlhIYPBv/iBoc+KS3M6et2XPtbT2HiCRfBXWtfpc9OAyostldxIf9YAYB6ypURBBbx+Qv6nyrLzASfJe+hBA==", "license": "MIT", "dependencies": { "@babel/helper-annotate-as-pure": "^7.27.3", @@ -3551,8 +3372,6 @@ }, "node_modules/@babel/template": { "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", - "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", "license": "MIT", "dependencies": { "@babel/code-frame": "^7.28.6", @@ -3565,8 +3384,6 @@ }, "node_modules/@babel/template/node_modules/@babel/code-frame": { "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", - "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", "license": "MIT", "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", @@ -3579,8 +3396,6 @@ }, "node_modules/@babel/traverse": { "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", - "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", "license": "MIT", "dependencies": { "@babel/code-frame": "^7.29.0", @@ -3626,8 +3441,6 @@ }, "node_modules/@babel/traverse/node_modules/@babel/code-frame": { "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", - "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", "license": "MIT", "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", @@ -3640,8 +3453,6 @@ }, "node_modules/@babel/types": { "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", - "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", "license": "MIT", "dependencies": { "@babel/helper-string-parser": "^7.27.1", @@ -3654,8 +3465,7 @@ "node_modules/@bcoe/v8-coverage": { "version": "0.2.3", "devOptional": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/@changesets/apply-release-plan": { "version": "7.0.14", @@ -4153,8 +3963,6 @@ }, "node_modules/@expo-google-fonts/material-symbols": { "version": "0.4.27", - "resolved": "https://registry.npmjs.org/@expo-google-fonts/material-symbols/-/material-symbols-0.4.27.tgz", - "integrity": "sha512-cnb3DZnWUWpezGFkJ8y4MT5f/lw6FcgDzeJzic+T+vpQHLHG1cg3SC3i1w1i8Bk4xKR4HPY3t9iIRNvtr5ml8A==", "license": "MIT AND Apache-2.0" }, "node_modules/@expo-google-fonts/merriweather": { @@ -4308,8 +4116,6 @@ }, "node_modules/@expo/dom-webview": { "version": "55.0.3", - "resolved": "https://registry.npmjs.org/@expo/dom-webview/-/dom-webview-55.0.3.tgz", - "integrity": "sha512-bY4/rfcZ0f43DvOtMn8/kmPlmo01tex5hRoc5hKbwBwQjqWQuQt0ACwu7akR9IHI4j0WNG48eL6cZB6dZUFrzg==", "license": "MIT", "peerDependencies": { "expo": "*", @@ -4377,8 +4183,6 @@ }, "node_modules/@expo/local-build-cache-provider": { "version": "55.0.7", - "resolved": "https://registry.npmjs.org/@expo/local-build-cache-provider/-/local-build-cache-provider-55.0.7.tgz", - "integrity": "sha512-Qg9uNZn1buv4zJUA4ZQaz+ZnKDCipRgjoEg2Gcp8Qfy+2Gq5yZKX4YN1TThCJ01LJk/pvJsCRxXlXZSwdZppgg==", "license": "MIT", "dependencies": { "@expo/config": "~55.0.10", @@ -4387,8 +4191,6 @@ }, "node_modules/@expo/local-build-cache-provider/node_modules/@babel/code-frame": { "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", - "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", "license": "MIT", "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", @@ -4401,8 +4203,6 @@ }, "node_modules/@expo/local-build-cache-provider/node_modules/@expo/config": { "version": "55.0.11", - "resolved": "https://registry.npmjs.org/@expo/config/-/config-55.0.11.tgz", - "integrity": "sha512-14AkSmR1gOIUhCsPJ0cAo5ZduMNsPQsmFV9jBNZn1xC5Zb3D8x5eqvUie5QzWaUwdcyrq79uYJ2bTCiC6+nD0Q==", "license": "MIT", "dependencies": { "@expo/config-plugins": "~55.0.7", @@ -4420,8 +4220,6 @@ }, "node_modules/@expo/local-build-cache-provider/node_modules/@expo/config-plugins": { "version": "55.0.7", - "resolved": "https://registry.npmjs.org/@expo/config-plugins/-/config-plugins-55.0.7.tgz", - "integrity": "sha512-XZUoDWrsHEkH3yasnDSJABM/UxP5a1ixzRwU/M+BToyn/f0nTrSJJe/Ay/FpxkI4JSNz2n0e06I23b2bleXKVA==", "license": "MIT", "dependencies": { "@expo/config-types": "^55.0.5", @@ -4441,14 +4239,10 @@ }, "node_modules/@expo/local-build-cache-provider/node_modules/@expo/config-types": { "version": "55.0.5", - "resolved": "https://registry.npmjs.org/@expo/config-types/-/config-types-55.0.5.tgz", - "integrity": "sha512-sCmSUZG4mZ/ySXvfyyBdhjivz8Q539X1NondwDdYG7s3SBsk+wsgPJzYsqgAG/P9+l0xWjUD2F+kQ1cAJ6NNLg==", "license": "MIT" }, "node_modules/@expo/local-build-cache-provider/node_modules/@expo/json-file": { "version": "10.0.12", - "resolved": "https://registry.npmjs.org/@expo/json-file/-/json-file-10.0.12.tgz", - "integrity": "sha512-inbDycp1rMAelAofg7h/mMzIe+Owx6F7pur3XdQ3EPTy00tme+4P6FWgHKUcjN8dBSrnbRNpSyh5/shzHyVCyQ==", "license": "MIT", "dependencies": { "@babel/code-frame": "^7.20.0", @@ -4457,8 +4251,6 @@ }, "node_modules/@expo/local-build-cache-provider/node_modules/@expo/plist": { "version": "0.5.2", - "resolved": "https://registry.npmjs.org/@expo/plist/-/plist-0.5.2.tgz", - "integrity": "sha512-o4xdVdBpe4aTl3sPMZ2u3fJH4iG1I768EIRk1xRZP+GaFI93MaR3JvoFibYqxeTmLQ1p1kNEVqylfUjezxx45g==", "license": "MIT", "dependencies": { "@xmldom/xmldom": "^0.8.8", @@ -4468,8 +4260,6 @@ }, "node_modules/@expo/local-build-cache-provider/node_modules/balanced-match": { "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", "license": "MIT", "engines": { "node": "18 || 20 || >=22" @@ -4477,8 +4267,6 @@ }, "node_modules/@expo/local-build-cache-provider/node_modules/brace-expansion": { "version": "5.0.5", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.5.tgz", - "integrity": "sha512-VZznLgtwhn+Mact9tfiwx64fA9erHH/MCXEUfB/0bX/6Fz6ny5EGTXYltMocqg4xFAQZtnO3DHWWXi8RiuN7cQ==", "license": "MIT", "dependencies": { "balanced-match": "^4.0.2" @@ -4489,8 +4277,6 @@ }, "node_modules/@expo/local-build-cache-provider/node_modules/glob": { "version": "13.0.6", - "resolved": "https://registry.npmjs.org/glob/-/glob-13.0.6.tgz", - "integrity": "sha512-Wjlyrolmm8uDpm/ogGyXZXb1Z+Ca2B8NbJwqBVg0axK9GbBeoS7yGV6vjXnYdGm6X53iehEuxxbyiKp8QmN4Vw==", "license": "BlueOak-1.0.0", "dependencies": { "minimatch": "^10.2.2", @@ -4506,8 +4292,6 @@ }, "node_modules/@expo/local-build-cache-provider/node_modules/lru-cache": { "version": "11.2.7", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.2.7.tgz", - "integrity": "sha512-aY/R+aEsRelme17KGQa/1ZSIpLpNYYrhcrepKTZgE+W3WM16YMCaPwOHLHsmopZHELU0Ojin1lPVxKR0MihncA==", "license": "BlueOak-1.0.0", "engines": { "node": "20 || >=22" @@ -4515,8 +4299,6 @@ }, "node_modules/@expo/local-build-cache-provider/node_modules/minimatch": { "version": "10.2.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", - "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", "license": "BlueOak-1.0.0", "dependencies": { "brace-expansion": "^5.0.5" @@ -4530,8 +4312,6 @@ }, "node_modules/@expo/local-build-cache-provider/node_modules/path-scurry": { "version": "2.0.2", - "resolved": "https://registry.npmjs.org/path-scurry/-/path-scurry-2.0.2.tgz", - "integrity": "sha512-3O/iVVsJAPsOnpwWIeD+d6z/7PmqApyQePUtCndjatj/9I5LylHvt5qluFaBT3I5h3r1ejfR056c+FCv+NnNXg==", "license": "BlueOak-1.0.0", "dependencies": { "lru-cache": "^11.0.0", @@ -4546,8 +4326,6 @@ }, "node_modules/@expo/log-box": { "version": "55.0.8", - "resolved": "https://registry.npmjs.org/@expo/log-box/-/log-box-55.0.8.tgz", - "integrity": "sha512-WVEuW1XcntUdOpQk8k9cUymM5FHKmEcPr6QO9SVIin3WYk5FbbwHRYr1T6GfwWF0UA2s9w9heeYolesq99vFIw==", "license": "MIT", "dependencies": { "@expo/dom-webview": "^55.0.3", @@ -4563,8 +4341,6 @@ }, "node_modules/@expo/metro": { "version": "54.2.0", - "resolved": "https://registry.npmjs.org/@expo/metro/-/metro-54.2.0.tgz", - "integrity": "sha512-h68TNZPGsk6swMmLm9nRSnE2UXm48rWwgcbtAHVMikXvbxdS41NDHHeqg1rcQ9AbznDRp6SQVC2MVpDnsRKU1w==", "license": "MIT", "dependencies": { "metro": "0.83.3", @@ -4611,8 +4387,6 @@ }, "node_modules/@expo/metro/node_modules/@babel/code-frame": { "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", - "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", "license": "MIT", "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", @@ -4625,8 +4399,6 @@ }, "node_modules/@expo/metro/node_modules/agent-base": { "version": "7.1.4", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", - "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", "license": "MIT", "engines": { "node": ">= 14" @@ -4634,20 +4406,14 @@ }, "node_modules/@expo/metro/node_modules/ci-info": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", - "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", "license": "MIT" }, "node_modules/@expo/metro/node_modules/hermes-estree": { "version": "0.32.0", - "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.32.0.tgz", - "integrity": "sha512-KWn3BqnlDOl97Xe1Yviur6NbgIZ+IP+UVSpshlZWkq+EtoHg6/cwiDj/osP9PCEgFE15KBm1O55JRwbMEm5ejQ==", "license": "MIT" }, "node_modules/@expo/metro/node_modules/hermes-parser": { "version": "0.32.0", - "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.32.0.tgz", - "integrity": "sha512-g4nBOWFpuiTqjR3LZdRxKUkij9iyveWeuks7INEsMX741f3r9xxrOe8TeQfUxtda0eXmiIFiMQzoeSQEno33Hw==", "license": "MIT", "dependencies": { "hermes-estree": "0.32.0" @@ -4655,8 +4421,6 @@ }, "node_modules/@expo/metro/node_modules/https-proxy-agent": { "version": "7.0.6", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", - "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", "license": "MIT", "dependencies": { "agent-base": "^7.1.2", @@ -4668,8 +4432,6 @@ }, "node_modules/@expo/metro/node_modules/metro": { "version": "0.83.3", - "resolved": "https://registry.npmjs.org/metro/-/metro-0.83.3.tgz", - "integrity": "sha512-+rP+/GieOzkt97hSJ0MrPOuAH/jpaS21ZDvL9DJ35QYRDlQcwzcvUlGUf79AnQxq/2NPiS/AULhhM4TKutIt8Q==", "license": "MIT", "dependencies": { "@babel/code-frame": "^7.24.7", @@ -4722,8 +4484,6 @@ }, "node_modules/@expo/metro/node_modules/metro-babel-transformer": { "version": "0.83.3", - "resolved": "https://registry.npmjs.org/metro-babel-transformer/-/metro-babel-transformer-0.83.3.tgz", - "integrity": "sha512-1vxlvj2yY24ES1O5RsSIvg4a4WeL7PFXgKOHvXTXiW0deLvQr28ExXj6LjwCCDZ4YZLhq6HddLpZnX4dEdSq5g==", "license": "MIT", "dependencies": { "@babel/core": "^7.25.2", @@ -4737,8 +4497,6 @@ }, "node_modules/@expo/metro/node_modules/metro-cache": { "version": "0.83.3", - "resolved": "https://registry.npmjs.org/metro-cache/-/metro-cache-0.83.3.tgz", - "integrity": "sha512-3jo65X515mQJvKqK3vWRblxDEcgY55Sk3w4xa6LlfEXgQ9g1WgMh9m4qVZVwgcHoLy0a2HENTPCCX4Pk6s8c8Q==", "license": "MIT", "dependencies": { "exponential-backoff": "^3.1.1", @@ -4752,8 +4510,6 @@ }, "node_modules/@expo/metro/node_modules/metro-cache-key": { "version": "0.83.3", - "resolved": "https://registry.npmjs.org/metro-cache-key/-/metro-cache-key-0.83.3.tgz", - "integrity": "sha512-59ZO049jKzSmvBmG/B5bZ6/dztP0ilp0o988nc6dpaDsU05Cl1c/lRf+yx8m9WW/JVgbmfO5MziBU559XjI5Zw==", "license": "MIT", "dependencies": { "flow-enums-runtime": "^0.0.6" @@ -4764,8 +4520,6 @@ }, "node_modules/@expo/metro/node_modules/metro-config": { "version": "0.83.3", - "resolved": "https://registry.npmjs.org/metro-config/-/metro-config-0.83.3.tgz", - "integrity": "sha512-mTel7ipT0yNjKILIan04bkJkuCzUUkm2SeEaTads8VfEecCh+ltXchdq6DovXJqzQAXuR2P9cxZB47Lg4klriA==", "license": "MIT", "dependencies": { "connect": "^3.6.5", @@ -4783,8 +4537,6 @@ }, "node_modules/@expo/metro/node_modules/metro-core": { "version": "0.83.3", - "resolved": "https://registry.npmjs.org/metro-core/-/metro-core-0.83.3.tgz", - "integrity": "sha512-M+X59lm7oBmJZamc96usuF1kusd5YimqG/q97g4Ac7slnJ3YiGglW5CsOlicTR5EWf8MQFxxjDoB6ytTqRe8Hw==", "license": "MIT", "dependencies": { "flow-enums-runtime": "^0.0.6", @@ -4797,8 +4549,6 @@ }, "node_modules/@expo/metro/node_modules/metro-file-map": { "version": "0.83.3", - "resolved": "https://registry.npmjs.org/metro-file-map/-/metro-file-map-0.83.3.tgz", - "integrity": "sha512-jg5AcyE0Q9Xbbu/4NAwwZkmQn7doJCKGW0SLeSJmzNB9Z24jBe0AL2PHNMy4eu0JiKtNWHz9IiONGZWq7hjVTA==", "license": "MIT", "dependencies": { "debug": "^4.4.0", @@ -4817,8 +4567,6 @@ }, "node_modules/@expo/metro/node_modules/metro-minify-terser": { "version": "0.83.3", - "resolved": "https://registry.npmjs.org/metro-minify-terser/-/metro-minify-terser-0.83.3.tgz", - "integrity": "sha512-O2BmfWj6FSfzBLrNCXt/rr2VYZdX5i6444QJU0fFoc7Ljg+Q+iqebwE3K0eTvkI6TRjELsXk1cjU+fXwAR4OjQ==", "license": "MIT", "dependencies": { "flow-enums-runtime": "^0.0.6", @@ -4830,8 +4578,6 @@ }, "node_modules/@expo/metro/node_modules/metro-resolver": { "version": "0.83.3", - "resolved": "https://registry.npmjs.org/metro-resolver/-/metro-resolver-0.83.3.tgz", - "integrity": "sha512-0js+zwI5flFxb1ktmR///bxHYg7OLpRpWZlBBruYG8OKYxeMP7SV0xQ/o/hUelrEMdK4LJzqVtHAhBm25LVfAQ==", "license": "MIT", "dependencies": { "flow-enums-runtime": "^0.0.6" @@ -4842,8 +4588,6 @@ }, "node_modules/@expo/metro/node_modules/metro-runtime": { "version": "0.83.3", - "resolved": "https://registry.npmjs.org/metro-runtime/-/metro-runtime-0.83.3.tgz", - "integrity": "sha512-JHCJb9ebr9rfJ+LcssFYA2x1qPYuSD/bbePupIGhpMrsla7RCwC/VL3yJ9cSU+nUhU4c9Ixxy8tBta+JbDeZWw==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.25.0", @@ -4855,8 +4599,6 @@ }, "node_modules/@expo/metro/node_modules/metro-source-map": { "version": "0.83.3", - "resolved": "https://registry.npmjs.org/metro-source-map/-/metro-source-map-0.83.3.tgz", - "integrity": "sha512-xkC3qwUBh2psVZgVavo8+r2C9Igkk3DibiOXSAht1aYRRcztEZNFtAMtfSB7sdO2iFMx2Mlyu++cBxz/fhdzQg==", "license": "MIT", "dependencies": { "@babel/traverse": "^7.25.3", @@ -4876,8 +4618,6 @@ }, "node_modules/@expo/metro/node_modules/metro-symbolicate": { "version": "0.83.3", - "resolved": "https://registry.npmjs.org/metro-symbolicate/-/metro-symbolicate-0.83.3.tgz", - "integrity": "sha512-F/YChgKd6KbFK3eUR5HdUsfBqVsanf5lNTwFd4Ca7uuxnHgBC3kR/Hba/RGkenR3pZaGNp5Bu9ZqqP52Wyhomw==", "license": "MIT", "dependencies": { "flow-enums-runtime": "^0.0.6", @@ -4896,8 +4636,6 @@ }, "node_modules/@expo/metro/node_modules/metro-transform-plugins": { "version": "0.83.3", - "resolved": "https://registry.npmjs.org/metro-transform-plugins/-/metro-transform-plugins-0.83.3.tgz", - "integrity": "sha512-eRGoKJU6jmqOakBMH5kUB7VitEWiNrDzBHpYbkBXW7C5fUGeOd2CyqrosEzbMK5VMiZYyOcNFEphvxk3OXey2A==", "license": "MIT", "dependencies": { "@babel/core": "^7.25.2", @@ -4913,8 +4651,6 @@ }, "node_modules/@expo/metro/node_modules/metro-transform-worker": { "version": "0.83.3", - "resolved": "https://registry.npmjs.org/metro-transform-worker/-/metro-transform-worker-0.83.3.tgz", - "integrity": "sha512-Ztekew9t/gOIMZX1tvJOgX7KlSLL5kWykl0Iwu2cL2vKMKVALRl1hysyhUw0vjpAvLFx+Kfq9VLjnHIkW32fPA==", "license": "MIT", "dependencies": { "@babel/core": "^7.25.2", @@ -4937,8 +4673,6 @@ }, "node_modules/@expo/metro/node_modules/ob1": { "version": "0.83.3", - "resolved": "https://registry.npmjs.org/ob1/-/ob1-0.83.3.tgz", - "integrity": "sha512-egUxXCDwoWG06NGCS5s5AdcpnumHKJlfd3HH06P3m9TEMwwScfcY35wpQxbm9oHof+dM/lVH9Rfyu1elTVelSA==", "license": "MIT", "dependencies": { "flow-enums-runtime": "^0.0.6" @@ -4949,8 +4683,6 @@ }, "node_modules/@expo/metro/node_modules/source-map": { "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -4958,8 +4690,6 @@ }, "node_modules/@expo/metro/node_modules/ws": { "version": "7.5.10", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", - "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", "license": "MIT", "engines": { "node": ">=8.3.0" @@ -5066,8 +4796,6 @@ }, "node_modules/@expo/require-utils": { "version": "55.0.3", - "resolved": "https://registry.npmjs.org/@expo/require-utils/-/require-utils-55.0.3.tgz", - "integrity": "sha512-TS1m5tW45q4zoaTlt6DwmdYHxvFTIxoLrTHKOFrIirHIqIXnHCzpceg8wumiBi+ZXSaGY2gobTbfv+WVhJY6Fw==", "license": "MIT", "dependencies": { "@babel/code-frame": "^7.20.0", @@ -5085,8 +4813,6 @@ }, "node_modules/@expo/require-utils/node_modules/@babel/code-frame": { "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", - "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", "license": "MIT", "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", @@ -5402,7 +5128,6 @@ "version": "29.7.0", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/console": "^29.7.0", "@jest/reporters": "^29.7.0", @@ -5449,7 +5174,6 @@ "version": "6.0.1", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "ansi-regex": "^5.0.1" }, @@ -5556,7 +5280,6 @@ "version": "29.7.0", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "@bcoe/v8-coverage": "^0.2.3", "@jest/console": "^29.7.0", @@ -5599,7 +5322,6 @@ "version": "1.1.12", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -5609,7 +5331,6 @@ "version": "7.2.3", "devOptional": true, "license": "ISC", - "peer": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -5629,7 +5350,6 @@ "version": "3.1.5", "devOptional": true, "license": "ISC", - "peer": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -5641,7 +5361,6 @@ "version": "6.0.1", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "ansi-regex": "^5.0.1" }, @@ -5663,7 +5382,6 @@ "version": "29.6.3", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "@jridgewell/trace-mapping": "^0.3.18", "callsites": "^3.0.0", @@ -5691,7 +5409,6 @@ "version": "29.7.0", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/test-result": "^29.7.0", "graceful-fs": "^4.2.9", @@ -5743,8 +5460,6 @@ }, "node_modules/@jridgewell/gen-mapping": { "version": "0.3.13", - "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.13.tgz", - "integrity": "sha512-2kkt/7niJ6MgEPxF0bYdQ6etZaA+fQvDcLKckhy1yIQOzaoKjBBjSj63/aLVjYE3qhRt5dvM+uUyfCg6UKCBbA==", "license": "MIT", "dependencies": { "@jridgewell/sourcemap-codec": "^1.5.0", @@ -5772,8 +5487,6 @@ }, "node_modules/@jridgewell/trace-mapping": { "version": "0.3.31", - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", - "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", "license": "MIT", "dependencies": { "@jridgewell/resolve-uri": "^3.1.0", @@ -6284,14 +5997,10 @@ }, "node_modules/@radix-ui/primitive": { "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@radix-ui/primitive/-/primitive-1.1.3.tgz", - "integrity": "sha512-JTF99U/6XIjCBo0wqkU5sK10glYe27MRRsfwoiq5zzOEZLHU3A3KCMa5X/azekYRCJ0HlwI0crAXS/5dEHTzDg==", "license": "MIT" }, "node_modules/@radix-ui/react-collection": { "version": "1.1.7", - "resolved": "https://registry.npmjs.org/@radix-ui/react-collection/-/react-collection-1.1.7.tgz", - "integrity": "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw==", "license": "MIT", "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", @@ -6329,8 +6038,6 @@ }, "node_modules/@radix-ui/react-context": { "version": "1.1.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-context/-/react-context-1.1.2.tgz", - "integrity": "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA==", "license": "MIT", "peerDependencies": { "@types/react": "*", @@ -6344,8 +6051,6 @@ }, "node_modules/@radix-ui/react-dialog": { "version": "1.1.15", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dialog/-/react-dialog-1.1.15.tgz", - "integrity": "sha512-TCglVRtzlffRNxRMEyR36DGBLJpeusFcgMVD9PZEzAKnUs1lKCgX5u9BmC2Yg+LL9MgZDugFFs1Vl+Jp4t/PGw==", "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.3", @@ -6380,8 +6085,6 @@ }, "node_modules/@radix-ui/react-direction": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-direction/-/react-direction-1.1.1.tgz", - "integrity": "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw==", "license": "MIT", "peerDependencies": { "@types/react": "*", @@ -6395,8 +6098,6 @@ }, "node_modules/@radix-ui/react-dismissable-layer": { "version": "1.1.11", - "resolved": "https://registry.npmjs.org/@radix-ui/react-dismissable-layer/-/react-dismissable-layer-1.1.11.tgz", - "integrity": "sha512-Nqcp+t5cTB8BinFkZgXiMJniQH0PsUt2k51FUhbdfeKvc4ACcG2uQniY/8+h1Yv6Kza4Q7lD7PQV0z0oicE0Mg==", "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.3", @@ -6422,8 +6123,6 @@ }, "node_modules/@radix-ui/react-focus-guards": { "version": "1.1.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-guards/-/react-focus-guards-1.1.3.tgz", - "integrity": "sha512-0rFg/Rj2Q62NCm62jZw0QX7a3sz6QCQU0LpZdNrJX8byRGaGVTqbrW9jAoIAHyMQqsNpeZ81YgSizOt5WXq0Pw==", "license": "MIT", "peerDependencies": { "@types/react": "*", @@ -6437,8 +6136,6 @@ }, "node_modules/@radix-ui/react-focus-scope": { "version": "1.1.7", - "resolved": "https://registry.npmjs.org/@radix-ui/react-focus-scope/-/react-focus-scope-1.1.7.tgz", - "integrity": "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw==", "license": "MIT", "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", @@ -6462,8 +6159,6 @@ }, "node_modules/@radix-ui/react-id": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-id/-/react-id-1.1.1.tgz", - "integrity": "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg==", "license": "MIT", "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.1" @@ -6480,8 +6175,6 @@ }, "node_modules/@radix-ui/react-portal": { "version": "1.1.9", - "resolved": "https://registry.npmjs.org/@radix-ui/react-portal/-/react-portal-1.1.9.tgz", - "integrity": "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ==", "license": "MIT", "dependencies": { "@radix-ui/react-primitive": "2.1.3", @@ -6504,8 +6197,6 @@ }, "node_modules/@radix-ui/react-presence": { "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@radix-ui/react-presence/-/react-presence-1.1.5.tgz", - "integrity": "sha512-/jfEwNDdQVBCNvjkGit4h6pMOzq8bHkopq458dPt2lMjx+eBQUohZNG9A7DtO/O5ukSbxuaNGXMjHicgwy6rQQ==", "license": "MIT", "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", @@ -6528,8 +6219,6 @@ }, "node_modules/@radix-ui/react-primitive": { "version": "2.1.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-primitive/-/react-primitive-2.1.3.tgz", - "integrity": "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ==", "license": "MIT", "dependencies": { "@radix-ui/react-slot": "1.2.3" @@ -6551,8 +6240,6 @@ }, "node_modules/@radix-ui/react-roving-focus": { "version": "1.1.11", - "resolved": "https://registry.npmjs.org/@radix-ui/react-roving-focus/-/react-roving-focus-1.1.11.tgz", - "integrity": "sha512-7A6S9jSgm/S+7MdtNDSb+IU859vQqJ/QAtcYQcfFC6W8RS4IxIZDldLR0xqCFZ6DCyrQLjLPsxtTNch5jVA4lA==", "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.3", @@ -6582,8 +6269,6 @@ }, "node_modules/@radix-ui/react-slot": { "version": "1.2.3", - "resolved": "https://registry.npmjs.org/@radix-ui/react-slot/-/react-slot-1.2.3.tgz", - "integrity": "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A==", "license": "MIT", "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" @@ -6600,8 +6285,6 @@ }, "node_modules/@radix-ui/react-tabs": { "version": "1.1.13", - "resolved": "https://registry.npmjs.org/@radix-ui/react-tabs/-/react-tabs-1.1.13.tgz", - "integrity": "sha512-7xdcatg7/U+7+Udyoj2zodtI9H/IIopqo+YOIcZOq1nJwXWBZ9p8xiu5llXlekDbZkca79a/fozEYQXIA4sW6A==", "license": "MIT", "dependencies": { "@radix-ui/primitive": "1.1.3", @@ -6630,8 +6313,6 @@ }, "node_modules/@radix-ui/react-use-callback-ref": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-callback-ref/-/react-use-callback-ref-1.1.1.tgz", - "integrity": "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg==", "license": "MIT", "peerDependencies": { "@types/react": "*", @@ -6645,8 +6326,6 @@ }, "node_modules/@radix-ui/react-use-controllable-state": { "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-controllable-state/-/react-use-controllable-state-1.2.2.tgz", - "integrity": "sha512-BjasUjixPFdS+NKkypcyyN5Pmg83Olst0+c6vGov0diwTEo6mgdqVR6hxcEgFuh4QrAs7Rc+9KuGJ9TVCj0Zzg==", "license": "MIT", "dependencies": { "@radix-ui/react-use-effect-event": "0.0.2", @@ -6664,8 +6343,6 @@ }, "node_modules/@radix-ui/react-use-effect-event": { "version": "0.0.2", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-effect-event/-/react-use-effect-event-0.0.2.tgz", - "integrity": "sha512-Qp8WbZOBe+blgpuUT+lw2xheLP8q0oatc9UpmiemEICxGvFLYmHm9QowVZGHtJlGbS6A6yJ3iViad/2cVjnOiA==", "license": "MIT", "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.1" @@ -6682,8 +6359,6 @@ }, "node_modules/@radix-ui/react-use-escape-keydown": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-escape-keydown/-/react-use-escape-keydown-1.1.1.tgz", - "integrity": "sha512-Il0+boE7w/XebUHyBjroE+DbByORGR9KKmITzbR7MyQ4akpORYP/ZmbhAr0DG7RmmBqoOnZdy2QlvajJ2QA59g==", "license": "MIT", "dependencies": { "@radix-ui/react-use-callback-ref": "1.1.1" @@ -6700,8 +6375,6 @@ }, "node_modules/@radix-ui/react-use-layout-effect": { "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@radix-ui/react-use-layout-effect/-/react-use-layout-effect-1.1.1.tgz", - "integrity": "sha512-RbJRS4UWQFkzHTTwVymMTUv8EqYhOp8dOOviLj2ugtTiXRaRQS7GLGxZTLL1jWhMeoSCf5zmcZkqTl9IiYfXcQ==", "license": "MIT", "peerDependencies": { "@types/react": "*", @@ -7104,8 +6777,6 @@ }, "node_modules/@react-native/assets-registry": { "version": "0.83.2", - "resolved": "https://registry.npmjs.org/@react-native/assets-registry/-/assets-registry-0.83.2.tgz", - "integrity": "sha512-9I5l3pGAKnlpQ15uVkeB9Mgjvt3cZEaEc8EDtdexvdtZvLSjtwBzgourrOW4yZUijbjJr8h3YO2Y0q+THwUHTA==", "license": "MIT", "engines": { "node": ">= 20.19.4" @@ -7236,8 +6907,6 @@ }, "node_modules/@react-native/community-cli-plugin": { "version": "0.83.2", - "resolved": "https://registry.npmjs.org/@react-native/community-cli-plugin/-/community-cli-plugin-0.83.2.tgz", - "integrity": "sha512-sTEF0eiUKtmImEP07Qo5c3Khvm1LIVX1Qyb6zWUqPL6W3MqFiXutZvKBjqLz6p49Szx8cplQLoXfLHT0bcDXKg==", "license": "MIT", "dependencies": { "@react-native/dev-middleware": "0.83.2", @@ -7266,8 +6935,6 @@ }, "node_modules/@react-native/community-cli-plugin/node_modules/@babel/code-frame": { "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.29.0.tgz", - "integrity": "sha512-9NhCeYjq9+3uxgdtp20LSiJXJvN0FeCtNGpJxuMFZ1Kv3cWUNb6DOhJwUvcVCzKGR66cw4njwM6hrJLqgOwbcw==", "license": "MIT", "dependencies": { "@babel/helper-validator-identifier": "^7.28.5", @@ -7280,8 +6947,6 @@ }, "node_modules/@react-native/community-cli-plugin/node_modules/@react-native/debugger-frontend": { "version": "0.83.2", - "resolved": "https://registry.npmjs.org/@react-native/debugger-frontend/-/debugger-frontend-0.83.2.tgz", - "integrity": "sha512-t4fYfa7xopbUF5S4+ihNEwgaq4wLZLKLY0Ms8z72lkMteVd3bOX2Foxa8E2wTfRvdhPOkSpOsTeNDmD8ON4DoQ==", "license": "BSD-3-Clause", "engines": { "node": ">= 20.19.4" @@ -7289,8 +6954,6 @@ }, "node_modules/@react-native/community-cli-plugin/node_modules/@react-native/dev-middleware": { "version": "0.83.2", - "resolved": "https://registry.npmjs.org/@react-native/dev-middleware/-/dev-middleware-0.83.2.tgz", - "integrity": "sha512-Zi4EVaAm28+icD19NN07Gh8Pqg/84QQu+jn4patfWKNkcToRFP5vPEbbp0eLOGWS+BVB1d1Fn5lvMrJsBbFcOg==", "license": "MIT", "dependencies": { "@isaacs/ttlcache": "^1.4.1", @@ -7312,8 +6975,6 @@ }, "node_modules/@react-native/community-cli-plugin/node_modules/accepts": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", - "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", "license": "MIT", "dependencies": { "mime-types": "^3.0.0", @@ -7325,8 +6986,6 @@ }, "node_modules/@react-native/community-cli-plugin/node_modules/agent-base": { "version": "7.1.4", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", - "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==", "license": "MIT", "engines": { "node": ">= 14" @@ -7334,20 +6993,14 @@ }, "node_modules/@react-native/community-cli-plugin/node_modules/ci-info": { "version": "2.0.0", - "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-2.0.0.tgz", - "integrity": "sha512-5tK7EtrZ0N+OLFMthtqOj4fI2Jeb88C4CAZPu25LDVUgXJ0A3Js4PMGqrn0JU1W0Mh1/Z8wZzYPxqUrXeBboCQ==", "license": "MIT" }, "node_modules/@react-native/community-cli-plugin/node_modules/hermes-estree": { "version": "0.33.3", - "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.33.3.tgz", - "integrity": "sha512-6kzYZHCk8Fy1Uc+t3HGYyJn3OL4aeqKLTyina4UFtWl8I0kSL7OmKThaiX+Uh2f8nGw3mo4Ifxg0M5Zk3/Oeqg==", "license": "MIT" }, "node_modules/@react-native/community-cli-plugin/node_modules/hermes-parser": { "version": "0.33.3", - "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.33.3.tgz", - "integrity": "sha512-Yg3HgaG4CqgyowtYjX/FsnPAuZdHOqSMtnbpylbptsQ9nwwSKsy6uRWcGO5RK0EqiX12q8HvDWKgeAVajRO5DA==", "license": "MIT", "dependencies": { "hermes-estree": "0.33.3" @@ -7355,8 +7008,6 @@ }, "node_modules/@react-native/community-cli-plugin/node_modules/https-proxy-agent": { "version": "7.0.6", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", - "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", "license": "MIT", "dependencies": { "agent-base": "^7.1.2", @@ -7368,8 +7019,6 @@ }, "node_modules/@react-native/community-cli-plugin/node_modules/metro": { "version": "0.83.5", - "resolved": "https://registry.npmjs.org/metro/-/metro-0.83.5.tgz", - "integrity": "sha512-BgsXevY1MBac/3ZYv/RfNFf/4iuW9X7f4H8ZNkiH+r667HD9sVujxcmu4jvEzGCAm4/WyKdZCuyhAcyhTHOucQ==", "license": "MIT", "dependencies": { "@babel/code-frame": "^7.29.0", @@ -7422,8 +7071,6 @@ }, "node_modules/@react-native/community-cli-plugin/node_modules/metro-babel-transformer": { "version": "0.83.5", - "resolved": "https://registry.npmjs.org/metro-babel-transformer/-/metro-babel-transformer-0.83.5.tgz", - "integrity": "sha512-d9FfmgUEVejTiSb7bkQeLRGl6aeno2UpuPm3bo3rCYwxewj03ymvOn8s8vnS4fBqAPQ+cE9iQM40wh7nGXR+eA==", "license": "MIT", "dependencies": { "@babel/core": "^7.25.2", @@ -7437,8 +7084,6 @@ }, "node_modules/@react-native/community-cli-plugin/node_modules/metro-cache": { "version": "0.83.5", - "resolved": "https://registry.npmjs.org/metro-cache/-/metro-cache-0.83.5.tgz", - "integrity": "sha512-oH+s4U+IfZyg8J42bne2Skc90rcuESIYf86dYittcdWQtPfcaFXWpByPyTuWk3rR1Zz3Eh5HOrcVImfEhhJLng==", "license": "MIT", "dependencies": { "exponential-backoff": "^3.1.1", @@ -7452,8 +7097,6 @@ }, "node_modules/@react-native/community-cli-plugin/node_modules/metro-cache-key": { "version": "0.83.5", - "resolved": "https://registry.npmjs.org/metro-cache-key/-/metro-cache-key-0.83.5.tgz", - "integrity": "sha512-Ycl8PBajB7bhbAI7Rt0xEyiF8oJ0RWX8EKkolV1KfCUlC++V/GStMSGpPLwnnBZXZWkCC5edBPzv1Hz1Yi0Euw==", "license": "MIT", "dependencies": { "flow-enums-runtime": "^0.0.6" @@ -7464,8 +7107,6 @@ }, "node_modules/@react-native/community-cli-plugin/node_modules/metro-config": { "version": "0.83.5", - "resolved": "https://registry.npmjs.org/metro-config/-/metro-config-0.83.5.tgz", - "integrity": "sha512-JQ/PAASXH7yczgV6OCUSRhZYME+NU8NYjI2RcaG5ga4QfQ3T/XdiLzpSb3awWZYlDCcQb36l4Vl7i0Zw7/Tf9w==", "license": "MIT", "dependencies": { "connect": "^3.6.5", @@ -7483,8 +7124,6 @@ }, "node_modules/@react-native/community-cli-plugin/node_modules/metro-core": { "version": "0.83.5", - "resolved": "https://registry.npmjs.org/metro-core/-/metro-core-0.83.5.tgz", - "integrity": "sha512-YcVcLCrf0ed4mdLa82Qob0VxYqfhmlRxUS8+TO4gosZo/gLwSvtdeOjc/Vt0pe/lvMNrBap9LlmvZM8FIsMgJQ==", "license": "MIT", "dependencies": { "flow-enums-runtime": "^0.0.6", @@ -7497,8 +7136,6 @@ }, "node_modules/@react-native/community-cli-plugin/node_modules/metro-file-map": { "version": "0.83.5", - "resolved": "https://registry.npmjs.org/metro-file-map/-/metro-file-map-0.83.5.tgz", - "integrity": "sha512-ZEt8s3a1cnYbn40nyCD+CsZdYSlwtFh2kFym4lo+uvfM+UMMH+r/BsrC6rbNClSrt+B7rU9T+Te/sh/NL8ZZKQ==", "license": "MIT", "dependencies": { "debug": "^4.4.0", @@ -7517,8 +7154,6 @@ }, "node_modules/@react-native/community-cli-plugin/node_modules/metro-minify-terser": { "version": "0.83.5", - "resolved": "https://registry.npmjs.org/metro-minify-terser/-/metro-minify-terser-0.83.5.tgz", - "integrity": "sha512-Toe4Md1wS1PBqbvB0cFxBzKEVyyuYTUb0sgifAZh/mSvLH84qA1NAWik9sISWatzvfWf3rOGoUoO5E3f193a3Q==", "license": "MIT", "dependencies": { "flow-enums-runtime": "^0.0.6", @@ -7530,8 +7165,6 @@ }, "node_modules/@react-native/community-cli-plugin/node_modules/metro-resolver": { "version": "0.83.5", - "resolved": "https://registry.npmjs.org/metro-resolver/-/metro-resolver-0.83.5.tgz", - "integrity": "sha512-7p3GtzVUpbAweJeCcUJihJeOQl1bDuimO5ueo1K0BUpUtR41q5EilbQ3klt16UTPPMpA+tISWBtsrqU556mY1A==", "license": "MIT", "dependencies": { "flow-enums-runtime": "^0.0.6" @@ -7542,8 +7175,6 @@ }, "node_modules/@react-native/community-cli-plugin/node_modules/metro-runtime": { "version": "0.83.5", - "resolved": "https://registry.npmjs.org/metro-runtime/-/metro-runtime-0.83.5.tgz", - "integrity": "sha512-f+b3ue9AWTVlZe2Xrki6TAoFtKIqw30jwfk7GQ1rDUBQaE0ZQ+NkiMEtb9uwH7uAjJ87U7Tdx1Jg1OJqUfEVlA==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.25.0", @@ -7555,8 +7186,6 @@ }, "node_modules/@react-native/community-cli-plugin/node_modules/metro-source-map": { "version": "0.83.5", - "resolved": "https://registry.npmjs.org/metro-source-map/-/metro-source-map-0.83.5.tgz", - "integrity": "sha512-VT9bb2KO2/4tWY9Z2yeZqTUao7CicKAOps9LUg2aQzsz+04QyuXL3qgf1cLUVRjA/D6G5u1RJAlN1w9VNHtODQ==", "license": "MIT", "dependencies": { "@babel/traverse": "^7.29.0", @@ -7575,8 +7204,6 @@ }, "node_modules/@react-native/community-cli-plugin/node_modules/metro-symbolicate": { "version": "0.83.5", - "resolved": "https://registry.npmjs.org/metro-symbolicate/-/metro-symbolicate-0.83.5.tgz", - "integrity": "sha512-EMIkrjNRz/hF+p0RDdxoE60+dkaTLPN3vaaGkFmX5lvFdO6HPfHA/Ywznzkev+za0VhPQ5KSdz49/MALBRteHA==", "license": "MIT", "dependencies": { "flow-enums-runtime": "^0.0.6", @@ -7595,8 +7222,6 @@ }, "node_modules/@react-native/community-cli-plugin/node_modules/metro-transform-plugins": { "version": "0.83.5", - "resolved": "https://registry.npmjs.org/metro-transform-plugins/-/metro-transform-plugins-0.83.5.tgz", - "integrity": "sha512-KxYKzZL+lt3Os5H2nx7YkbkWVduLZL5kPrE/Yq+Prm/DE1VLhpfnO6HtPs8vimYFKOa58ncl60GpoX0h7Wm0Vw==", "license": "MIT", "dependencies": { "@babel/core": "^7.25.2", @@ -7612,8 +7237,6 @@ }, "node_modules/@react-native/community-cli-plugin/node_modules/metro-transform-worker": { "version": "0.83.5", - "resolved": "https://registry.npmjs.org/metro-transform-worker/-/metro-transform-worker-0.83.5.tgz", - "integrity": "sha512-8N4pjkNXc6ytlP9oAM6MwqkvUepNSW39LKYl9NjUMpRDazBQ7oBpQDc8Sz4aI8jnH6AGhF7s1m/ayxkN1t04yA==", "license": "MIT", "dependencies": { "@babel/core": "^7.25.2", @@ -7636,8 +7259,6 @@ }, "node_modules/@react-native/community-cli-plugin/node_modules/mime-types": { "version": "3.0.2", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", - "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", "license": "MIT", "dependencies": { "mime-db": "^1.54.0" @@ -7652,8 +7273,6 @@ }, "node_modules/@react-native/community-cli-plugin/node_modules/negotiator": { "version": "1.0.0", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", - "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", "license": "MIT", "engines": { "node": ">= 0.6" @@ -7661,8 +7280,6 @@ }, "node_modules/@react-native/community-cli-plugin/node_modules/ob1": { "version": "0.83.5", - "resolved": "https://registry.npmjs.org/ob1/-/ob1-0.83.5.tgz", - "integrity": "sha512-vNKPYC8L5ycVANANpF/S+WZHpfnRWKx/F3AYP4QMn6ZJTh+l2HOrId0clNkEmua58NB9vmI9Qh7YOoV/4folYg==", "license": "MIT", "dependencies": { "flow-enums-runtime": "^0.0.6" @@ -7673,8 +7290,6 @@ }, "node_modules/@react-native/community-cli-plugin/node_modules/source-map": { "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -7682,8 +7297,6 @@ }, "node_modules/@react-native/community-cli-plugin/node_modules/ws": { "version": "7.5.10", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", - "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", "license": "MIT", "engines": { "node": ">=8.3.0" @@ -7711,8 +7324,6 @@ }, "node_modules/@react-native/debugger-shell": { "version": "0.83.2", - "resolved": "https://registry.npmjs.org/@react-native/debugger-shell/-/debugger-shell-0.83.2.tgz", - "integrity": "sha512-z9go6NJMsLSDJT5MW6VGugRsZHjYvUTwxtsVc3uLt4U9W6T3J6FWI2wHpXIzd2dUkXRfAiRQ3Zi8ZQQ8fRFg9A==", "license": "MIT", "dependencies": { "cross-spawn": "^7.0.6", @@ -7766,8 +7377,6 @@ }, "node_modules/@react-native/gradle-plugin": { "version": "0.83.2", - "resolved": "https://registry.npmjs.org/@react-native/gradle-plugin/-/gradle-plugin-0.83.2.tgz", - "integrity": "sha512-PqN11fXRAU+uJ0inZY1HWYlwJOXHOhF4SPyeHBBxjajKpm2PGunmvFWwkmBjmmUkP/CNO0ezTUudV0oj+2wiHQ==", "license": "MIT", "engines": { "node": ">= 20.19.4" @@ -7775,8 +7384,6 @@ }, "node_modules/@react-native/js-polyfills": { "version": "0.83.2", - "resolved": "https://registry.npmjs.org/@react-native/js-polyfills/-/js-polyfills-0.83.2.tgz", - "integrity": "sha512-dk6fIY2OrKW/2Nk2HydfYNrQau8g6LOtd7NVBrgaqa+lvuRyIML5iimShP5qPqQnx2ofHuzjFw+Ya0b5Q7nDbA==", "license": "MIT", "engines": { "node": ">= 20.19.4" @@ -7789,8 +7396,6 @@ }, "node_modules/@react-native/virtualized-lists": { "version": "0.83.2", - "resolved": "https://registry.npmjs.org/@react-native/virtualized-lists/-/virtualized-lists-0.83.2.tgz", - "integrity": "sha512-N7mRjHLW/+KWxMp9IHRWyE3VIkeG1m3PnZJAGEFLCN8VFb7e4VfI567o7tE/HYcdcXCylw+Eqhlciz8gDeQ71g==", "license": "MIT", "dependencies": { "invariant": "^2.2.4", @@ -8127,8 +7732,6 @@ }, "node_modules/@types/react": { "version": "19.2.14", - "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", - "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", "devOptional": true, "license": "MIT", "dependencies": { @@ -8371,8 +7974,6 @@ }, "node_modules/@ungap/structured-clone": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.3.0.tgz", - "integrity": "sha512-WmoN8qaIAo7WTYWbAZuG8PYEhn5fkz7dZrqTBZ7dtt//lL2Gwms1IcnQ5yHqjDfX8Ft5j4YzDM23f87zBfDe9g==", "license": "ISC" }, "node_modules/@urql/core": { @@ -8868,8 +8469,6 @@ }, "node_modules/aria-hidden": { "version": "1.2.6", - "resolved": "https://registry.npmjs.org/aria-hidden/-/aria-hidden-1.2.6.tgz", - "integrity": "sha512-ik3ZgC9dY/lYVVM++OISsaYDeg1tb0VtP5uL3ouh1koGOaUMDPpbFIei4JkFimWUFPn90sbMNMXQAIVOlnYKJA==", "license": "MIT", "dependencies": { "tslib": "^2.0.0" @@ -9572,7 +9171,6 @@ "version": "3.1.0", "devOptional": true, "license": "MIT", - "peer": true, "engines": { "node": ">=6" } @@ -9721,8 +9319,7 @@ "node_modules/cjs-module-lexer": { "version": "1.4.3", "devOptional": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/cli-cursor": { "version": "2.1.0", @@ -9797,7 +9394,6 @@ "version": "4.6.0", "devOptional": true, "license": "MIT", - "peer": true, "engines": { "iojs": ">= 1.0.0", "node": ">= 0.12.0" @@ -10022,7 +9618,6 @@ "version": "29.7.0", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/types": "^29.6.3", "chalk": "^4.0.0", @@ -10101,8 +9696,6 @@ }, "node_modules/csstype": { "version": "3.2.3", - "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.2.3.tgz", - "integrity": "sha512-z1HGKcYy2xA8AGQfwrn0PAy+PB7X/GSj3UVJW9qKyn43xWa+gl5nXmU4qqLMRzWVLFC8KusUX8T/0kCiOYpAIQ==", "devOptional": true, "license": "MIT" }, @@ -10322,15 +9915,12 @@ "version": "3.1.0", "devOptional": true, "license": "MIT", - "peer": true, "engines": { "node": ">=8" } }, "node_modules/detect-node-es": { "version": "1.1.0", - "resolved": "https://registry.npmjs.org/detect-node-es/-/detect-node-es-1.1.0.tgz", - "integrity": "sha512-ypdmJU/TbBby2Dxibuv7ZLW3Bs1QEmM7nHjEANfohJLvE0XVujisn1qPJcZxg+qDucsr+bP6fLD1rPS3AhJ7EQ==", "license": "MIT" }, "node_modules/diff": { @@ -10362,8 +9952,6 @@ }, "node_modules/dnssd-advertise": { "version": "1.1.4", - "resolved": "https://registry.npmjs.org/dnssd-advertise/-/dnssd-advertise-1.1.4.tgz", - "integrity": "sha512-AmGyK9WpNf06WeP5TjHZq/wNzP76OuEeaiTlKr9E/EEelYLczywUKoqRz+DPRq/ErssjT4lU+/W7wzJW+7K/ZA==", "license": "MIT" }, "node_modules/doctrine": { @@ -10525,7 +10113,6 @@ "version": "1.3.2", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "is-arrayish": "^0.2.1" } @@ -11386,7 +10973,6 @@ "version": "5.1.1", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "cross-spawn": "^7.0.3", "get-stream": "^6.0.0", @@ -11408,13 +10994,11 @@ "node_modules/execa/node_modules/signal-exit": { "version": "3.0.7", "devOptional": true, - "license": "ISC", - "peer": true + "license": "ISC" }, "node_modules/exit": { "version": "0.1.2", "devOptional": true, - "peer": true, "engines": { "node": ">= 0.8.0" } @@ -11683,8 +11267,6 @@ }, "node_modules/expo-server": { "version": "55.0.6", - "resolved": "https://registry.npmjs.org/expo-server/-/expo-server-55.0.6.tgz", - "integrity": "sha512-xI72FTm469FfuuBL2R5aNtthgH+GR7ygOpsx/KcPS0K8AZaZd7VjtEExbzn9/qyyYkWW3T+3dAmCDKOMX8gdmQ==", "license": "MIT", "engines": { "node": ">=20.16.0" @@ -11692,8 +11274,6 @@ }, "node_modules/expo-symbols": { "version": "55.0.5", - "resolved": "https://registry.npmjs.org/expo-symbols/-/expo-symbols-55.0.5.tgz", - "integrity": "sha512-W/QYRvnYVes947ZYOHtuKL8Gobs7BUjeu9oknzbo4jGnou7Ks6bj1CwdT0ZWNBgaTopbS4/POXumJIkW4cTPSQ==", "license": "MIT", "dependencies": { "@expo-google-fonts/material-symbols": "^0.4.1", @@ -11708,8 +11288,6 @@ }, "node_modules/expo-task-manager": { "version": "55.0.12", - "resolved": "https://registry.npmjs.org/expo-task-manager/-/expo-task-manager-55.0.12.tgz", - "integrity": "sha512-lKt0uLiIIZyWTn8tD7KDMFr0QZVAbBo8OLn0IBGN2aapJBB2A//VSd7EuD+NUkU9jdlxfG6Co63ZCt0X1/NeUA==", "license": "MIT", "dependencies": { "unimodules-app-loader": "~55.0.4" @@ -11787,8 +11365,6 @@ }, "node_modules/fb-dotslash": { "version": "0.5.8", - "resolved": "https://registry.npmjs.org/fb-dotslash/-/fb-dotslash-0.5.8.tgz", - "integrity": "sha512-XHYLKk9J4BupDxi9bSEhkfss0m+Vr9ChTrjhf9l2iw3jB5C7BnY4GVPoMcqbrTutsKJso6yj2nAB6BI/F2oZaA==", "license": "(MIT OR Apache-2.0)", "bin": { "dotslash": "bin/dotslash" @@ -11830,8 +11406,6 @@ }, "node_modules/fetch-nodeshim": { "version": "0.4.10", - "resolved": "https://registry.npmjs.org/fetch-nodeshim/-/fetch-nodeshim-0.4.10.tgz", - "integrity": "sha512-m6I8ALe4L4XpdETy7MJZWs6L1IVMbjs99bwbpIKphxX+0CTns4IKDWJY0LWfr4YsFjfg+z1TjzTMU8lKl8rG0w==", "license": "MIT" }, "node_modules/file-entry-cache": { @@ -12111,8 +11685,6 @@ }, "node_modules/get-nonce": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/get-nonce/-/get-nonce-1.0.1.tgz", - "integrity": "sha512-FJhYRoDaiatfEkUK8HKlicmu/3SGFD51q3itKDGoSTysQJBnfOcxU5GxnhE1E6soB76MbT0MBtnKJuXyAx+96Q==", "license": "MIT", "engines": { "node": ">=6" @@ -12141,7 +11713,6 @@ "version": "6.0.1", "devOptional": true, "license": "MIT", - "peer": true, "engines": { "node": ">=10" }, @@ -12284,6 +11855,26 @@ "dev": true, "license": "MIT" }, + "node_modules/handlebars": { + "version": "4.7.9", + "dev": true, + "license": "MIT", + "dependencies": { + "minimist": "^1.2.5", + "neo-async": "^2.6.2", + "source-map": "^0.6.1", + "wordwrap": "^1.0.0" + }, + "bin": { + "handlebars": "bin/handlebars" + }, + "engines": { + "node": ">=0.4.7" + }, + "optionalDependencies": { + "uglify-js": "^3.1.4" + } + }, "node_modules/has-bigints": { "version": "1.1.0", "dev": true, @@ -12364,8 +11955,6 @@ }, "node_modules/hermes-compiler": { "version": "0.14.1", - "resolved": "https://registry.npmjs.org/hermes-compiler/-/hermes-compiler-0.14.1.tgz", - "integrity": "sha512-+RPPQlayoZ9n6/KXKt5SFILWXCGJ/LV5d24L5smXrvTDrPS4L6dSctPczXauuvzFP3QEJbD1YO7Z3Ra4a+4IhA==", "license": "MIT" }, "node_modules/hermes-estree": { @@ -12418,8 +12007,7 @@ "node_modules/html-escaper": { "version": "2.0.2", "devOptional": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/http-errors": { "version": "2.0.0", @@ -12479,7 +12067,6 @@ "version": "2.1.0", "devOptional": true, "license": "Apache-2.0", - "peer": true, "engines": { "node": ">=10.17.0" } @@ -12567,7 +12154,6 @@ "version": "3.2.0", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "pkg-dir": "^4.2.0", "resolve-cwd": "^3.0.0" @@ -12660,8 +12246,7 @@ "node_modules/is-arrayish": { "version": "0.2.1", "devOptional": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/is-async-function": { "version": "2.1.1", @@ -12832,7 +12417,6 @@ "version": "2.1.0", "devOptional": true, "license": "MIT", - "peer": true, "engines": { "node": ">=6" } @@ -13125,7 +12709,6 @@ "version": "3.0.1", "devOptional": true, "license": "BSD-3-Clause", - "peer": true, "dependencies": { "istanbul-lib-coverage": "^3.0.0", "make-dir": "^4.0.0", @@ -13139,7 +12722,6 @@ "version": "4.0.0", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "semver": "^7.5.3" }, @@ -13154,7 +12736,6 @@ "version": "4.0.1", "devOptional": true, "license": "BSD-3-Clause", - "peer": true, "dependencies": { "debug": "^4.1.1", "istanbul-lib-coverage": "^3.0.0", @@ -13168,7 +12749,6 @@ "version": "3.2.0", "devOptional": true, "license": "BSD-3-Clause", - "peer": true, "dependencies": { "html-escaper": "^2.0.0", "istanbul-lib-report": "^3.0.0" @@ -13210,7 +12790,6 @@ "version": "29.7.0", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/core": "^29.7.0", "@jest/types": "^29.6.3", @@ -13236,7 +12815,6 @@ "version": "29.7.0", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "execa": "^5.0.0", "jest-util": "^29.7.0", @@ -13250,7 +12828,6 @@ "version": "29.7.0", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/environment": "^29.7.0", "@jest/expect": "^29.7.0", @@ -13281,7 +12858,6 @@ "version": "29.7.0", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/core": "^29.7.0", "@jest/test-result": "^29.7.0", @@ -13314,7 +12890,6 @@ "version": "29.7.0", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/core": "^7.11.6", "@jest/test-sequencer": "^29.7.0", @@ -13359,7 +12934,6 @@ "version": "1.1.12", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -13369,7 +12943,6 @@ "version": "7.2.3", "devOptional": true, "license": "ISC", - "peer": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -13389,7 +12962,6 @@ "version": "3.1.5", "devOptional": true, "license": "ISC", - "peer": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -13415,7 +12987,6 @@ "version": "29.7.0", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "detect-newline": "^3.0.0" }, @@ -13427,7 +12998,6 @@ "version": "29.7.0", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/types": "^29.6.3", "chalk": "^4.0.0", @@ -13544,7 +13114,6 @@ "version": "29.7.0", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "jest-get-type": "^29.6.3", "pretty-format": "^29.7.0" @@ -13613,7 +13182,6 @@ "version": "1.2.3", "devOptional": true, "license": "MIT", - "peer": true, "engines": { "node": ">=6" }, @@ -13637,7 +13205,6 @@ "version": "29.7.0", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "chalk": "^4.0.0", "graceful-fs": "^4.2.9", @@ -13657,7 +13224,6 @@ "version": "29.7.0", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "jest-regex-util": "^29.6.3", "jest-snapshot": "^29.7.0" @@ -13670,7 +13236,6 @@ "version": "29.7.0", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/console": "^29.7.0", "@jest/environment": "^29.7.0", @@ -13702,7 +13267,6 @@ "version": "0.5.13", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "buffer-from": "^1.0.0", "source-map": "^0.6.0" @@ -13712,7 +13276,6 @@ "version": "29.7.0", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "@jest/environment": "^29.7.0", "@jest/fake-timers": "^29.7.0", @@ -13745,7 +13308,6 @@ "version": "1.1.12", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -13755,7 +13317,6 @@ "version": "7.2.3", "devOptional": true, "license": "ISC", - "peer": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -13775,7 +13336,6 @@ "version": "3.1.5", "devOptional": true, "license": "ISC", - "peer": true, "dependencies": { "brace-expansion": "^1.1.7" }, @@ -14100,8 +13660,7 @@ "node_modules/json-parse-even-better-errors": { "version": "2.3.1", "devOptional": true, - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/json-schema-traverse": { "version": "1.0.0", @@ -15182,8 +14741,6 @@ }, "node_modules/minipass": { "version": "7.1.3", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", - "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==", "license": "BlueOak-1.0.0", "engines": { "node": ">=16 || 14 >=14.17" @@ -15224,8 +14781,6 @@ }, "node_modules/multitars": { "version": "0.2.4", - "resolved": "https://registry.npmjs.org/multitars/-/multitars-0.2.4.tgz", - "integrity": "sha512-XgLbg1HHchFauMCQPRwMj6MSyDd5koPlTA1hM3rUFkeXzGpjU/I9fP3to7yrObE9jcN8ChIOQGrM0tV0kUZaKg==", "license": "MIT" }, "node_modules/mz": { @@ -15366,7 +14921,6 @@ "version": "4.0.1", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "path-key": "^3.0.0" }, @@ -15809,7 +15363,6 @@ "version": "5.2.0", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "@babel/code-frame": "^7.0.0", "error-ex": "^1.3.1", @@ -16021,7 +15574,6 @@ "version": "4.2.0", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "find-up": "^4.0.0" }, @@ -16033,7 +15585,6 @@ "version": "4.1.0", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "locate-path": "^5.0.0", "path-exists": "^4.0.0" @@ -16046,7 +15597,6 @@ "version": "5.0.0", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "p-locate": "^4.1.0" }, @@ -16058,7 +15608,6 @@ "version": "2.3.0", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "p-try": "^2.0.0" }, @@ -16073,7 +15622,6 @@ "version": "4.1.0", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "p-limit": "^2.2.0" }, @@ -16295,8 +15843,7 @@ "url": "https://opencollective.com/fast-check" } ], - "license": "MIT", - "peer": true + "license": "MIT" }, "node_modules/qrcode-terminal": { "version": "0.11.0", @@ -16398,8 +15945,6 @@ }, "node_modules/react": { "version": "19.2.4", - "resolved": "https://registry.npmjs.org/react/-/react-19.2.4.tgz", - "integrity": "sha512-9nfp2hYpCwOjAN+8TZFGhtWEwgvWHXqESH8qT89AT/lWklpLON22Lc8pEtnpsZz7VmawabSU0gCjnj8aC0euHQ==", "license": "MIT", "engines": { "node": ">=0.10.0" @@ -16407,8 +15952,6 @@ }, "node_modules/react-devtools-core": { "version": "6.1.5", - "resolved": "https://registry.npmjs.org/react-devtools-core/-/react-devtools-core-6.1.5.tgz", - "integrity": "sha512-ePrwPfxAnB+7hgnEr8vpKxL9cmnp7F322t8oqcPshbIQQhDKgFDW4tjhF2wjVbdXF9O/nyuy3sQWd9JGpiLPvA==", "license": "MIT", "dependencies": { "shell-quote": "^1.6.1", @@ -16417,8 +15960,6 @@ }, "node_modules/react-devtools-core/node_modules/ws": { "version": "7.5.10", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", - "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", "license": "MIT", "engines": { "node": ">=8.3.0" @@ -16438,8 +15979,6 @@ }, "node_modules/react-dom": { "version": "19.2.4", - "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-19.2.4.tgz", - "integrity": "sha512-AXJdLo8kgMbimY95O2aKQqsz2iWi9jMgKJhRBAxECE4IFxfcazB2LmzloIoibJI3C12IlY20+KFaLv+71bUJeQ==", "license": "MIT", "dependencies": { "scheduler": "^0.27.0" @@ -16468,8 +16007,6 @@ }, "node_modules/react-native": { "version": "0.83.2", - "resolved": "https://registry.npmjs.org/react-native/-/react-native-0.83.2.tgz", - "integrity": "sha512-ZDma3SLkRN2U2dg0/EZqxNBAx4of/oTnPjXAQi299VLq2gdnbZowGy9hzqv+O7sTA62g+lM1v+2FM5DUnJ/6hg==", "license": "MIT", "dependencies": { "@jest/create-cache-key-function": "^29.7.0", @@ -16582,8 +16119,6 @@ }, "node_modules/react-native-worklets": { "version": "0.7.4", - "resolved": "https://registry.npmjs.org/react-native-worklets/-/react-native-worklets-0.7.4.tgz", - "integrity": "sha512-NYOdM1MwBb3n+AtMqy1tFy3Mn8DliQtd8sbzAVRf9Gc+uvQ0zRfxN7dS8ZzoyX7t6cyQL5THuGhlnX+iFlQTag==", "license": "MIT", "dependencies": { "@babel/plugin-transform-arrow-functions": "7.27.1", @@ -16606,8 +16141,6 @@ }, "node_modules/react-native/node_modules/@react-native/codegen": { "version": "0.83.2", - "resolved": "https://registry.npmjs.org/@react-native/codegen/-/codegen-0.83.2.tgz", - "integrity": "sha512-9uK6X1miCXqtL4c759l74N/XbQeneWeQVjoV7SD2CGJuW7ZefxaoYenwGPs7rMoCdtS6wuIyR3hXQ+uWEBGYXA==", "license": "MIT", "dependencies": { "@babel/core": "^7.25.2", @@ -16627,14 +16160,10 @@ }, "node_modules/react-native/node_modules/@react-native/normalize-colors": { "version": "0.83.2", - "resolved": "https://registry.npmjs.org/@react-native/normalize-colors/-/normalize-colors-0.83.2.tgz", - "integrity": "sha512-gkZAb9LoVVzNuYzzOviH7DiPTXQoZPHuiTH2+O2+VWNtOkiznjgvqpwYAhg58a5zfRq5GXlbBdf5mzRj5+3Y5Q==", "license": "MIT" }, "node_modules/react-native/node_modules/babel-plugin-syntax-hermes-parser": { "version": "0.32.0", - "resolved": "https://registry.npmjs.org/babel-plugin-syntax-hermes-parser/-/babel-plugin-syntax-hermes-parser-0.32.0.tgz", - "integrity": "sha512-m5HthL++AbyeEA2FcdwOLfVFvWYECOBObLHNqdR8ceY4TsEdn4LdX2oTvbB2QJSSElE2AWA/b2MXZ/PF/CqLZg==", "license": "MIT", "dependencies": { "hermes-parser": "0.32.0" @@ -16668,14 +16197,10 @@ }, "node_modules/react-native/node_modules/hermes-estree": { "version": "0.32.0", - "resolved": "https://registry.npmjs.org/hermes-estree/-/hermes-estree-0.32.0.tgz", - "integrity": "sha512-KWn3BqnlDOl97Xe1Yviur6NbgIZ+IP+UVSpshlZWkq+EtoHg6/cwiDj/osP9PCEgFE15KBm1O55JRwbMEm5ejQ==", "license": "MIT" }, "node_modules/react-native/node_modules/hermes-parser": { "version": "0.32.0", - "resolved": "https://registry.npmjs.org/hermes-parser/-/hermes-parser-0.32.0.tgz", - "integrity": "sha512-g4nBOWFpuiTqjR3LZdRxKUkij9iyveWeuks7INEsMX741f3r9xxrOe8TeQfUxtda0eXmiIFiMQzoeSQEno33Hw==", "license": "MIT", "dependencies": { "hermes-estree": "0.32.0" @@ -16683,8 +16208,6 @@ }, "node_modules/react-native/node_modules/metro-runtime": { "version": "0.83.5", - "resolved": "https://registry.npmjs.org/metro-runtime/-/metro-runtime-0.83.5.tgz", - "integrity": "sha512-f+b3ue9AWTVlZe2Xrki6TAoFtKIqw30jwfk7GQ1rDUBQaE0ZQ+NkiMEtb9uwH7uAjJ87U7Tdx1Jg1OJqUfEVlA==", "license": "MIT", "dependencies": { "@babel/runtime": "^7.25.0", @@ -16696,8 +16219,6 @@ }, "node_modules/react-native/node_modules/metro-source-map": { "version": "0.83.5", - "resolved": "https://registry.npmjs.org/metro-source-map/-/metro-source-map-0.83.5.tgz", - "integrity": "sha512-VT9bb2KO2/4tWY9Z2yeZqTUao7CicKAOps9LUg2aQzsz+04QyuXL3qgf1cLUVRjA/D6G5u1RJAlN1w9VNHtODQ==", "license": "MIT", "dependencies": { "@babel/traverse": "^7.29.0", @@ -16716,8 +16237,6 @@ }, "node_modules/react-native/node_modules/metro-symbolicate": { "version": "0.83.5", - "resolved": "https://registry.npmjs.org/metro-symbolicate/-/metro-symbolicate-0.83.5.tgz", - "integrity": "sha512-EMIkrjNRz/hF+p0RDdxoE60+dkaTLPN3vaaGkFmX5lvFdO6HPfHA/Ywznzkev+za0VhPQ5KSdz49/MALBRteHA==", "license": "MIT", "dependencies": { "flow-enums-runtime": "^0.0.6", @@ -16746,8 +16265,6 @@ }, "node_modules/react-native/node_modules/ob1": { "version": "0.83.5", - "resolved": "https://registry.npmjs.org/ob1/-/ob1-0.83.5.tgz", - "integrity": "sha512-vNKPYC8L5ycVANANpF/S+WZHpfnRWKx/F3AYP4QMn6ZJTh+l2HOrId0clNkEmua58NB9vmI9Qh7YOoV/4folYg==", "license": "MIT", "dependencies": { "flow-enums-runtime": "^0.0.6" @@ -16758,8 +16275,6 @@ }, "node_modules/react-native/node_modules/source-map": { "version": "0.5.7", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", - "integrity": "sha512-LbrmJOMUSdEVxIKvdcJzQC+nQhe8FUZQTXQy6+I75skNgn3OoQ0DZA8YnFa7gp8tqtL3KPf1kmo0R5DoApeSGQ==", "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" @@ -16767,8 +16282,6 @@ }, "node_modules/react-native/node_modules/ws": { "version": "7.5.10", - "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", - "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", "license": "MIT", "engines": { "node": ">=8.3.0" @@ -16795,8 +16308,6 @@ }, "node_modules/react-remove-scroll": { "version": "2.7.2", - "resolved": "https://registry.npmjs.org/react-remove-scroll/-/react-remove-scroll-2.7.2.tgz", - "integrity": "sha512-Iqb9NjCCTt6Hf+vOdNIZGdTiH1QSqr27H/Ek9sv/a97gfueI/5h1s3yRi1nngzMUaOOToin5dI1dXKdXiF+u0Q==", "license": "MIT", "dependencies": { "react-remove-scroll-bar": "^2.3.7", @@ -16820,8 +16331,6 @@ }, "node_modules/react-remove-scroll-bar": { "version": "2.3.8", - "resolved": "https://registry.npmjs.org/react-remove-scroll-bar/-/react-remove-scroll-bar-2.3.8.tgz", - "integrity": "sha512-9r+yi9+mgU33AKcj6IbT9oRCO78WriSj6t/cF8DWBZJ9aOGPOTEDvdUDz1FwKim7QXWwmHqtdHnRJfhAxEG46Q==", "license": "MIT", "dependencies": { "react-style-singleton": "^2.2.2", @@ -16860,8 +16369,6 @@ }, "node_modules/react-style-singleton": { "version": "2.2.3", - "resolved": "https://registry.npmjs.org/react-style-singleton/-/react-style-singleton-2.2.3.tgz", - "integrity": "sha512-b6jSvxvVnyptAiLjbkWLE/lOnR4lfTtDAl+eUC7RZy+QQWc6wRzIV2CE6xBuMmDxc2qIihtDCZD5NPOFl7fRBQ==", "license": "MIT", "dependencies": { "get-nonce": "^1.0.0", @@ -16882,8 +16389,6 @@ }, "node_modules/react-test-renderer": { "version": "19.2.4", - "resolved": "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-19.2.4.tgz", - "integrity": "sha512-Ttl5D7Rnmi6JGMUpri4UjB4BAN0FPs4yRDnu2XSsigCWOLm11o8GwRlVsh27ER+4WFqsGtrBuuv5zumUaRCmKw==", "devOptional": true, "license": "MIT", "dependencies": { @@ -17141,7 +16646,6 @@ "version": "3.0.0", "devOptional": true, "license": "MIT", - "peer": true, "dependencies": { "resolve-from": "^5.0.0" }, @@ -17171,7 +16675,6 @@ "node_modules/resolve.exports": { "version": "2.0.3", "license": "MIT", - "peer": true, "engines": { "node": ">=10" } @@ -17377,8 +16880,6 @@ }, "node_modules/scheduler": { "version": "0.27.0", - "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.27.0.tgz", - "integrity": "sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==", "license": "MIT" }, "node_modules/schema-utils": { @@ -17402,8 +16903,6 @@ }, "node_modules/semver": { "version": "7.7.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", - "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", "license": "ISC", "bin": { "semver": "bin/semver.js" @@ -17656,8 +17155,6 @@ }, "node_modules/shell-quote": { "version": "1.8.3", - "resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.8.3.tgz", - "integrity": "sha512-ObmnIF4hXNg1BqhnHmgbDETF8dLPCggZWBjkQfhZpbszZnYur5DUljTcCHii5LC3J5E0yeO/1LIMyH+UvHQgyw==", "license": "MIT", "engines": { "node": ">= 0.4" @@ -18135,7 +17632,6 @@ "version": "4.0.0", "devOptional": true, "license": "MIT", - "peer": true, "engines": { "node": ">=8" } @@ -18162,7 +17658,6 @@ "version": "3.1.1", "devOptional": true, "license": "MIT", - "peer": true, "engines": { "node": ">=8" }, @@ -18512,8 +18007,6 @@ }, "node_modules/toqr": { "version": "0.1.1", - "resolved": "https://registry.npmjs.org/toqr/-/toqr-0.1.1.tgz", - "integrity": "sha512-FWAPzCIHZHnrE/5/w9MPk0kK25hSQSH2IKhYh9PyjS3SG/+IEMvlwIHbhz+oF7xl54I+ueZlVnMjyzdSwLmAwA==", "license": "MIT" }, "node_modules/tough-cookie": { @@ -18962,6 +18455,18 @@ "node": "*" } }, + "node_modules/uglify-js": { + "version": "3.19.3", + "dev": true, + "license": "BSD-2-Clause", + "optional": true, + "bin": { + "uglifyjs": "bin/uglifyjs" + }, + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/unbox-primitive": { "version": "1.1.0", "dev": true, @@ -19025,8 +18530,6 @@ }, "node_modules/unimodules-app-loader": { "version": "55.0.4", - "resolved": "https://registry.npmjs.org/unimodules-app-loader/-/unimodules-app-loader-55.0.4.tgz", - "integrity": "sha512-l3vMWR/lYLTj3JE4rhIX5vDVMDY9nGS550XaB90HENqUQnBEMdhhOpI8tOA37QJOUZE6pCQGeQX6mIdEu3uqzg==", "license": "MIT" }, "node_modules/unique-string": { @@ -19111,8 +18614,6 @@ }, "node_modules/use-callback-ref": { "version": "1.3.3", - "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz", - "integrity": "sha512-jQL3lRnocaFtu3V00JToYz/4QkNWswxijDaCVNZRiRTO3HQDLsdu1ZtmIUvV4yPp+rvWm5j0y0TG/S61cuijTg==", "license": "MIT", "dependencies": { "tslib": "^2.0.0" @@ -19139,8 +18640,6 @@ }, "node_modules/use-sidecar": { "version": "1.1.3", - "resolved": "https://registry.npmjs.org/use-sidecar/-/use-sidecar-1.1.3.tgz", - "integrity": "sha512-Fedw0aZvkhynoPYlA5WXrMCAMm+nSWdZt6lzJQ7Ok8S6Q+VsHmHpRWndVRJ8Be0ZbkfPc5LRYH+5XrzXcEeLRQ==", "license": "MIT", "dependencies": { "detect-node-es": "^1.1.0", @@ -19189,7 +18688,6 @@ "version": "9.3.0", "devOptional": true, "license": "ISC", - "peer": true, "dependencies": { "@jridgewell/trace-mapping": "^0.3.12", "@types/istanbul-lib-coverage": "^2.0.1", @@ -19215,8 +18713,6 @@ }, "node_modules/vaul": { "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vaul/-/vaul-1.1.2.tgz", - "integrity": "sha512-ZFkClGpWyI2WUQjdLJ/BaGuV6AVQiJ3uELGk3OYtP+B6yCO7Cmn9vPFXVJkRaGkOJu3m8bQMgtyzNHixULceQA==", "license": "MIT", "dependencies": { "@radix-ui/react-dialog": "^1.1.1" @@ -19460,8 +18956,6 @@ }, "node_modules/whatwg-url-minimum": { "version": "0.1.1", - "resolved": "https://registry.npmjs.org/whatwg-url-minimum/-/whatwg-url-minimum-0.1.1.tgz", - "integrity": "sha512-u2FNVjFVFZhdjb502KzXy1gKn1mEisQRJssmSJT8CPhZdZa0AP6VCbWlXERKyGu0l09t0k50FiDiralpGhBxgA==", "license": "MIT" }, "node_modules/whatwg-url-without-unicode": { @@ -19593,6 +19087,11 @@ "node": ">=0.10.0" } }, + "node_modules/wordwrap": { + "version": "1.0.0", + "dev": true, + "license": "MIT" + }, "node_modules/wrap-ansi": { "version": "7.0.0", "license": "MIT", @@ -19948,12 +19447,91 @@ "vd-tool": "^4.0.2", "xcode": "^3.0.1" }, + "devDependencies": { + "@types/jest": "^29.5.14", + "@types/node": "^20.19.25", + "jest": "^29.7.0", + "ts-jest": "^29.3.4" + }, "peerDependencies": { "expo": "*", "react": "*", "react-native": "*" } }, + "packages/expo-plugin/node_modules/semver": { + "version": "7.7.4", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "packages/expo-plugin/node_modules/ts-jest": { + "version": "29.4.9", + "dev": true, + "license": "MIT", + "dependencies": { + "bs-logger": "^0.2.6", + "fast-json-stable-stringify": "^2.1.0", + "handlebars": "^4.7.9", + "json5": "^2.2.3", + "lodash.memoize": "^4.1.2", + "make-error": "^1.3.6", + "semver": "^7.7.4", + "type-fest": "^4.41.0", + "yargs-parser": "^21.1.1" + }, + "bin": { + "ts-jest": "cli.js" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || ^18.0.0 || >=20.0.0" + }, + "peerDependencies": { + "@babel/core": ">=7.0.0-beta.0 <8", + "@jest/transform": "^29.0.0 || ^30.0.0", + "@jest/types": "^29.0.0 || ^30.0.0", + "babel-jest": "^29.0.0 || ^30.0.0", + "jest": "^29.0.0 || ^30.0.0", + "jest-util": "^29.0.0 || ^30.0.0", + "typescript": ">=4.3 <7" + }, + "peerDependenciesMeta": { + "@babel/core": { + "optional": true + }, + "@jest/transform": { + "optional": true + }, + "@jest/types": { + "optional": true + }, + "babel-jest": { + "optional": true + }, + "esbuild": { + "optional": true + }, + "jest-util": { + "optional": true + } + } + }, + "packages/expo-plugin/node_modules/type-fest": { + "version": "4.41.0", + "dev": true, + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "packages/ios": { "name": "@use-voltra/ios", "version": "1.4.1", diff --git a/packages/expo-plugin/jest.config.js b/packages/expo-plugin/jest.config.js new file mode 100644 index 00000000..dcdfc6cb --- /dev/null +++ b/packages/expo-plugin/jest.config.js @@ -0,0 +1,14 @@ +/** @type {import('jest').Config} */ +module.exports = { + testEnvironment: 'node', + testMatch: ['/src/**/*.node.test.ts'], + modulePathIgnorePatterns: ['/build'], + transform: { + '^.+\\.tsx?$': [ + 'ts-jest', + { + tsconfig: '/tsconfig.jest.json', + }, + ], + }, +} diff --git a/packages/expo-plugin/package.json b/packages/expo-plugin/package.json index c124a705..28b3f497 100644 --- a/packages/expo-plugin/package.json +++ b/packages/expo-plugin/package.json @@ -22,6 +22,7 @@ "build": "node ../../scripts/build-package.mjs packages/expo-plugin", "clean": "rm -rf build", "lint": "oxlint src", + "test": "jest --config jest.config.js", "typecheck": "tsc -p tsconfig.typecheck.json --noEmit" }, "dependencies": { @@ -54,5 +55,11 @@ "expo": "*", "react": "*", "react-native": "*" + }, + "devDependencies": { + "@types/jest": "^29.5.14", + "@types/node": "^20.19.25", + "jest": "^29.7.0", + "ts-jest": "^29.3.4" } } diff --git a/packages/expo-plugin/src/android/files/initialStates.ts b/packages/expo-plugin/src/android/files/initialStates.ts index f6f77acf..3f4512ac 100644 --- a/packages/expo-plugin/src/android/files/initialStates.ts +++ b/packages/expo-plugin/src/android/files/initialStates.ts @@ -3,8 +3,12 @@ import path from 'path' import type { AndroidWidgetConfig } from '../../types' import { logger } from '../../utils/logger' +import type { PrerenderedWidgetStates } from '../../utils/prerender' import { prerenderWidgetState } from '../../utils/prerender' +/** Wrapped asset shape when multiple locales are built; matches Android reader in VoltraWidgetManager */ +export const VOLTRA_LOCALIZED_INITIAL_STATE_KEY = '__voltraLocales' + export interface GenerateInitialStatesOptions { widgets: AndroidWidgetConfig[] projectRoot: string @@ -29,8 +33,12 @@ export async function generateAndroidInitialStates(options: GenerateInitialState renderAndroidWidgetToString: RenderAndroidWidgetToString } - // Prerender widget states - const prerenderedStates = await prerenderWidgetState(widgets, projectRoot, renderAndroidWidgetToString) + // Prerender widget states (per locale when `initialStatePath` is a locale map) + const prerenderedStates: PrerenderedWidgetStates = await prerenderWidgetState( + widgets, + projectRoot, + renderAndroidWidgetToString + ) if (prerenderedStates.size === 0) { return @@ -42,16 +50,22 @@ export async function generateAndroidInitialStates(options: GenerateInitialState fs.mkdirSync(assetsDir, { recursive: true }) } - // Convert Map to Object for JSON serialization - const statesObj: Record = {} - for (const [id, stateJson] of prerenderedStates.entries()) { + // Convert Map to Object for JSON serialization (legacy flat shape vs localized wrapper) + const statesObj: Record = {} + for (const [id, perLocale] of prerenderedStates.entries()) { try { - // Parse the JSON string so it's embedded as an object in the final JSON - statesObj[id] = JSON.parse(stateJson) + if (perLocale.size === 1 && perLocale.has('__default')) { + const raw = perLocale.get('__default')! + statesObj[id] = JSON.parse(raw) + } else { + const locales: Record = {} + for (const [localeKey, jsonStr] of perLocale.entries()) { + locales[localeKey] = JSON.parse(jsonStr) + } + statesObj[id] = { [VOLTRA_LOCALIZED_INITIAL_STATE_KEY]: locales } + } } catch (e) { logger.warn(`Failed to parse prerendered state for widget ${id}: ${e}`) - // If it's not valid JSON, we might skip it or include it as string? - // renderWidgetToString should return valid JSON. } } diff --git a/packages/expo-plugin/src/android/files/kotlin.ts b/packages/expo-plugin/src/android/files/kotlin.ts index f858d6f0..10f1cfc4 100644 --- a/packages/expo-plugin/src/android/files/kotlin.ts +++ b/packages/expo-plugin/src/android/files/kotlin.ts @@ -3,6 +3,7 @@ import * as fs from 'fs' import * as path from 'path' import type { AndroidWidgetConfig } from '../../types' +import { widgetLabelEnglish } from '../../utils/widgetLabel' export interface GenerateKotlinFilesProps { platformProjectRoot: string @@ -49,6 +50,7 @@ export async function generateWidgetReceivers(props: GenerateKotlinFilesProps): */ function generateWidgetReceiverClass(widget: AndroidWidgetConfig, packageName: string): string { const className = `VoltraWidget_${widget.id}Receiver` + const labelForComment = widgetLabelEnglish(widget.displayName) if (widget.serverUpdate) { const refreshEnabled = widget.serverUpdate.refresh === true @@ -62,7 +64,7 @@ function generateWidgetReceiverClass(widget: AndroidWidgetConfig, packageName: s import voltra.widget.VoltraWidgetUpdateScheduler /** - * Auto-generated widget receiver for ${widget.displayName} + * Auto-generated widget receiver for ${labelForComment} * Widget ID: ${widget.id} * Server Update: ${widget.serverUpdate.url} (every ${widget.serverUpdate.intervalMinutes ?? 15} minutes) * Refresh Button: ${refreshEnabled} @@ -110,7 +112,7 @@ function generateWidgetReceiverClass(widget: AndroidWidgetConfig, packageName: s import voltra.widget.VoltraWidgetReceiver /** - * Auto-generated widget receiver for ${widget.displayName} + * Auto-generated widget receiver for ${labelForComment} * Widget ID: ${widget.id} */ class ${className} : VoltraWidgetReceiver() { diff --git a/packages/expo-plugin/src/android/files/xml.node.test.ts b/packages/expo-plugin/src/android/files/xml.node.test.ts new file mode 100644 index 00000000..78ef6765 --- /dev/null +++ b/packages/expo-plugin/src/android/files/xml.node.test.ts @@ -0,0 +1,18 @@ +import { __test__ } from './xml' + +describe('localeKeyToAndroidValuesQualifier', () => { + it('maps plain language tags to classic Android qualifiers', () => { + expect(__test__.localeKeyToAndroidValuesQualifier('pl')).toBe('pl') + }) + + it('maps language-region tags to classic Android qualifiers', () => { + expect(__test__.localeKeyToAndroidValuesQualifier('pt-BR')).toBe('pt-rBR') + expect(__test__.localeKeyToAndroidValuesQualifier('pt_BR')).toBe('pt-rBR') + }) + + it('maps script tags to Android BCP-47 qualifiers', () => { + expect(__test__.localeKeyToAndroidValuesQualifier('zh-Hans')).toBe('b+zh+Hans') + expect(__test__.localeKeyToAndroidValuesQualifier('zh-Hans-CN')).toBe('b+zh+Hans+CN') + expect(__test__.localeKeyToAndroidValuesQualifier('sr-Latn-RS')).toBe('b+sr+Latn+RS') + }) +}) diff --git a/packages/expo-plugin/src/android/files/xml.ts b/packages/expo-plugin/src/android/files/xml.ts index 602be7f4..dc9a9514 100644 --- a/packages/expo-plugin/src/android/files/xml.ts +++ b/packages/expo-plugin/src/android/files/xml.ts @@ -4,6 +4,7 @@ import * as path from 'path' import type { AndroidWidgetConfig } from '../../types' import { logger } from '../../utils/logger' +import { isWidgetLocalizedMap, widgetLabelEnglish } from '../../utils/widgetLabel' export interface GenerateXmlFilesProps { platformProjectRoot: string @@ -25,7 +26,8 @@ export async function generateWidgetInfoFiles(props: { }): Promise { const { platformProjectRoot, widgets } = props const xmlPath = path.join(platformProjectRoot, 'app', 'src', 'main', 'res', 'xml') - const valuesPath = path.join(platformProjectRoot, 'app', 'src', 'main', 'res', 'values') + const mainRes = path.join(platformProjectRoot, 'app', 'src', 'main', 'res') + const valuesPath = path.join(mainRes, 'values') // Ensure directories exist if (!fs.existsSync(xmlPath)) { @@ -35,10 +37,30 @@ export async function generateWidgetInfoFiles(props: { fs.mkdirSync(valuesPath, { recursive: true }) } - // Generate string resources for all widgets - const stringsPath = path.join(valuesPath, 'voltra_widgets.xml') - const stringsContent = generateStringResourcesXml(widgets) - fs.writeFileSync(stringsPath, stringsContent, 'utf8') + // Default strings (development / fallback language — prefers English locale entries in locale maps): res/values/voltra_widgets.xml + const stringsPath = path.join(valuesPath, VOLTRA_WIDGET_STRINGS_FILE) + fs.writeFileSync(stringsPath, generateVoltraWidgetsStringResourcesXml(widgets, null), 'utf8') + + const localeKeys = collectAndroidLocaleKeysFromWidgets(widgets) + /** Qualifiers we generated under res/values-/ (Android resource folder suffixes) */ + const generatedQualifiers = new Set() + + for (const localeKey of localeKeys) { + const qualifier = localeKeyToAndroidValuesQualifier(localeKey) + if (qualifier === DEFAULT_WIDGET_LOCALE_QUALIFIER) { + continue + } + generatedQualifiers.add(qualifier) + const localizedValuesDir = path.join(mainRes, `values-${qualifier}`) + fs.mkdirSync(localizedValuesDir, { recursive: true }) + fs.writeFileSync( + path.join(localizedValuesDir, VOLTRA_WIDGET_STRINGS_FILE), + generateVoltraWidgetsStringResourcesXml(widgets, localeKey), + 'utf8' + ) + } + + cleanupStaleVoltraWidgetLocaleFiles(mainRes, generatedQualifiers) } /** @@ -131,29 +153,185 @@ function generateWidgetInfoXml( } // ============================================================================ -// String Resources XML +// String Resources XML (multilingual: res/values/ + res/values-/) +// https://stackoverflow.com/questions/47976576/working-with-strings-xml-and-translations +// https://developer.android.com/guide/topics/resources/localization // ============================================================================ +const VOLTRA_WIDGET_STRINGS_FILE = 'voltra_widgets.xml' + +/** Locale maps use `en` for the canonical default English locale; unqualified `values/` covers that so skip `values-en/`. */ +const DEFAULT_WIDGET_LOCALE_QUALIFIER = 'en' + +function isAlpha(value: string): boolean { + return /^[a-z]+$/i.test(value) +} + +function isNumeric(value: string): boolean { + return /^\d+$/.test(value) +} + +function isScriptSubtag(value: string): boolean { + return value.length === 4 && isAlpha(value) +} + +function isRegionSubtag(value: string): boolean { + return (value.length === 2 && isAlpha(value)) || (value.length === 3 && isNumeric(value)) +} + +function formatScriptSubtag(value: string): string { + const lower = value.toLowerCase() + return `${lower[0]?.toUpperCase() ?? ''}${lower.slice(1)}` +} + /** - * Generates string resources for widget display names and descriptions + * Maps BCP-style locale keys from app.json to Android resource folder qualifiers. + * Examples: `pl` → `pl`, `pt-BR` / `pt_BR` → `pt-rBR`, `zh-Hans` → `b+zh+Hans` */ -function generateStringResourcesXml(widgets: AndroidWidgetConfig[]): string { +function localeKeyToAndroidValuesQualifier(localeKey: string): string { + const normalized = localeKey.trim().replace(/_/g, '-') + const segments = normalized.split('-').filter(Boolean) + + const language = segments[0]?.toLowerCase() + if (!language) { + return normalized.toLowerCase() + } + + const rest = segments.slice(1) + if (rest.length === 0) { + return language + } + + const [first, ...tail] = rest + if (first && isRegionSubtag(first) && tail.length === 0) { + return `${language}-r${first.toUpperCase()}` + } + + const bcp47Segments = [language] + for (const segment of rest) { + if (isScriptSubtag(segment)) { + bcp47Segments.push(formatScriptSubtag(segment)) + continue + } + if (isRegionSubtag(segment)) { + bcp47Segments.push(segment.toUpperCase()) + continue + } + bcp47Segments.push(segment.toLowerCase()) + } + + return `b+${bcp47Segments.join('+')}` +} + +export const __test__ = { + localeKeyToAndroidValuesQualifier, +} + +function collectAndroidLocaleKeysFromWidgets(widgets: AndroidWidgetConfig[]): Set { + const locales = new Set() + for (const w of widgets) { + for (const field of ['displayName', 'description'] as const) { + const v = w[field] + if (isWidgetLocalizedMap(v)) { + for (const [localeKey, text] of Object.entries(v)) { + if (typeof text === 'string' && text.trim()) { + locales.add(localeKey) + } + } + } + } + } + return locales +} + +function resolveAndroidWidgetLabel( + widget: AndroidWidgetConfig, + field: 'displayName' | 'description', + localeKey: string | null +): string { + const v = widget[field] + if (!isWidgetLocalizedMap(v)) { + return v + } + if (localeKey !== null) { + const localized = v[localeKey] + if (typeof localized === 'string' && localized.trim()) { + return localized + } + } + return widgetLabelEnglish(v) +} + +function escapeAndroidStringRes(text: string): string { + return text + .replace(/\\/g, '\\\\') + .replace(/"/g, '\\"') + .replace(/'/g, "\\'") + .replace(/\n/g, '\\n') + .replace(/\r/g, '\\r') + .replace(/&/g, '&') + .replace(//g, '>') +} + +function generateVoltraWidgetsStringResourcesXml(widgets: AndroidWidgetConfig[], localeKey: string | null): string { + const localeComment = + localeKey === null + ? 'default (values/)' + : `locale ${localeKey} → values-${localeKeyToAndroidValuesQualifier(localeKey)}` + const stringEntries = widgets - .map( - (widget) => - `${widget.displayName}\n ${widget.description}` - ) + .map((widget) => { + const label = escapeAndroidStringRes(resolveAndroidWidgetLabel(widget, 'displayName', localeKey)) + const desc = escapeAndroidStringRes(resolveAndroidWidgetLabel(widget, 'description', localeKey)) + return `${label}\n ${desc}` + }) .join('\n ') return dedent` - + ${stringEntries} ` } +/** + * Removes generated voltra_widgets.xml from values-/ folders we no longer use. + */ +function cleanupStaleVoltraWidgetLocaleFiles(mainRes: string, activeQualifiers: Set): void { + if (!fs.existsSync(mainRes)) { + return + } + + for (const entry of fs.readdirSync(mainRes)) { + if (!entry.startsWith('values-')) { + continue + } + + const qualifier = entry.slice('values-'.length) + const stringsFile = path.join(mainRes, entry, VOLTRA_WIDGET_STRINGS_FILE) + if (!fs.existsSync(stringsFile)) { + continue + } + + if (activeQualifiers.has(qualifier)) { + continue + } + + fs.unlinkSync(stringsFile) + try { + const dir = path.join(mainRes, entry) + if (fs.readdirSync(dir).length === 0) { + fs.rmdirSync(dir) + } + } catch { + /* ignore */ + } + } +} + // ============================================================================ // Placeholder Layout XML // ============================================================================ @@ -169,12 +347,12 @@ function generatePlaceholderLayoutXml(): string { android:layout_width="match_parent" android:layout_height="match_parent" android:background="?android:attr/colorBackground"> - + android:indeterminate="true" /> ` } diff --git a/packages/expo-plugin/src/index.ts b/packages/expo-plugin/src/index.ts index 50b7f218..5ff78226 100644 --- a/packages/expo-plugin/src/index.ts +++ b/packages/expo-plugin/src/index.ts @@ -91,5 +91,8 @@ export type { VoltraConfigPlugin, WidgetConfig, WidgetFamily, + WidgetInitialStatePath, + WidgetLabel, + WidgetLocalizedCopy, WidgetServerUpdateConfig, } from './types' diff --git a/packages/expo-plugin/src/ios-widget/files/swift.node.test.ts b/packages/expo-plugin/src/ios-widget/files/swift.node.test.ts new file mode 100644 index 00000000..8fa35bd7 --- /dev/null +++ b/packages/expo-plugin/src/ios-widget/files/swift.node.test.ts @@ -0,0 +1,22 @@ +import { __test__ } from './swift' + +describe('generateInitialStatesSwift', () => { + it('embeds the locale helper into generated initial states Swift', () => { + const swift = __test__.generateInitialStatesSwift( + new Map([ + [ + 'weather', + new Map([ + ['en-US', '{"ok":true}'], + ['pl', '{"ok":false}'], + ]), + ], + ]) + ) + + expect(swift).toContain('private enum VoltraGeneratedInitialStateLocale') + expect(swift).toContain('VoltraGeneratedInitialStateLocale.preferredLanguageTags()') + expect(swift).toContain('VoltraGeneratedInitialStateLocale.pickJson(from: perLocale, preferredLanguages: tags)') + expect(swift).not.toContain('VoltraInitialStateLocale.preferredLanguageTags()') + }) +}) diff --git a/packages/expo-plugin/src/ios-widget/files/swift.ts b/packages/expo-plugin/src/ios-widget/files/swift.ts index 88dcec97..cccfad54 100644 --- a/packages/expo-plugin/src/ios-widget/files/swift.ts +++ b/packages/expo-plugin/src/ios-widget/files/swift.ts @@ -3,9 +3,12 @@ import * as fs from 'fs' import * as path from 'path' import { DEFAULT_WIDGET_FAMILIES, WIDGET_FAMILY_MAP } from '../../constants' -import type { WidgetConfig } from '../../types' +import type { WidgetConfig, WidgetLabel } from '../../types' +import { VOLTRA_WIDGET_STRINGS_BASENAME } from '../../utils/fileDiscovery' import { logger } from '../../utils/logger' +import type { PrerenderedWidgetStates } from '../../utils/prerender' import { prerenderWidgetState } from '../../utils/prerender' +import { isWidgetLocalizedMap, widgetLabelEnglish } from '../../utils/widgetLabel' export interface GenerateSwiftFilesOptions { targetPath: string @@ -39,12 +42,16 @@ export async function generateSwiftFiles(options: GenerateSwiftFilesOptions): Pr // Prerender widget initial states if any widgets have initialStatePath configured const prerenderedStates = await prerenderWidgetState(widgets || [], projectRoot, renderWidgetToString) + syncVoltraWidgetGalleryStrings(targetPath, widgets) + // Generate the initial states Swift file const initialStatesContent = generateInitialStatesSwift(prerenderedStates) const initialStatesPath = path.join(targetPath, 'VoltraWidgetInitialStates.swift') fs.writeFileSync(initialStatesPath, initialStatesContent) - logger.info(`Generated VoltraWidgetInitialStates.swift with ${prerenderedStates.size} pre-rendered widget states`) + logger.info( + `Generated VoltraWidgetInitialStates.swift with ${prerenderedStates.size} widget(s) (localized initial states where configured)` + ) // Generate the widget bundle Swift file const widgetBundleContent = @@ -56,10 +63,201 @@ export async function generateSwiftFiles(options: GenerateSwiftFilesOptions): Pr logger.info(`Generated VoltraWidgetBundle.swift with ${widgets?.length ?? 0} home screen widgets`) } +const GENERATED_INITIAL_STATE_LOCALE_HELPER = dedent` + private enum VoltraGeneratedInitialStateLocale { + static func pickJson(from perLocale: [String: String], preferredLanguages: [String]) -> String? { + let entries = perLocale.filter { !$0.value.isEmpty } + if entries.isEmpty { + return nil + } + + func normalize(_ tag: String) -> String { + tag.trimmingCharacters(in: .whitespacesAndNewlines) + .lowercased() + .replacingOccurrences(of: "_", with: "-") + } + + var byNorm: [String: String] = [:] + for (k, v) in entries { + byNorm[normalize(k)] = v + } + + for pref in preferredLanguages { + let n = normalize(pref) + if let direct = byNorm[n] { + return direct + } + let lang = n.split(separator: "-").first.map(String.init) ?? n + for (key, val) in entries { + let kn = normalize(key) + let keyLang = kn.split(separator: "-").first.map(String.init) ?? kn + if keyLang == lang { + return val + } + } + } + + if let en = byNorm["en"] { + return en + } + if let englishFamily = entries.keys.sorted().first(where: { + let normalized = normalize($0) + return normalized == "en" || normalized.hasPrefix("en-") + }) { + return entries[englishFamily] + } + if let def = byNorm["__default"] { + return def + } + + let sorted = entries.keys.sorted() + guard let firstKey = sorted.first else { + return nil + } + return entries[firstKey] + } + + static func preferredLanguageTags() -> [String] { + Locale.preferredLanguages + } + } +` + // ============================================================================ -// Widget Bundle +// Widget gallery localization: *.lproj/VoltraWidgets.strings + LocalizedStringResource +// +// We use classic .strings tables (not .xcstrings): the `xcode` npm library assigns +// unknown lastKnownFileType to .xcstrings, so Xcode may not treat them as string tables +// and lookups fall back to English defaultValue. +// +// LocalizedStringResource is still appropriate for extensions (deferred resolution). +// https://developer.apple.com/documentation/foundation/localizedstringresource // ============================================================================ +function escapeForSwiftStringLiteral(s: string): string { + return s.replace(/\\/g, '\\\\').replace(/"/g, '\\"').replace(/\n/g, '\\n').replace(/\r/g, '\\r') +} + +function escapeDotStringsValue(s: string): string { + return escapeForSwiftStringLiteral(s) +} + +function collectGalleryStringsByLocale(widgets: WidgetConfig[]): Map> { + const byLocale = new Map>() + + const add = (locale: string, key: string, value: string) => { + let bucket = byLocale.get(locale) + if (!bucket) { + bucket = {} + byLocale.set(locale, bucket) + } + bucket[key] = value + } + + for (const w of widgets) { + if (isWidgetLocalizedMap(w.displayName)) { + const key = `voltra_widget_${w.id}_displayName` + for (const [locale, val] of Object.entries(w.displayName)) { + if (typeof val === 'string' && val.trim()) { + add(locale, key, val) + } + } + } + if (isWidgetLocalizedMap(w.description)) { + const key = `voltra_widget_${w.id}_description` + for (const [locale, val] of Object.entries(w.description)) { + if (typeof val === 'string' && val.trim()) { + add(locale, key, val) + } + } + } + } + + return byLocale +} + +function formatVoltraWidgetsStringsFile(entries: Record): string { + const sortedKeys = Object.keys(entries).sort() + const lines = sortedKeys.map((k) => `"${k}" = "${escapeDotStringsValue(entries[k]!)}";`) + return `/* Voltra widget gallery strings (auto-generated) */\n${lines.join('\n')}\n` +} + +/** Remove generated gallery strings and obsolete string catalog before rewriting. */ +function clearVoltraWidgetGalleryStringArtifacts(targetPath: string): void { + const catalogPath = path.join(targetPath, 'VoltraWidgets.xcstrings') + if (fs.existsSync(catalogPath)) { + fs.unlinkSync(catalogPath) + } + + if (!fs.existsSync(targetPath)) { + return + } + + for (const entry of fs.readdirSync(targetPath)) { + if (!entry.endsWith('.lproj')) { + continue + } + const stringsPath = path.join(targetPath, entry, VOLTRA_WIDGET_STRINGS_BASENAME) + if (fs.existsSync(stringsPath)) { + fs.unlinkSync(stringsPath) + } + const dirPath = path.join(targetPath, entry) + try { + if (fs.existsSync(dirPath) && fs.readdirSync(dirPath).length === 0) { + fs.rmdirSync(dirPath) + } + } catch { + /* ignore */ + } + } +} + +/** + * Writes `.lproj/VoltraWidgets.strings` for locale-map gallery labels. + */ +function syncVoltraWidgetGalleryStrings(targetPath: string, widgets: WidgetConfig[] | undefined): void { + const list = widgets ?? [] + clearVoltraWidgetGalleryStringArtifacts(targetPath) + + if (list.length === 0) { + return + } + + const byLocale = collectGalleryStringsByLocale(list) + if (byLocale.size === 0) { + return + } + + for (const [locale, kv] of byLocale) { + const lproj = path.join(targetPath, `${locale}.lproj`) + fs.mkdirSync(lproj, { recursive: true }) + fs.writeFileSync(path.join(lproj, VOLTRA_WIDGET_STRINGS_BASENAME), formatVoltraWidgetsStringsFile(kv), 'utf8') + } +} + +function widgetUsesGalleryLocalization(widget: WidgetConfig): boolean { + return isWidgetLocalizedMap(widget.displayName) || isWidgetLocalizedMap(widget.description) +} + +/** + * Widget gallery title / description: deferred lookup via LocalizedStringResource when using a locale map + * (recommended for extensions); plain Text for single-string config. + */ +function iosWidgetGalleryLabelSwiftExpr( + widgetId: string, + field: 'displayName' | 'description', + label: WidgetLabel +): string { + if (!isWidgetLocalizedMap(label)) { + return `Text("${escapeForSwiftStringLiteral(label)}")` + } + + const key = `voltra_widget_${widgetId}_${field}` + const defaultEnglish = escapeForSwiftStringLiteral(widgetLabelEnglish(label)) + + return `Text(LocalizedStringResource("${key}", defaultValue: String.LocalizationValue("${defaultEnglish}"), table: "VoltraWidgets"))` +} + /** * Generates Swift code for a single widget struct */ @@ -70,6 +268,9 @@ function generateWidgetStruct(widget: WidgetConfig): string { // Sanitize the widget id for use as a Swift identifier const structName = `VoltraWidget_${widget.id}` + const displayNameExpr = iosWidgetGalleryLabelSwiftExpr(widget.id, 'displayName', widget.displayName) + const descriptionExpr = iosWidgetGalleryLabelSwiftExpr(widget.id, 'description', widget.description) + return dedent` public struct ${structName}: Widget { private let widgetId = "${widget.id}" @@ -86,8 +287,8 @@ function generateWidgetStruct(widget: WidgetConfig): string { ) { entry in VoltraHomeWidgetView(entry: entry) } - .configurationDisplayName("${widget.displayName}") - .description("${widget.description}") + .configurationDisplayName(${displayNameExpr}) + .description(${descriptionExpr}) .supportedFamilies([${familiesSwift}]) .contentMarginsDisabled() } @@ -105,6 +306,9 @@ function generateWidgetBundleSwift(widgets: WidgetConfig[]): string { // Generate widget bundle body entries const widgetInstances = widgets.map((w) => `VoltraWidget_${w.id}()`).join('\n ') + const needsFoundation = widgets.some(widgetUsesGalleryLocalization) + const foundationImport = needsFoundation ? 'import Foundation\n' : '' + return dedent` // // VoltraWidgetBundle.swift @@ -113,7 +317,7 @@ function generateWidgetBundleSwift(widgets: WidgetConfig[]): string { // This file defines which Voltra widgets are available in your app. // - import SwiftUI + ${foundationImport}import SwiftUI import WidgetKit import VoltraWidget @@ -166,18 +370,22 @@ function generateDefaultWidgetBundleSwift(): string { // ============================================================================ /** - * Generates Swift code that bundles pre-rendered widget initial states. + * Generates Swift code that bundles pre-rendered widget initial states (per locale when configured). */ -function generateInitialStatesSwift(prerenderedStates: Map): string { +function generateInitialStatesSwift(prerenderedStates: PrerenderedWidgetStates): string { if (prerenderedStates.size === 0) { return generateEmptyInitialStatesSwift() } - // Generate the bundled states dictionary - const stateEntries = Array.from(prerenderedStates.entries()) - .map(([widgetId, json]) => { - const delimiter = getSwiftRawStringDelimiter(json) - return `"${widgetId}": ${delimiter}"${json}"${delimiter}` + const widgetEntries = Array.from(prerenderedStates.entries()) + .map(([widgetId, perLocale]) => { + const localeEntries = Array.from(perLocale.entries()) + .map(([localeKey, json]) => { + const delimiter = getSwiftRawStringDelimiter(json) + return `"${escapeForSwiftStringLiteral(localeKey)}": ${delimiter}"${json}"${delimiter}` + }) + .join(',\n ') + return `"${widgetId}": [\n ${localeEntries}\n ]` }) .join(',\n ') @@ -191,15 +399,21 @@ function generateInitialStatesSwift(prerenderedStates: Map): str import Foundation + ${GENERATED_INITIAL_STATE_LOCALE_HELPER} + public enum VoltraWidgetInitialStates { - private static let bundledStates: [String: String] = [ - ${stateEntries} + private static let bundledLocalizedStates: [String: [String: String]] = [ + ${widgetEntries} ] - /// Get the bundled initial state JSON for a widget. + /// Get the bundled initial state JSON for a widget, matching the device locale when multiple locales were built. /// Returns nil if no initial state was configured for the widget. public static func getInitialState(for widgetId: String) -> Data? { - guard let jsonString = bundledStates[widgetId] else { return nil } + guard let perLocale = bundledLocalizedStates[widgetId] else { return nil } + let tags = VoltraGeneratedInitialStateLocale.preferredLanguageTags() + guard let jsonString = VoltraGeneratedInitialStateLocale.pickJson(from: perLocale, preferredLanguages: tags) else { + return nil + } return jsonString.data(using: .utf8) } } @@ -252,3 +466,7 @@ function getSwiftRawStringDelimiter(str: string): string { const maxHashes = Math.max(...matches.map((m) => m.length - 1)) // -1 to exclude the '"' return '#'.repeat(maxHashes + 1) } + +export const __test__ = { + generateInitialStatesSwift, +} diff --git a/packages/expo-plugin/src/ios-widget/xcode/buildPhases.ts b/packages/expo-plugin/src/ios-widget/xcode/buildPhases.ts index 565a937f..fc3ab7e1 100644 --- a/packages/expo-plugin/src/ios-widget/xcode/buildPhases.ts +++ b/packages/expo-plugin/src/ios-widget/xcode/buildPhases.ts @@ -29,7 +29,8 @@ export function addBuildPhases(xcodeProject: XcodeProject, options: AddBuildPhas const buildPath = `""` const folderType = 'app_extension' - const { swiftFiles, intentFiles, assetDirectories } = widgetFiles + const { swiftFiles, intentFiles, assetDirectories, localizedStringResources } = widgetFiles + const resourcePaths = [...assetDirectories, ...localizedStringResources] // Sources build phase xcodeProject.addBuildPhase( @@ -61,7 +62,7 @@ export function addBuildPhases(xcodeProject: XcodeProject, options: AddBuildPhas xcodeProject.addBuildPhase([], 'PBXFrameworksBuildPhase', 'Frameworks', targetUuid, folderType, buildPath) // Resources build phase - xcodeProject.addBuildPhase([...assetDirectories], 'PBXResourcesBuildPhase', 'Resources', targetUuid) + xcodeProject.addBuildPhase([...resourcePaths], 'PBXResourcesBuildPhase', 'Resources', targetUuid) } /** @@ -73,7 +74,8 @@ export function ensureBuildPhases(xcodeProject: XcodeProject, options: EnsureBui const folderType = 'app_extension' const mainTargetUuid = options.mainTargetUuid ?? xcodeProject.getFirstTarget().uuid - const { swiftFiles, intentFiles, assetDirectories } = widgetFiles + const { swiftFiles, intentFiles, assetDirectories, localizedStringResources } = widgetFiles + const resourcePaths = [...assetDirectories, ...localizedStringResources] dedupeBuildPhasesForTarget(xcodeProject, targetUuid, 'PBXSourcesBuildPhase', 'Sources') dedupeBuildPhasesForTarget(xcodeProject, targetUuid, 'PBXFrameworksBuildPhase', 'Frameworks') @@ -115,11 +117,11 @@ export function ensureBuildPhases(xcodeProject: XcodeProject, options: EnsureBui // Resources build phase let resourcesPhase = xcodeProject.buildPhaseObject('PBXResourcesBuildPhase', 'Resources', targetUuid) if (!resourcesPhase) { - xcodeProject.addBuildPhase([...assetDirectories], 'PBXResourcesBuildPhase', 'Resources', targetUuid) + xcodeProject.addBuildPhase([...resourcePaths], 'PBXResourcesBuildPhase', 'Resources', targetUuid) resourcesPhase = xcodeProject.buildPhaseObject('PBXResourcesBuildPhase', 'Resources', targetUuid) } if (resourcesPhase) { - ensureBuildPhaseFiles(xcodeProject, resourcesPhase, [...assetDirectories]) + ensureBuildPhaseFiles(xcodeProject, resourcesPhase, [...resourcePaths]) } } diff --git a/packages/expo-plugin/src/ios-widget/xcode/groups.ts b/packages/expo-plugin/src/ios-widget/xcode/groups.ts index ebc6c7f3..8fae66d4 100644 --- a/packages/expo-plugin/src/ios-widget/xcode/groups.ts +++ b/packages/expo-plugin/src/ios-widget/xcode/groups.ts @@ -14,11 +14,19 @@ export interface AddPbxGroupOptions { */ export function addPbxGroup(xcodeProject: XcodeProject, options: AddPbxGroupOptions): void { const { targetName, widgetFiles } = options - const { swiftFiles, intentFiles, assetDirectories, entitlementFiles, plistFiles } = widgetFiles + const { swiftFiles, intentFiles, assetDirectories, entitlementFiles, plistFiles, localizedStringResources } = + widgetFiles // Add PBX group with all widget files const { uuid: pbxGroupUuid } = xcodeProject.addPbxGroup( - [...swiftFiles, ...intentFiles, ...entitlementFiles, ...plistFiles, ...assetDirectories], + [ + ...swiftFiles, + ...intentFiles, + ...entitlementFiles, + ...plistFiles, + ...assetDirectories, + ...localizedStringResources, + ], targetName, targetName ) @@ -39,8 +47,16 @@ export function addPbxGroup(xcodeProject: XcodeProject, options: AddPbxGroupOpti */ export function ensurePbxGroup(xcodeProject: XcodeProject, options: AddPbxGroupOptions): void { const { targetName, widgetFiles } = options - const { swiftFiles, intentFiles, assetDirectories, entitlementFiles, plistFiles } = widgetFiles - const allFiles = [...swiftFiles, ...intentFiles, ...entitlementFiles, ...plistFiles, ...assetDirectories] + const { swiftFiles, intentFiles, assetDirectories, entitlementFiles, plistFiles, localizedStringResources } = + widgetFiles + const allFiles = [ + ...swiftFiles, + ...intentFiles, + ...entitlementFiles, + ...plistFiles, + ...assetDirectories, + ...localizedStringResources, + ] const existingGroup = xcodeProject.pbxGroupByName(targetName) if (!existingGroup) { diff --git a/packages/expo-plugin/src/types.ts b/packages/expo-plugin/src/types.ts index b9883a76..dacfe5da 100644 --- a/packages/expo-plugin/src/types.ts +++ b/packages/expo-plugin/src/types.ts @@ -8,6 +8,20 @@ import { ConfigPlugin } from '@expo/config-plugins' // Widget Types // ============================================================================ +/** + * Per-locale strings for widget picker/gallery labels (`displayName`, `description`). + * Keys should be BCP-47-style locale tags (e.g. `en`, `pl`, `pt-BR`). Plain `string` is still allowed for a single-language setup. + */ +export type WidgetLocalizedCopy = Record + +export type WidgetLabel = string | WidgetLocalizedCopy + +/** + * Build-time widget initial state source: a single file path, or per-locale paths (same key rules as `WidgetLocalizedCopy`). + * Each path must point to a module that exports the widget variants / default export for prerendering. + */ +export type WidgetInitialStatePath = string | WidgetLocalizedCopy + /** * Supported widget size families */ @@ -30,23 +44,24 @@ export interface WidgetConfig { */ id: string /** - * Display name shown in the widget gallery + * Display name shown in the widget gallery. + * For locale maps, keys must be BCP-47-like (`en`, `pl`, `pt-BR`, `zh-Hans`); include an English locale when possible so defaults align with Android `values/` and iOS fallbacks. */ - displayName: string + displayName: WidgetLabel /** - * Description shown in the widget gallery + * Description shown in the widget gallery (same rules as `displayName`). */ - description: string + description: WidgetLabel /** * Supported widget sizes * @default ['systemSmall', 'systemMedium', 'systemLarge'] */ supportedFamilies?: WidgetFamily[] /** - * Path to a file that default exports a WidgetVariants object for initial widget state. + * Path to a file that default exports a WidgetVariants object for initial widget state (or a locale map of paths). * This will be pre-rendered at build time and bundled into the iOS app. */ - initialStatePath?: string + initialStatePath?: WidgetInitialStatePath /** * Configuration for server-driven widget updates. * When configured, the widget will periodically fetch new content from a remote server @@ -89,6 +104,8 @@ export interface WidgetFiles { plistFiles: string[] assetDirectories: string[] intentFiles: string[] + /** Paths relative to the widget extension root (e.g. en.lproj/VoltraWidgets.strings) */ + localizedStringResources: string[] } // ============================================================================ @@ -104,13 +121,13 @@ export interface AndroidWidgetConfig { */ id: string /** - * Display name shown in the widget picker + * Display name shown in the widget picker (same localization rules as iOS `widgets[].displayName`). */ - displayName: string + displayName: WidgetLabel /** - * Description shown in the widget picker + * Description shown in the widget picker (same localization rules as iOS `widgets[].description`). */ - description: string + description: WidgetLabel /** * Minimum width in dp. If provided, takes precedence over minCellWidth. */ @@ -146,10 +163,10 @@ export interface AndroidWidgetConfig { */ widgetCategory?: 'home_screen' | 'keyguard' | 'home_screen|keyguard' /** - * Path to a file that default exports a WidgetVariants object for initial widget state. + * Path to a file that default exports a WidgetVariants object for initial widget state (or a locale map of paths). * This will be pre-rendered at build time and bundled into the app. */ - initialStatePath?: string + initialStatePath?: WidgetInitialStatePath /** * Configuration for server-driven widget updates. * When configured, the widget will periodically fetch new content from a remote server diff --git a/packages/expo-plugin/src/utils/fileDiscovery.ts b/packages/expo-plugin/src/utils/fileDiscovery.ts index d10baedb..4da07365 100644 --- a/packages/expo-plugin/src/utils/fileDiscovery.ts +++ b/packages/expo-plugin/src/utils/fileDiscovery.ts @@ -4,6 +4,9 @@ import * as path from 'path' import type { WidgetFiles } from '../types' import { logger } from './logger' +/** Table name matches basename without extension; co-located per locale in *.lproj */ +export const VOLTRA_WIDGET_STRINGS_BASENAME = 'VoltraWidgets.strings' + /** * Scans the widget extension target directory and returns categorized file lists. * @@ -21,6 +24,7 @@ export function getWidgetFiles(targetPath: string, targetName: string): WidgetFi plistFiles: [], assetDirectories: [], intentFiles: [], + localizedStringResources: [], } if (!fs.existsSync(targetPath)) { @@ -35,6 +39,16 @@ export function getWidgetFiles(targetPath: string, targetName: string): WidgetFi const files = fs.readdirSync(targetPath) + for (const entry of files) { + if (!entry.endsWith('.lproj')) { + continue + } + const stringsPath = path.join(targetPath, entry, VOLTRA_WIDGET_STRINGS_BASENAME) + if (fs.existsSync(stringsPath)) { + widgetFiles.localizedStringResources.push(`${entry}/${VOLTRA_WIDGET_STRINGS_BASENAME}`) + } + } + for (const file of files) { const itemPath = path.join(targetPath, file) const isDirectory = fs.lstatSync(itemPath).isDirectory() diff --git a/packages/expo-plugin/src/utils/localePick.node.test.ts b/packages/expo-plugin/src/utils/localePick.node.test.ts new file mode 100644 index 00000000..78493031 --- /dev/null +++ b/packages/expo-plugin/src/utils/localePick.node.test.ts @@ -0,0 +1,24 @@ +import { pickLocalizedValue } from './localePick' + +describe('pickLocalizedValue', () => { + it('prefers exact locale tag match', () => { + const out = pickLocalizedValue({ en: '{"a":1}', pl: '{"a":2}', 'pt-BR': '{"a":3}' }, ['pt-BR', 'en']) + expect(out).toBe('{"a":3}') + }) + + it('falls back to language-only match', () => { + const out = pickLocalizedValue({ en: 'en', pl: 'pl' }, ['pl-PL']) + expect(out).toBe('pl') + }) + + it('uses en then __default then first sorted key', () => { + expect(pickLocalizedValue({ de: 'de', en: 'en' }, ['fr'])).toBe('en') + expect(pickLocalizedValue({ __default: 'd', pl: 'pl' }, ['fr'])).toBe('d') + expect(pickLocalizedValue({ zz: 'z', aa: 'a' }, ['fr'])).toBe('a') + }) + + it('prefers english-family locales when plain en is absent', () => { + expect(pickLocalizedValue({ 'en-US': 'us', pl: 'pl' }, ['en'])).toBe('us') + expect(pickLocalizedValue({ de: 'de', en_GB: 'gb' }, ['en'])).toBe('gb') + }) +}) diff --git a/packages/expo-plugin/src/utils/localePick.ts b/packages/expo-plugin/src/utils/localePick.ts new file mode 100644 index 00000000..798fc1a5 --- /dev/null +++ b/packages/expo-plugin/src/utils/localePick.ts @@ -0,0 +1,53 @@ +/** + * Picks a localized string value from a locale-keyed map using the same fallback rules as iOS/Android runtimes: + * preferred language tags (full match, then language-only), then `en`, then `__default`, then first value. + */ +export function normalizeLocaleTag(tag: string): string { + return tag.trim().toLowerCase().replace(/_/g, '-') +} + +export function pickLocalizedValue( + perLocale: Record, + preferredLanguages: string[] +): string | undefined { + const entries = Object.entries(perLocale).filter(([, v]) => typeof v === 'string' && v.length > 0) + if (entries.length === 0) { + return undefined + } + + const byNorm = new Map() + for (const [k, v] of entries) { + byNorm.set(normalizeLocaleTag(k), v) + } + + const DEFAULT_KEY = '__default' + + for (const pref of preferredLanguages) { + const n = normalizeLocaleTag(pref) + const direct = byNorm.get(n) + if (direct !== undefined) { + return direct + } + const lang = n.split('-')[0] ?? n + for (const [key, val] of entries) { + const kn = normalizeLocaleTag(key) + const keyLang = kn.split('-')[0] ?? kn + if (keyLang === lang) { + return val + } + } + } + + const en = byNorm.get('en') ?? entries.find(([k]) => normalizeLocaleTag(k) === 'en')?.[1] + if (en !== undefined) { + return en + } + + const defaultVal = byNorm.get(DEFAULT_KEY) ?? entries.find(([k]) => k === DEFAULT_KEY)?.[1] + if (defaultVal !== undefined) { + return defaultVal + } + + const sorted = [...entries].sort(([a], [b]) => a.localeCompare(b)) + return sorted[0]?.[1] +} diff --git a/packages/expo-plugin/src/utils/prerender.ts b/packages/expo-plugin/src/utils/prerender.ts index f8c68113..828e0c01 100644 --- a/packages/expo-plugin/src/utils/prerender.ts +++ b/packages/expo-plugin/src/utils/prerender.ts @@ -5,6 +5,8 @@ import vm from 'node:vm' import * as babel from '@babel/core' import { MODULE_EXTENSIONS } from '../constants' +import type { WidgetInitialStatePath, WidgetLabel } from '../types' +import { isWidgetLocalizedMap } from './widgetLabel' /** * Type for the widget renderer function @@ -16,9 +18,12 @@ export type WidgetRenderer = (variants: any) => string */ export interface PrerenderableWidget { id: string - initialStatePath?: string + initialStatePath?: WidgetInitialStatePath } +/** widgetId -> locale key -> prerendered JSON string (single-file widgets use `__default`) */ +export type PrerenderedWidgetStates = Map> + /** * Check if a module specifier is a relative or absolute path (local file) */ @@ -168,36 +173,38 @@ function evaluateWidgetModule(projectRoot: string, filePath: string): any { * @param widgets - Array of widget configurations * @param projectRoot - Root directory of the Expo project * @param renderer - The renderer function to use (voltra/server or voltra/android/server) - * @returns Map of widgetId -> prerendered widget state as JSON string + * @returns Map of widgetId -> (locale key -> prerendered JSON string) */ export async function prerenderWidgetState( widgets: PrerenderableWidget[], projectRoot: string, renderer: WidgetRenderer -): Promise> { - const prerenderedStates = new Map() +): Promise { + const prerenderedStates: PrerenderedWidgetStates = new Map() for (const widget of widgets) { if (!widget.initialStatePath) { continue } - try { - // Resolve the absolute path to the widget file - const absoluteWidgetPath = path.resolve(projectRoot, widget.initialStatePath) - - // Evaluate the module (transpiles with Babel and executes in VM) - const widgetVariants = evaluateWidgetModule(projectRoot, absoluteWidgetPath) + const pathSpec = widget.initialStatePath + const perLocalePaths: Record = isWidgetLocalizedMap(pathSpec as WidgetLabel) + ? (pathSpec as Record) + : { __default: pathSpec as string } - // Render the widget variants to a JSON string - const prerenderedState = renderer(widgetVariants) + const inner = new Map() - prerenderedStates.set(widget.id, prerenderedState) + try { + for (const [localeKey, relativePath] of Object.entries(perLocalePaths)) { + const absoluteWidgetPath = path.resolve(projectRoot, relativePath) + const widgetVariants = evaluateWidgetModule(projectRoot, absoluteWidgetPath) + const prerenderedState = renderer(widgetVariants) + inner.set(localeKey, prerenderedState) + } + prerenderedStates.set(widget.id, inner) } catch (error) { throw new Error( - `Failed to prerender widget state for ${widget.id} (${widget.initialStatePath}): ${ - error instanceof Error ? error.message : String(error) - }` + `Failed to prerender widget state for ${widget.id}: ${error instanceof Error ? error.message : String(error)}` ) } } diff --git a/packages/expo-plugin/src/utils/widgetLabel.ts b/packages/expo-plugin/src/utils/widgetLabel.ts new file mode 100644 index 00000000..6437622d --- /dev/null +++ b/packages/expo-plugin/src/utils/widgetLabel.ts @@ -0,0 +1,19 @@ +import type { WidgetLabel, WidgetLocalizedCopy } from '../types' + +import { pickLocalizedValue } from './localePick' + +export function isWidgetLocalizedMap(label: WidgetLabel): label is WidgetLocalizedCopy { + return typeof label === 'object' && label !== null && !Array.isArray(label) +} + +/** + * Development / fallback English copy for LocalizedStringResource.defaultValue. + * Prefers `en`, then `en-*`, then default fallback order from locale picking. + */ +export function widgetLabelEnglish(label: WidgetLabel): string { + if (!isWidgetLocalizedMap(label)) { + return label + } + + return pickLocalizedValue(label, ['en']) ?? '' +} diff --git a/packages/expo-plugin/src/validation.node.test.ts b/packages/expo-plugin/src/validation.node.test.ts new file mode 100644 index 00000000..9ac4394b --- /dev/null +++ b/packages/expo-plugin/src/validation.node.test.ts @@ -0,0 +1,15 @@ +import { validateInitialStatePath } from './validation' + +describe('validateInitialStatePath', () => { + it('accepts a plain path without projectRoot', () => { + expect(() => validateInitialStatePath('./widgets/a.tsx', 'w')).not.toThrow() + }) + + it('accepts a locale map of paths', () => { + expect(() => validateInitialStatePath({ en: './widgets/en.tsx', pl: './widgets/pl.tsx' }, 'w')).not.toThrow() + }) + + it('rejects empty locale map', () => { + expect(() => validateInitialStatePath({} as any, 'w')).toThrow(/must not be empty/) + }) +}) diff --git a/packages/expo-plugin/src/validation.ts b/packages/expo-plugin/src/validation.ts index 30f4884f..76195afd 100644 --- a/packages/expo-plugin/src/validation.ts +++ b/packages/expo-plugin/src/validation.ts @@ -1,12 +1,163 @@ import * as fs from 'fs' import * as path from 'path' -import type { AndroidWidgetConfig, ConfigPluginProps, WidgetConfig, WidgetFamily } from './types' +import type { + AndroidWidgetConfig, + ConfigPluginProps, + WidgetConfig, + WidgetFamily, + WidgetInitialStatePath, +} from './types' /** * Validation functions for the Voltra plugin */ +/** Widget id: Swift / Kotlin identifier fragment and Android XML token fragment */ +const WIDGET_ID_PATTERN = /^[a-zA-Z_][a-zA-Z0-9_]*$/ + +/** + * Locale keys in app.json become iOS `.lproj` folder names and Android `values-*` qualifiers (after mapping). + * Restrict to safe, BCP-47-like tags: no slashes, spaces, or exotic punctuation. + */ +const LOCALE_KEY_PATTERN = /^[a-zA-Z][a-zA-Z0-9]*([_-][a-zA-Z0-9]+)*$/ + +const MAX_LOCALE_KEY_LENGTH = 32 + +function validateHomeScreenWidgetId(widgetId: unknown): asserts widgetId is string { + if (!widgetId || typeof widgetId !== 'string') { + throw new Error('Widget ID is required and must be a string') + } + + if (!WIDGET_ID_PATTERN.test(widgetId)) { + throw new Error( + `Widget ID '${widgetId}' is invalid. ` + + 'Must start with a letter or underscore and contain only alphanumeric characters and underscores.' + ) + } +} + +function assertValidLocaleKey(localeKey: string, widgetId: string, fieldName: string): void { + if (localeKey.trim() !== localeKey) { + throw new Error( + `Widget '${widgetId}': ${fieldName} locale key '${localeKey}' must not have leading or trailing whitespace` + ) + } + + if (!localeKey) { + throw new Error(`Widget '${widgetId}': ${fieldName} locale map contains an empty locale key`) + } + + if (localeKey.length > MAX_LOCALE_KEY_LENGTH) { + throw new Error( + `Widget '${widgetId}': ${fieldName} locale key '${localeKey}' exceeds ${MAX_LOCALE_KEY_LENGTH} characters` + ) + } + + if (!LOCALE_KEY_PATTERN.test(localeKey)) { + throw new Error( + `Widget '${widgetId}': ${fieldName} locale key '${localeKey}' is invalid. ` + + 'Use BCP-47-style tags (e.g. en, pl, pt-BR, zh-Hans). Letters, digits, single separators _ or - only.' + ) + } +} + +function validateWidgetLabel(value: unknown, widgetId: string, fieldName: string): void { + if (typeof value === 'string') { + if (!value.trim()) { + throw new Error(`Widget '${widgetId}': ${fieldName} is required`) + } + return + } + + if (typeof value !== 'object' || value === null || Array.isArray(value)) { + throw new Error( + `Widget '${widgetId}': ${fieldName} must be a string or a locale map (e.g. { "en": "...", "pl": "..." })` + ) + } + + const entries = Object.entries(value as Record) + if (entries.length === 0) { + throw new Error(`Widget '${widgetId}': ${fieldName} locale map must not be empty`) + } + + const localeKeys = new Set() + for (const [locale, v] of entries) { + assertValidLocaleKey(locale, widgetId, fieldName) + + const normalized = locale.toLowerCase().replace(/_/g, '-') + if (localeKeys.has(normalized)) { + throw new Error( + `Widget '${widgetId}': ${fieldName} duplicates locale '${locale}' (underscore and hyphen forms count as the same, e.g. pt_BR vs pt-BR)` + ) + } + localeKeys.add(normalized) + + if (typeof v !== 'string' || !v.trim()) { + throw new Error(`Widget '${widgetId}': ${fieldName}.${locale} must be a non-empty string`) + } + } +} + +/** + * Validates optional initialStatePath: plain string or locale map of project-relative file paths. + */ +export function validateInitialStatePath( + value: WidgetInitialStatePath | undefined, + widgetId: string, + projectRoot?: string +): void { + if (value === undefined) { + return + } + + const assertPathExists = (relativePath: string, ctx: string) => { + if (!relativePath.trim()) { + throw new Error(`Widget '${widgetId}': initialStatePath ${ctx} must be a non-empty path`) + } + if (projectRoot) { + const fullPath = path.join(projectRoot, relativePath) + if (!fs.existsSync(fullPath)) { + throw new Error(`Widget '${widgetId}': initialStatePath file not found at ${relativePath}`) + } + } + } + + if (typeof value === 'string') { + assertPathExists(value, '') + return + } + + if (typeof value !== 'object' || value === null || Array.isArray(value)) { + throw new Error( + `Widget '${widgetId}': initialStatePath must be a string or a locale map of paths (e.g. { "en": "./widgets/en.tsx", "pl": "./widgets/pl.tsx" })` + ) + } + + const entries = Object.entries(value as Record) + if (entries.length === 0) { + throw new Error(`Widget '${widgetId}': initialStatePath locale map must not be empty`) + } + + const localeKeys = new Set() + for (const [locale, v] of entries) { + assertValidLocaleKey(locale, widgetId, 'initialStatePath') + + const normalized = locale.toLowerCase().replace(/_/g, '-') + if (localeKeys.has(normalized)) { + throw new Error( + `Widget '${widgetId}': initialStatePath duplicates locale '${locale}' (underscore and hyphen forms count as the same)` + ) + } + localeKeys.add(normalized) + + if (typeof v !== 'string' || !v.trim()) { + throw new Error(`Widget '${widgetId}': initialStatePath.${locale} must be a non-empty path string`) + } + assertPathExists(v, `.${locale}`) + } +} + // ============================================================================ // iOS Widget Validation // ============================================================================ @@ -26,27 +177,12 @@ const VALID_FAMILIES: Set = new Set([ * Throws an error if validation fails. */ export function validateWidgetConfig(widget: WidgetConfig): void { - // Validate widget ID - if (!widget.id || typeof widget.id !== 'string') { - throw new Error('Widget ID is required and must be a string') - } - - if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(widget.id)) { - throw new Error( - `Widget ID '${widget.id}' is invalid. ` + - 'Must start with a letter or underscore and contain only alphanumeric characters and underscores.' - ) - } - - // Validate display name - if (!widget.displayName?.trim()) { - throw new Error(`Widget '${widget.id}': displayName is required`) - } + validateHomeScreenWidgetId(widget.id) - // Validate description - if (!widget.description?.trim()) { - throw new Error(`Widget '${widget.id}': description is required`) - } + validateWidgetLabel(widget.displayName, widget.id, 'displayName') + validateWidgetLabel(widget.description, widget.id, 'description') + /** File existence is checked when `projectRoot` is available (e.g. Android prebuild). */ + validateInitialStatePath(widget.initialStatePath, widget.id) // Validate supported families if provided if (widget.supportedFamilies) { @@ -74,27 +210,11 @@ export function validateWidgetConfig(widget: WidgetConfig): void { * Throws an error if validation fails. */ export function validateAndroidWidgetConfig(widget: AndroidWidgetConfig, projectRoot?: string): void { - // Validate widget ID - if (!widget.id || typeof widget.id !== 'string') { - throw new Error('Widget ID is required and must be a string') - } + validateHomeScreenWidgetId(widget.id) - if (!/^[a-zA-Z_][a-zA-Z0-9_]*$/.test(widget.id)) { - throw new Error( - `Widget ID '${widget.id}' is invalid. ` + - 'Must start with a letter or underscore and contain only alphanumeric characters and underscores.' - ) - } - - // Validate display name - if (!widget.displayName?.trim()) { - throw new Error(`Widget '${widget.id}': displayName is required`) - } - - // Validate description - if (!widget.description?.trim()) { - throw new Error(`Widget '${widget.id}': description is required`) - } + validateWidgetLabel(widget.displayName, widget.id, 'displayName') + validateWidgetLabel(widget.description, widget.id, 'description') + validateInitialStatePath(widget.initialStatePath, widget.id, projectRoot) // Validate targetCellWidth if (typeof widget.targetCellWidth !== 'number') { diff --git a/packages/expo-plugin/tsconfig.base.json b/packages/expo-plugin/tsconfig.base.json index 49f9c30e..6bb57ecd 100644 --- a/packages/expo-plugin/tsconfig.base.json +++ b/packages/expo-plugin/tsconfig.base.json @@ -13,5 +13,5 @@ "isolatedModules": true }, "include": ["./src"], - "exclude": ["**/__mocks__/*", "**/__tests__/*", "**/__rsc_tests__/*"] + "exclude": ["**/__mocks__/*", "**/__tests__/*", "**/__rsc_tests__/*", "**/*.node.test.ts"] } diff --git a/packages/expo-plugin/tsconfig.jest.json b/packages/expo-plugin/tsconfig.jest.json new file mode 100644 index 00000000..c0c766d0 --- /dev/null +++ b/packages/expo-plugin/tsconfig.jest.json @@ -0,0 +1,8 @@ +{ + "extends": "./tsconfig.base.json", + "compilerOptions": { + "noEmit": true, + "types": ["jest", "node"] + }, + "include": ["./src/**/*.ts"] +} diff --git a/packages/voltra/android/src/main/java/voltra/widget/VoltraWidgetManager.kt b/packages/voltra/android/src/main/java/voltra/widget/VoltraWidgetManager.kt index 72ad25e8..0626c748 100644 --- a/packages/voltra/android/src/main/java/voltra/widget/VoltraWidgetManager.kt +++ b/packages/voltra/android/src/main/java/voltra/widget/VoltraWidgetManager.kt @@ -4,6 +4,8 @@ import android.appwidget.AppWidgetManager import android.content.ComponentName import android.content.Context import android.content.SharedPreferences +import android.content.res.Resources +import android.os.Build import android.util.Log import android.widget.RemoteViews import androidx.glance.appwidget.ExperimentalGlanceRemoteViewsApi @@ -25,6 +27,9 @@ class VoltraWidgetManager( private const val KEY_JSON_PREFIX = "Voltra_Widget_JSON_" private const val KEY_DEEP_LINK_PREFIX = "Voltra_Widget_DeepLinkURL_" private const val ASSET_INITIAL_STATES = "voltra_initial_states.json" + + /** Keys must match `packages/expo-plugin/src/android/files/initialStates.ts` */ + private const val LOCALIZED_INITIAL_STATE_KEY = "__voltraLocales" } private val prefs: SharedPreferences = @@ -92,16 +97,81 @@ class VoltraWidgetManager( val jsonString = String(buffer, Charset.forName("UTF-8")) val jsonObject = JSONObject(jsonString) - if (jsonObject.has(widgetId)) { - jsonObject.get(widgetId).toString() - } else { + if (!jsonObject.has(widgetId)) { null + } else { + val raw = jsonObject.get(widgetId) + when (raw) { + is JSONObject -> resolveInitialStatePayload(raw, context.resources) + else -> raw.toString() + } } } catch (e: Exception) { // Asset might not exist or be invalid, which is fine if no pre-rendering was configured null } + /** + * Legacy flat payload vs localized `{ "__voltraLocales": { "en": {...}, "pl": {...} } }`. + */ + private fun resolveInitialStatePayload( + obj: JSONObject, + res: Resources, + ): String { + if (!obj.has(LOCALIZED_INITIAL_STATE_KEY)) { + return obj.toString() + } + val perLocale = obj.optJSONObject(LOCALIZED_INITIAL_STATE_KEY) ?: return obj.toString() + val picked = pickLocalizedPayload(perLocale, res) ?: return obj.toString() + return picked.toString() + } + + private fun preferredLanguageTags(res: Resources): List = + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) { + val locales = res.configuration.locales + (0 until locales.size()).map { locales[it].toLanguageTag() } + } else { + @Suppress("DEPRECATION") + listOf(res.configuration.locale.toLanguageTag()) + } + + private fun normalizeLocaleTag(tag: String): String = tag.trim().lowercase().replace('_', '-') + + /** Mirrors `packages/expo-plugin/src/utils/localePick.ts` */ + private fun pickLocalizedPayload( + perLocale: JSONObject, + res: Resources, + ): JSONObject? { + val keys = perLocale.keys().asSequence().toList() + if (keys.isEmpty()) { + return null + } + val preferred = preferredLanguageTags(res) + + fun keyNorm(k: String) = normalizeLocaleTag(k) + val byNorm = keys.associateBy { keyNorm(it) } + + for (pref in preferred) { + val n = keyNorm(pref) + byNorm[n]?.let { k -> return perLocale.optJSONObject(k) } + val lang = n.substringBefore('-') + for (k in keys) { + val kn = keyNorm(k) + val keyLang = kn.substringBefore('-') + if (keyLang == lang) { + return perLocale.optJSONObject(k) + } + } + } + if (perLocale.has("en")) { + return perLocale.optJSONObject("en") + } + if (perLocale.has("__default")) { + return perLocale.optJSONObject("__default") + } + return keys.sorted().firstOrNull()?.let { perLocale.optJSONObject(it) } + } + /** * Read widget deep link URL from SharedPreferences */ diff --git a/packages/voltra/ios/shared/VoltraInitialStateLocale.swift b/packages/voltra/ios/shared/VoltraInitialStateLocale.swift new file mode 100644 index 00000000..cdc17572 --- /dev/null +++ b/packages/voltra/ios/shared/VoltraInitialStateLocale.swift @@ -0,0 +1,54 @@ +import Foundation + +/// Matches `packages/expo-plugin/src/utils/localePick.ts` fallback order for bundled widget initial JSON. +public enum VoltraInitialStateLocale { + public static func pickJson(from perLocale: [String: String], preferredLanguages: [String]) -> String? { + let entries = perLocale.filter { !$0.value.isEmpty } + if entries.isEmpty { + return nil + } + + func normalize(_ tag: String) -> String { + tag.trimmingCharacters(in: .whitespacesAndNewlines) + .lowercased() + .replacingOccurrences(of: "_", with: "-") + } + + var byNorm: [String: String] = [:] + for (k, v) in entries { + byNorm[normalize(k)] = v + } + + for pref in preferredLanguages { + let n = normalize(pref) + if let direct = byNorm[n] { + return direct + } + let lang = n.split(separator: "-").first.map(String.init) ?? n + for (key, val) in entries { + let kn = normalize(key) + let keyLang = kn.split(separator: "-").first.map(String.init) ?? kn + if keyLang == lang { + return val + } + } + } + + if let en = byNorm["en"] { + return en + } + if let def = byNorm["__default"] { + return def + } + + let sorted = entries.keys.sorted() + guard let firstKey = sorted.first else { + return nil + } + return entries[firstKey] + } + + public static func preferredLanguageTags() -> [String] { + Locale.preferredLanguages + } +} diff --git a/packages/voltra/ios/target/VoltraHomeWidget.swift b/packages/voltra/ios/target/VoltraHomeWidget.swift index 91acd1f5..b34f2049 100644 --- a/packages/voltra/ios/target/VoltraHomeWidget.swift +++ b/packages/voltra/ios/target/VoltraHomeWidget.swift @@ -348,19 +348,14 @@ public struct VoltraHomeWidgetView: View { } private func placeholderView(widgetId _: String) -> some View { - VStack(alignment: .leading, spacing: 8) { - Text("Almost ready") - .font(.headline) - Text("Open the app once to sync data for this widget.") - .font(.caption) - .foregroundStyle(.secondary) - } - .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading) - .padding(16) - .background( + ZStack { RoundedRectangle(cornerRadius: 18, style: .continuous) .fill(Color(UIColor.secondarySystemBackground)) - ) + ProgressView() + .progressViewStyle(.circular) + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + .padding(16) } } diff --git a/skills/voltra/references/plugin-schema.md b/skills/voltra/references/plugin-schema.md index 69bb6dbe..7b183759 100644 --- a/skills/voltra/references/plugin-schema.md +++ b/skills/voltra/references/plugin-schema.md @@ -18,10 +18,10 @@ Voltra plugin config lives under `expo.plugins`. Use top-level `widgets` for iOS widget gallery registration. - `id`: unique identifier, use alphanumeric and underscores only -- `displayName` -- `description` +- `displayName` (string or per-locale map) +- `description` (string or per-locale map) - `supportedFamilies`: array of iOS families such as `systemSmall`, `systemMedium`, `systemLarge` -- `initialStatePath` +- `initialStatePath` (string or per-locale map of paths for localized pre-render) - `serverUpdate.url`: widget endpoint, Voltra appends `widgetId`, `platform=ios`, and `family` - `serverUpdate.intervalMinutes`: polling interval, default `15`, subject to WidgetKit throttling @@ -38,8 +38,8 @@ Other important Apple-side keys: Use `android.widgets` for Android widget registration. - `id`: unique identifier, use alphanumeric and underscores only -- `displayName` -- `description` +- `displayName` (string or per-locale map) +- `description` (string or per-locale map) - `targetCellWidth` - `targetCellHeight` - `minCellWidth` @@ -48,7 +48,7 @@ Use `android.widgets` for Android widget registration. - `minHeight` - `resizeMode` - `widgetCategory` -- `initialStatePath` +- `initialStatePath` (string or per-locale map of paths) - `serverUpdate.url`: widget endpoint, Voltra appends `widgetId` and `platform=android` - `serverUpdate.intervalMinutes`: polling interval; use at least 15 minutes - `previewImage` diff --git a/website/docs/android/api/plugin-configuration.md b/website/docs/android/api/plugin-configuration.md index 3defdb82..79582758 100644 --- a/website/docs/android/api/plugin-configuration.md +++ b/website/docs/android/api/plugin-configuration.md @@ -54,8 +54,8 @@ Array of widget configurations for Home Screen widgets. Each widget will be avai **Widget Configuration Properties:** - `id`: Unique identifier for the widget (alphanumeric with underscores only) -- `displayName`: Name shown in the widget picker -- `description`: Description shown in the widget picker +- `displayName`: Name shown in the widget picker (plain string, or per-locale map; same rules as iOS `widgets[].displayName`) +- `description`: Description shown in the widget picker (same rules as `displayName`) - `targetCellWidth`: Target widget width in grid cells (1-5, required) - `targetCellHeight`: Target widget height in grid cells (1-5, required) - `minCellWidth`: (optional) Minimum width in grid cells (defaults to targetCellWidth) @@ -64,7 +64,7 @@ Array of widget configurations for Home Screen widgets. Each widget will be avai - `minHeight`: (optional) Minimum height in dp (overrides minCellHeight calculation) - `resizeMode`: (optional) Widget resize behavior (`"none"` | `"horizontal"` | `"vertical"` | `"horizontal|vertical"`, default: `"horizontal|vertical"`) - `widgetCategory`: (optional) Widget category (`"home_screen"` | `"keyguard"` | `"home_screen|keyguard"`, default: `"home_screen"`) -- `initialStatePath`: (optional) Path to a file that exports initial widget state (see [Widget Pre-rendering](../development/widget-pre-rendering)) +- `initialStatePath`: (optional) Path to a file that exports initial widget state, or a locale map of paths for localized build-time pre-rendering (see [Widget Pre-rendering](../development/widget-pre-rendering)) - `previewImage`: (optional) Path to preview image for widget picker (PNG/JPG/WebP) - `previewLayout`: (optional) Path to custom XML layout for widget picker preview (Android 12+) - `serverUpdate`: (optional) Enable server-driven updates. See [Server-driven widgets](../development/server-driven-widgets) for full details. @@ -72,6 +72,43 @@ Array of widget configurations for Home Screen widgets. Each widget will be avai - `intervalMinutes`: Update interval in minutes (default: `15`, minimum 15 per WorkManager) - `refresh`: Show a native refresh button (default: `false`) +### Localizing `displayName` and `description` + +Use a locale map when the widget picker label should be translated: + +```json +{ + "android": { + "widgets": [ + { + "id": "weather", + "displayName": { + "en": "Weather", + "pl": "Pogoda", + "zh-Hans": "天气" + }, + "description": { + "en": "Current weather conditions", + "pl": "Aktualne warunki pogodowe", + "zh-Hans": "当前天气状况" + }, + "targetCellWidth": 2, + "targetCellHeight": 2 + } + ] + } +} +``` + +Use BCP-47-style locale tags such as `en`, `en-US`, `pt-BR`, or `zh-Hans`. + +Fallback behavior: + +- Voltra first tries the device locale. +- If there is no exact match, it falls back to the language-only match. +- If there is still no match, it prefers an English locale such as `en` or `en-US`. +- If no English entry exists, it uses the first configured locale. + ## Widget Sizing ### Grid Cells vs Density-Independent Pixels (dp) diff --git a/website/docs/android/development/widget-pre-rendering.md b/website/docs/android/development/widget-pre-rendering.md index f9b72617..82288465 100644 --- a/website/docs/android/development/widget-pre-rendering.md +++ b/website/docs/android/development/widget-pre-rendering.md @@ -17,7 +17,10 @@ Add `initialStatePath` to your widget configuration in the `android` section of "widgets": [ { "id": "weather", - "name": "Weather Widget", + "displayName": "Weather Widget", + "description": "Shows current weather", + "targetCellWidth": 2, + "targetCellHeight": 2, "initialStatePath": "./widgets/weather-android-initial.tsx" } ] @@ -29,6 +32,8 @@ Add `initialStatePath` to your widget configuration in the `android` section of } ``` +For multiple locales, set `initialStatePath` to a map of locale tag → file path (same pattern as iOS); the first-run payload matches the device locale when possible. + ## Implementation Create a file at the specified `initialStatePath` that exports a default Voltra component (or a React element). For Android, this should use `VoltraAndroid` primitives. diff --git a/website/docs/ios/api/plugin-configuration.md b/website/docs/ios/api/plugin-configuration.md index b7558041..0aa22973 100644 --- a/website/docs/ios/api/plugin-configuration.md +++ b/website/docs/ios/api/plugin-configuration.md @@ -94,10 +94,10 @@ Array of widget configurations for Home Screen widgets. Each widget will be avai **Widget Configuration Properties:** - `id`: Unique identifier for the widget (alphanumeric with underscores only) -- `displayName`: Name shown in the widget gallery -- `description`: Description shown in the widget gallery +- `displayName`: Name shown in the widget gallery (plain string, or per-locale map like `{ "en": "Weather", "pl": "Pogoda" }`; locale keys are BCP‑47-style tags) +- `description`: Description shown in the widget gallery (same localization rules as `displayName`) - `supportedFamilies`: Array of supported widget sizes (`systemSmall`, `systemMedium`, `systemLarge`) -- `initialStatePath`: (optional) Path to a file that exports initial widget state (see [Widget Pre-rendering](../development/widget-pre-rendering)) +- `initialStatePath`: (optional) Project-relative path to a file that exports initial widget state, **or** a locale map of paths for localized build-time pre-rendering (see [Widget Pre-rendering](../development/widget-pre-rendering)) - `serverUpdate`: (optional) Enable server-driven updates. See [Server-driven widgets](../development/server-driven-widgets) for full details. - `url`: The Voltra SSR endpoint URL - `intervalMinutes`: Update interval in minutes (default: `15`) @@ -113,7 +113,10 @@ Array of widget configurations for Home Screen widgets. Each widget will be avai "displayName": "Weather Widget", "description": "Current weather conditions", "supportedFamilies": ["systemSmall", "systemMedium", "systemLarge"], - "initialStatePath": "./widgets/weather-initial.tsx", + "initialStatePath": { + "en": "./widgets/weather-initial.tsx", + "pl": "./widgets/weather-initial-pl.tsx" + }, "serverUpdate": { "url": "https://api.example.com/widgets/render", "intervalMinutes": 30, @@ -123,3 +126,36 @@ Array of widget configurations for Home Screen widgets. Each widget will be avai ] } ``` + +### Localizing `displayName` and `description` + +Use a locale map when the widget gallery label should be translated: + +```json +{ + "widgets": [ + { + "id": "weather", + "displayName": { + "en": "Weather", + "pl": "Pogoda", + "zh-Hans": "天气" + }, + "description": { + "en": "Current weather conditions", + "pl": "Aktualne warunki pogodowe", + "zh-Hans": "当前天气状况" + } + } + ] +} +``` + +Use BCP-47-style locale tags such as `en`, `en-US`, `pt-BR`, or `zh-Hans`. + +Fallback behavior: + +- Voltra first tries the device locale. +- If there is no exact match, it falls back to the language-only match. +- If there is still no match, it prefers an English locale such as `en` or `en-US`. +- If no English entry exists, it uses the first configured locale. diff --git a/website/docs/ios/development/widget-pre-rendering.md b/website/docs/ios/development/widget-pre-rendering.md index 8eea604a..c9002c9d 100644 --- a/website/docs/ios/development/widget-pre-rendering.md +++ b/website/docs/ios/development/widget-pre-rendering.md @@ -29,9 +29,20 @@ Add `initialStatePath` to your widget configuration in `app.json`: } ``` +### Localized initial states + +Use a locale map so the config plugin pre-renders one bundle per language; iOS and Android pick the best match at runtime from the device locale (fallback order: preferred languages → language-only → `en` → first locale): + +```json +"initialStatePath": { + "en": "./widgets/weather-initial.tsx", + "pl": "./widgets/weather-initial-pl.tsx" +} +``` + ## Implementation -Create a file at the specified `initialStatePath` that exports a `WidgetVariants` object: +Create a file at each configured path that exports a `WidgetVariants` object (or use the same file path for multiple locales if copy is identical): ```tsx import { Voltra, type WidgetVariants } from 'voltra'