Procedurally generates a radio weather report

Compare changes

Choose any two refs to compare.

+15 -2
.github/workflows/test.yml
··· 14 14 timeout-minutes: 20 15 15 strategy: 16 16 fail-fast: true 17 + matrix: 18 + nodever: 19 + [ 20 + 'latest', 21 + 'lts/*', 22 + 'lts/hydrogen' 23 + ] 17 24 permissions: 18 25 contents: read 19 26 ··· 23 30 24 31 - name: Setup nodejs 25 32 uses: https://github.com/actions/setup-node@v4 33 + with: 34 + node-version: "${{ matrix.nodever }}" 35 + check-latest: true 26 36 27 37 - name: Install dependencies 28 38 run: npm ci 39 + 40 + - name: Build 41 + run: npm run build 29 42 30 - - run: npm test 31 - name: Run tests 43 + - name: Run tests 44 + run: npm test
+2
.gitignore
··· 103 103 distribution/ 104 104 .env 105 105 test/*.js* 106 + output.wav 107 + output.mp3
+18
.tangled/workflows/audit.yml
··· 1 + when: 2 + - event: ["push"] 3 + branch: ["master"] 4 + 5 + engine: "nixery" 6 + 7 + dependencies: 8 + nixpkgs: 9 + - nodejs 10 + 11 + steps: 12 + - name: "NPM Audit" 13 + command: "npm audit" 14 + 15 + clone: 16 + skip: false 17 + depth: 3 18 + submodules: false
+24
.tangled/workflows/test.yml
··· 1 + when: 2 + - event: ["push"] 3 + branch: ["master"] 4 + 5 + engine: "nixery" 6 + 7 + dependencies: 8 + nixpkgs: 9 + - nodejs 10 + 11 + steps: 12 + - name: "Install dependencies" 13 + command: "npm ci" 14 + 15 + - name: "tsc" 16 + command: "npm run build && echo 'done.'" 17 + 18 + - name: "tests" 19 + command: "npm test" 20 + 21 + clone: 22 + skip: false 23 + depth: 3 24 + submodules: false
+22
.vscode/launch.json
··· 1 + { 2 + // Use IntelliSense to learn about possible attributes. 3 + // Hover to view descriptions of existing attributes. 4 + // For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387 5 + "version": "0.2.0", 6 + "configurations": [ 7 + 8 + { 9 + "type": "node", 10 + "request": "launch", 11 + "name": "Launch Program", 12 + "skipFiles": [ 13 + "<node_internals>/**" 14 + ], 15 + "program": "${workspaceFolder}/src/index.ts", 16 + "preLaunchTask": "tsc: build - tsconfig.json", 17 + "outFiles": [ 18 + "${workspaceFolder}/distribution/**/*.js" 19 + ] 20 + } 21 + ] 22 + }
+4 -4
config/config.example.json5
··· 49 49 "hilo 1": { 50 50 "tracks": [ 51 51 "audio/hi_01.flac", 52 - "%cory hi", 52 + "%cory weather.temp.max", 53 53 "audio/lo_01.flac", 54 - "%cory lo" 54 + "%cory weather.temp.min" 55 55 ] 56 56 }, 57 57 "hilo 2": { 58 58 "tracks": [ 59 59 "audio/hi_02.flac", 60 - "%cory hi", 60 + "%cory weather.temp.max", 61 61 "audio/lo_02.flac", 62 - "%cory lo" 62 + "%cory weather.temp.min" 63 63 ] 64 64 }, 65 65 "rain 1": {
+300 -311
package-lock.json
··· 1 1 { 2 2 "name": "morning-report", 3 - "version": "0.0.1", 3 + "version": "0.0.5", 4 4 "lockfileVersion": 3, 5 5 "requires": true, 6 6 "packages": { 7 7 "": { 8 8 "name": "morning-report", 9 - "version": "0.0.1", 9 + "version": "0.0.5", 10 10 "license": "MIT", 11 11 "dependencies": { 12 12 "json5": "2.2.3", 13 - "openweathermap-ts": "1.2.10" 13 + "openweather-api-node": "3.1.5" 14 + }, 15 + "bin": { 16 + "morning-report": "distribution/src/index.js" 14 17 }, 15 18 "devDependencies": { 16 - "@types/node": "24.3.0", 19 + "@types/node": "24.10.1", 17 20 "@vitest/coverage-v8": "3.2.4", 18 - "typescript": "5.9.2", 21 + "typescript": "5.9.3", 19 22 "vitest": "3.2.4" 20 23 } 21 24 }, ··· 44 47 } 45 48 }, 46 49 "node_modules/@babel/helper-validator-identifier": { 47 - "version": "7.27.1", 48 - "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.27.1.tgz", 49 - "integrity": "sha512-D2hP9eA+Sqx1kBZgzxZh0y1trbuU+JoDkiEwqhQ36nodYqJwyEIhPSdMNd7lOm/4io72luTPWH20Yda0xOuUow==", 50 + "version": "7.28.5", 51 + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz", 52 + "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==", 50 53 "dev": true, 51 54 "license": "MIT", 52 55 "engines": { ··· 54 57 } 55 58 }, 56 59 "node_modules/@babel/parser": { 57 - "version": "7.28.3", 58 - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.3.tgz", 59 - "integrity": "sha512-7+Ey1mAgYqFAx2h0RuoxcQT5+MlG3GTV0TQrgr7/ZliKsm/MNDxVVutlWaziMq7wJNAz8MTqz55XLpWvva6StA==", 60 + "version": "7.28.5", 61 + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz", 62 + "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==", 60 63 "dev": true, 61 64 "license": "MIT", 62 65 "dependencies": { 63 - "@babel/types": "^7.28.2" 66 + "@babel/types": "^7.28.5" 64 67 }, 65 68 "bin": { 66 69 "parser": "bin/babel-parser.js" ··· 70 73 } 71 74 }, 72 75 "node_modules/@babel/types": { 73 - "version": "7.28.2", 74 - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.2.tgz", 75 - "integrity": "sha512-ruv7Ae4J5dUYULmeXw1gmb7rYRz57OWCPM57pHojnLq/3Z1CK2lNSLTCVjxVk1F/TZHwOZZrOWi0ur95BbLxNQ==", 76 + "version": "7.28.5", 77 + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz", 78 + "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==", 76 79 "dev": true, 77 80 "license": "MIT", 78 81 "dependencies": { 79 82 "@babel/helper-string-parser": "^7.27.1", 80 - "@babel/helper-validator-identifier": "^7.27.1" 83 + "@babel/helper-validator-identifier": "^7.28.5" 81 84 }, 82 85 "engines": { 83 86 "node": ">=6.9.0" ··· 94 97 } 95 98 }, 96 99 "node_modules/@esbuild/aix-ppc64": { 97 - "version": "0.25.9", 98 - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.9.tgz", 99 - "integrity": "sha512-OaGtL73Jck6pBKjNIe24BnFE6agGl+6KxDtTfHhy1HmhthfKouEcOhqpSL64K4/0WCtbKFLOdzD/44cJ4k9opA==", 100 + "version": "0.25.12", 101 + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", 102 + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", 100 103 "cpu": [ 101 104 "ppc64" 102 105 ], ··· 111 114 } 112 115 }, 113 116 "node_modules/@esbuild/android-arm": { 114 - "version": "0.25.9", 115 - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.9.tgz", 116 - "integrity": "sha512-5WNI1DaMtxQ7t7B6xa572XMXpHAaI/9Hnhk8lcxF4zVN4xstUgTlvuGDorBguKEnZO70qwEcLpfifMLoxiPqHQ==", 117 + "version": "0.25.12", 118 + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", 119 + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", 117 120 "cpu": [ 118 121 "arm" 119 122 ], ··· 128 131 } 129 132 }, 130 133 "node_modules/@esbuild/android-arm64": { 131 - "version": "0.25.9", 132 - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.9.tgz", 133 - "integrity": "sha512-IDrddSmpSv51ftWslJMvl3Q2ZT98fUSL2/rlUXuVqRXHCs5EUF1/f+jbjF5+NG9UffUDMCiTyh8iec7u8RlTLg==", 134 + "version": "0.25.12", 135 + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", 136 + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", 134 137 "cpu": [ 135 138 "arm64" 136 139 ], ··· 145 148 } 146 149 }, 147 150 "node_modules/@esbuild/android-x64": { 148 - "version": "0.25.9", 149 - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.9.tgz", 150 - "integrity": "sha512-I853iMZ1hWZdNllhVZKm34f4wErd4lMyeV7BLzEExGEIZYsOzqDWDf+y082izYUE8gtJnYHdeDpN/6tUdwvfiw==", 151 + "version": "0.25.12", 152 + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", 153 + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", 151 154 "cpu": [ 152 155 "x64" 153 156 ], ··· 162 165 } 163 166 }, 164 167 "node_modules/@esbuild/darwin-arm64": { 165 - "version": "0.25.9", 166 - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.9.tgz", 167 - "integrity": "sha512-XIpIDMAjOELi/9PB30vEbVMs3GV1v2zkkPnuyRRURbhqjyzIINwj+nbQATh4H9GxUgH1kFsEyQMxwiLFKUS6Rg==", 168 + "version": "0.25.12", 169 + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", 170 + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", 168 171 "cpu": [ 169 172 "arm64" 170 173 ], ··· 179 182 } 180 183 }, 181 184 "node_modules/@esbuild/darwin-x64": { 182 - "version": "0.25.9", 183 - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.9.tgz", 184 - "integrity": "sha512-jhHfBzjYTA1IQu8VyrjCX4ApJDnH+ez+IYVEoJHeqJm9VhG9Dh2BYaJritkYK3vMaXrf7Ogr/0MQ8/MeIefsPQ==", 185 + "version": "0.25.12", 186 + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", 187 + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", 185 188 "cpu": [ 186 189 "x64" 187 190 ], ··· 196 199 } 197 200 }, 198 201 "node_modules/@esbuild/freebsd-arm64": { 199 - "version": "0.25.9", 200 - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.9.tgz", 201 - "integrity": "sha512-z93DmbnY6fX9+KdD4Ue/H6sYs+bhFQJNCPZsi4XWJoYblUqT06MQUdBCpcSfuiN72AbqeBFu5LVQTjfXDE2A6Q==", 202 + "version": "0.25.12", 203 + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", 204 + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", 202 205 "cpu": [ 203 206 "arm64" 204 207 ], ··· 213 216 } 214 217 }, 215 218 "node_modules/@esbuild/freebsd-x64": { 216 - "version": "0.25.9", 217 - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.9.tgz", 218 - "integrity": "sha512-mrKX6H/vOyo5v71YfXWJxLVxgy1kyt1MQaD8wZJgJfG4gq4DpQGpgTB74e5yBeQdyMTbgxp0YtNj7NuHN0PoZg==", 219 + "version": "0.25.12", 220 + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", 221 + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", 219 222 "cpu": [ 220 223 "x64" 221 224 ], ··· 230 233 } 231 234 }, 232 235 "node_modules/@esbuild/linux-arm": { 233 - "version": "0.25.9", 234 - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.9.tgz", 235 - "integrity": "sha512-HBU2Xv78SMgaydBmdor38lg8YDnFKSARg1Q6AT0/y2ezUAKiZvc211RDFHlEZRFNRVhcMamiToo7bDx3VEOYQw==", 236 + "version": "0.25.12", 237 + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", 238 + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", 236 239 "cpu": [ 237 240 "arm" 238 241 ], ··· 247 250 } 248 251 }, 249 252 "node_modules/@esbuild/linux-arm64": { 250 - "version": "0.25.9", 251 - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.9.tgz", 252 - "integrity": "sha512-BlB7bIcLT3G26urh5Dmse7fiLmLXnRlopw4s8DalgZ8ef79Jj4aUcYbk90g8iCa2467HX8SAIidbL7gsqXHdRw==", 253 + "version": "0.25.12", 254 + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", 255 + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", 253 256 "cpu": [ 254 257 "arm64" 255 258 ], ··· 264 267 } 265 268 }, 266 269 "node_modules/@esbuild/linux-ia32": { 267 - "version": "0.25.9", 268 - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.9.tgz", 269 - "integrity": "sha512-e7S3MOJPZGp2QW6AK6+Ly81rC7oOSerQ+P8L0ta4FhVi+/j/v2yZzx5CqqDaWjtPFfYz21Vi1S0auHrap3Ma3A==", 270 + "version": "0.25.12", 271 + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", 272 + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", 270 273 "cpu": [ 271 274 "ia32" 272 275 ], ··· 281 284 } 282 285 }, 283 286 "node_modules/@esbuild/linux-loong64": { 284 - "version": "0.25.9", 285 - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.9.tgz", 286 - "integrity": "sha512-Sbe10Bnn0oUAB2AalYztvGcK+o6YFFA/9829PhOCUS9vkJElXGdphz0A3DbMdP8gmKkqPmPcMJmJOrI3VYB1JQ==", 287 + "version": "0.25.12", 288 + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", 289 + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", 287 290 "cpu": [ 288 291 "loong64" 289 292 ], ··· 298 301 } 299 302 }, 300 303 "node_modules/@esbuild/linux-mips64el": { 301 - "version": "0.25.9", 302 - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.9.tgz", 303 - "integrity": "sha512-YcM5br0mVyZw2jcQeLIkhWtKPeVfAerES5PvOzaDxVtIyZ2NUBZKNLjC5z3/fUlDgT6w89VsxP2qzNipOaaDyA==", 304 + "version": "0.25.12", 305 + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", 306 + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", 304 307 "cpu": [ 305 308 "mips64el" 306 309 ], ··· 315 318 } 316 319 }, 317 320 "node_modules/@esbuild/linux-ppc64": { 318 - "version": "0.25.9", 319 - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.9.tgz", 320 - "integrity": "sha512-++0HQvasdo20JytyDpFvQtNrEsAgNG2CY1CLMwGXfFTKGBGQT3bOeLSYE2l1fYdvML5KUuwn9Z8L1EWe2tzs1w==", 321 + "version": "0.25.12", 322 + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", 323 + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", 321 324 "cpu": [ 322 325 "ppc64" 323 326 ], ··· 332 335 } 333 336 }, 334 337 "node_modules/@esbuild/linux-riscv64": { 335 - "version": "0.25.9", 336 - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.9.tgz", 337 - "integrity": "sha512-uNIBa279Y3fkjV+2cUjx36xkx7eSjb8IvnL01eXUKXez/CBHNRw5ekCGMPM0BcmqBxBcdgUWuUXmVWwm4CH9kg==", 338 + "version": "0.25.12", 339 + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", 340 + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", 338 341 "cpu": [ 339 342 "riscv64" 340 343 ], ··· 349 352 } 350 353 }, 351 354 "node_modules/@esbuild/linux-s390x": { 352 - "version": "0.25.9", 353 - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.9.tgz", 354 - "integrity": "sha512-Mfiphvp3MjC/lctb+7D287Xw1DGzqJPb/J2aHHcHxflUo+8tmN/6d4k6I2yFR7BVo5/g7x2Monq4+Yew0EHRIA==", 355 + "version": "0.25.12", 356 + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", 357 + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", 355 358 "cpu": [ 356 359 "s390x" 357 360 ], ··· 366 369 } 367 370 }, 368 371 "node_modules/@esbuild/linux-x64": { 369 - "version": "0.25.9", 370 - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.9.tgz", 371 - "integrity": "sha512-iSwByxzRe48YVkmpbgoxVzn76BXjlYFXC7NvLYq+b+kDjyyk30J0JY47DIn8z1MO3K0oSl9fZoRmZPQI4Hklzg==", 372 + "version": "0.25.12", 373 + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", 374 + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", 372 375 "cpu": [ 373 376 "x64" 374 377 ], ··· 383 386 } 384 387 }, 385 388 "node_modules/@esbuild/netbsd-arm64": { 386 - "version": "0.25.9", 387 - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.9.tgz", 388 - "integrity": "sha512-9jNJl6FqaUG+COdQMjSCGW4QiMHH88xWbvZ+kRVblZsWrkXlABuGdFJ1E9L7HK+T0Yqd4akKNa/lO0+jDxQD4Q==", 389 + "version": "0.25.12", 390 + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", 391 + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", 389 392 "cpu": [ 390 393 "arm64" 391 394 ], ··· 400 403 } 401 404 }, 402 405 "node_modules/@esbuild/netbsd-x64": { 403 - "version": "0.25.9", 404 - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.9.tgz", 405 - "integrity": "sha512-RLLdkflmqRG8KanPGOU7Rpg829ZHu8nFy5Pqdi9U01VYtG9Y0zOG6Vr2z4/S+/3zIyOxiK6cCeYNWOFR9QP87g==", 406 + "version": "0.25.12", 407 + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", 408 + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", 406 409 "cpu": [ 407 410 "x64" 408 411 ], ··· 417 420 } 418 421 }, 419 422 "node_modules/@esbuild/openbsd-arm64": { 420 - "version": "0.25.9", 421 - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.9.tgz", 422 - "integrity": "sha512-YaFBlPGeDasft5IIM+CQAhJAqS3St3nJzDEgsgFixcfZeyGPCd6eJBWzke5piZuZ7CtL656eOSYKk4Ls2C0FRQ==", 423 + "version": "0.25.12", 424 + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", 425 + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", 423 426 "cpu": [ 424 427 "arm64" 425 428 ], ··· 434 437 } 435 438 }, 436 439 "node_modules/@esbuild/openbsd-x64": { 437 - "version": "0.25.9", 438 - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.9.tgz", 439 - "integrity": "sha512-1MkgTCuvMGWuqVtAvkpkXFmtL8XhWy+j4jaSO2wxfJtilVCi0ZE37b8uOdMItIHz4I6z1bWWtEX4CJwcKYLcuA==", 440 + "version": "0.25.12", 441 + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", 442 + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", 440 443 "cpu": [ 441 444 "x64" 442 445 ], ··· 451 454 } 452 455 }, 453 456 "node_modules/@esbuild/openharmony-arm64": { 454 - "version": "0.25.9", 455 - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.9.tgz", 456 - "integrity": "sha512-4Xd0xNiMVXKh6Fa7HEJQbrpP3m3DDn43jKxMjxLLRjWnRsfxjORYJlXPO4JNcXtOyfajXorRKY9NkOpTHptErg==", 457 + "version": "0.25.12", 458 + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", 459 + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", 457 460 "cpu": [ 458 461 "arm64" 459 462 ], ··· 468 471 } 469 472 }, 470 473 "node_modules/@esbuild/sunos-x64": { 471 - "version": "0.25.9", 472 - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.9.tgz", 473 - "integrity": "sha512-WjH4s6hzo00nNezhp3wFIAfmGZ8U7KtrJNlFMRKxiI9mxEK1scOMAaa9i4crUtu+tBr+0IN6JCuAcSBJZfnphw==", 474 + "version": "0.25.12", 475 + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", 476 + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", 474 477 "cpu": [ 475 478 "x64" 476 479 ], ··· 485 488 } 486 489 }, 487 490 "node_modules/@esbuild/win32-arm64": { 488 - "version": "0.25.9", 489 - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.9.tgz", 490 - "integrity": "sha512-mGFrVJHmZiRqmP8xFOc6b84/7xa5y5YvR1x8djzXpJBSv/UsNK6aqec+6JDjConTgvvQefdGhFDAs2DLAds6gQ==", 491 + "version": "0.25.12", 492 + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", 493 + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", 491 494 "cpu": [ 492 495 "arm64" 493 496 ], ··· 502 505 } 503 506 }, 504 507 "node_modules/@esbuild/win32-ia32": { 505 - "version": "0.25.9", 506 - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.9.tgz", 507 - "integrity": "sha512-b33gLVU2k11nVx1OhX3C8QQP6UHQK4ZtN56oFWvVXvz2VkDoe6fbG8TOgHFxEvqeqohmRnIHe5A1+HADk4OQww==", 508 + "version": "0.25.12", 509 + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", 510 + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", 508 511 "cpu": [ 509 512 "ia32" 510 513 ], ··· 519 522 } 520 523 }, 521 524 "node_modules/@esbuild/win32-x64": { 522 - "version": "0.25.9", 523 - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.9.tgz", 524 - "integrity": "sha512-PPOl1mi6lpLNQxnGoyAfschAodRFYXJ+9fs6WHXz7CSWKbOqiMZsubC+BQsVKuul+3vKLuwTHsS2c2y9EoKwxQ==", 525 + "version": "0.25.12", 526 + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", 527 + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", 525 528 "cpu": [ 526 529 "x64" 527 530 ], ··· 592 595 "license": "MIT" 593 596 }, 594 597 "node_modules/@jridgewell/trace-mapping": { 595 - "version": "0.3.30", 596 - "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.30.tgz", 597 - "integrity": "sha512-GQ7Nw5G2lTu/BtHTKfXhKHok2WGetd4XYcVKGx00SjAk8GMwgJM3zr6zORiPGuOE+/vkc90KtTosSSvaCjKb2Q==", 598 + "version": "0.3.31", 599 + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.31.tgz", 600 + "integrity": "sha512-zzNR+SdQSDJzc8joaeP8QQoCQr8NuYx2dIIytl1QeBEZHJ9uW6hebsrYgbz8hJwUQao3TWCMtmfV8Nu1twOLAw==", 598 601 "dev": true, 599 602 "license": "MIT", 600 603 "dependencies": { ··· 614 617 } 615 618 }, 616 619 "node_modules/@rollup/rollup-android-arm-eabi": { 617 - "version": "4.48.0", 618 - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.48.0.tgz", 619 - "integrity": "sha512-aVzKH922ogVAWkKiyKXorjYymz2084zrhrZRXtLrA5eEx5SO8Dj0c/4FpCHZyn7MKzhW2pW4tK28vVr+5oQ2xw==", 620 + "version": "4.53.3", 621 + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.53.3.tgz", 622 + "integrity": "sha512-mRSi+4cBjrRLoaal2PnqH82Wqyb+d3HsPUN/W+WslCXsZsyHa9ZeQQX/pQsZaVIWDkPcpV6jJ+3KLbTbgnwv8w==", 620 623 "cpu": [ 621 624 "arm" 622 625 ], ··· 628 631 ] 629 632 }, 630 633 "node_modules/@rollup/rollup-android-arm64": { 631 - "version": "4.48.0", 632 - "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.48.0.tgz", 633 - "integrity": "sha512-diOdQuw43xTa1RddAFbhIA8toirSzFMcnIg8kvlzRbK26xqEnKJ/vqQnghTAajy2Dcy42v+GMPMo6jq67od+Dw==", 634 + "version": "4.53.3", 635 + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.53.3.tgz", 636 + "integrity": "sha512-CbDGaMpdE9sh7sCmTrTUyllhrg65t6SwhjlMJsLr+J8YjFuPmCEjbBSx4Z/e4SmDyH3aB5hGaJUP2ltV/vcs4w==", 634 637 "cpu": [ 635 638 "arm64" 636 639 ], ··· 642 645 ] 643 646 }, 644 647 "node_modules/@rollup/rollup-darwin-arm64": { 645 - "version": "4.48.0", 646 - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.48.0.tgz", 647 - "integrity": "sha512-QhR2KA18fPlJWFefySJPDYZELaVqIUVnYgAOdtJ+B/uH96CFg2l1TQpX19XpUMWUqMyIiyY45wje8K6F4w4/CA==", 648 + "version": "4.53.3", 649 + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.53.3.tgz", 650 + "integrity": "sha512-Nr7SlQeqIBpOV6BHHGZgYBuSdanCXuw09hon14MGOLGmXAFYjx1wNvquVPmpZnl0tLjg25dEdr4IQ6GgyToCUA==", 648 651 "cpu": [ 649 652 "arm64" 650 653 ], ··· 656 659 ] 657 660 }, 658 661 "node_modules/@rollup/rollup-darwin-x64": { 659 - "version": "4.48.0", 660 - "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.48.0.tgz", 661 - "integrity": "sha512-Q9RMXnQVJ5S1SYpNSTwXDpoQLgJ/fbInWOyjbCnnqTElEyeNvLAB3QvG5xmMQMhFN74bB5ZZJYkKaFPcOG8sGg==", 662 + "version": "4.53.3", 663 + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.53.3.tgz", 664 + "integrity": "sha512-DZ8N4CSNfl965CmPktJ8oBnfYr3F8dTTNBQkRlffnUarJ2ohudQD17sZBa097J8xhQ26AwhHJ5mvUyQW8ddTsQ==", 662 665 "cpu": [ 663 666 "x64" 664 667 ], ··· 670 673 ] 671 674 }, 672 675 "node_modules/@rollup/rollup-freebsd-arm64": { 673 - "version": "4.48.0", 674 - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.48.0.tgz", 675 - "integrity": "sha512-3jzOhHWM8O8PSfyft+ghXZfBkZawQA0PUGtadKYxFqpcYlOYjTi06WsnYBsbMHLawr+4uWirLlbhcYLHDXR16w==", 676 + "version": "4.53.3", 677 + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.53.3.tgz", 678 + "integrity": "sha512-yMTrCrK92aGyi7GuDNtGn2sNW+Gdb4vErx4t3Gv/Tr+1zRb8ax4z8GWVRfr3Jw8zJWvpGHNpss3vVlbF58DZ4w==", 676 679 "cpu": [ 677 680 "arm64" 678 681 ], ··· 684 687 ] 685 688 }, 686 689 "node_modules/@rollup/rollup-freebsd-x64": { 687 - "version": "4.48.0", 688 - "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.48.0.tgz", 689 - "integrity": "sha512-NcD5uVUmE73C/TPJqf78hInZmiSBsDpz3iD5MF/BuB+qzm4ooF2S1HfeTChj5K4AV3y19FFPgxonsxiEpy8v/A==", 690 + "version": "4.53.3", 691 + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.53.3.tgz", 692 + "integrity": "sha512-lMfF8X7QhdQzseM6XaX0vbno2m3hlyZFhwcndRMw8fbAGUGL3WFMBdK0hbUBIUYcEcMhVLr1SIamDeuLBnXS+Q==", 690 693 "cpu": [ 691 694 "x64" 692 695 ], ··· 698 701 ] 699 702 }, 700 703 "node_modules/@rollup/rollup-linux-arm-gnueabihf": { 701 - "version": "4.48.0", 702 - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.48.0.tgz", 703 - "integrity": "sha512-JWnrj8qZgLWRNHr7NbpdnrQ8kcg09EBBq8jVOjmtlB3c8C6IrynAJSMhMVGME4YfTJzIkJqvSUSVJRqkDnu/aA==", 704 + "version": "4.53.3", 705 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.53.3.tgz", 706 + "integrity": "sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw==", 704 707 "cpu": [ 705 708 "arm" 706 709 ], ··· 712 715 ] 713 716 }, 714 717 "node_modules/@rollup/rollup-linux-arm-musleabihf": { 715 - "version": "4.48.0", 716 - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.48.0.tgz", 717 - "integrity": "sha512-9xu92F0TxuMH0tD6tG3+GtngwdgSf8Bnz+YcsPG91/r5Vgh5LNofO48jV55priA95p3c92FLmPM7CvsVlnSbGQ==", 718 + "version": "4.53.3", 719 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.53.3.tgz", 720 + "integrity": "sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg==", 718 721 "cpu": [ 719 722 "arm" 720 723 ], ··· 726 729 ] 727 730 }, 728 731 "node_modules/@rollup/rollup-linux-arm64-gnu": { 729 - "version": "4.48.0", 730 - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.48.0.tgz", 731 - "integrity": "sha512-NLtvJB5YpWn7jlp1rJiY0s+G1Z1IVmkDuiywiqUhh96MIraC0n7XQc2SZ1CZz14shqkM+XN2UrfIo7JB6UufOA==", 732 + "version": "4.53.3", 733 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.53.3.tgz", 734 + "integrity": "sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==", 732 735 "cpu": [ 733 736 "arm64" 734 737 ], ··· 740 743 ] 741 744 }, 742 745 "node_modules/@rollup/rollup-linux-arm64-musl": { 743 - "version": "4.48.0", 744 - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.48.0.tgz", 745 - "integrity": "sha512-QJ4hCOnz2SXgCh+HmpvZkM+0NSGcZACyYS8DGbWn2PbmA0e5xUk4bIP8eqJyNXLtyB4gZ3/XyvKtQ1IFH671vQ==", 746 + "version": "4.53.3", 747 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.53.3.tgz", 748 + "integrity": "sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A==", 746 749 "cpu": [ 747 750 "arm64" 748 751 ], ··· 753 756 "linux" 754 757 ] 755 758 }, 756 - "node_modules/@rollup/rollup-linux-loongarch64-gnu": { 757 - "version": "4.48.0", 758 - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loongarch64-gnu/-/rollup-linux-loongarch64-gnu-4.48.0.tgz", 759 - "integrity": "sha512-Pk0qlGJnhILdIC5zSKQnprFjrGmjfDM7TPZ0FKJxRkoo+kgMRAg4ps1VlTZf8u2vohSicLg7NP+cA5qE96PaFg==", 759 + "node_modules/@rollup/rollup-linux-loong64-gnu": { 760 + "version": "4.53.3", 761 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.53.3.tgz", 762 + "integrity": "sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g==", 760 763 "cpu": [ 761 764 "loong64" 762 765 ], ··· 768 771 ] 769 772 }, 770 773 "node_modules/@rollup/rollup-linux-ppc64-gnu": { 771 - "version": "4.48.0", 772 - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.48.0.tgz", 773 - "integrity": "sha512-/dNFc6rTpoOzgp5GKoYjT6uLo8okR/Chi2ECOmCZiS4oqh3mc95pThWma7Bgyk6/WTEvjDINpiBCuecPLOgBLQ==", 774 + "version": "4.53.3", 775 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.53.3.tgz", 776 + "integrity": "sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw==", 774 777 "cpu": [ 775 778 "ppc64" 776 779 ], ··· 782 785 ] 783 786 }, 784 787 "node_modules/@rollup/rollup-linux-riscv64-gnu": { 785 - "version": "4.48.0", 786 - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.48.0.tgz", 787 - "integrity": "sha512-YBwXsvsFI8CVA4ej+bJF2d9uAeIiSkqKSPQNn0Wyh4eMDY4wxuSp71BauPjQNCKK2tD2/ksJ7uhJ8X/PVY9bHQ==", 788 + "version": "4.53.3", 789 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.53.3.tgz", 790 + "integrity": "sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g==", 788 791 "cpu": [ 789 792 "riscv64" 790 793 ], ··· 796 799 ] 797 800 }, 798 801 "node_modules/@rollup/rollup-linux-riscv64-musl": { 799 - "version": "4.48.0", 800 - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.48.0.tgz", 801 - "integrity": "sha512-FI3Rr2aGAtl1aHzbkBIamsQyuauYtTF9SDUJ8n2wMXuuxwchC3QkumZa1TEXYIv/1AUp1a25Kwy6ONArvnyeVQ==", 802 + "version": "4.53.3", 803 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.53.3.tgz", 804 + "integrity": "sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A==", 802 805 "cpu": [ 803 806 "riscv64" 804 807 ], ··· 810 813 ] 811 814 }, 812 815 "node_modules/@rollup/rollup-linux-s390x-gnu": { 813 - "version": "4.48.0", 814 - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.48.0.tgz", 815 - "integrity": "sha512-Dx7qH0/rvNNFmCcIRe1pyQ9/H0XO4v/f0SDoafwRYwc2J7bJZ5N4CHL/cdjamISZ5Cgnon6iazAVRFlxSoHQnQ==", 816 + "version": "4.53.3", 817 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.53.3.tgz", 818 + "integrity": "sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg==", 816 819 "cpu": [ 817 820 "s390x" 818 821 ], ··· 824 827 ] 825 828 }, 826 829 "node_modules/@rollup/rollup-linux-x64-gnu": { 827 - "version": "4.48.0", 828 - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.48.0.tgz", 829 - "integrity": "sha512-GUdZKTeKBq9WmEBzvFYuC88yk26vT66lQV8D5+9TgkfbewhLaTHRNATyzpQwwbHIfJvDJ3N9WJ90wK/uR3cy3Q==", 830 + "version": "4.53.3", 831 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.53.3.tgz", 832 + "integrity": "sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==", 830 833 "cpu": [ 831 834 "x64" 832 835 ], ··· 838 841 ] 839 842 }, 840 843 "node_modules/@rollup/rollup-linux-x64-musl": { 841 - "version": "4.48.0", 842 - "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.48.0.tgz", 843 - "integrity": "sha512-ao58Adz/v14MWpQgYAb4a4h3fdw73DrDGtaiF7Opds5wNyEQwtO6M9dBh89nke0yoZzzaegq6J/EXs7eBebG8A==", 844 + "version": "4.53.3", 845 + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.53.3.tgz", 846 + "integrity": "sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q==", 844 847 "cpu": [ 845 848 "x64" 846 849 ], ··· 851 854 "linux" 852 855 ] 853 856 }, 857 + "node_modules/@rollup/rollup-openharmony-arm64": { 858 + "version": "4.53.3", 859 + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.53.3.tgz", 860 + "integrity": "sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw==", 861 + "cpu": [ 862 + "arm64" 863 + ], 864 + "dev": true, 865 + "license": "MIT", 866 + "optional": true, 867 + "os": [ 868 + "openharmony" 869 + ] 870 + }, 854 871 "node_modules/@rollup/rollup-win32-arm64-msvc": { 855 - "version": "4.48.0", 856 - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.48.0.tgz", 857 - "integrity": "sha512-kpFno46bHtjZVdRIOxqaGeiABiToo2J+st7Yce+aiAoo1H0xPi2keyQIP04n2JjDVuxBN6bSz9R6RdTK5hIppw==", 872 + "version": "4.53.3", 873 + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.53.3.tgz", 874 + "integrity": "sha512-GOFuKpsxR/whszbF/bzydebLiXIHSgsEUp6M0JI8dWvi+fFa1TD6YQa4aSZHtpmh2/uAlj/Dy+nmby3TJ3pkTw==", 858 875 "cpu": [ 859 876 "arm64" 860 877 ], ··· 866 883 ] 867 884 }, 868 885 "node_modules/@rollup/rollup-win32-ia32-msvc": { 869 - "version": "4.48.0", 870 - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.48.0.tgz", 871 - "integrity": "sha512-rFYrk4lLk9YUTIeihnQMiwMr6gDhGGSbWThPEDfBoU/HdAtOzPXeexKi7yU8jO+LWRKnmqPN9NviHQf6GDwBcQ==", 886 + "version": "4.53.3", 887 + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.53.3.tgz", 888 + "integrity": "sha512-iah+THLcBJdpfZ1TstDFbKNznlzoxa8fmnFYK4V67HvmuNYkVdAywJSoteUszvBQ9/HqN2+9AZghbajMsFT+oA==", 872 889 "cpu": [ 873 890 "ia32" 874 891 ], ··· 879 896 "win32" 880 897 ] 881 898 }, 899 + "node_modules/@rollup/rollup-win32-x64-gnu": { 900 + "version": "4.53.3", 901 + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.53.3.tgz", 902 + "integrity": "sha512-J9QDiOIZlZLdcot5NXEepDkstocktoVjkaKUtqzgzpt2yWjGlbYiKyp05rWwk4nypbYUNoFAztEgixoLaSETkg==", 903 + "cpu": [ 904 + "x64" 905 + ], 906 + "dev": true, 907 + "license": "MIT", 908 + "optional": true, 909 + "os": [ 910 + "win32" 911 + ] 912 + }, 882 913 "node_modules/@rollup/rollup-win32-x64-msvc": { 883 - "version": "4.48.0", 884 - "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.48.0.tgz", 885 - "integrity": "sha512-sq0hHLTgdtwOPDB5SJOuaoHyiP1qSwg+71TQWk8iDS04bW1wIE0oQ6otPiRj2ZvLYNASLMaTp8QRGUVZ+5OL5A==", 914 + "version": "4.53.3", 915 + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.53.3.tgz", 916 + "integrity": "sha512-UhTd8u31dXadv0MopwGgNOBpUVROFKWVQgAg5N1ESyCz8AuBcMqm4AuTjrwgQKGDfoFuz02EuMRHQIw/frmYKQ==", 886 917 "cpu": [ 887 918 "x64" 888 919 ], ··· 894 925 ] 895 926 }, 896 927 "node_modules/@types/chai": { 897 - "version": "5.2.2", 898 - "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.2.tgz", 899 - "integrity": "sha512-8kB30R7Hwqf40JPiKhVzodJs2Qc1ZJ5zuT3uzw5Hq/dhNCl3G3l83jfpdI1e20BP348+fV7VIL/+FxaXkqBmWg==", 928 + "version": "5.2.3", 929 + "resolved": "https://registry.npmjs.org/@types/chai/-/chai-5.2.3.tgz", 930 + "integrity": "sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==", 900 931 "dev": true, 901 932 "license": "MIT", 902 933 "dependencies": { 903 - "@types/deep-eql": "*" 934 + "@types/deep-eql": "*", 935 + "assertion-error": "^2.0.1" 904 936 } 905 937 }, 906 938 "node_modules/@types/deep-eql": { ··· 918 950 "license": "MIT" 919 951 }, 920 952 "node_modules/@types/node": { 921 - "version": "24.3.0", 922 - "resolved": "https://registry.npmjs.org/@types/node/-/node-24.3.0.tgz", 923 - "integrity": "sha512-aPTXCrfwnDLj4VvXrm+UUCQjNEvJgNA8s5F1cvwQU+3KNltTOkBm1j30uNLyqqPNe7gE3KFzImYoZEfLhp4Yow==", 953 + "version": "24.10.1", 954 + "resolved": "https://registry.npmjs.org/@types/node/-/node-24.10.1.tgz", 955 + "integrity": "sha512-GNWcUTRBgIRJD5zj+Tq0fKOJ5XZajIiBroOF0yvj2bSU1WvNdYS/dn9UxwsujGW4JX06dnHyjV2y9rRaybH0iQ==", 924 956 "dev": true, 925 957 "license": "MIT", 926 958 "dependencies": { 927 - "undici-types": "~7.10.0" 959 + "undici-types": "~7.16.0" 928 960 } 929 961 }, 930 962 "node_modules/@vitest/coverage-v8": { ··· 1077 1109 } 1078 1110 }, 1079 1111 "node_modules/ansi-regex": { 1080 - "version": "6.2.0", 1081 - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.0.tgz", 1082 - "integrity": "sha512-TKY5pyBkHyADOPYlRT9Lx6F544mPl0vS5Ew7BJ45hA08Q+t3GjbueLliBWN3sMICk6+y7HdyxSzC4bWS8baBdg==", 1112 + "version": "6.2.2", 1113 + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-6.2.2.tgz", 1114 + "integrity": "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg==", 1083 1115 "dev": true, 1084 1116 "license": "MIT", 1085 1117 "engines": { ··· 1090 1122 } 1091 1123 }, 1092 1124 "node_modules/ansi-styles": { 1093 - "version": "6.2.1", 1094 - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.1.tgz", 1095 - "integrity": "sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug==", 1125 + "version": "6.2.3", 1126 + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-6.2.3.tgz", 1127 + "integrity": "sha512-4Dj6M28JB+oAH8kFkTLUo+a2jwOFkuqb3yucU0CANcRRUbxS0cP0nZYCGjcc3BNXwRIsUVmDGgzawme7zvJHvg==", 1096 1128 "dev": true, 1097 1129 "license": "MIT", 1098 1130 "engines": { ··· 1113 1145 } 1114 1146 }, 1115 1147 "node_modules/ast-v8-to-istanbul": { 1116 - "version": "0.3.4", 1117 - "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-0.3.4.tgz", 1118 - "integrity": "sha512-cxrAnZNLBnQwBPByK4CeDaw5sWZtMilJE/Q3iDA0aamgaIVNDF9T6K2/8DfYDZEejZ2jNnDrG9m8MY72HFd0KA==", 1148 + "version": "0.3.8", 1149 + "resolved": "https://registry.npmjs.org/ast-v8-to-istanbul/-/ast-v8-to-istanbul-0.3.8.tgz", 1150 + "integrity": "sha512-szgSZqUxI5T8mLKvS7WTjF9is+MVbOeLADU73IseOcrqhxr/VAvy6wfoVE39KnKzA7JRhjF5eUagNlHwvZPlKQ==", 1119 1151 "dev": true, 1120 1152 "license": "MIT", 1121 1153 "dependencies": { 1122 - "@jridgewell/trace-mapping": "^0.3.29", 1154 + "@jridgewell/trace-mapping": "^0.3.31", 1123 1155 "estree-walker": "^3.0.3", 1124 1156 "js-tokens": "^9.0.1" 1125 1157 } ··· 1263 1295 "license": "MIT" 1264 1296 }, 1265 1297 "node_modules/esbuild": { 1266 - "version": "0.25.9", 1267 - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.9.tgz", 1268 - "integrity": "sha512-CRbODhYyQx3qp7ZEwzxOk4JBqmD/seJrzPa/cGjY1VtIn5E09Oi9/dB4JwctnfZ8Q8iT7rioVv5k/FNT/uf54g==", 1298 + "version": "0.25.12", 1299 + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", 1300 + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", 1269 1301 "dev": true, 1270 1302 "hasInstallScript": true, 1271 1303 "license": "MIT", ··· 1276 1308 "node": ">=18" 1277 1309 }, 1278 1310 "optionalDependencies": { 1279 - "@esbuild/aix-ppc64": "0.25.9", 1280 - "@esbuild/android-arm": "0.25.9", 1281 - "@esbuild/android-arm64": "0.25.9", 1282 - "@esbuild/android-x64": "0.25.9", 1283 - "@esbuild/darwin-arm64": "0.25.9", 1284 - "@esbuild/darwin-x64": "0.25.9", 1285 - "@esbuild/freebsd-arm64": "0.25.9", 1286 - "@esbuild/freebsd-x64": "0.25.9", 1287 - "@esbuild/linux-arm": "0.25.9", 1288 - "@esbuild/linux-arm64": "0.25.9", 1289 - "@esbuild/linux-ia32": "0.25.9", 1290 - "@esbuild/linux-loong64": "0.25.9", 1291 - "@esbuild/linux-mips64el": "0.25.9", 1292 - "@esbuild/linux-ppc64": "0.25.9", 1293 - "@esbuild/linux-riscv64": "0.25.9", 1294 - "@esbuild/linux-s390x": "0.25.9", 1295 - "@esbuild/linux-x64": "0.25.9", 1296 - "@esbuild/netbsd-arm64": "0.25.9", 1297 - "@esbuild/netbsd-x64": "0.25.9", 1298 - "@esbuild/openbsd-arm64": "0.25.9", 1299 - "@esbuild/openbsd-x64": "0.25.9", 1300 - "@esbuild/openharmony-arm64": "0.25.9", 1301 - "@esbuild/sunos-x64": "0.25.9", 1302 - "@esbuild/win32-arm64": "0.25.9", 1303 - "@esbuild/win32-ia32": "0.25.9", 1304 - "@esbuild/win32-x64": "0.25.9" 1311 + "@esbuild/aix-ppc64": "0.25.12", 1312 + "@esbuild/android-arm": "0.25.12", 1313 + "@esbuild/android-arm64": "0.25.12", 1314 + "@esbuild/android-x64": "0.25.12", 1315 + "@esbuild/darwin-arm64": "0.25.12", 1316 + "@esbuild/darwin-x64": "0.25.12", 1317 + "@esbuild/freebsd-arm64": "0.25.12", 1318 + "@esbuild/freebsd-x64": "0.25.12", 1319 + "@esbuild/linux-arm": "0.25.12", 1320 + "@esbuild/linux-arm64": "0.25.12", 1321 + "@esbuild/linux-ia32": "0.25.12", 1322 + "@esbuild/linux-loong64": "0.25.12", 1323 + "@esbuild/linux-mips64el": "0.25.12", 1324 + "@esbuild/linux-ppc64": "0.25.12", 1325 + "@esbuild/linux-riscv64": "0.25.12", 1326 + "@esbuild/linux-s390x": "0.25.12", 1327 + "@esbuild/linux-x64": "0.25.12", 1328 + "@esbuild/netbsd-arm64": "0.25.12", 1329 + "@esbuild/netbsd-x64": "0.25.12", 1330 + "@esbuild/openbsd-arm64": "0.25.12", 1331 + "@esbuild/openbsd-x64": "0.25.12", 1332 + "@esbuild/openharmony-arm64": "0.25.12", 1333 + "@esbuild/sunos-x64": "0.25.12", 1334 + "@esbuild/win32-arm64": "0.25.12", 1335 + "@esbuild/win32-ia32": "0.25.12", 1336 + "@esbuild/win32-x64": "0.25.12" 1305 1337 } 1306 1338 }, 1307 1339 "node_modules/estree-walker": { ··· 1375 1407 } 1376 1408 }, 1377 1409 "node_modules/glob": { 1378 - "version": "10.4.5", 1379 - "resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", 1380 - "integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", 1410 + "version": "10.5.0", 1411 + "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz", 1412 + "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==", 1381 1413 "dev": true, 1382 1414 "license": "ISC", 1383 1415 "dependencies": { ··· 1533 1565 "license": "ISC" 1534 1566 }, 1535 1567 "node_modules/magic-string": { 1536 - "version": "0.30.18", 1537 - "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.18.tgz", 1538 - "integrity": "sha512-yi8swmWbO17qHhwIBNeeZxTceJMeBvWJaId6dyvTSOwTipqeHhMhOrz6513r1sOKnpvQ7zkhlG8tPrpilwTxHQ==", 1568 + "version": "0.30.21", 1569 + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", 1570 + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", 1539 1571 "dev": true, 1540 1572 "license": "MIT", 1541 1573 "dependencies": { ··· 1622 1654 "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" 1623 1655 } 1624 1656 }, 1625 - "node_modules/node-fetch": { 1626 - "version": "2.7.0", 1627 - "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", 1628 - "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", 1629 - "license": "MIT", 1630 - "dependencies": { 1631 - "whatwg-url": "^5.0.0" 1632 - }, 1633 - "engines": { 1634 - "node": "4.x || >=6.0.0" 1635 - }, 1636 - "peerDependencies": { 1637 - "encoding": "^0.1.0" 1638 - }, 1639 - "peerDependenciesMeta": { 1640 - "encoding": { 1641 - "optional": true 1642 - } 1643 - } 1644 - }, 1645 - "node_modules/openweathermap-ts": { 1646 - "version": "1.2.10", 1647 - "resolved": "https://registry.npmjs.org/openweathermap-ts/-/openweathermap-ts-1.2.10.tgz", 1648 - "integrity": "sha512-Zckv2aXN8ENSeAeroces2jJciLWb6aLNXEmvG6pmF+BcIMw2kwRo6++/AKUNoU5suOp47UWA6lllDV0TNm//OA==", 1649 - "license": "MIT", 1650 - "dependencies": { 1651 - "node-fetch": "^2.6.0" 1652 - } 1657 + "node_modules/openweather-api-node": { 1658 + "version": "3.1.5", 1659 + "resolved": "https://registry.npmjs.org/openweather-api-node/-/openweather-api-node-3.1.5.tgz", 1660 + "integrity": "sha512-FGLE0bWOTvp4XHaswmzMfisYMMEtwEwOEJR0vaS07L31OUcutV/UUO5/vRuktkRPoqfk3KZOoqddsRTGTxT7Aw==", 1661 + "license": "MIT" 1653 1662 }, 1654 1663 "node_modules/package-json-from-dist": { 1655 1664 "version": "1.0.1", ··· 1752 1761 } 1753 1762 }, 1754 1763 "node_modules/rollup": { 1755 - "version": "4.48.0", 1756 - "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.48.0.tgz", 1757 - "integrity": "sha512-BXHRqK1vyt9XVSEHZ9y7xdYtuYbwVod2mLwOMFP7t/Eqoc1pHRlG/WdV2qNeNvZHRQdLedaFycljaYYM96RqJQ==", 1764 + "version": "4.53.3", 1765 + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.53.3.tgz", 1766 + "integrity": "sha512-w8GmOxZfBmKknvdXU1sdM9NHcoQejwF/4mNgj2JuEEdRaHwwF12K7e9eXn1nLZ07ad+du76mkVsyeb2rKGllsA==", 1758 1767 "dev": true, 1759 1768 "license": "MIT", 1760 1769 "dependencies": { ··· 1768 1777 "npm": ">=8.0.0" 1769 1778 }, 1770 1779 "optionalDependencies": { 1771 - "@rollup/rollup-android-arm-eabi": "4.48.0", 1772 - "@rollup/rollup-android-arm64": "4.48.0", 1773 - "@rollup/rollup-darwin-arm64": "4.48.0", 1774 - "@rollup/rollup-darwin-x64": "4.48.0", 1775 - "@rollup/rollup-freebsd-arm64": "4.48.0", 1776 - "@rollup/rollup-freebsd-x64": "4.48.0", 1777 - "@rollup/rollup-linux-arm-gnueabihf": "4.48.0", 1778 - "@rollup/rollup-linux-arm-musleabihf": "4.48.0", 1779 - "@rollup/rollup-linux-arm64-gnu": "4.48.0", 1780 - "@rollup/rollup-linux-arm64-musl": "4.48.0", 1781 - "@rollup/rollup-linux-loongarch64-gnu": "4.48.0", 1782 - "@rollup/rollup-linux-ppc64-gnu": "4.48.0", 1783 - "@rollup/rollup-linux-riscv64-gnu": "4.48.0", 1784 - "@rollup/rollup-linux-riscv64-musl": "4.48.0", 1785 - "@rollup/rollup-linux-s390x-gnu": "4.48.0", 1786 - "@rollup/rollup-linux-x64-gnu": "4.48.0", 1787 - "@rollup/rollup-linux-x64-musl": "4.48.0", 1788 - "@rollup/rollup-win32-arm64-msvc": "4.48.0", 1789 - "@rollup/rollup-win32-ia32-msvc": "4.48.0", 1790 - "@rollup/rollup-win32-x64-msvc": "4.48.0", 1780 + "@rollup/rollup-android-arm-eabi": "4.53.3", 1781 + "@rollup/rollup-android-arm64": "4.53.3", 1782 + "@rollup/rollup-darwin-arm64": "4.53.3", 1783 + "@rollup/rollup-darwin-x64": "4.53.3", 1784 + "@rollup/rollup-freebsd-arm64": "4.53.3", 1785 + "@rollup/rollup-freebsd-x64": "4.53.3", 1786 + "@rollup/rollup-linux-arm-gnueabihf": "4.53.3", 1787 + "@rollup/rollup-linux-arm-musleabihf": "4.53.3", 1788 + "@rollup/rollup-linux-arm64-gnu": "4.53.3", 1789 + "@rollup/rollup-linux-arm64-musl": "4.53.3", 1790 + "@rollup/rollup-linux-loong64-gnu": "4.53.3", 1791 + "@rollup/rollup-linux-ppc64-gnu": "4.53.3", 1792 + "@rollup/rollup-linux-riscv64-gnu": "4.53.3", 1793 + "@rollup/rollup-linux-riscv64-musl": "4.53.3", 1794 + "@rollup/rollup-linux-s390x-gnu": "4.53.3", 1795 + "@rollup/rollup-linux-x64-gnu": "4.53.3", 1796 + "@rollup/rollup-linux-x64-musl": "4.53.3", 1797 + "@rollup/rollup-openharmony-arm64": "4.53.3", 1798 + "@rollup/rollup-win32-arm64-msvc": "4.53.3", 1799 + "@rollup/rollup-win32-ia32-msvc": "4.53.3", 1800 + "@rollup/rollup-win32-x64-gnu": "4.53.3", 1801 + "@rollup/rollup-win32-x64-msvc": "4.53.3", 1791 1802 "fsevents": "~2.3.2" 1792 1803 } 1793 1804 }, ··· 1865 1876 "license": "MIT" 1866 1877 }, 1867 1878 "node_modules/std-env": { 1868 - "version": "3.9.0", 1869 - "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.9.0.tgz", 1870 - "integrity": "sha512-UGvjygr6F6tpH7o2qyqR6QYpwraIjKSdtzyBdyytFOHmPZY917kwdwLG0RbOjWOnKmnm3PeHjaoLLMie7kPLQw==", 1879 + "version": "3.10.0", 1880 + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", 1881 + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", 1871 1882 "dev": true, 1872 1883 "license": "MIT" 1873 1884 }, ··· 1936 1947 } 1937 1948 }, 1938 1949 "node_modules/strip-ansi": { 1939 - "version": "7.1.0", 1940 - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.0.tgz", 1941 - "integrity": "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ==", 1950 + "version": "7.1.2", 1951 + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-7.1.2.tgz", 1952 + "integrity": "sha512-gmBGslpoQJtgnMAvOVqGZpEz9dyoKTCzy2nfz/n8aIFhN/jCE/rCmcxabB6jOOHV+0WNnylOxaxBQPSvcWklhA==", 1942 1953 "dev": true, 1943 1954 "license": "MIT", 1944 1955 "dependencies": { ··· 1976 1987 } 1977 1988 }, 1978 1989 "node_modules/strip-literal": { 1979 - "version": "3.0.0", 1980 - "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.0.0.tgz", 1981 - "integrity": "sha512-TcccoMhJOM3OebGhSBEmp3UZ2SfDMZUEBdRA/9ynfLi8yYajyWX3JiXArcJt4Umh4vISpspkQIY8ZZoCqjbviA==", 1990 + "version": "3.1.0", 1991 + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-3.1.0.tgz", 1992 + "integrity": "sha512-8r3mkIM/2+PpjHoOtiAW8Rg3jJLHaV7xPwG+YRGrv6FP0wwk/toTpATxWYOW0BKdWwl82VT2tFYi5DlROa0Mxg==", 1982 1993 "dev": true, 1983 1994 "license": "MIT", 1984 1995 "dependencies": { ··· 2031 2042 "license": "MIT" 2032 2043 }, 2033 2044 "node_modules/tinyglobby": { 2034 - "version": "0.2.14", 2035 - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.14.tgz", 2036 - "integrity": "sha512-tX5e7OM1HnYr2+a2C/4V0htOcSQcoSTH9KgJnVvNm5zm/cyEWKJ7j7YutsH9CxMdtOkkLFy2AHrMci9IM8IPZQ==", 2045 + "version": "0.2.15", 2046 + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.15.tgz", 2047 + "integrity": "sha512-j2Zq4NyQYG5XMST4cbs02Ak8iJUdxRM0XI5QyxXuZOzKOINmWurp3smXu3y5wDcJrptwpSjgXHzIQxR0omXljQ==", 2037 2048 "dev": true, 2038 2049 "license": "MIT", 2039 2050 "dependencies": { 2040 - "fdir": "^6.4.4", 2041 - "picomatch": "^4.0.2" 2051 + "fdir": "^6.5.0", 2052 + "picomatch": "^4.0.3" 2042 2053 }, 2043 2054 "engines": { 2044 2055 "node": ">=12.0.0" ··· 2068 2079 } 2069 2080 }, 2070 2081 "node_modules/tinyspy": { 2071 - "version": "4.0.3", 2072 - "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.3.tgz", 2073 - "integrity": "sha512-t2T/WLB2WRgZ9EpE4jgPJ9w+i66UZfDc8wHh0xrwiRNN+UwH98GIJkTeZqX9rg0i0ptwzqW+uYeIF0T4F8LR7A==", 2082 + "version": "4.0.4", 2083 + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-4.0.4.tgz", 2084 + "integrity": "sha512-azl+t0z7pw/z958Gy9svOTuzqIk6xq+NSheJzn5MMWtWTFywIacg2wUlzKFGtt3cthx0r2SxMK0yzJOR0IES7Q==", 2074 2085 "dev": true, 2075 2086 "license": "MIT", 2076 2087 "engines": { 2077 2088 "node": ">=14.0.0" 2078 2089 } 2079 2090 }, 2080 - "node_modules/tr46": { 2081 - "version": "0.0.3", 2082 - "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", 2083 - "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", 2084 - "license": "MIT" 2085 - }, 2086 2091 "node_modules/typescript": { 2087 - "version": "5.9.2", 2088 - "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.2.tgz", 2089 - "integrity": "sha512-CWBzXQrc/qOkhidw1OzBTQuYRbfyxDXJMVJ1XNwUHGROVmuaeiEm3OslpZ1RV96d7SKKjZKrSJu3+t/xlw3R9A==", 2092 + "version": "5.9.3", 2093 + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", 2094 + "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", 2090 2095 "dev": true, 2091 2096 "license": "Apache-2.0", 2092 2097 "bin": { ··· 2098 2103 } 2099 2104 }, 2100 2105 "node_modules/undici-types": { 2101 - "version": "7.10.0", 2102 - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.10.0.tgz", 2103 - "integrity": "sha512-t5Fy/nfn+14LuOc2KNYg75vZqClpAiqscVvMygNnlsHBFpSXdJaYtXMcdNLpl/Qvc3P2cB3s6lOV51nqsFq4ag==", 2106 + "version": "7.16.0", 2107 + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.16.0.tgz", 2108 + "integrity": "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==", 2104 2109 "dev": true, 2105 2110 "license": "MIT" 2106 2111 }, 2107 2112 "node_modules/vite": { 2108 - "version": "7.1.3", 2109 - "resolved": "https://registry.npmjs.org/vite/-/vite-7.1.3.tgz", 2110 - "integrity": "sha512-OOUi5zjkDxYrKhTV3V7iKsoS37VUM7v40+HuwEmcrsf11Cdx9y3DIr2Px6liIcZFwt3XSRpQvFpL3WVy7ApkGw==", 2113 + "version": "7.2.6", 2114 + "resolved": "https://registry.npmjs.org/vite/-/vite-7.2.6.tgz", 2115 + "integrity": "sha512-tI2l/nFHC5rLh7+5+o7QjKjSR04ivXDF4jcgV0f/bTQ+OJiITy5S6gaynVsEM+7RqzufMnVbIon6Sr5x1SDYaQ==", 2111 2116 "dev": true, 2112 2117 "license": "MIT", 2113 2118 "dependencies": { ··· 2116 2121 "picomatch": "^4.0.3", 2117 2122 "postcss": "^8.5.6", 2118 2123 "rollup": "^4.43.0", 2119 - "tinyglobby": "^0.2.14" 2124 + "tinyglobby": "^0.2.15" 2120 2125 }, 2121 2126 "bin": { 2122 2127 "vite": "bin/vite.js" ··· 2273 2278 "jsdom": { 2274 2279 "optional": true 2275 2280 } 2276 - } 2277 - }, 2278 - "node_modules/webidl-conversions": { 2279 - "version": "3.0.1", 2280 - "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", 2281 - "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", 2282 - "license": "BSD-2-Clause" 2283 - }, 2284 - "node_modules/whatwg-url": { 2285 - "version": "5.0.0", 2286 - "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", 2287 - "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", 2288 - "license": "MIT", 2289 - "dependencies": { 2290 - "tr46": "~0.0.3", 2291 - "webidl-conversions": "^3.0.0" 2292 2281 } 2293 2282 }, 2294 2283 "node_modules/which": {
+11 -5
package.json
··· 1 1 { 2 2 "name": "morning-report", 3 - "version": "0.0.1", 3 + "version": "0.0.5", 4 4 "description": "Procedurally generates a radio weather report", 5 5 "keywords": [ 6 6 "weather", ··· 19 19 }, 20 20 "type": "module", 21 21 "main": "distribution/index.js", 22 + "bin": { 23 + "morning-report": "./distribution/src/index.js" 24 + }, 25 + "files": [ 26 + "distribution" 27 + ], 22 28 "scripts": { 23 29 "build": "tsc", 24 - "start": "node distribution/index.js", 30 + "start": "node distribution/src/index.js", 25 31 "test": "vitest" 26 32 }, 27 33 "dependencies": { 28 34 "json5": "2.2.3", 29 - "openweathermap-ts": "1.2.10" 35 + "openweather-api-node": "3.1.5" 30 36 }, 31 37 "devDependencies": { 32 - "typescript": "5.9.2", 33 - "@types/node": "24.3.0", 38 + "typescript": "5.9.3", 39 + "@types/node": "24.10.1", 34 40 "vitest": "3.2.4", 35 41 "@vitest/coverage-v8": "3.2.4" 36 42 }
+6 -4
src/index.ts
··· 1 + #!/usr/bin/env node 1 2 import path from 'path'; 2 3 import fsp from 'fs/promises'; 3 4 import json5 from 'json5'; 4 5 import Sequencer from './sequencer.js'; 6 + import { Stitcher } from './stitcher.js'; 5 7 import type {Programs, Segments, Sequences} from './sequencer.js'; 6 8 import type { Voices } from './voice.js'; 7 - import type { WeatherConfig } from './weather.js'; 9 + import type { Options } from 'openweather-api-node'; 8 10 9 11 10 12 interface Config { ··· 12 14 segments: Segments, 13 15 sequences: Sequences, 14 16 voices: Voices, 15 - weather: WeatherConfig 17 + weather: Options 16 18 } 17 19 18 20 console.log('morning-report\nCory Sanin 2025\n'); 19 21 20 22 const config: Config = json5.parse(await fsp.readFile(process.env['CONFIG'] || path.join('config', 'config.json5'), { encoding: 'utf-8' })); 21 - const sequence = Sequencer(config); 23 + const sequence = await Sequencer(config); 22 24 console.log(sequence.join('\n')); 23 - 25 + await Stitcher(sequence); 24 26 25 27 export type { Config };
+72 -14
src/sequencer.ts
··· 1 + import { OpenWeatherAPI, type DailyWeather } from 'openweather-api-node'; 2 + import { voiceLines } from './voice.js'; 1 3 import type { Config } from './index.js'; 4 + import type { Voice } from './voice.js'; 5 + import crypto from 'crypto'; 2 6 3 7 type SegmentName = string; 4 8 type SequenceName = string; 5 9 type Programs = SegmentName[][]; 6 10 type Segments = { [segment: SegmentName]: SequenceName[] }; 7 11 type Sequence = { 8 - condition?: string; 12 + conditions?: string[]; 9 13 tracks: string[]; 10 14 } 11 15 type Sequences = { [sequence: SequenceName]: Sequence }; ··· 13 17 let config: Config = null; 14 18 15 19 function selectOne<T>(arr: T[]): T { 16 - return arr[Math.floor(Math.random() * arr.length)]; 20 + return arr[crypto.randomInt(0, arr.length)]; 21 + } 22 + 23 + function resolveSide(side: string, currentWeather: DailyWeather) { 24 + if (!side.startsWith('weather')) { 25 + return side.includes('.') ? parseFloat(side) : parseInt(side); 26 + } 27 + 28 + const tokens = side.split('.'); 29 + let w = currentWeather; 30 + tokens.forEach(t => w = w[t]); 31 + return typeof w === 'object' ? JSON.stringify(w) : w as (string | number); 32 + } 33 + 34 + function notNotANumber(something: number | string, defaultVal: string) { 35 + if (typeof something === 'string' || !isNaN(something)) { 36 + return something; 37 + } 38 + return defaultVal; 17 39 } 18 40 19 - function conditionIsMet(condition: string | undefined = undefined): boolean { 41 + function conditionIsMet(condition: string | undefined, currentWeather: DailyWeather): boolean { 20 42 if (typeof condition !== 'string') { 21 43 return true; 22 44 } 23 - // TODO: parse condition, return bool 24 - return false; 45 + const [lhs, relational, rhs] = condition.split(' '); 46 + if (lhs === undefined || relational === undefined || rhs === undefined) { 47 + throw new Error(`Condition "${condition}" is not in the correct format`); 48 + } 49 + const lhsResolved = notNotANumber(resolveSide(lhs, currentWeather), lhs); 50 + const rhsResolved = notNotANumber(resolveSide(rhs, currentWeather), rhs); 51 + switch (relational) { 52 + case '=': 53 + case '==': 54 + return lhsResolved == rhsResolved; 55 + case '!=': 56 + return lhsResolved != rhsResolved; 57 + case '<': 58 + return lhsResolved < rhsResolved; 59 + case '<=': 60 + return lhsResolved <= rhsResolved; 61 + case '>': 62 + return lhsResolved > rhsResolved; 63 + case '>=': 64 + return lhsResolved >= rhsResolved; 65 + default: 66 + throw new Error(`Unsupported relational operator: ${relational}`); 67 + } 25 68 } 26 69 27 - function processSequence(sequence: Sequence): string[] { 70 + function resolveMacro(str: string, currentWeather: DailyWeather): string[] { 71 + if (str.startsWith('%')) { 72 + const [profile, subject] = str.substring(1).split(' ', 2); 73 + const voiceProfile: Voice = config.voices[profile]; 74 + let resolvedSubject: any = currentWeather; 75 + subject.split('.').forEach(t => resolvedSubject = resolvedSubject[t]); 76 + return voiceLines(voiceProfile, resolvedSubject); 77 + } 78 + return [str]; 79 + } 80 + 81 + function processSequence(sequence: Sequence, currentWeather: DailyWeather): string[] { 28 82 const tracks = sequence.tracks; 29 - // TODO: process voice macros 30 - return tracks; 83 + return tracks.map(t => resolveMacro(t, currentWeather)).flat().filter(t => t !== null); 31 84 } 32 85 33 - function processSegment(segment: SegmentName): string[] { 86 + function processSegment(segment: SegmentName, currentWeather: DailyWeather): string[] { 34 87 if (!(segment in config.segments)) { 35 - return processSequence(config.sequences[segment]); 88 + return (config.sequences[segment].conditions || []).every(c => conditionIsMet(c, currentWeather)) ? processSequence(config.sequences[segment], currentWeather) : []; 36 89 } 37 - const potentialSequences: SequenceName[] = config.segments[segment].filter(s => conditionIsMet(config.sequences[s].condition)); 90 + const potentialSequences: SequenceName[] = config.segments[segment].filter(s => (config.sequences[s].conditions || []).every(c => conditionIsMet(c, currentWeather))); 38 91 if (potentialSequences.length === 0) { 39 92 return []; 40 93 } 41 - return processSequence(config.sequences[selectOne(potentialSequences)]); 94 + return processSequence(config.sequences[selectOne(potentialSequences)], currentWeather); 42 95 } 43 96 44 - function Sequencer(conf: Config): string[] { 97 + async function Sequencer(conf: Config): Promise<string[]> { 45 98 config = conf; 99 + const weather = new OpenWeatherAPI(conf.weather); 100 + const currentWeather = await weather.getToday(); 46 101 const sequence: string[] = []; 47 102 const program: SegmentName[] = selectOne(conf.programs); 48 - program.forEach(segment => sequence.push(...processSegment(segment))); 103 + for (let i = 0; i < program.length; i++) { 104 + const segment = program[i]; 105 + sequence.push(...(processSegment(segment, currentWeather))); 106 + } 49 107 return sequence; 50 108 } 51 109
+34
src/stitcher.ts
··· 1 + import { spawn } from 'child_process'; 2 + 3 + const ENCTOOL = process.env['ENCTOOL'] || 'ffmpeg'; 4 + 5 + function ffmpeg(args: string[], files: number): Promise<void> { 6 + return new Promise((resolve, reject) => { 7 + console.log(`${ENCTOOL} ${args.join(' ')}`); 8 + const process = spawn(ENCTOOL, args); 9 + const to = setTimeout(async () => { 10 + process.kill(); 11 + reject(new Error('timed out')); 12 + }, 5000 * files); 13 + process.on('exit', async (code) => { 14 + clearTimeout(to); 15 + if (code !== 0) { 16 + reject(new Error(`exited with ${code}`)); 17 + } 18 + else { 19 + resolve(); 20 + } 21 + }); 22 + }); 23 + } 24 + 25 + async function Stitcher(files: string[]) { 26 + const args: string[] = []; 27 + files.forEach(f => args.push('-i', f)); 28 + args.push('-filter_complex', `[0:a][1:a][2:a]concat=n=${files.length}:v=0:a=1[out]`); 29 + args.push('-map', '[out]', '-ar', '44100', '-ac', '2', '-c:a', 'pcm_s16le', 'output.wav', '-y'); 30 + await ffmpeg(args, files.length); 31 + } 32 + 33 + export default Stitcher; 34 + export { Stitcher };
-101
src/weather.ts
··· 1 - import OpenWeatherMap from 'openweathermap-ts'; 2 - import type { ThreeHourResponse, CurrentResponse, CountryCode } from 'openweathermap-ts/dist/types/index.js'; 3 - 4 - interface CityName { 5 - cityName: string; 6 - state: string; 7 - countryCode: CountryCode; 8 - } 9 - 10 - interface WeatherConfig { 11 - key: string; 12 - lang?: string; 13 - coordinates?: number[] | string; 14 - zip?: number; 15 - country?: CountryCode; 16 - cityid?: number; 17 - city?: CityName; 18 - } 19 - 20 - type LocationType = 'coordinates' | 'zip' | 'cityid' | 'city' | null; 21 - 22 - function parseCoords(coords: number[] | string): number[] { 23 - if (typeof coords == 'string') { 24 - return coords.replace(/\s/g, '').split(',').map(Number.parseFloat); 25 - } 26 - return coords; 27 - } 28 - 29 - 30 - class Weather { 31 - private openWeather: OpenWeatherMap.default; 32 - private locationType: LocationType; 33 - private current: CurrentResponse; 34 - private threeDay: ThreeHourResponse; 35 - 36 - constructor(options: WeatherConfig) { 37 - this.locationType = this.current = this.threeDay = null; 38 - this.openWeather = new OpenWeatherMap.default({ 39 - apiKey: options.key 40 - }); 41 - if ('city' in options && 'cityName' in options.city && 'state' in options.city && 'countryCode' in options.city) { 42 - this.openWeather.setCityName(options.city); 43 - this.locationType = 'city'; 44 - } 45 - if ('cityid' in options) { 46 - this.openWeather.setCityId(options.cityid); 47 - this.locationType = 'cityid'; 48 - } 49 - if ('zip' in options && 'country' in options) { 50 - this.openWeather.setZipCode(options.zip, options.country) 51 - this.locationType = 'zip'; 52 - } 53 - if ('coordinates' in options) { 54 - const coords = parseCoords(options.coordinates); 55 - if (coords.length >= 2) { 56 - this.openWeather.setGeoCoordinates(coords[0], coords[1]); 57 - this.locationType = 'coordinates'; 58 - } 59 - } 60 - } 61 - 62 - async getCurrentWeather(): Promise<CurrentResponse> { 63 - if (this.current) { 64 - return this.current; 65 - } 66 - switch (this.locationType) { 67 - case 'city': 68 - return this.current = await this.openWeather.getCurrentWeatherByCityName(); 69 - case 'cityid': 70 - return this.current = await this.openWeather.getCurrentWeatherByCityId(); 71 - case 'zip': 72 - return this.current = await this.openWeather.getCurrentWeatherByZipcode(); 73 - case 'coordinates': 74 - return this.current = await this.openWeather.getCurrentWeatherByGeoCoordinates(); 75 - default: 76 - throw new Error(`Can't fetch weather for location type '${this.locationType}'`); 77 - } 78 - } 79 - 80 - async getThreeHourForecast(): Promise<ThreeHourResponse> { 81 - if (this.threeDay) { 82 - return this.threeDay; 83 - } 84 - switch (this.locationType) { 85 - case 'city': 86 - return this.threeDay = await this.openWeather.getThreeHourForecastByCityName(); 87 - case 'cityid': 88 - return this.threeDay = await this.openWeather.getThreeHourForecastByCityId(); 89 - case 'zip': 90 - return this.threeDay = await this.openWeather.getThreeHourForecastByZipcode(); 91 - case 'coordinates': 92 - return this.threeDay = await this.openWeather.getThreeHourForecastByGeoCoordinates(); 93 - default: 94 - throw new Error(`Can't fetch weather for location type '${this.locationType}'`); 95 - } 96 - } 97 - } 98 - 99 - export default Weather; 100 - export { Weather }; 101 - export type { WeatherConfig, CityName, CurrentResponse, ThreeHourResponse };
+189
test/sequencer.test.ts
··· 1 + import { describe, expect, it, vi } from 'vitest'; 2 + import { type Options } from 'openweather-api-node'; 3 + import { Sequencer } from '../src/sequencer.js'; 4 + 5 + const dummyWeather: Options = { key: 'dummy' }; 6 + 7 + vi.mock('openweather-api-node', () => { 8 + return { 9 + OpenWeatherAPI: vi.fn().mockImplementation((_) => { 10 + return { 11 + getToday: vi.fn(() => { 12 + return { 13 + "lat": 43.0748, 14 + "lon": -89.3838, 15 + "dt": "2025-08-29T06:31:05.000Z", 16 + "dtRaw": 1756449065, 17 + "timezoneOffset": -18000, 18 + "astronomical": { 19 + "sunrise": "2025-08-29T11:19:05.000Z", 20 + "sunriseRaw": 1756466345, 21 + "sunset": "2025-08-30T00:38:08.000Z", 22 + "sunsetRaw": 1756514288 23 + }, 24 + "weather": { 25 + "temp": { 26 + "cur": 55.85, 27 + "min": 52.99, 28 + "max": 58.01 29 + }, 30 + "feelsLike": { 31 + "cur": 55.31 32 + }, 33 + "pressure": 1022, 34 + "humidity": 89, 35 + "clouds": 0, 36 + "visibility": 10000, 37 + "wind": { 38 + "deg": 140, 39 + "speed": 5.75 40 + }, 41 + "rain": 0, 42 + "snow": 0, 43 + "conditionId": 800, 44 + "main": "Clear", 45 + "description": "clear sky", 46 + "icon": { 47 + "url": "http://openweathermap.org/img/wn/01n@2x.png", 48 + "raw": "01n" 49 + } 50 + } 51 + } 52 + }) 53 + } 54 + }) 55 + } 56 + }); 57 + 58 + describe('sequencer', () => { 59 + it('can generate a list from a static config', async () => { 60 + expect(await Sequencer({ 61 + programs: [['sequence 1', 'segment 1', 'segment 2']], 62 + segments: { 63 + 'segment 1': ['sequence 1'], 64 + 'segment 2': ['sequence 2'] 65 + }, 66 + sequences: { 67 + 'sequence 1': { 68 + 'tracks': [ 69 + 'seq1.flac' 70 + ] 71 + }, 72 + 'sequence 2': { 73 + 'tracks': [ 74 + 'seq2.flac' 75 + ] 76 + } 77 + }, 78 + voices: {}, 79 + weather: dummyWeather 80 + })).to.include.ordered.members(['seq1.flac', 'seq1.flac', 'seq2.flac']); 81 + }); 82 + 83 + it('can include tracks conditionally', async () => { 84 + expect(await Sequencer({ 85 + programs: [['segment 1', 'sequence 1', 'segment 2', 'sequence 2']], 86 + segments: { 87 + 'segment 1': ['sequence 1'], 88 + 'segment 2': ['sequence 2'] 89 + }, 90 + sequences: { 91 + 'sequence 1': { 92 + 'conditions': ['1 = 1', '1.1 > 1', '2 >= 1', '1 < 2', '1 <= 1', '-1 != 0', undefined], 93 + 'tracks': [ 94 + 'seq1.flac' 95 + ] 96 + }, 97 + 'sequence 2': { 98 + 'conditions': ['weather.lat = -500'], 99 + 'tracks': [ 100 + 'seq2.flac' 101 + ] 102 + } 103 + }, 104 + voices: {}, 105 + weather: dummyWeather 106 + })).to.be.ordered.members(['seq1.flac', 'seq1.flac']); 107 + }); 108 + 109 + it('throws an error on invalid conditions', async () => { 110 + await expect(Sequencer({ 111 + programs: [['sequence 1']], 112 + segments: { 113 + }, 114 + sequences: { 115 + 'sequence 1': { 116 + 'conditions': ['100'], 117 + 'tracks': [ 118 + 'seq1.flac' 119 + ] 120 + } 121 + }, 122 + voices: {}, 123 + weather: dummyWeather 124 + })).rejects.toThrow(/not in the correct format/); 125 + 126 + await expect(Sequencer({ 127 + programs: [['sequence 1']], 128 + segments: { 129 + }, 130 + sequences: { 131 + 'sequence 1': { 132 + 'conditions': ['1 ~ 2'], 133 + 'tracks': [ 134 + 'seq1.flac' 135 + ] 136 + } 137 + }, 138 + voices: {}, 139 + weather: dummyWeather 140 + })).rejects.toThrow(/Unsupported relational operator/); 141 + }); 142 + 143 + it('can stringify conditions', async () => { 144 + expect(await Sequencer({ 145 + programs: [['sequence 1']], 146 + segments: { 147 + }, 148 + sequences: { 149 + 'sequence 1': { 150 + 'conditions': ['weather.feelsLike = {"cur":55.31}'], 151 + 'tracks': [ 152 + 'seq1.flac' 153 + ] 154 + } 155 + }, 156 + voices: {}, 157 + weather: dummyWeather 158 + })).to.be.ordered.members(['seq1.flac']); 159 + }); 160 + 161 + it('can parse voice macros', async () => { 162 + expect(await Sequencer({ 163 + programs: [['sequence 1']], 164 + segments: { 165 + }, 166 + sequences: { 167 + 'sequence 1': { 168 + 'tracks': [ 169 + '%alice weather.temp.max' 170 + ] 171 + } 172 + }, 173 + voices: { 174 + "alice": { 175 + "directory": "alice/", 176 + "extension": "flac" 177 + } 178 + }, 179 + weather: dummyWeather 180 + })).to.be.ordered.members([ 181 + 'alice/fifty.flac', 182 + 'alice/eight.flac', 183 + 'alice/point.flac', 184 + 'alice/zero.flac', 185 + 'alice/one.flac' 186 + ]); 187 + }); 188 + }); 189 +
+90
test/stitcher.test.ts
··· 1 + import { Stitcher } from '../src/stitcher.js'; 2 + import { describe, expect, it, vi, beforeEach } from 'vitest'; 3 + import { EventEmitter } from 'events'; 4 + import { spawn } from 'child_process'; 5 + 6 + const mockChildProcess = new (class MockChildProcess 7 + extends EventEmitter { 8 + kill = vi.fn(() => { 9 + return true; 10 + }); 11 + })(); 12 + 13 + vi.mock('child_process', () => { 14 + return { 15 + spawn: vi.fn(() => mockChildProcess) 16 + } 17 + }); 18 + 19 + describe('stitcher', () => { 20 + 21 + beforeEach(() => { 22 + vi.clearAllMocks(); 23 + }); 24 + 25 + it('passes the correct arguments to ffmpeg', async () => { 26 + const p = Stitcher(['1.flac', 'dir/2.flac']); 27 + mockChildProcess.emit('exit', 0, null); 28 + await p; 29 + expect(spawn).toBeCalledWith('ffmpeg', [ 30 + "-i", 31 + '1.flac', 32 + '-i', 33 + 'dir/2.flac', 34 + '-filter_complex', 35 + '[0:a][1:a][2:a]concat=n=2:v=0:a=1[out]', 36 + '-map', 37 + '[out]', 38 + '-ar', 39 + '44100', 40 + '-ac', 41 + '2', 42 + '-c:a', 43 + 'pcm_s16le', 44 + 'output.wav', 45 + '-y' 46 + ]); 47 + }); 48 + 49 + it('throws an error when ffmpeg fails', async () => { 50 + const p = Stitcher(['sound.mp3']); 51 + mockChildProcess.emit('exit', 1, null); 52 + await expect(p).rejects.toThrow('exited with 1'); 53 + expect(spawn).toBeCalledWith('ffmpeg', [ 54 + "-i", 55 + 'sound.mp3', 56 + '-filter_complex', 57 + '[0:a][1:a][2:a]concat=n=1:v=0:a=1[out]', 58 + '-map', 59 + '[out]', 60 + '-ar', 61 + '44100', 62 + '-ac', 63 + '2', 64 + '-c:a', 65 + 'pcm_s16le', 66 + 'output.wav', 67 + '-y' 68 + ]); 69 + }); 70 + 71 + it('throws an error when ffmpeg takes longer than expected', { timeout: 6000 }, async () => { 72 + await expect(Stitcher(['in.wav'])).rejects.toThrow('timed out'); 73 + expect(spawn).toBeCalledWith('ffmpeg', [ 74 + "-i", 75 + 'in.wav', 76 + '-filter_complex', 77 + '[0:a][1:a][2:a]concat=n=1:v=0:a=1[out]', 78 + '-map', 79 + '[out]', 80 + '-ar', 81 + '44100', 82 + '-ac', 83 + '2', 84 + '-c:a', 85 + 'pcm_s16le', 86 + 'output.wav', 87 + '-y' 88 + ]); 89 + }); 90 + });
+15 -15
test/voice.test.ts
··· 10 10 11 11 describe('voiceLines', () => { 12 12 it('handles integers', () => { 13 - expect(voiceLines(dummyVoice, 16549872)).to.include.ordered.members( 13 + expect(voiceLines(dummyVoice, 16549872)).to.be.ordered.members( 14 14 [ 15 15 LINES.SIX, LINES.TEEN, LINES.MILLION, LINES.FIVE, LINES.HUNDRED, LINES.FORTY, 16 16 LINES.NINE, LINES.THOUSAND, LINES.EIGHT, LINES.HUNDRED, LINES.SEVENTY, LINES.TWO ··· 19 19 }); 20 20 21 21 it('handles floating point', () => { 22 - expect(voiceLines(dummyVoice, 672.09435)).to.include.ordered.members( 22 + expect(voiceLines(dummyVoice, 672.09435)).to.be.ordered.members( 23 23 [ 24 24 LINES.SIX, LINES.HUNDRED, LINES.SEVENTY, LINES.TWO, LINES.POINT, LINES.ZERO, 25 25 LINES.NINE, LINES.FOUR, LINES.THREE, LINES.FIVE ··· 28 28 }); 29 29 30 30 it('handles the negative', () => { 31 - expect(voiceLines(dummyVoice, -672.09435)).to.include.ordered.members( 31 + expect(voiceLines(dummyVoice, -672.09435)).to.be.ordered.members( 32 32 [ 33 33 LINES.NEGATIVE, LINES.SIX, LINES.HUNDRED, LINES.SEVENTY, LINES.TWO, LINES.POINT, LINES.ZERO, 34 34 LINES.NINE, LINES.FOUR, LINES.THREE, LINES.FIVE ··· 37 37 }); 38 38 39 39 it('handles zero', () => { 40 - expect(voiceLines(dummyVoice, 0)).to.include.ordered.members( 40 + expect(voiceLines(dummyVoice, 0)).to.be.ordered.members( 41 41 [ 42 42 LINES.ZERO 43 43 ] ··· 45 45 }); 46 46 47 47 it('handles large numbers with many zeroes', () => { 48 - expect(voiceLines(dummyVoice, 700000000000001)).to.include.ordered.members( 48 + expect(voiceLines(dummyVoice, 700000000000001)).to.be.ordered.members( 49 49 [ 50 50 LINES.SEVEN, LINES.HUNDRED, LINES.TRILLION, LINES.ONE 51 51 ] 52 52 ); 53 53 54 - expect(voiceLines(dummyVoice, 1000001)).to.include.ordered.members( 54 + expect(voiceLines(dummyVoice, 1000001)).to.be.ordered.members( 55 55 [ 56 56 LINES.ONE, LINES.MILLION, LINES.ONE 57 57 ] 58 58 ); 59 59 60 - expect(voiceLines(dummyVoice, 9000000001000)).to.include.ordered.members( 60 + expect(voiceLines(dummyVoice, 9000000001000)).to.be.ordered.members( 61 61 [ 62 62 LINES.NINE, LINES.TRILLION, LINES.ONE, LINES.THOUSAND 63 63 ] 64 64 ); 65 65 66 - expect(voiceLines(dummyVoice, 60002000000000.12)).to.include.ordered.members( 66 + expect(voiceLines(dummyVoice, 60002000000000.12)).to.be.ordered.members( 67 67 [ 68 68 LINES.SIXTY, LINES.TRILLION, LINES.TWO, LINES.BILLION, LINES.POINT, LINES.ONE, LINES.TWO 69 69 ] 70 70 ); 71 71 72 - expect(voiceLines(dummyVoice, 100010001)).to.include.ordered.members( 72 + expect(voiceLines(dummyVoice, 100010001)).to.be.ordered.members( 73 73 [ 74 74 LINES.ONE, LINES.HUNDRED, LINES.MILLION, LINES.TEN, LINES.THOUSAND, LINES.ONE 75 75 ] ··· 77 77 }); 78 78 79 79 it('handles irregularly named numbers', () => { 80 - expect(voiceLines(dummyVoice, 210)).to.include.ordered.members( 80 + expect(voiceLines(dummyVoice, 210)).to.be.ordered.members( 81 81 [ 82 82 LINES.TWO, LINES.HUNDRED, LINES.TEN 83 83 ] 84 84 ); 85 85 86 - expect(voiceLines(dummyVoice, 311)).to.include.ordered.members( 86 + expect(voiceLines(dummyVoice, 311)).to.be.ordered.members( 87 87 [ 88 88 LINES.THREE, LINES.HUNDRED, LINES.ELEVEN 89 89 ] 90 90 ); 91 91 92 - expect(voiceLines(dummyVoice, 412)).to.include.ordered.members( 92 + expect(voiceLines(dummyVoice, 412)).to.be.ordered.members( 93 93 [ 94 94 LINES.FOUR, LINES.HUNDRED, LINES.TWELVE 95 95 ] 96 96 ); 97 97 98 - expect(voiceLines(dummyVoice, 513)).to.include.ordered.members( 98 + expect(voiceLines(dummyVoice, 513)).to.be.ordered.members( 99 99 [ 100 100 LINES.FIVE, LINES.HUNDRED, LINES.THIRTEEN 101 101 ] 102 102 ); 103 103 104 - expect(voiceLines(dummyVoice, 615)).to.include.ordered.members( 104 + expect(voiceLines(dummyVoice, 615)).to.be.ordered.members( 105 105 [ 106 106 LINES.SIX, LINES.HUNDRED, LINES.FIFTEEN 107 107 ] ··· 121 121 directory, 122 122 extension: 'flac' 123 123 } 124 - expect(voiceLines(v, 29)).to.include.ordered.members([ 124 + expect(voiceLines(v, 29)).to.be.ordered.members([ 125 125 path.join(directory, `twenty.flac`), 126 126 path.join(directory, `nine.flac`), 127 127 ]);
-338
test/weather.test.ts
··· 1 - import OpenWeatherMap from 'openweathermap-ts'; 2 - import { describe, expect, it, vi, beforeEach, type Mocked } from 'vitest'; 3 - import { Weather } from '../src/weather.js'; 4 - import type { CurrentResponse, ThreeHourResponse } from '../src/weather.js'; 5 - 6 - // #region mock API responses 7 - const current: CurrentResponse = { 8 - "coord": { 9 - "lon": 7.367, 10 - "lat": 45.133 11 - }, 12 - "weather": [ 13 - { 14 - "id": 501, 15 - "main": "Rain", 16 - "description": "moderate rain", 17 - "icon": "10d" 18 - } 19 - ], 20 - "base": "stations", 21 - "main": { 22 - "temp": 284.2, 23 - "feels_like": 282.93, 24 - "temp_min": 283.06, 25 - "temp_max": 286.82, 26 - "pressure": 1021, 27 - "humidity": 60, 28 - "sea_level": 1021, 29 - "grnd_level": 910 30 - }, 31 - "visibility": 10000, 32 - "wind": { 33 - "speed": 4.09, 34 - "deg": 121, 35 - "gust": 3.47 36 - }, 37 - "rain": { 38 - "1h": 2.73 39 - }, 40 - "clouds": { 41 - "all": 83 42 - }, 43 - "dt": 1726660758, 44 - "sys": { 45 - "type": 1, 46 - "id": 6736, 47 - "country": "IT", 48 - "sunrise": 1726636384, 49 - "sunset": 1726680975 50 - }, 51 - "timezone": 7200, 52 - "id": 3165523, 53 - "name": "Province of Turin", 54 - "cod": 200 55 - }; 56 - 57 - const threeHour: ThreeHourResponse = { 58 - "cod": "200", 59 - "message": 0, 60 - "cnt": 96, 61 - "list": [ 62 - { 63 - "dt": 1661875200, 64 - "main": { 65 - "temp": 296.34, 66 - "temp_min": 296.34, 67 - "temp_max": 298.24, 68 - "pressure": 1015, 69 - "sea_level": 1015, 70 - "grnd_level": 933, 71 - "humidity": 50, 72 - "temp_kf": -1.9 73 - }, 74 - "weather": [ 75 - { 76 - "id": 500, 77 - "main": "Rain", 78 - "description": "light rain", 79 - "icon": "10d" 80 - } 81 - ], 82 - "clouds": { 83 - "all": 97 84 - }, 85 - "wind": { 86 - "speed": 1.06, 87 - "deg": 66 88 - }, 89 - "rain": { 90 - "3h": 1 91 - }, 92 - "sys": { 93 - "pod": "d" 94 - }, 95 - "dt_txt": "2022-08-30 16:00:00" 96 - }, 97 - { 98 - "dt": 1661878800, 99 - "main": { 100 - "temp": 296.31, 101 - "temp_min": 296.2, 102 - "temp_max": 296.31, 103 - "pressure": 1015, 104 - "sea_level": 1015, 105 - "grnd_level": 932, 106 - "humidity": 53, 107 - "temp_kf": 0.11 108 - }, 109 - "weather": [ 110 - { 111 - "id": 500, 112 - "main": "Rain", 113 - "description": "light rain", 114 - "icon": "10d" 115 - } 116 - ], 117 - "clouds": { 118 - "all": 95 119 - }, 120 - "wind": { 121 - "speed": 1.58, 122 - "deg": 103 123 - }, 124 - "rain": { 125 - "3h": 0.24 126 - }, 127 - "sys": { 128 - "pod": "d" 129 - }, 130 - "dt_txt": "2022-08-30 17:00:00" 131 - }, 132 - { 133 - "dt": 1661882400, 134 - "main": { 135 - "temp": 294.94, 136 - "temp_min": 292.84, 137 - "temp_max": 294.94, 138 - "pressure": 1015, 139 - "sea_level": 1015, 140 - "grnd_level": 931, 141 - "humidity": 60, 142 - "temp_kf": 2.1 143 - }, 144 - "weather": [ 145 - { 146 - "id": 500, 147 - "main": "Rain", 148 - "description": "light rain", 149 - "icon": "10n" 150 - } 151 - ], 152 - "clouds": { 153 - "all": 93 154 - }, 155 - "wind": { 156 - "speed": 1.97, 157 - "deg": 157 158 - }, 159 - "rain": { 160 - "3h": 0.2 161 - }, 162 - "sys": { 163 - "pod": "n" 164 - }, 165 - "dt_txt": "2022-08-30 18:00:00" 166 - }, 167 - { 168 - "dt": 1662217200, 169 - "main": { 170 - "temp": 294.14, 171 - "temp_min": 294.14, 172 - "temp_max": 294.14, 173 - "pressure": 1014, 174 - "sea_level": 1014, 175 - "grnd_level": 931, 176 - "humidity": 65, 177 - "temp_kf": 0 178 - }, 179 - "weather": [ 180 - { 181 - "id": 804, 182 - "main": "Clouds", 183 - "description": "overcast clouds", 184 - "icon": "04d" 185 - } 186 - ], 187 - "clouds": { 188 - "all": 100 189 - }, 190 - "wind": { 191 - "speed": 0.91, 192 - "deg": 104 193 - }, 194 - "sys": { 195 - "pod": "d" 196 - }, 197 - "dt_txt": "2022-09-03 15:00:00" 198 - } 199 - ], 200 - "city": { 201 - "id": 3163858, 202 - "name": "Zocca", 203 - "coord": { 204 - "lat": 44.34, 205 - "lon": 10.99 206 - }, 207 - "country": "IT" 208 - } 209 - } 210 - // #endregion 211 - 212 - vi.mock('openweathermap-ts', () => { 213 - return { 214 - default: { 215 - default: vi.fn().mockImplementation((_) => { 216 - return { 217 - setCityName: vi.fn(() => undefined), 218 - setCityId: vi.fn(() => undefined), 219 - setZipCode: vi.fn(() => undefined), 220 - setGeoCoordinates: vi.fn(() => undefined), 221 - getCurrentWeatherByCityName: vi.fn(async () => current), 222 - getCurrentWeatherByCityId: vi.fn(async () => current), 223 - getCurrentWeatherByZipcode: vi.fn(async () => current), 224 - getCurrentWeatherByGeoCoordinates: vi.fn(async () => current), 225 - getThreeHourForecastByCityName: vi.fn(async () => threeHour), 226 - getThreeHourForecastByCityId: vi.fn(async () => threeHour), 227 - getThreeHourForecastByZipcode: vi.fn(async () => threeHour), 228 - getThreeHourForecastByGeoCoordinates: vi.fn(async () => threeHour), 229 - } 230 - }) 231 - } 232 - } 233 - }); 234 - 235 - let weather: Weather = null; 236 - 237 - describe.for( 238 - [ 239 - { 240 - weatherFactory: () => new Weather({ 241 - key: 'api-key', 242 - city: { 243 - cityName: 'Madison', 244 - state: 'Wisconsin', 245 - countryCode: 'US' 246 - } 247 - }), 248 - by: 'CityName' 249 - }, 250 - { 251 - weatherFactory: () => new Weather({ 252 - key: 'api-key', 253 - cityid: 1 254 - }), 255 - by: 'CityId' 256 - }, 257 - { 258 - weatherFactory: () => new Weather({ 259 - key: 'api-key', 260 - zip: 53702, 261 - country: 'US' 262 - }), 263 - by: 'Zipcode' 264 - }, 265 - { 266 - weatherFactory: () => new Weather({ 267 - key: 'api-key', 268 - coordinates: [0, 0] 269 - }), 270 - by: 'GeoCoordinates' 271 - }, 272 - { 273 - weatherFactory: () => new Weather({ 274 - key: 'api-key', 275 - coordinates: '1000 , 1000' 276 - }), 277 - by: 'GeoCoordinates' 278 - } 279 - ])('weather API using city name', ({ weatherFactory, by }) => { 280 - beforeEach(() => { 281 - vi.clearAllMocks(); 282 - weather = weatherFactory(); 283 - }); 284 - 285 - it('gets current weather', async () => { 286 - expect(await weather.getCurrentWeather()).to.deep.equal(current); 287 - expect(OpenWeatherMap.default).toBeCalledWith({ 288 - apiKey: 'api-key' 289 - }); 290 - const MockedOpenWeather = vi.mocked(OpenWeatherMap.default); 291 - const openWeather = MockedOpenWeather.mock.results[0].value; 292 - expect(openWeather[`getCurrentWeatherBy${by}`]).toBeCalledTimes(1); 293 - }); 294 - 295 - it('gets the 3h forecast', async () => { 296 - expect(await weather.getThreeHourForecast()).to.deep.equal(threeHour); 297 - expect(OpenWeatherMap.default).toBeCalledWith({ 298 - apiKey: 'api-key' 299 - }); 300 - const MockedOpenWeather = vi.mocked(OpenWeatherMap.default); 301 - const openWeather = MockedOpenWeather.mock.results[0].value; 302 - expect(openWeather[`getThreeHourForecastBy${by}`]).toBeCalledTimes(1); 303 - }); 304 - 305 - it('only calls the api once', async () => { 306 - expect(await weather.getCurrentWeather()).to.deep.equal(current); 307 - expect(await weather.getCurrentWeather()).to.deep.equal(current); 308 - expect(await weather.getThreeHourForecast()).to.deep.equal(threeHour); 309 - expect(await weather.getThreeHourForecast()).to.deep.equal(threeHour); 310 - expect(OpenWeatherMap.default).toBeCalledWith({ 311 - apiKey: 'api-key' 312 - }); 313 - const MockedOpenWeather = vi.mocked(OpenWeatherMap.default); 314 - const openWeather = MockedOpenWeather.mock.results[0].value; 315 - expect(openWeather[`getCurrentWeatherBy${by}`]).toBeCalledTimes(1); 316 - expect(openWeather[`getThreeHourForecastBy${by}`]).toBeCalledTimes(1); 317 - }); 318 - } 319 - ); 320 - 321 - describe('invalid weather object', () => { 322 - beforeEach(() => { 323 - vi.clearAllMocks(); 324 - weather = new Weather({ 325 - key: 'api-key', 326 - cityid: 1 327 - }); 328 - weather['locationType'] = null; 329 - }); 330 - 331 - it('throws an exception when getCurrentWeather is called', async () => { 332 - await expect(weather.getCurrentWeather()).rejects.toThrow(/location type/); 333 - }); 334 - 335 - it('throws an exception when getThreeHourForecast is called', async () => { 336 - await expect(weather.getThreeHourForecast()).rejects.toThrow(/location type/); 337 - }); 338 - });