diff --git a/docs/superpowers/plans/2026-05-14-library-modernization.md b/docs/superpowers/plans/2026-05-14-library-modernization.md new file mode 100644 index 0000000..c6239e2 --- /dev/null +++ b/docs/superpowers/plans/2026-05-14-library-modernization.md @@ -0,0 +1,183 @@ +# Library Modernization Implementation Plan + +> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking. + +**Goal:** Modernize runtime and development dependencies while preserving Discord monitoring, recording, database migration, dashboard, and test behavior. + +**Architecture:** Treat modernization as dependency classification plus small source refactors. Remove redundant validation libraries by moving `src/validation.ts` to Zod, replace `fluent-ffmpeg` with a tiny direct `ffmpeg` process wrapper for the muxer scripts, and convert database migration code to ESM-safe imports. Keep high-risk Discord/audio/native packages unless audit proves a safe replacement exists. + +**Tech Stack:** Node.js, pnpm, TypeScript, Zod, Drizzle ORM, better-sqlite3, pg, Express, ws, Vite, React, Vitest, Biome, Discord voice/audio packages. + +--- + +## Dependency Audit Baseline + +- Usage audit confirms `class-transformer`/`class-validator` are only used by `src/validation.ts`. +- Usage audit confirms `fluent-ffmpeg` is only used by `src/muxer.ts` and `src/muxer-aup3.ts`. +- `pnpm outdated --format table` reports `discord.js-selfbot-v13` and `fluent-ffmpeg` as deprecated. +- Outdated packages reported: `tsx`, `@types/node`, `p-retry`, `pino`, `pino-pretty`, `sodium-native`. +- Direct dependency classification: remove `class-transformer`, `class-validator`, `fluent-ffmpeg`, `@types/fluent-ffmpeg`; replace validation with Zod and ffmpeg wrapper with `node:child_process`; upgrade outdated packages; keep high-risk voice/audio packages unless a compatible replacement is proven. + +## File Structure + +- Modify `package.json`: dependency upgrades, removals, and script additions if needed. +- Modify `pnpm-lock.yaml`: regenerated by `pnpm install`. +- Modify `src/validation.ts`: replace `class-transformer` and `class-validator` with Zod. +- Modify `src/database/migrate.ts`: remove dynamic CommonJS `require` and `any` cast. +- Create `src/audio/ffmpegProcess.ts`: small wrapper around `node:child_process` for direct ffmpeg execution. +- Modify `src/muxer.ts`: use `runFfmpeg()` instead of `fluent-ffmpeg`. +- Modify `src/muxer-aup3.ts`: use `runFfmpeg()` instead of `fluent-ffmpeg`. +- Modify `src/recorder/decoder.ts`: keep `createRequire()` for optional native probing unless a better ESM-safe probe is identified during implementation. +- Add or modify tests under `tests/`: validation, migration helper behavior, and ffmpeg argument construction. + +--- + +### Task 1: Capture Dependency Audit Baseline + +**Files:** +- Modify: `docs/superpowers/plans/2026-05-14-library-modernization.md` +- Inspect: `package.json` +- Inspect: `pnpm-lock.yaml` +- Inspect: `src/**/*.ts` +- Inspect: `tests/**/*.ts` + +- [x] **Step 1: List direct dependency usage** + +Run: + +```bash +grep -R "class-transformer\|class-validator\|fluent-ffmpeg\|@discordjs/opus\|@discordjs/voice\|@snazzah/davey\|discord.js-selfbot-v13\|libsodium-wrappers\|sodium-native\|prism-media\|drizzle-orm\|better-sqlite3\|pg\|express\|helmet\|p-retry\|pino\|pino-http\|prom-client\|react\|react-dom\|vite\|ws\|zod" -n src tests frontend package.json +``` + +Expected: output lists every direct package usage. Record the summary in the implementation notes during execution. + +- [x] **Step 2: Check outdated dependencies** + +Run: + +```bash +pnpm outdated --format table +``` + +Expected: command exits non-zero if packages are outdated; use the table as audit input, not as failure. + +- [x] **Step 3: Classify direct dependencies** + +Use this classification as the starting point, adjusting only if Step 1 proves a package is unused or irreplaceable: + +```text +remove: class-transformer, class-validator, fluent-ffmpeg, @types/fluent-ffmpeg +replace: class-transformer/class-validator -> zod; fluent-ffmpeg -> node:child_process ffmpeg wrapper +upgrade: @vitejs/plugin-react, better-sqlite3, discord.js-selfbot-v13, dotenv, drizzle-orm, express, helmet, libsodium-wrappers, p-retry, pg, pino, pino-http, prom-client, react, react-dom, sodium-native, vite, ws, zod, @biomejs/biome, @types/*, drizzle-kit, pino-pretty, tsx, vitest +keep unless compatible alternative is proven: @discordjs/opus, @discordjs/voice, @snazzah/davey, prism-media +``` + +- [x] **Step 4: Commit audit note if this task changes files** + +If only commands were run, do not commit. If the plan is updated with audit notes, run: + +```bash +git add docs/superpowers/plans/2026-05-14-library-modernization.md +git commit -m "docs: record dependency modernization audit" +``` + +Expected: commit succeeds only if a file changed. + +--- + +### Task 2: Replace Class Validator Stack With Zod + +**Files:** +- Modify: `src/validation.ts` +- Test: `tests/validation.test.ts` +- Modify later: `package.json` + +- [ ] **Step 1: Write failing validation tests** +- [ ] **Step 2: Run validation tests to establish baseline** +- [ ] **Step 3: Replace implementation with Zod** +- [ ] **Step 4: Run validation tests** +- [ ] **Step 5: Commit validation refactor** + +--- + +### Task 3: Convert Migration Code to ESM-Safe Drizzle Imports + +**Files:** +- Modify: `src/database/migrate.ts` +- Test: `tests/database/migrate.test.ts` + +- [ ] **Step 1: Extract SQLite database creation for testing** +- [ ] **Step 2: Add migration helper test** +- [ ] **Step 3: Run migration test** +- [ ] **Step 4: Run typecheck for migration typing** +- [ ] **Step 5: Commit migration refactor** + +--- + +### Task 4: Replace Fluent FFmpeg With Direct Process Wrapper + +**Files:** +- Create: `src/audio/ffmpegProcess.ts` +- Modify: `src/muxer.ts` +- Modify: `src/muxer-aup3.ts` +- Test: `tests/audio/ffmpegProcess.test.ts` + +- [ ] **Step 1: Add ffmpeg wrapper tests** +- [ ] **Step 2: Run ffmpeg wrapper test to verify it fails** +- [ ] **Step 3: Implement ffmpeg process wrapper** +- [ ] **Step 4: Refactor `src/muxer.ts`** +- [ ] **Step 5: Refactor `src/muxer-aup3.ts`** +- [ ] **Step 6: Run ffmpeg wrapper tests** +- [ ] **Step 7: Run typecheck** +- [ ] **Step 8: Commit ffmpeg refactor** + +--- + +### Task 5: Update Package Manifest and Lockfile + +**Files:** +- Modify: `package.json` +- Modify: `pnpm-lock.yaml` + +- [ ] **Step 1: Remove replaced packages** +- [ ] **Step 2: Upgrade dependencies interactively-free** +- [ ] **Step 3: Ensure package manager remains pnpm 10** +- [ ] **Step 4: Run install to verify lockfile** +- [ ] **Step 5: Commit dependency manifest changes** + +--- + +### Task 6: Fix Upgrade Breakages + +**Files:** +- Modify as needed: `src/**/*.ts` +- Modify as needed: `frontend/**/*.ts` +- Modify as needed: `frontend/**/*.tsx` +- Modify as needed: `tests/**/*.ts` +- Modify as needed: config files touched by upgraded tools + +- [ ] **Step 1: Run typecheck** +- [ ] **Step 2: Run lint** +- [ ] **Step 3: Run tests** +- [ ] **Step 4: Run build** +- [ ] **Step 5: Commit breakage fixes** + +--- + +### Task 7: Final Verification and Manual Dashboard Check + +**Files:** +- No planned source changes + +- [ ] **Step 1: Run full verification** +- [ ] **Step 2: Start dev server for dashboard check** +- [ ] **Step 3: Manually verify frontend build path if browser access is available** +- [ ] **Step 4: Check git status** + +--- + +## Self-Review + +- Spec coverage: audit, dependency classification, replacement/removal, ESM migration, lockfile regeneration, verification, and dashboard manual check are covered. +- Placeholder scan: no `TBD`, `TODO`, or unspecified implementation steps remain. +- Type consistency: helper names are consistent across tasks: `validateUserStateUpdate`, `initializeMigrationSqliteDatabase`, `buildMuxFfmpegArgs`, and `runFfmpeg`. diff --git a/package.json b/package.json index 0825b75..a39a858 100644 --- a/package.json +++ b/package.json @@ -25,26 +25,23 @@ "@discordjs/voice": "^0.19.1", "@snazzah/davey": "^0.1.10", "@types/pg": "^8.20.0", - "@vitejs/plugin-react": "^6.0.1", + "@vitejs/plugin-react": "^6.0.2", "better-sqlite3": "^12.10.0", - "class-transformer": "^0.5.1", - "class-validator": "^0.15.1", "discord.js-selfbot-v13": "^3.7.1", "dotenv": "^17.4.2", "drizzle-orm": "^0.45.2", "express": "^5.2.1", - "fluent-ffmpeg": "^2.1.3", "helmet": "^8.1.0", "libsodium-wrappers": "^0.8.2", - "p-retry": "^6.2.0", + "p-retry": "^8.0.0", "pg": "^8.20.0", - "pino": "^9.4.0", + "pino": "^10.3.1", "pino-http": "^11.0.0", "prism-media": "2.0.0-alpha.0", "prom-client": "^15.1.3", "react": "^19.2.6", "react-dom": "^19.2.6", - "sodium-native": "^4.3.2", + "sodium-native": "^5.1.0", "vite": "^8.0.13", "ws": "^8.20.1", "zod": "^4.4.3" @@ -53,14 +50,13 @@ "@biomejs/biome": "latest", "@types/better-sqlite3": "^7.6.13", "@types/express": "^5.0.6", - "@types/fluent-ffmpeg": "^2.1.28", - "@types/node": "^24.10.1", + "@types/node": "^25.8.0", "@types/react": "^19.2.14", "@types/react-dom": "^19.2.3", "@types/ws": "^8.18.1", "drizzle-kit": "^0.31.10", - "pino-pretty": "^10.3.1", - "tsx": "^4.20.6", + "pino-pretty": "^13.1.3", + "tsx": "^4.22.0", "vitest": "latest" } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cdf8b12..9b8f9cd 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -21,17 +21,11 @@ importers: specifier: ^8.20.0 version: 8.20.0 '@vitejs/plugin-react': - specifier: ^6.0.1 - version: 6.0.1(vite@8.0.13(@types/node@24.12.4)(esbuild@0.27.7)(tsx@4.21.0)) + specifier: ^6.0.2 + version: 6.0.2(vite@8.0.13(@types/node@25.8.0)(esbuild@0.28.0)(tsx@4.22.0)) better-sqlite3: specifier: ^12.10.0 version: 12.10.0 - class-transformer: - specifier: ^0.5.1 - version: 0.5.1 - class-validator: - specifier: ^0.15.1 - version: 0.15.1 discord.js-selfbot-v13: specifier: ^3.7.1 version: 3.7.1(@discordjs/opus@0.10.0) @@ -44,9 +38,6 @@ importers: express: specifier: ^5.2.1 version: 5.2.1 - fluent-ffmpeg: - specifier: ^2.1.3 - version: 2.1.3 helmet: specifier: ^8.1.0 version: 8.1.0 @@ -54,14 +45,14 @@ importers: specifier: ^0.8.2 version: 0.8.4 p-retry: - specifier: ^6.2.0 - version: 6.2.1 + specifier: ^8.0.0 + version: 8.0.0 pg: specifier: ^8.20.0 version: 8.20.0 pino: - specifier: ^9.4.0 - version: 9.14.0 + specifier: ^10.3.1 + version: 10.3.1 pino-http: specifier: ^11.0.0 version: 11.0.0 @@ -78,11 +69,11 @@ importers: specifier: ^19.2.6 version: 19.2.6(react@19.2.6) sodium-native: - specifier: ^4.3.2 - version: 4.3.3 + specifier: ^5.1.0 + version: 5.1.0(bare-events@2.8.3) vite: specifier: ^8.0.13 - version: 8.0.13(@types/node@24.12.4)(esbuild@0.27.7)(tsx@4.21.0) + version: 8.0.13(@types/node@25.8.0)(esbuild@0.28.0)(tsx@4.22.0) ws: specifier: ^8.20.1 version: 8.20.1 @@ -99,12 +90,9 @@ importers: '@types/express': specifier: ^5.0.6 version: 5.0.6 - '@types/fluent-ffmpeg': - specifier: ^2.1.28 - version: 2.1.28 '@types/node': - specifier: ^24.10.1 - version: 24.12.4 + specifier: ^25.8.0 + version: 25.8.0 '@types/react': specifier: ^19.2.14 version: 19.2.14 @@ -118,14 +106,14 @@ importers: specifier: ^0.31.10 version: 0.31.10 pino-pretty: - specifier: ^10.3.1 - version: 10.3.1 + specifier: ^13.1.3 + version: 13.1.3 tsx: - specifier: ^4.20.6 - version: 4.21.0 + specifier: ^4.22.0 + version: 4.22.0 vitest: specifier: latest - version: 4.1.6(@opentelemetry/api@1.9.1)(@types/node@24.12.4)(vite@8.0.13(@types/node@24.12.4)(esbuild@0.27.7)(tsx@4.21.0)) + version: 4.1.6(@opentelemetry/api@1.9.1)(@types/node@25.8.0)(vite@8.0.13(@types/node@25.8.0)(esbuild@0.28.0)(tsx@4.22.0)) packages: @@ -236,8 +224,8 @@ packages: cpu: [ppc64] os: [aix] - '@esbuild/aix-ppc64@0.27.7': - resolution: {integrity: sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==} + '@esbuild/aix-ppc64@0.28.0': + resolution: {integrity: sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] @@ -254,8 +242,8 @@ packages: cpu: [arm64] os: [android] - '@esbuild/android-arm64@0.27.7': - resolution: {integrity: sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==} + '@esbuild/android-arm64@0.28.0': + resolution: {integrity: sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw==} engines: {node: '>=18'} cpu: [arm64] os: [android] @@ -272,8 +260,8 @@ packages: cpu: [arm] os: [android] - '@esbuild/android-arm@0.27.7': - resolution: {integrity: sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==} + '@esbuild/android-arm@0.28.0': + resolution: {integrity: sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ==} engines: {node: '>=18'} cpu: [arm] os: [android] @@ -290,8 +278,8 @@ packages: cpu: [x64] os: [android] - '@esbuild/android-x64@0.27.7': - resolution: {integrity: sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==} + '@esbuild/android-x64@0.28.0': + resolution: {integrity: sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA==} engines: {node: '>=18'} cpu: [x64] os: [android] @@ -308,8 +296,8 @@ packages: cpu: [arm64] os: [darwin] - '@esbuild/darwin-arm64@0.27.7': - resolution: {integrity: sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==} + '@esbuild/darwin-arm64@0.28.0': + resolution: {integrity: sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] @@ -326,8 +314,8 @@ packages: cpu: [x64] os: [darwin] - '@esbuild/darwin-x64@0.27.7': - resolution: {integrity: sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==} + '@esbuild/darwin-x64@0.28.0': + resolution: {integrity: sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ==} engines: {node: '>=18'} cpu: [x64] os: [darwin] @@ -344,8 +332,8 @@ packages: cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-arm64@0.27.7': - resolution: {integrity: sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==} + '@esbuild/freebsd-arm64@0.28.0': + resolution: {integrity: sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] @@ -362,8 +350,8 @@ packages: cpu: [x64] os: [freebsd] - '@esbuild/freebsd-x64@0.27.7': - resolution: {integrity: sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==} + '@esbuild/freebsd-x64@0.28.0': + resolution: {integrity: sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] @@ -380,8 +368,8 @@ packages: cpu: [arm64] os: [linux] - '@esbuild/linux-arm64@0.27.7': - resolution: {integrity: sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==} + '@esbuild/linux-arm64@0.28.0': + resolution: {integrity: sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A==} engines: {node: '>=18'} cpu: [arm64] os: [linux] @@ -398,8 +386,8 @@ packages: cpu: [arm] os: [linux] - '@esbuild/linux-arm@0.27.7': - resolution: {integrity: sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==} + '@esbuild/linux-arm@0.28.0': + resolution: {integrity: sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw==} engines: {node: '>=18'} cpu: [arm] os: [linux] @@ -416,8 +404,8 @@ packages: cpu: [ia32] os: [linux] - '@esbuild/linux-ia32@0.27.7': - resolution: {integrity: sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==} + '@esbuild/linux-ia32@0.28.0': + resolution: {integrity: sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ==} engines: {node: '>=18'} cpu: [ia32] os: [linux] @@ -434,8 +422,8 @@ packages: cpu: [loong64] os: [linux] - '@esbuild/linux-loong64@0.27.7': - resolution: {integrity: sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==} + '@esbuild/linux-loong64@0.28.0': + resolution: {integrity: sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg==} engines: {node: '>=18'} cpu: [loong64] os: [linux] @@ -452,8 +440,8 @@ packages: cpu: [mips64el] os: [linux] - '@esbuild/linux-mips64el@0.27.7': - resolution: {integrity: sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==} + '@esbuild/linux-mips64el@0.28.0': + resolution: {integrity: sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] @@ -470,8 +458,8 @@ packages: cpu: [ppc64] os: [linux] - '@esbuild/linux-ppc64@0.27.7': - resolution: {integrity: sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==} + '@esbuild/linux-ppc64@0.28.0': + resolution: {integrity: sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] @@ -488,8 +476,8 @@ packages: cpu: [riscv64] os: [linux] - '@esbuild/linux-riscv64@0.27.7': - resolution: {integrity: sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==} + '@esbuild/linux-riscv64@0.28.0': + resolution: {integrity: sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] @@ -506,8 +494,8 @@ packages: cpu: [s390x] os: [linux] - '@esbuild/linux-s390x@0.27.7': - resolution: {integrity: sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==} + '@esbuild/linux-s390x@0.28.0': + resolution: {integrity: sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q==} engines: {node: '>=18'} cpu: [s390x] os: [linux] @@ -524,8 +512,8 @@ packages: cpu: [x64] os: [linux] - '@esbuild/linux-x64@0.27.7': - resolution: {integrity: sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==} + '@esbuild/linux-x64@0.28.0': + resolution: {integrity: sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ==} engines: {node: '>=18'} cpu: [x64] os: [linux] @@ -536,8 +524,8 @@ packages: cpu: [arm64] os: [netbsd] - '@esbuild/netbsd-arm64@0.27.7': - resolution: {integrity: sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==} + '@esbuild/netbsd-arm64@0.28.0': + resolution: {integrity: sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw==} engines: {node: '>=18'} cpu: [arm64] os: [netbsd] @@ -554,8 +542,8 @@ packages: cpu: [x64] os: [netbsd] - '@esbuild/netbsd-x64@0.27.7': - resolution: {integrity: sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==} + '@esbuild/netbsd-x64@0.28.0': + resolution: {integrity: sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] @@ -566,8 +554,8 @@ packages: cpu: [arm64] os: [openbsd] - '@esbuild/openbsd-arm64@0.27.7': - resolution: {integrity: sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==} + '@esbuild/openbsd-arm64@0.28.0': + resolution: {integrity: sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] @@ -584,8 +572,8 @@ packages: cpu: [x64] os: [openbsd] - '@esbuild/openbsd-x64@0.27.7': - resolution: {integrity: sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==} + '@esbuild/openbsd-x64@0.28.0': + resolution: {integrity: sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] @@ -596,8 +584,8 @@ packages: cpu: [arm64] os: [openharmony] - '@esbuild/openharmony-arm64@0.27.7': - resolution: {integrity: sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==} + '@esbuild/openharmony-arm64@0.28.0': + resolution: {integrity: sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w==} engines: {node: '>=18'} cpu: [arm64] os: [openharmony] @@ -614,8 +602,8 @@ packages: cpu: [x64] os: [sunos] - '@esbuild/sunos-x64@0.27.7': - resolution: {integrity: sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==} + '@esbuild/sunos-x64@0.28.0': + resolution: {integrity: sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw==} engines: {node: '>=18'} cpu: [x64] os: [sunos] @@ -632,8 +620,8 @@ packages: cpu: [arm64] os: [win32] - '@esbuild/win32-arm64@0.27.7': - resolution: {integrity: sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==} + '@esbuild/win32-arm64@0.28.0': + resolution: {integrity: sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA==} engines: {node: '>=18'} cpu: [arm64] os: [win32] @@ -650,8 +638,8 @@ packages: cpu: [ia32] os: [win32] - '@esbuild/win32-ia32@0.27.7': - resolution: {integrity: sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==} + '@esbuild/win32-ia32@0.28.0': + resolution: {integrity: sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA==} engines: {node: '>=18'} cpu: [ia32] os: [win32] @@ -668,8 +656,8 @@ packages: cpu: [x64] os: [win32] - '@esbuild/win32-x64@0.27.7': - resolution: {integrity: sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==} + '@esbuild/win32-x64@0.28.0': + resolution: {integrity: sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw==} engines: {node: '>=18'} cpu: [x64] os: [win32] @@ -806,9 +794,6 @@ packages: '@rolldown/pluginutils@1.0.0': resolution: {integrity: sha512-aKs/3GSWyV0mrhNmt/96/Z3yczC3yvrzYATCiCXQebBsGyYzjNdUphRVLeJQ67ySKVXRfMxt2lm12pmXvbPFQQ==} - '@rolldown/pluginutils@1.0.0-rc.7': - resolution: {integrity: sha512-qujRfC8sFVInYSPPMLQByRh7zhwkGFS4+tyMQ83srV1qrxL4g8E2tyxVVyxd0+8QeBM1mIk9KbWxkegRr76XzA==} - '@sapphire/async-queue@1.5.5': resolution: {integrity: sha512-cvGzxbba6sav2zZkH8GPf2oGk9yYoD5qrNWdu9fRehifgnFZJMV+nuy2nON2roRO4yQQ+v7MK/Pktl/HgfsUXg==} engines: {node: '>=v14.0.0', npm: '>=7.0.0'} @@ -937,15 +922,15 @@ packages: '@types/express@5.0.6': resolution: {integrity: sha512-sKYVuV7Sv9fbPIt/442koC7+IIwK5olP1KWeD88e/idgoJqDm3JV/YUiPwkoKK92ylff2MGxSz1CSjsXelx0YA==} - '@types/fluent-ffmpeg@2.1.28': - resolution: {integrity: sha512-5ovxsDwBcPfJ+eYs1I/ZpcYCnkce7pvH9AHSvrZllAp1ZPpTRDZAFjF3TRFbukxSgIYTTNYePbS0rKUmaxVbXw==} - '@types/http-errors@2.0.5': resolution: {integrity: sha512-r8Tayk8HJnX0FztbZN7oVqGccWgw98T/0neJphO91KkmOzug1KkofZURD4UaD5uH8AqcFLfdPErnBod0u71/qg==} '@types/node@24.12.4': resolution: {integrity: sha512-GUUEShf+PBCGW2KaXwcIt3Yk+e3pkKwWKb9GSyM9WQVE+ep2jzmHdGsHzu4wgcZy5fN9FBdVzjpBQsYlpfpgLA==} + '@types/node@25.8.0': + resolution: {integrity: sha512-TCFSk8IZh+iLX1xtksoBVtdmgL+1IX0fC9BeU4QqFSuNdN/K+HUlhqOzEmSYYpZUVsLYcPqc9KX+60iDuninSQ==} + '@types/pg@8.20.0': resolution: {integrity: sha512-bEPFOaMAHTEP1EzpvHTbmwR8UsFyHSKsRisLIHVMXnpNefSbGA1bD6CVy+qKjGSqmZqNqBDV2azOBo8TgkcVow==} @@ -963,23 +948,17 @@ packages: '@types/react@19.2.14': resolution: {integrity: sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==} - '@types/retry@0.12.2': - resolution: {integrity: sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow==} - '@types/send@1.2.1': resolution: {integrity: sha512-arsCikDvlU99zl1g69TcAB3mzZPpxgw0UQnaHeC1Nwb015xp8bknZv5rIfri9xTOcMuaVgvabfIRA7PSZVuZIQ==} '@types/serve-static@2.2.0': resolution: {integrity: sha512-8mam4H1NHLtu7nmtalF7eyBH14QyOASmcxHhSfEoRyr0nP/YdoesEtU+uSRvMe96TW/HPTtkoKqQLl53N7UXMQ==} - '@types/validator@13.15.10': - resolution: {integrity: sha512-T8L6i7wCuyoK8A/ZeLYt1+q0ty3Zb9+qbSSvrIVitzT3YjZqkTZ40IbRsPanlB4h1QB3JVL1SYCdR6ngtFYcuA==} - '@types/ws@8.18.1': resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==} - '@vitejs/plugin-react@6.0.1': - resolution: {integrity: sha512-l9X/E3cDb+xY3SWzlG1MOGt2usfEHGMNIaegaUGFsLkb3RCn/k8/TOXBcab+OndDI4TBtktT8/9BwwW8Vi9KUQ==} + '@vitejs/plugin-react@6.0.2': + resolution: {integrity: sha512-DlSMqo4WhThw4vB8Mpn0Woe9J+Jfq1geJ61AKW0QEgLzGMNwtIMdxbDUzLxcun8W7NbJO0e2Jg/Nxm3cCSVzzg==} engines: {node: ^20.19.0 || >=22.12.0} peerDependencies: '@rolldown/plugin-babel': ^0.1.7 || ^0.2.0 @@ -1023,10 +1002,6 @@ packages: abbrev@1.1.1: resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==} - abort-controller@3.0.0: - resolution: {integrity: sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==} - engines: {node: '>=6.5'} - accepts@2.0.0: resolution: {integrity: sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==} engines: {node: '>= 0.6'} @@ -1058,13 +1033,18 @@ packages: resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==} engines: {node: '>=12'} - async@0.2.10: - resolution: {integrity: sha512-eAkdoKxU6/LkKDBzLpT+t6Ff5EtfSF4wx1WfJiPEEV7WNLnDaRXk0oVysiEPm262roaachGexwUv94WhSgN5TQ==} - atomic-sleep@1.0.0: resolution: {integrity: sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==} engines: {node: '>=8.0.0'} + b4a@1.8.1: + resolution: {integrity: sha512-aiqre1Nr0B/6DgE2N5vwTc+2/oQZ4Wh1t4NznYY4E00y8LCt6NqdRv81so00oo27D8MVKTpUa/MwUUtBLXCoDw==} + peerDependencies: + react-native-b4a: '*' + peerDependenciesMeta: + react-native-b4a: + optional: true + balanced-match@1.0.2: resolution: {integrity: sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==} @@ -1076,6 +1056,29 @@ packages: bare-url: optional: true + bare-ansi-escapes@2.2.3: + resolution: {integrity: sha512-02ES4/E2RbrtZSnHJ9LntBhYkLA6lPpSEeP8iqS3MccBIVhVBlEmruF1I7HZqx5Q8aiTeYfQVeqmrU9YO2yYoQ==} + peerDependencies: + bare-buffer: '*' + peerDependenciesMeta: + bare-buffer: + optional: true + + bare-assert@1.2.0: + resolution: {integrity: sha512-c6uvgvTJBspTDxtVnPgrBKmLgcpW3Fp72NVKDLg6oT4QjQbhGtvrkHMhGYMK1sh4vjBHOBmuUalyt9hSzV37fQ==} + + bare-events@2.8.3: + resolution: {integrity: sha512-HdUm8EMQBLaJvGUdidNNbqpA1kYkwNcb+MYxkxCLAPJGQzlv9J0C24h8V65Z4c5GLd/JEALDvpFCQgpLJqc0zw==} + peerDependencies: + bare-abort-controller: '*' + peerDependenciesMeta: + bare-abort-controller: + optional: true + + bare-inspect@3.1.4: + resolution: {integrity: sha512-jfW5KRA84o3REpI6Vr4nbvMn+hqVAw8GU1mMdRwUsY5yJovQamxYeKGVKGqdzs+8ZbG4jRzGUXP/3Ji/DnqfPg==} + engines: {bare: '>=1.18.0'} + bare-module-resolve@1.12.2: resolution: {integrity: sha512-j+hiD5k99qec4KjJvYsI67q5AOBifmy9JG3oeMVxTmvrhn2sIdp8StrUvZu4YNgwTpO+NhniQG16N1ETDe1k5w==} peerDependencies: @@ -1087,6 +1090,24 @@ packages: bare-semver@1.0.3: resolution: {integrity: sha512-HS/A30bi2+PiRJfU6R4+Kp+6KeLSCSByjYM2iiobOKzLAvtu1CT+S8xWfiU7wz0erknjkUoC+yXy108tzIuP5Q==} + bare-stream@2.13.1: + resolution: {integrity: sha512-Vp0cnjYyrEC4whYTymQ+YZi6pBpfiICZO3cfRG8sy67ZNWe951urv1x4eW1BKNngw3U+3fPYb5JQvHbCtxH7Ow==} + peerDependencies: + bare-abort-controller: '*' + bare-buffer: '*' + bare-events: '*' + peerDependenciesMeta: + bare-abort-controller: + optional: true + bare-buffer: + optional: true + bare-events: + optional: true + + bare-type@1.1.0: + resolution: {integrity: sha512-LdtnnEEYldOc87Dr4GpsKnStStZk3zfgoEMXy8yvEZkXrcCv9RtYDrUYWFsBQHtaB0s1EUWmcvS6XmEZYIj3Bw==} + engines: {bare: '>=1.2.0'} + base64-js@1.5.1: resolution: {integrity: sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==} @@ -1150,12 +1171,6 @@ packages: resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} engines: {node: '>=10'} - class-transformer@0.5.1: - resolution: {integrity: sha512-SQa1Ws6hUbfC98vKGxZH3KFY0Y1lm5Zm0SY8XX9zbK7FJCyVEac3ATW0RIpwzW+oOfmHE5PMPufDG9hCfoEOMw==} - - class-validator@0.15.1: - resolution: {integrity: sha512-LqoS80HBBSCVhz/3KloUly0ovokxpdOLR++Al3J3+dHXWt9sTKlKd4eYtoxhxyUjoe5+UcIM+5k9MIxyBWnRTw==} - cliui@6.0.0: resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==} @@ -1400,8 +1415,8 @@ packages: engines: {node: '>=18'} hasBin: true - esbuild@0.27.7: - resolution: {integrity: sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==} + esbuild@0.28.0: + resolution: {integrity: sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw==} engines: {node: '>=18'} hasBin: true @@ -1415,13 +1430,8 @@ packages: resolution: {integrity: sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==} engines: {node: '>= 0.6'} - event-target-shim@5.0.1: - resolution: {integrity: sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==} - engines: {node: '>=6'} - - events@3.3.0: - resolution: {integrity: sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==} - engines: {node: '>=0.8.x'} + events-universal@1.0.1: + resolution: {integrity: sha512-LUd5euvbMLpwOF8m6ivPCbhQeSiYVNb8Vs0fQ8QjXo0JTkEHpz8pxdQf0gStltaPpw0Cca8b39KxvK9cfKRiAw==} expand-template@2.0.3: resolution: {integrity: sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==} @@ -1435,12 +1445,15 @@ packages: resolution: {integrity: sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==} engines: {node: '>= 18'} - fast-copy@3.0.2: - resolution: {integrity: sha512-dl0O9Vhju8IrcLndv2eU4ldt1ftXMqqfgN4H1cpmGV7P6jeB9FwpN9a2c8DPGE1Ys88rNUJVYDHq73CGAGOPfQ==} + fast-copy@4.0.3: + resolution: {integrity: sha512-58apWr0GUiDFM8+3afrO6eYwJBn9ZAhDOzG3L+/9llab/haCARS2UIfffmOurYLwbgDRs8n0rfr6qAAPEAuAQw==} fast-deep-equal@3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} + fast-fifo@1.3.2: + resolution: {integrity: sha512-/d9sfos4yxzpwkDkuN7k2SqFKtYNmCTzgfEpz82x34IM9/zc8KGxQoXg1liNC/izpRM/MBdt44Nmx41ZWqk+FQ==} + fast-safe-stringify@2.1.1: resolution: {integrity: sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==} @@ -1471,11 +1484,6 @@ packages: resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==} engines: {node: '>=8'} - fluent-ffmpeg@2.1.3: - resolution: {integrity: sha512-Be3narBNt2s6bsaqP6Jzq91heDgOEaDCJAXcE3qcma/EJBSy5FB4cvO31XBInuAuKBx8Kptf8dkhjK0IOru39Q==} - engines: {node: '>=18'} - deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. - forwarded@0.2.0: resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} engines: {node: '>= 0.6'} @@ -1595,16 +1603,10 @@ packages: is-promise@4.0.0: resolution: {integrity: sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==} - isexe@2.0.0: - resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} - joycon@3.1.1: resolution: {integrity: sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==} engines: {node: '>=10'} - libphonenumber-js@1.13.1: - resolution: {integrity: sha512-GEw0GLL7YUUA6nv21IsCvVjtI5Ejn84sjbdfQ9KxdbqEVOk1PZh7xejn01EEiniKw+dBeCfim+8MGeuvVuE2BA==} - libsodium-wrappers@0.8.4: resolution: {integrity: sha512-mu8aAWucZjTB5O/BtGXtW4e1agy7uHxNYG7zPthmmD1jU43LCDmSWZLN4JhflbdPXj3yDO4lxM1O9hLDgIOXDw==} @@ -1826,9 +1828,9 @@ packages: resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==} engines: {node: '>=8'} - p-retry@6.2.1: - resolution: {integrity: sha512-hEt02O4hUct5wtwg4H4KcWgDdm+l1bOaEy/hWzd8xtXB9BqxTWBBhb+2ImAtH4Cv4rPjV76xN3Zumqk3k3AhhQ==} - engines: {node: '>=16.17'} + p-retry@8.0.0: + resolution: {integrity: sha512-kFVqH1HxOHp8LupNsOys7bSV09VYTRLxarH/mokO4Rqhk6wGi70E0jh4VzvVGXfEVNggHoHLAMWsQqHyU1Ey9A==} + engines: {node: '>=22'} p-try@2.2.0: resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} @@ -1893,20 +1895,14 @@ packages: resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} engines: {node: '>=12'} - pino-abstract-transport@1.2.0: - resolution: {integrity: sha512-Guhh8EZfPCfH+PMXAb6rKOjGQEoy0xlAIn+irODG5kgfYV+BQ0rGYYWTIel3P5mmyXqkYkPmdIkywsn6QKUR1Q==} - - pino-abstract-transport@2.0.0: - resolution: {integrity: sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==} - pino-abstract-transport@3.0.0: resolution: {integrity: sha512-wlfUczU+n7Hy/Ha5j9a/gZNy7We5+cXp8YL+X+PG8S0KXxw7n/JXA3c46Y0zQznIJ83URJiwy7Lh56WLokNuxg==} pino-http@11.0.0: resolution: {integrity: sha512-wqg5XIAGRRIWtTk8qPGxkbrfiwEWz1lgedVLvhLALudKXvg1/L2lTFgTGPJ4Z2e3qcRmxoFxDuSdMdMGNM6I1g==} - pino-pretty@10.3.1: - resolution: {integrity: sha512-az8JbIYeN/1iLj2t0jR9DV48/LQ3RC6hZPpapKPkb84Q+yTidMCpgWxIT3N0flnBDilyBQ1luWNpOeJptjdp/g==} + pino-pretty@13.1.3: + resolution: {integrity: sha512-ttXRkkOz6WWC95KeY9+xxWL6AtImwbyMHrL1mSwqwW9u+vLp/WIElvHvCSDg0xO/Dzrggz1zv3rN5ovTRVowKg==} hasBin: true pino-std-serializers@7.1.0: @@ -1916,10 +1912,6 @@ packages: resolution: {integrity: sha512-r34yH/GlQpKZbU1BvFFqOjhISRo1MNx1tWYsYvmj6KIRHSPMT2+yHOEb1SG6NMvRoHRF0a07kCOox/9yakl1vg==} hasBin: true - pino@9.14.0: - resolution: {integrity: sha512-8OEwKp5juEvb/MjpIc4hjqfgCNysrS94RIOMXYvpYCdm/jglrKEiAYmiumbmGhCvs+IcInsphYDFwqrjr7398w==} - hasBin: true - pngjs@5.0.0: resolution: {integrity: sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==} engines: {node: '>=10.13.0'} @@ -1973,10 +1965,6 @@ packages: process-warning@5.0.0: resolution: {integrity: sha512-a39t9ApHNx2L4+HBnQKqxxHNs1r7KF+Intd8Q/g1bUh6q0WIp9voPXJ/x0j+ZL45KF1pJd9+q2jLIRMfvEshkA==} - process@0.11.10: - resolution: {integrity: sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==} - engines: {node: '>= 0.6.0'} - prom-client@15.1.3: resolution: {integrity: sha512-6ZiOBfCywsD4k1BN9IX0uZhF+tJkV8q8llP64G5Hajs4JOeVLPCwpPVcpXy3BwYiUGgyJzsJJQeOIv7+hDSq8g==} engines: {node: ^16 || ^18 || >=20} @@ -2025,10 +2013,6 @@ packages: resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} engines: {node: '>= 6'} - readable-stream@4.7.0: - resolution: {integrity: sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==} - engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - real-require@0.2.0: resolution: {integrity: sha512-57frrGM/OCTLqLOAh0mhVA9VBMHd+9U7Zb2THMGdBUoZVOtGbJzjxsYGDJ3A9AYYCP4hn6y1TVbaOfzWtm5GFg==} engines: {node: '>= 12.13.0'} @@ -2050,10 +2034,6 @@ packages: resolve-pkg-maps@1.0.0: resolution: {integrity: sha512-seS2Tj26TBVOC2NIc2rOe2y2ZO7efxITtLZcGSOnHHNOQ7CkiUBfw0Iw2ck6xkIhPwLhKNLS8BO+hEpngQlqzw==} - retry@0.13.1: - resolution: {integrity: sha512-XQBQ3I8W1Cge0Seh+6gjj03LbmRFWuoszgK9ooCpwYIrhhoO80pfq4cUkU5DkknwfOfFteRwlZ56PYOGYyFWdg==} - engines: {node: '>= 4'} - rimraf@3.0.2: resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} deprecated: Rimraf versions prior to v4 are no longer supported @@ -2081,8 +2061,8 @@ packages: scheduler@0.27.0: resolution: {integrity: sha512-eNv+WrVbKu1f3vbYJT/xtiF5syA5HPIMtf9IgY/nKg0sWqzAUEvqY/xm7OcZc/qafLx/iO9FgOmeSAp4v5ti/Q==} - secure-json-parse@2.7.0: - resolution: {integrity: sha512-6aU+Rwsezw7VR8/nyvKTx8QpWH9FrcYiXXlqC4z5d5XQBDRqtbfsRjnwGyqbi3gddNtWHuEk9OANUotL26qKUw==} + secure-json-parse@4.1.0: + resolution: {integrity: sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA==} semver@6.3.1: resolution: {integrity: sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==} @@ -2138,11 +2118,9 @@ packages: simple-get@4.0.1: resolution: {integrity: sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==} - sodium-native@4.3.3: - resolution: {integrity: sha512-OnxSlN3uyY8D0EsLHpmm2HOFmKddQVvEMmsakCrXUzSd8kjjbzL413t4ZNF3n0UxSwNgwTyUvkmZHTfuCeiYSw==} - - sonic-boom@3.8.1: - resolution: {integrity: sha512-y4Z8LCDBuum+PBP3lSV7RHrXscqksve/bi0as7mhwVnBW+/wUqKT/2Kb7um8yqcFy0duYbbPxzt89Zy2nOCaxg==} + sodium-native@5.1.0: + resolution: {integrity: sha512-3RxgyWyJlhTsABPnJVpCI5CoTDANZTqqFrEPqr+kjfnRaBihpVtMUE3yTF40ukdoB1APXeoBNKF3MzZAIHg39g==} + engines: {bare: '>=1.16.0'} sonic-boom@4.2.1: resolution: {integrity: sha512-w6AxtubXa2wTXAUsZMMWERrsIRAdrK0Sc+FUytWvYAhBJLyuI4llrMIC1DtlNSdI99EI86KZum2MMq3EAZlF9Q==} @@ -2172,6 +2150,9 @@ packages: std-env@4.1.0: resolution: {integrity: sha512-Rq7ybcX2RuC55r9oaPVEW7/xu3tj8u4GeBYHBWCychFtzMIr86A7e3PPEBPT37sHStKX3+TiX/Fr/ACmJLVlLQ==} + streamx@2.25.0: + resolution: {integrity: sha512-0nQuG6jf1w+wddNEEXCF4nTg3LtufWINB5eFEN+5TNZW7KWJp6x87+JFL43vaAUPyCfH1wID+mNVyW6OHtFamg==} + string-width@4.2.3: resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==} engines: {node: '>=8'} @@ -2187,9 +2168,9 @@ packages: resolution: {integrity: sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==} engines: {node: '>=0.10.0'} - strip-json-comments@3.1.1: - resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} - engines: {node: '>=8'} + strip-json-comments@5.0.3: + resolution: {integrity: sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw==} + engines: {node: '>=14.16'} supports-color@7.2.0: resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==} @@ -2210,13 +2191,16 @@ packages: tdigest@0.1.2: resolution: {integrity: sha512-+G0LLgjjo9BZX2MfdvPfH+MKLCrxlXSYec5DaPYP1fe6Iyhf0/fSmJ0bFiZ1F8BT6cGXl2LpltQptzjXKWEkKA==} + teex@1.0.1: + resolution: {integrity: sha512-eYE6iEI62Ni1H8oIa7KlDU6uQBtqr4Eajni3wX7rpfXD8ysFx8z0+dri+KWEPWpBsxXfxu58x/0jvTVT1ekOSg==} + + text-decoder@1.2.7: + resolution: {integrity: sha512-vlLytXkeP4xvEq2otHeJfSQIRyWxo/oZGEbXrtEEF9Hnmrdly59sUbzZ/QgyWuLYHctCHxFF4tRQZNQ9k60ExQ==} + thirty-two@1.0.2: resolution: {integrity: sha512-OEI0IWCe+Dw46019YLl6V10Us5bi574EvlJEOcAkB29IzQ/mYD1A6RyNHLjZPiHCmuodxvgF6U+vZO1L15lxVA==} engines: {node: '>=0.2.6'} - thread-stream@3.1.0: - resolution: {integrity: sha512-OqyPZ9u96VohAyMfJykzmivOrY2wfMSf3C5TtFJVgN+Hm6aj+voFhlK+kZEIv2FBh1X6Xp3DlnCOfEQ3B2J86A==} - thread-stream@4.1.0: resolution: {integrity: sha512-Bw6h2iBDt16v6iHLChBIoVYU8CBo9GPsW8TG7h1hRVhqKhIkH6N8qkxNSmiOZTKsCLPbtWG4ViWLkU6KeKXpig==} engines: {node: '>=20'} @@ -2275,8 +2259,8 @@ packages: tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} - tsx@4.21.0: - resolution: {integrity: sha512-5C1sg4USs1lfG0GFb2RLXsdpXqBSEhAaA/0kPL01wxzpMqLILNxIxIOKiILz+cdg/pLnOUxFYOR5yhHU666wbw==} + tsx@4.22.0: + resolution: {integrity: sha512-8ccZMPD69s1AbKXx0C5ddTNZfNjwV04iIKgjZmKfKxMynEtSYcK0Lh7iQFh53fI5Yu4pb9usgAiqyPmEONaALg==} engines: {node: '>=18.0.0'} hasBin: true @@ -2290,6 +2274,9 @@ packages: undici-types@7.16.0: resolution: {integrity: sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw==} + undici-types@7.24.6: + resolution: {integrity: sha512-WRNW+sJgj5OBN4/0JpHFqtqzhpbnV0GuB+OozA9gCL7a993SmU+1JBZCzLNxYsbMfIeDL+lTsphD5jN5N+n0zg==} + undici@7.25.0: resolution: {integrity: sha512-xXnp4kTyor2Zq+J1FfPI6Eq3ew5h6Vl0F/8d9XU5zZQf1tX9s2Su1/3PiMmUANFULpmksxkClamIZcaUqryHsQ==} engines: {node: '>=20.18.1'} @@ -2301,10 +2288,6 @@ packages: util-deprecate@1.0.2: resolution: {integrity: sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==} - validator@13.15.35: - resolution: {integrity: sha512-TQ5pAGhd5whStmqWvYF4OjQROlmv9SMFVt37qoCBdqRffuuklWYQlCNnEs2ZaIBD1kZRNnikiZOS1eqgkar0iw==} - engines: {node: '>= 0.10'} - vary@1.1.2: resolution: {integrity: sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==} engines: {node: '>= 0.8'} @@ -2406,9 +2389,8 @@ packages: which-module@2.0.1: resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==} - which@1.3.1: - resolution: {integrity: sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==} - hasBin: true + which-runtime@1.3.2: + resolution: {integrity: sha512-5kwCfWml7+b2NO7KrLMhYihjRx0teKkd3yGp1Xk5Vaf2JGdSh+rgVhEALAD9c/59dP+YwJHXoEO7e8QPy7gOkw==} why-is-node-running@2.3.0: resolution: {integrity: sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==} @@ -2587,7 +2569,7 @@ snapshots: '@esbuild/aix-ppc64@0.25.12': optional: true - '@esbuild/aix-ppc64@0.27.7': + '@esbuild/aix-ppc64@0.28.0': optional: true '@esbuild/android-arm64@0.18.20': @@ -2596,7 +2578,7 @@ snapshots: '@esbuild/android-arm64@0.25.12': optional: true - '@esbuild/android-arm64@0.27.7': + '@esbuild/android-arm64@0.28.0': optional: true '@esbuild/android-arm@0.18.20': @@ -2605,7 +2587,7 @@ snapshots: '@esbuild/android-arm@0.25.12': optional: true - '@esbuild/android-arm@0.27.7': + '@esbuild/android-arm@0.28.0': optional: true '@esbuild/android-x64@0.18.20': @@ -2614,7 +2596,7 @@ snapshots: '@esbuild/android-x64@0.25.12': optional: true - '@esbuild/android-x64@0.27.7': + '@esbuild/android-x64@0.28.0': optional: true '@esbuild/darwin-arm64@0.18.20': @@ -2623,7 +2605,7 @@ snapshots: '@esbuild/darwin-arm64@0.25.12': optional: true - '@esbuild/darwin-arm64@0.27.7': + '@esbuild/darwin-arm64@0.28.0': optional: true '@esbuild/darwin-x64@0.18.20': @@ -2632,7 +2614,7 @@ snapshots: '@esbuild/darwin-x64@0.25.12': optional: true - '@esbuild/darwin-x64@0.27.7': + '@esbuild/darwin-x64@0.28.0': optional: true '@esbuild/freebsd-arm64@0.18.20': @@ -2641,7 +2623,7 @@ snapshots: '@esbuild/freebsd-arm64@0.25.12': optional: true - '@esbuild/freebsd-arm64@0.27.7': + '@esbuild/freebsd-arm64@0.28.0': optional: true '@esbuild/freebsd-x64@0.18.20': @@ -2650,7 +2632,7 @@ snapshots: '@esbuild/freebsd-x64@0.25.12': optional: true - '@esbuild/freebsd-x64@0.27.7': + '@esbuild/freebsd-x64@0.28.0': optional: true '@esbuild/linux-arm64@0.18.20': @@ -2659,7 +2641,7 @@ snapshots: '@esbuild/linux-arm64@0.25.12': optional: true - '@esbuild/linux-arm64@0.27.7': + '@esbuild/linux-arm64@0.28.0': optional: true '@esbuild/linux-arm@0.18.20': @@ -2668,7 +2650,7 @@ snapshots: '@esbuild/linux-arm@0.25.12': optional: true - '@esbuild/linux-arm@0.27.7': + '@esbuild/linux-arm@0.28.0': optional: true '@esbuild/linux-ia32@0.18.20': @@ -2677,7 +2659,7 @@ snapshots: '@esbuild/linux-ia32@0.25.12': optional: true - '@esbuild/linux-ia32@0.27.7': + '@esbuild/linux-ia32@0.28.0': optional: true '@esbuild/linux-loong64@0.18.20': @@ -2686,7 +2668,7 @@ snapshots: '@esbuild/linux-loong64@0.25.12': optional: true - '@esbuild/linux-loong64@0.27.7': + '@esbuild/linux-loong64@0.28.0': optional: true '@esbuild/linux-mips64el@0.18.20': @@ -2695,7 +2677,7 @@ snapshots: '@esbuild/linux-mips64el@0.25.12': optional: true - '@esbuild/linux-mips64el@0.27.7': + '@esbuild/linux-mips64el@0.28.0': optional: true '@esbuild/linux-ppc64@0.18.20': @@ -2704,7 +2686,7 @@ snapshots: '@esbuild/linux-ppc64@0.25.12': optional: true - '@esbuild/linux-ppc64@0.27.7': + '@esbuild/linux-ppc64@0.28.0': optional: true '@esbuild/linux-riscv64@0.18.20': @@ -2713,7 +2695,7 @@ snapshots: '@esbuild/linux-riscv64@0.25.12': optional: true - '@esbuild/linux-riscv64@0.27.7': + '@esbuild/linux-riscv64@0.28.0': optional: true '@esbuild/linux-s390x@0.18.20': @@ -2722,7 +2704,7 @@ snapshots: '@esbuild/linux-s390x@0.25.12': optional: true - '@esbuild/linux-s390x@0.27.7': + '@esbuild/linux-s390x@0.28.0': optional: true '@esbuild/linux-x64@0.18.20': @@ -2731,13 +2713,13 @@ snapshots: '@esbuild/linux-x64@0.25.12': optional: true - '@esbuild/linux-x64@0.27.7': + '@esbuild/linux-x64@0.28.0': optional: true '@esbuild/netbsd-arm64@0.25.12': optional: true - '@esbuild/netbsd-arm64@0.27.7': + '@esbuild/netbsd-arm64@0.28.0': optional: true '@esbuild/netbsd-x64@0.18.20': @@ -2746,13 +2728,13 @@ snapshots: '@esbuild/netbsd-x64@0.25.12': optional: true - '@esbuild/netbsd-x64@0.27.7': + '@esbuild/netbsd-x64@0.28.0': optional: true '@esbuild/openbsd-arm64@0.25.12': optional: true - '@esbuild/openbsd-arm64@0.27.7': + '@esbuild/openbsd-arm64@0.28.0': optional: true '@esbuild/openbsd-x64@0.18.20': @@ -2761,13 +2743,13 @@ snapshots: '@esbuild/openbsd-x64@0.25.12': optional: true - '@esbuild/openbsd-x64@0.27.7': + '@esbuild/openbsd-x64@0.28.0': optional: true '@esbuild/openharmony-arm64@0.25.12': optional: true - '@esbuild/openharmony-arm64@0.27.7': + '@esbuild/openharmony-arm64@0.28.0': optional: true '@esbuild/sunos-x64@0.18.20': @@ -2776,7 +2758,7 @@ snapshots: '@esbuild/sunos-x64@0.25.12': optional: true - '@esbuild/sunos-x64@0.27.7': + '@esbuild/sunos-x64@0.28.0': optional: true '@esbuild/win32-arm64@0.18.20': @@ -2785,7 +2767,7 @@ snapshots: '@esbuild/win32-arm64@0.25.12': optional: true - '@esbuild/win32-arm64@0.27.7': + '@esbuild/win32-arm64@0.28.0': optional: true '@esbuild/win32-ia32@0.18.20': @@ -2794,7 +2776,7 @@ snapshots: '@esbuild/win32-ia32@0.25.12': optional: true - '@esbuild/win32-ia32@0.27.7': + '@esbuild/win32-ia32@0.28.0': optional: true '@esbuild/win32-x64@0.18.20': @@ -2803,7 +2785,7 @@ snapshots: '@esbuild/win32-x64@0.25.12': optional: true - '@esbuild/win32-x64@0.27.7': + '@esbuild/win32-x64@0.28.0': optional: true '@jridgewell/sourcemap-codec@1.5.5': {} @@ -2897,8 +2879,6 @@ snapshots: '@rolldown/pluginutils@1.0.0': {} - '@rolldown/pluginutils@1.0.0-rc.7': {} - '@sapphire/async-queue@1.5.5': {} '@sapphire/shapeshift@4.0.0': @@ -3017,16 +2997,16 @@ snapshots: '@types/express-serve-static-core': 5.1.1 '@types/serve-static': 2.2.0 - '@types/fluent-ffmpeg@2.1.28': - dependencies: - '@types/node': 24.12.4 - '@types/http-errors@2.0.5': {} '@types/node@24.12.4': dependencies: undici-types: 7.16.0 + '@types/node@25.8.0': + dependencies: + undici-types: 7.24.6 + '@types/pg@8.20.0': dependencies: '@types/node': 24.12.4 @@ -3045,8 +3025,6 @@ snapshots: dependencies: csstype: 3.2.3 - '@types/retry@0.12.2': {} - '@types/send@1.2.1': dependencies: '@types/node': 24.12.4 @@ -3056,16 +3034,14 @@ snapshots: '@types/http-errors': 2.0.5 '@types/node': 24.12.4 - '@types/validator@13.15.10': {} - '@types/ws@8.18.1': dependencies: '@types/node': 24.12.4 - '@vitejs/plugin-react@6.0.1(vite@8.0.13(@types/node@24.12.4)(esbuild@0.27.7)(tsx@4.21.0))': + '@vitejs/plugin-react@6.0.2(vite@8.0.13(@types/node@25.8.0)(esbuild@0.28.0)(tsx@4.22.0))': dependencies: - '@rolldown/pluginutils': 1.0.0-rc.7 - vite: 8.0.13(@types/node@24.12.4)(esbuild@0.27.7)(tsx@4.21.0) + '@rolldown/pluginutils': 1.0.0 + vite: 8.0.13(@types/node@25.8.0)(esbuild@0.28.0)(tsx@4.22.0) '@vitest/expect@4.1.6': dependencies: @@ -3076,13 +3052,13 @@ snapshots: chai: 6.2.2 tinyrainbow: 3.1.0 - '@vitest/mocker@4.1.6(vite@8.0.13(@types/node@24.12.4)(esbuild@0.27.7)(tsx@4.21.0))': + '@vitest/mocker@4.1.6(vite@8.0.13(@types/node@25.8.0)(esbuild@0.28.0)(tsx@4.22.0))': dependencies: '@vitest/spy': 4.1.6 estree-walker: 3.0.3 magic-string: 0.30.21 optionalDependencies: - vite: 8.0.13(@types/node@24.12.4)(esbuild@0.27.7)(tsx@4.21.0) + vite: 8.0.13(@types/node@25.8.0)(esbuild@0.28.0)(tsx@4.22.0) '@vitest/pretty-format@4.1.6': dependencies: @@ -3110,10 +3086,6 @@ snapshots: abbrev@1.1.1: {} - abort-controller@3.0.0: - dependencies: - event-target-shim: 5.0.1 - accepts@2.0.0: dependencies: mime-types: 3.0.2 @@ -3142,10 +3114,10 @@ snapshots: assertion-error@2.0.1: {} - async@0.2.10: {} - atomic-sleep@1.0.0: {} + b4a@1.8.1: {} + balanced-match@1.0.2: {} bare-addon-resolve@1.10.0: @@ -3153,12 +3125,52 @@ snapshots: bare-module-resolve: 1.12.2 bare-semver: 1.0.3 + bare-ansi-escapes@2.2.3(bare-events@2.8.3): + dependencies: + bare-stream: 2.13.1(bare-events@2.8.3) + transitivePeerDependencies: + - bare-abort-controller + - bare-events + - react-native-b4a + + bare-assert@1.2.0(bare-events@2.8.3): + dependencies: + bare-inspect: 3.1.4(bare-events@2.8.3) + transitivePeerDependencies: + - bare-abort-controller + - bare-buffer + - bare-events + - react-native-b4a + + bare-events@2.8.3: {} + + bare-inspect@3.1.4(bare-events@2.8.3): + dependencies: + bare-ansi-escapes: 2.2.3(bare-events@2.8.3) + bare-type: 1.1.0 + transitivePeerDependencies: + - bare-abort-controller + - bare-buffer + - bare-events + - react-native-b4a + bare-module-resolve@1.12.2: dependencies: bare-semver: 1.0.3 bare-semver@1.0.3: {} + bare-stream@2.13.1(bare-events@2.8.3): + dependencies: + streamx: 2.25.0 + teex: 1.0.1 + optionalDependencies: + bare-events: 2.8.3 + transitivePeerDependencies: + - react-native-b4a + + bare-type@1.1.0: {} + base64-js@1.5.1: {} better-sqlite3@12.10.0: @@ -3234,14 +3246,6 @@ snapshots: chownr@2.0.0: {} - class-transformer@0.5.1: {} - - class-validator@0.15.1: - dependencies: - '@types/validator': 13.15.10 - libphonenumber-js: 1.13.1 - validator: 13.15.35 - cliui@6.0.0: dependencies: string-width: 4.2.3 @@ -3334,7 +3338,7 @@ snapshots: '@drizzle-team/brocli': 0.10.2 '@esbuild-kit/esm-loader': 2.6.5 esbuild: 0.25.12 - tsx: 4.21.0 + tsx: 4.22.0 drizzle-orm@0.45.2(@opentelemetry/api@1.9.1)(@types/better-sqlite3@7.6.13)(@types/pg@8.20.0)(better-sqlite3@12.10.0)(pg@8.20.0): optionalDependencies: @@ -3426,34 +3430,34 @@ snapshots: '@esbuild/win32-ia32': 0.25.12 '@esbuild/win32-x64': 0.25.12 - esbuild@0.27.7: + esbuild@0.28.0: optionalDependencies: - '@esbuild/aix-ppc64': 0.27.7 - '@esbuild/android-arm': 0.27.7 - '@esbuild/android-arm64': 0.27.7 - '@esbuild/android-x64': 0.27.7 - '@esbuild/darwin-arm64': 0.27.7 - '@esbuild/darwin-x64': 0.27.7 - '@esbuild/freebsd-arm64': 0.27.7 - '@esbuild/freebsd-x64': 0.27.7 - '@esbuild/linux-arm': 0.27.7 - '@esbuild/linux-arm64': 0.27.7 - '@esbuild/linux-ia32': 0.27.7 - '@esbuild/linux-loong64': 0.27.7 - '@esbuild/linux-mips64el': 0.27.7 - '@esbuild/linux-ppc64': 0.27.7 - '@esbuild/linux-riscv64': 0.27.7 - '@esbuild/linux-s390x': 0.27.7 - '@esbuild/linux-x64': 0.27.7 - '@esbuild/netbsd-arm64': 0.27.7 - '@esbuild/netbsd-x64': 0.27.7 - '@esbuild/openbsd-arm64': 0.27.7 - '@esbuild/openbsd-x64': 0.27.7 - '@esbuild/openharmony-arm64': 0.27.7 - '@esbuild/sunos-x64': 0.27.7 - '@esbuild/win32-arm64': 0.27.7 - '@esbuild/win32-ia32': 0.27.7 - '@esbuild/win32-x64': 0.27.7 + '@esbuild/aix-ppc64': 0.28.0 + '@esbuild/android-arm': 0.28.0 + '@esbuild/android-arm64': 0.28.0 + '@esbuild/android-x64': 0.28.0 + '@esbuild/darwin-arm64': 0.28.0 + '@esbuild/darwin-x64': 0.28.0 + '@esbuild/freebsd-arm64': 0.28.0 + '@esbuild/freebsd-x64': 0.28.0 + '@esbuild/linux-arm': 0.28.0 + '@esbuild/linux-arm64': 0.28.0 + '@esbuild/linux-ia32': 0.28.0 + '@esbuild/linux-loong64': 0.28.0 + '@esbuild/linux-mips64el': 0.28.0 + '@esbuild/linux-ppc64': 0.28.0 + '@esbuild/linux-riscv64': 0.28.0 + '@esbuild/linux-s390x': 0.28.0 + '@esbuild/linux-x64': 0.28.0 + '@esbuild/netbsd-arm64': 0.28.0 + '@esbuild/netbsd-x64': 0.28.0 + '@esbuild/openbsd-arm64': 0.28.0 + '@esbuild/openbsd-x64': 0.28.0 + '@esbuild/openharmony-arm64': 0.28.0 + '@esbuild/sunos-x64': 0.28.0 + '@esbuild/win32-arm64': 0.28.0 + '@esbuild/win32-ia32': 0.28.0 + '@esbuild/win32-x64': 0.28.0 escape-html@1.0.3: {} @@ -3463,9 +3467,11 @@ snapshots: etag@1.8.1: {} - event-target-shim@5.0.1: {} - - events@3.3.0: {} + events-universal@1.0.1: + dependencies: + bare-events: 2.8.3 + transitivePeerDependencies: + - bare-abort-controller expand-template@2.0.3: {} @@ -3504,10 +3510,12 @@ snapshots: transitivePeerDependencies: - supports-color - fast-copy@3.0.2: {} + fast-copy@4.0.3: {} fast-deep-equal@3.1.3: {} + fast-fifo@1.3.2: {} + fast-safe-stringify@2.1.1: {} fdir@6.5.0(picomatch@4.0.4): @@ -3543,11 +3551,6 @@ snapshots: locate-path: 5.0.0 path-exists: 4.0.0 - fluent-ffmpeg@2.1.3: - dependencies: - async: 0.2.10 - which: 1.3.1 - forwarded@0.2.0: {} fresh@2.0.0: {} @@ -3666,12 +3669,8 @@ snapshots: is-promise@4.0.0: {} - isexe@2.0.0: {} - joycon@3.1.1: {} - libphonenumber-js@1.13.1: {} - libsodium-wrappers@0.8.4: dependencies: libsodium: 0.8.4 @@ -3839,11 +3838,9 @@ snapshots: dependencies: p-limit: 2.3.0 - p-retry@6.2.1: + p-retry@8.0.0: dependencies: - '@types/retry': 0.12.2 is-network-error: 1.3.2 - retry: 0.13.1 p-try@2.2.0: {} @@ -3896,15 +3893,6 @@ snapshots: picomatch@4.0.4: {} - pino-abstract-transport@1.2.0: - dependencies: - readable-stream: 4.7.0 - split2: 4.2.0 - - pino-abstract-transport@2.0.0: - dependencies: - split2: 4.2.0 - pino-abstract-transport@3.0.0: dependencies: split2: 4.2.0 @@ -3916,22 +3904,21 @@ snapshots: pino-std-serializers: 7.1.0 process-warning: 5.0.0 - pino-pretty@10.3.1: + pino-pretty@13.1.3: dependencies: colorette: 2.0.20 dateformat: 4.6.3 - fast-copy: 3.0.2 + fast-copy: 4.0.3 fast-safe-stringify: 2.1.1 help-me: 5.0.0 joycon: 3.1.1 minimist: 1.2.8 on-exit-leak-free: 2.1.2 - pino-abstract-transport: 1.2.0 + pino-abstract-transport: 3.0.0 pump: 3.0.4 - readable-stream: 4.7.0 - secure-json-parse: 2.7.0 - sonic-boom: 3.8.1 - strip-json-comments: 3.1.1 + secure-json-parse: 4.1.0 + sonic-boom: 4.2.1 + strip-json-comments: 5.0.3 pino-std-serializers@7.1.0: {} @@ -3949,20 +3936,6 @@ snapshots: sonic-boom: 4.2.1 thread-stream: 4.1.0 - pino@9.14.0: - dependencies: - '@pinojs/redact': 0.4.0 - atomic-sleep: 1.0.0 - on-exit-leak-free: 2.1.2 - pino-abstract-transport: 2.0.0 - pino-std-serializers: 7.1.0 - process-warning: 5.0.0 - quick-format-unescaped: 4.0.4 - real-require: 0.2.0 - safe-stable-stringify: 2.5.0 - sonic-boom: 4.2.1 - thread-stream: 3.1.0 - pngjs@5.0.0: {} postcss@8.5.14: @@ -4006,8 +3979,6 @@ snapshots: process-warning@5.0.0: {} - process@0.11.10: {} - prom-client@15.1.3: dependencies: '@opentelemetry/api': 1.9.1 @@ -4064,14 +4035,6 @@ snapshots: string_decoder: 1.3.0 util-deprecate: 1.0.2 - readable-stream@4.7.0: - dependencies: - abort-controller: 3.0.0 - buffer: 6.0.3 - events: 3.3.0 - process: 0.11.10 - string_decoder: 1.3.0 - real-require@0.2.0: {} real-require@1.0.0: {} @@ -4088,8 +4051,6 @@ snapshots: resolve-pkg-maps@1.0.0: {} - retry@0.13.1: {} - rimraf@3.0.2: dependencies: glob: 7.2.3 @@ -4133,7 +4094,7 @@ snapshots: scheduler@0.27.0: {} - secure-json-parse@2.7.0: {} + secure-json-parse@4.1.0: {} semver@6.3.1: {} @@ -4210,15 +4171,17 @@ snapshots: once: 1.4.0 simple-concat: 1.0.1 - sodium-native@4.3.3: + sodium-native@5.1.0(bare-events@2.8.3): dependencies: + bare-assert: 1.2.0(bare-events@2.8.3) require-addon: 1.2.0 + which-runtime: 1.3.2 transitivePeerDependencies: + - bare-abort-controller + - bare-buffer + - bare-events - bare-url - - sonic-boom@3.8.1: - dependencies: - atomic-sleep: 1.0.0 + - react-native-b4a sonic-boom@4.2.1: dependencies: @@ -4241,6 +4204,15 @@ snapshots: std-env@4.1.0: {} + streamx@2.25.0: + dependencies: + events-universal: 1.0.1 + fast-fifo: 1.3.2 + text-decoder: 1.2.7 + transitivePeerDependencies: + - bare-abort-controller + - react-native-b4a + string-width@4.2.3: dependencies: emoji-regex: 8.0.0 @@ -4257,7 +4229,7 @@ snapshots: strip-json-comments@2.0.1: {} - strip-json-comments@3.1.1: {} + strip-json-comments@5.0.3: {} supports-color@7.2.0: dependencies: @@ -4291,11 +4263,20 @@ snapshots: dependencies: bintrees: 1.0.2 - thirty-two@1.0.2: {} - - thread-stream@3.1.0: + teex@1.0.1: dependencies: - real-require: 0.2.0 + streamx: 2.25.0 + transitivePeerDependencies: + - bare-abort-controller + - react-native-b4a + + text-decoder@1.2.7: + dependencies: + b4a: 1.8.1 + transitivePeerDependencies: + - react-native-b4a + + thirty-two@1.0.2: {} thread-stream@4.1.0: dependencies: @@ -4342,10 +4323,9 @@ snapshots: tslib@2.8.1: {} - tsx@4.21.0: + tsx@4.22.0: dependencies: - esbuild: 0.27.7 - get-tsconfig: 4.14.0 + esbuild: 0.28.0 optionalDependencies: fsevents: 2.3.3 @@ -4361,17 +4341,17 @@ snapshots: undici-types@7.16.0: {} + undici-types@7.24.6: {} + undici@7.25.0: {} unpipe@1.0.0: {} util-deprecate@1.0.2: {} - validator@13.15.35: {} - vary@1.1.2: {} - vite@8.0.13(@types/node@24.12.4)(esbuild@0.27.7)(tsx@4.21.0): + vite@8.0.13(@types/node@25.8.0)(esbuild@0.28.0)(tsx@4.22.0): dependencies: lightningcss: 1.32.0 picomatch: 4.0.4 @@ -4379,15 +4359,15 @@ snapshots: rolldown: 1.0.1 tinyglobby: 0.2.16 optionalDependencies: - '@types/node': 24.12.4 - esbuild: 0.27.7 + '@types/node': 25.8.0 + esbuild: 0.28.0 fsevents: 2.3.3 - tsx: 4.21.0 + tsx: 4.22.0 - vitest@4.1.6(@opentelemetry/api@1.9.1)(@types/node@24.12.4)(vite@8.0.13(@types/node@24.12.4)(esbuild@0.27.7)(tsx@4.21.0)): + vitest@4.1.6(@opentelemetry/api@1.9.1)(@types/node@25.8.0)(vite@8.0.13(@types/node@25.8.0)(esbuild@0.28.0)(tsx@4.22.0)): dependencies: '@vitest/expect': 4.1.6 - '@vitest/mocker': 4.1.6(vite@8.0.13(@types/node@24.12.4)(esbuild@0.27.7)(tsx@4.21.0)) + '@vitest/mocker': 4.1.6(vite@8.0.13(@types/node@25.8.0)(esbuild@0.28.0)(tsx@4.22.0)) '@vitest/pretty-format': 4.1.6 '@vitest/runner': 4.1.6 '@vitest/snapshot': 4.1.6 @@ -4404,11 +4384,11 @@ snapshots: tinyexec: 1.1.2 tinyglobby: 0.2.16 tinyrainbow: 3.1.0 - vite: 8.0.13(@types/node@24.12.4)(esbuild@0.27.7)(tsx@4.21.0) + vite: 8.0.13(@types/node@25.8.0)(esbuild@0.28.0)(tsx@4.22.0) why-is-node-running: 2.3.0 optionalDependencies: '@opentelemetry/api': 1.9.1 - '@types/node': 24.12.4 + '@types/node': 25.8.0 transitivePeerDependencies: - msw @@ -4429,9 +4409,7 @@ snapshots: which-module@2.0.1: {} - which@1.3.1: - dependencies: - isexe: 2.0.0 + which-runtime@1.3.2: {} why-is-node-running@2.3.0: dependencies: diff --git a/src/audio/ffmpegProcess.ts b/src/audio/ffmpegProcess.ts new file mode 100644 index 0000000..9fb5675 --- /dev/null +++ b/src/audio/ffmpegProcess.ts @@ -0,0 +1,61 @@ +import { spawn } from "child_process"; + +export interface MuxFfmpegArgsOptions { + inputs: string[]; + filter: string; + output: string; + codec: string; + audioFrequency?: number; + audioChannels?: number; +} + +/** + * Builds ffmpeg argument array for muxing audio clips. + */ +export function buildMuxFfmpegArgs(options: MuxFfmpegArgsOptions): string[] { + const args: string[] = ["-y"]; + + for (const input of options.inputs) { + args.push("-i", input); + } + + args.push("-filter_complex", options.filter); + args.push("-map", "[out]"); + args.push("-codec:a", options.codec); + + if (options.audioFrequency !== undefined) { + args.push("-ar", String(options.audioFrequency)); + } + + if (options.audioChannels !== undefined) { + args.push("-ac", String(options.audioChannels)); + } + + args.push(options.output); + + return args; +} + +/** + * Runs ffmpeg with the given arguments. + * Resolves on successful (code 0) exit, rejects on error or non-zero exit. + */ +export function runFfmpeg(args: string[]): Promise { + return new Promise((resolve, reject) => { + const proc = spawn("ffmpeg", args, { + stdio: ["ignore", "inherit", "inherit"], + }); + + proc.on("close", (code) => { + if (code === 0) { + resolve(); + } else { + reject(new Error(`ffmpeg exited with code ${code}`)); + } + }); + + proc.on("error", (err) => { + reject(err); + }); + }); +} diff --git a/src/database/migrate.ts b/src/database/migrate.ts index ed53e81..fa3df42 100644 --- a/src/database/migrate.ts +++ b/src/database/migrate.ts @@ -5,13 +5,11 @@ import { migrate as migrateSqlite } from "drizzle-orm/better-sqlite3/migrator"; import { migrate as migratePostgres } from "drizzle-orm/node-postgres/migrator"; import { config } from "../config"; import { createChildLogger } from "../logger"; -import { initializeDatabase } from "./drizzle"; +import { closeDatabase, initializeDatabase } from "./drizzle"; const logger = createChildLogger("migrate"); -export async function initializeMigrationSqliteDatabase( - path = ".muxer-queue.db", -) { +export function initializeMigrationSqliteDatabase(path = ".muxer-queue.db") { const sqlite = new Database(path); sqlite.pragma("journal_mode = WAL"); return { sqlite, db: drizzleSqlite(sqlite) }; @@ -23,17 +21,23 @@ export async function runMigrations(): Promise { if (config.DATABASE_TYPE === "postgres") { logger.info("Running PostgreSQL migrations"); - const db = await initializeDatabase(); - await migratePostgres(db as any, { - migrationsFolder: "./drizzle/migrations", - }); + const db = (await initializeDatabase()) as Parameters< + typeof migratePostgres + >[0]; + try { + await migratePostgres(db, { migrationsFolder: "./drizzle/migrations" }); + } finally { + await closeDatabase(); + } logger.info("PostgreSQL migrations completed successfully"); } else { logger.info("Running SQLite migrations"); - const { sqlite, db } = await initializeMigrationSqliteDatabase(); - migrateSqlite(db, { migrationsFolder: "./drizzle/migrations" }); - // Ensure the SQLite connection is closed after migrations - sqlite.close(); + const { sqlite, db } = initializeMigrationSqliteDatabase(); + try { + migrateSqlite(db, { migrationsFolder: "./drizzle/migrations" }); + } finally { + sqlite.close(); + } logger.info("SQLite migrations completed successfully"); } } catch (error) { @@ -45,7 +49,6 @@ export async function runMigrations(): Promise { } } -// Run migrations if called directly if (import.meta.url === `file://${process.argv[1]}`) { runMigrations() .then(() => { diff --git a/src/muxer-aup3.ts b/src/muxer-aup3.ts index 99039cc..807cecc 100644 --- a/src/muxer-aup3.ts +++ b/src/muxer-aup3.ts @@ -1,6 +1,6 @@ -import fs from "node:fs"; -import path from "node:path"; -import ffmpeg from "fluent-ffmpeg"; +import fs from "fs"; +import path from "path"; +import { buildMuxFfmpegArgs, runFfmpeg } from "./audio/ffmpegProcess"; const recordingsDir = process.env.RECORDINGS_DIR ?? "./recordings"; @@ -73,7 +73,7 @@ async function startMuxingToAup3() { // Check if OGG file has valid header (starts with "OggS") const oggBuffer = fs.readFileSync(oggPath); - const oggHeader = oggBuffer.slice(0, 4).toString(); + const oggHeader = oggBuffer.subarray(0, 4).toString(); if (oggHeader !== "OggS") { console.warn( `[muxer-aup3] Skipping invalid OGG file (bad header): ${oggPath}`, @@ -116,15 +116,12 @@ async function startMuxingToAup3() { `[muxer-aup3] Found ${clips.length} clips. Base timestamp: ${globalStartTime}`, ); - const command = ffmpeg(); const filterParts: string[] = []; console.log( `[muxer-aup3] Creating audio filters for ${clips.length} clips...`, ); clips.forEach((clip, index) => { - command.input(clip.oggPath); - // Calculate delay relative to the global start time const delayMs = clip.meta.startTime - globalStartTime; @@ -154,33 +151,29 @@ async function startMuxingToAup3() { const timestamp = Date.now(); const wavFilename = path.join(recordingsDir, `muxed-${timestamp}.wav`); const aup3Filename = path.join(recordingsDir, `muxed-${timestamp}.aup3`); + const inputs = clips.map((clip) => clip.oggPath); console.log( `[muxer-aup3] Combining clips to WAV. This might take a while...`, ); - // Using fluent-ffmpeg's complexFilter - command - .complexFilter(filterParts, "out") - .audioCodec("pcm_s16le") - .audioFrequency(44100) - .audioChannels(2) - .save(wavFilename) - .on("progress", (progress) => { - if (progress.percent) { - console.log( - `[muxer-aup3] WAV Progress: ${progress.percent.toFixed(2)}%`, - ); - } - }) - .on("end", () => { - console.log(`[muxer-aup3] WAV file created: ${wavFilename}`); - console.log(`[muxer-aup3] Creating AUP3 project file...`); - createAup3Project(wavFilename, aup3Filename, clips, globalStartTime); - }) - .on("error", (err) => { - console.error(`[muxer-aup3] FFmpeg Error:`, err); + try { + const args = buildMuxFfmpegArgs({ + inputs, + filter: filterParts.join(";"), + output: wavFilename, + codec: "pcm_s16le", + audioFrequency: 44100, + audioChannels: 2, }); + + await runFfmpeg(args); + console.log(`[muxer-aup3] WAV file created: ${wavFilename}`); + console.log(`[muxer-aup3] Creating AUP3 project file...`); + createAup3Project(wavFilename, aup3Filename, clips, globalStartTime); + } catch (err) { + console.error(`[muxer-aup3] FFmpeg Error:`, err); + } } function createAup3Project( diff --git a/src/muxer.ts b/src/muxer.ts index 8eeb2cd..070c0eb 100644 --- a/src/muxer.ts +++ b/src/muxer.ts @@ -1,6 +1,6 @@ -import fs from "node:fs"; -import path from "node:path"; -import ffmpeg from "fluent-ffmpeg"; +import fs from "fs"; +import path from "path"; +import { buildMuxFfmpegArgs, runFfmpeg } from "./audio/ffmpegProcess"; const recordingsDir = process.env.RECORDINGS_DIR ?? "./recordings"; @@ -71,7 +71,7 @@ async function startMuxing() { // Check if OGG file has valid header (starts with "OggS") const oggBuffer = fs.readFileSync(oggPath); - const oggHeader = oggBuffer.slice(0, 4).toString(); + const oggHeader = oggBuffer.subarray(0, 4).toString(); if (oggHeader !== "OggS") { console.warn( `[muxer] Skipping invalid OGG file (bad header): ${oggPath}`, @@ -114,13 +114,10 @@ async function startMuxing() { `[muxer] Found ${clips.length} clips. Base timestamp: ${globalStartTime}`, ); - const command = ffmpeg(); const filterParts: string[] = []; console.log(`[muxer] Creating audio filters for ${clips.length} clips...`); clips.forEach((clip, index) => { - command.input(clip.oggPath); - // Calculate delay relative to the global start time const delayMs = clip.meta.startTime - globalStartTime; @@ -148,27 +145,25 @@ async function startMuxing() { ); const outputFilename = path.join(recordingsDir, `muxed-${Date.now()}.mp3`); + const inputs = clips.map((clip) => clip.oggPath); console.log(`[muxer] Combining clips. This might take a while...`); - // Using fluent-ffmpeg's complexFilter - command - .complexFilter(filterParts, "out") - .audioCodec("libmp3lame") - .save(outputFilename) - .on("progress", (progress) => { - if (progress.percent) { - console.log(`[muxer] Progress: ${progress.percent.toFixed(2)}%`); - } - }) - .on("end", () => { - console.log( - `[muxer] Successfully muxed! Output saved to: ${outputFilename}`, - ); - }) - .on("error", (err) => { - console.error(`[muxer] FFmpeg Error:`, err); + try { + const args = buildMuxFfmpegArgs({ + inputs, + filter: filterParts.join(";"), + output: outputFilename, + codec: "libmp3lame", }); + + await runFfmpeg(args); + console.log( + `[muxer] Successfully muxed! Output saved to: ${outputFilename}`, + ); + } catch (err) { + console.error(`[muxer] FFmpeg Error:`, err); + } } startMuxing(); diff --git a/src/retry.ts b/src/retry.ts index 4934b67..342b0ff 100644 --- a/src/retry.ts +++ b/src/retry.ts @@ -32,7 +32,7 @@ export async function retryWithBackoff( { attempt: error.attemptNumber, retriesLeft: error.retriesLeft, - error: error.message, + error: error.error.message, }, "Retry attempt", ); diff --git a/src/validation.ts b/src/validation.ts index 46c7353..c599795 100644 --- a/src/validation.ts +++ b/src/validation.ts @@ -1,6 +1,6 @@ import { z } from "zod"; -export const userStateUpdateSchema = z.object({ +const userStateUpdateSchema = z.object({ userId: z.string(), username: z.string(), avatar: z.string(), diff --git a/tests/audio/ffmpegProcess.test.ts b/tests/audio/ffmpegProcess.test.ts new file mode 100644 index 0000000..9ee645f --- /dev/null +++ b/tests/audio/ffmpegProcess.test.ts @@ -0,0 +1,102 @@ +import { spawn } from "node:child_process"; +import { EventEmitter } from "node:events"; +import { describe, expect, it, vi } from "vitest"; +import { buildMuxFfmpegArgs, runFfmpeg } from "../../src/audio/ffmpegProcess"; + +vi.mock("node:child_process", () => ({ + spawn: vi.fn(), +})); + +const spawnMock = vi.mocked(spawn); + +describe("buildMuxFfmpegArgs", () => { + it("builds args with multiple inputs and libmp3lame codec", () => { + const args = buildMuxFfmpegArgs({ + inputs: ["a.ogg", "b.ogg"], + filter: + "[0:a]adelay=0|0[pad0];[1:a]adelay=1000|1000[pad1];[pad0][pad1]amix=inputs=2:dropout_transition=0[out]", + output: "out.mp3", + codec: "libmp3lame", + }); + + expect(args).toEqual([ + "-y", + "-i", + "a.ogg", + "-i", + "b.ogg", + "-filter_complex", + "[0:a]adelay=0|0[pad0];[1:a]adelay=1000|1000[pad1];[pad0][pad1]amix=inputs=2:dropout_transition=0[out]", + "-map", + "[out]", + "-codec:a", + "libmp3lame", + "out.mp3", + ]); + }); + + it("includes audio frequency and channel options when provided", () => { + const args = buildMuxFfmpegArgs({ + inputs: ["a.ogg"], + filter: + "[0:a]adelay=0|0[pad0];[pad0]amix=inputs=1:dropout_transition=0[out]", + output: "out.wav", + codec: "pcm_s16le", + audioFrequency: 44100, + audioChannels: 2, + }); + + expect(args).toContain("-ar"); + expect(args).toContain("44100"); + expect(args).toContain("-ac"); + expect(args).toContain("2"); + }); + + it("does not include -ar or -ac when audioFrequency and audioChannels are not provided", () => { + const args = buildMuxFfmpegArgs({ + inputs: ["a.ogg"], + filter: + "[0:a]adelay=0|0[pad0];[pad0]amix=inputs=1:dropout_transition=0[out]", + output: "out.mp3", + codec: "libmp3lame", + }); + + expect(args).not.toContain("-ar"); + expect(args).not.toContain("-ac"); + }); +}); + +describe("runFfmpeg", () => { + it("spawns ffmpeg and resolves on exit code 0", async () => { + const proc = new EventEmitter(); + spawnMock.mockReturnValue(proc as ReturnType); + + const result = runFfmpeg(["-version"]); + proc.emit("close", 0); + + await expect(result).resolves.toBeUndefined(); + expect(spawnMock).toHaveBeenCalledWith("ffmpeg", ["-version"], { + stdio: ["ignore", "inherit", "inherit"], + }); + }); + + it("rejects on non-zero exit code", async () => { + const proc = new EventEmitter(); + spawnMock.mockReturnValue(proc as ReturnType); + + const result = runFfmpeg(["-bad"]); + proc.emit("close", 1); + + await expect(result).rejects.toThrow("ffmpeg exited with code 1"); + }); + + it("rejects on spawn error", async () => { + const proc = new EventEmitter(); + spawnMock.mockReturnValue(proc as ReturnType); + + const result = runFfmpeg(["-version"]); + proc.emit("error", new Error("spawn ffmpeg ENOENT")); + + await expect(result).rejects.toThrow("spawn ffmpeg ENOENT"); + }); +}); diff --git a/tests/database/migrate.test.ts b/tests/database/migrate.test.ts new file mode 100644 index 0000000..53072cd --- /dev/null +++ b/tests/database/migrate.test.ts @@ -0,0 +1,21 @@ +import { mkdtempSync, rmSync } from "node:fs"; +import { tmpdir } from "node:os"; +import { join } from "node:path"; +import { describe, expect, it } from "vitest"; +import { initializeMigrationSqliteDatabase } from "../../src/database/migrate"; + +describe("initializeMigrationSqliteDatabase", () => { + it("creates a SQLite DB with WAL journal mode", () => { + const dir = mkdtempSync(join(tmpdir(), "bete-migrate-")); + const dbPath = join(dir, "test.db"); + const { sqlite, db } = initializeMigrationSqliteDatabase(dbPath); + + try { + expect(db).toBeDefined(); + expect(sqlite.pragma("journal_mode", { simple: true })).toBe("wal"); + } finally { + sqlite.close(); + rmSync(dir, { recursive: true, force: true }); + } + }); +}); diff --git a/tests/validation.test.ts b/tests/validation.test.ts index 1228677..7a19d45 100644 --- a/tests/validation.test.ts +++ b/tests/validation.test.ts @@ -1,30 +1,35 @@ -import { expect, test } from "vitest"; -import { UserStateUpdate, validateUserStateUpdate } from "../src/validation"; +import { describe, expect, it } from "vitest"; +import { validateUserStateUpdate } from "../src/validation"; -test("valid object returns typed object", async () => { - const input = { - userId: "123", - username: "testuser", - avatar: "avatar.png", - speaking: true, - }; - const result = await validateUserStateUpdate(input); - expect(result).toEqual(input as UserStateUpdate); -}); +describe("validateUserStateUpdate", () => { + it("returns typed data for a valid user state update", async () => { + const result = await validateUserStateUpdate({ + userId: "123", + username: "aseph", + avatar: "https://example.invalid/avatar.png", + speaking: true, + }); -test("non-object input returns null", async () => { - // @ts-expect-error testing invalid input - const result = await validateUserStateUpdate("not an object"); - expect(result).toBeNull(); -}); + expect(result).toEqual({ + userId: "123", + username: "aseph", + avatar: "https://example.invalid/avatar.png", + speaking: true, + }); + }); -test("invalid field types return null", async () => { - const input = { - userId: "123", - username: "testuser", - avatar: "avatar.png", - speaking: "true", // invalid type - }; - const result = await validateUserStateUpdate(input as unknown); - expect(result).toBeNull(); + it("returns null for non-object input", async () => { + await expect(validateUserStateUpdate("bad")).resolves.toBeNull(); + }); + + it("returns null for invalid field types", async () => { + const result = await validateUserStateUpdate({ + userId: "123", + username: "aseph", + avatar: "https://example.invalid/avatar.png", + speaking: "true", + }); + + expect(result).toBeNull(); + }); });