From 7dbfde116475e0b738396c19dbdc44db85f7b065 Mon Sep 17 00:00:00 2001 From: Dylan Audius Date: Fri, 22 May 2026 16:53:13 -0700 Subject: [PATCH 1/2] feat(migrate-tool): add track migration tool MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A small Vite + React SPA backed by Vercel functions + Supabase that lets an artist request migration of tracks from an old Audius account they've lost access to onto a new account they control. Every migration sits in a pending queue until an Audius team member approves it via an admin route gated by a bearer token — the README spells out that identity verification of the requester has to happen out-of-band. The frontend uses the SDK's PKCE OAuth flow with apiKey only; on approval the backend re-uploads each track via createSdkWithServices using the dev app's API key + bearer token, acting on behalf of the new owner. Designed for migrate.audius.co. Co-Authored-By: Claude Opus 4.7 --- package-lock.json | 2662 +++++++++++++---- packages/migrate-tool/.env.example | 25 + packages/migrate-tool/.gitignore | 7 + packages/migrate-tool/README.md | 120 + packages/migrate-tool/api/_lib/audius.ts | 37 + packages/migrate-tool/api/_lib/auth.ts | 34 + packages/migrate-tool/api/_lib/migrate.ts | 145 + packages/migrate-tool/api/_lib/serialize.ts | 18 + packages/migrate-tool/api/_lib/supabase.ts | 20 + packages/migrate-tool/api/_lib/types.ts | 38 + packages/migrate-tool/api/admin/approve.ts | 64 + packages/migrate-tool/api/admin/reject.ts | 39 + packages/migrate-tool/api/admin/requests.ts | 31 + packages/migrate-tool/api/requests/[id].ts | 37 + packages/migrate-tool/api/requests/index.ts | 52 + packages/migrate-tool/index.html | 12 + packages/migrate-tool/package.json | 33 + packages/migrate-tool/src/App.tsx | 67 + packages/migrate-tool/src/config.ts | 17 + packages/migrate-tool/src/main.tsx | 14 + packages/migrate-tool/src/pages/Admin.tsx | 211 ++ packages/migrate-tool/src/pages/Home.tsx | 255 ++ packages/migrate-tool/src/pages/Status.tsx | 168 ++ packages/migrate-tool/src/sdk.ts | 22 + packages/migrate-tool/src/styles.css | 190 ++ packages/migrate-tool/src/types.ts | 38 + packages/migrate-tool/src/vite-env.d.ts | 10 + .../supabase/migrations/0001_init.sql | 31 + packages/migrate-tool/tsconfig.api.json | 19 + packages/migrate-tool/tsconfig.app.json | 21 + packages/migrate-tool/tsconfig.json | 7 + packages/migrate-tool/vercel.json | 13 + packages/migrate-tool/vite.config.ts | 25 + 33 files changed, 3935 insertions(+), 547 deletions(-) create mode 100644 packages/migrate-tool/.env.example create mode 100644 packages/migrate-tool/.gitignore create mode 100644 packages/migrate-tool/README.md create mode 100644 packages/migrate-tool/api/_lib/audius.ts create mode 100644 packages/migrate-tool/api/_lib/auth.ts create mode 100644 packages/migrate-tool/api/_lib/migrate.ts create mode 100644 packages/migrate-tool/api/_lib/serialize.ts create mode 100644 packages/migrate-tool/api/_lib/supabase.ts create mode 100644 packages/migrate-tool/api/_lib/types.ts create mode 100644 packages/migrate-tool/api/admin/approve.ts create mode 100644 packages/migrate-tool/api/admin/reject.ts create mode 100644 packages/migrate-tool/api/admin/requests.ts create mode 100644 packages/migrate-tool/api/requests/[id].ts create mode 100644 packages/migrate-tool/api/requests/index.ts create mode 100644 packages/migrate-tool/index.html create mode 100644 packages/migrate-tool/package.json create mode 100644 packages/migrate-tool/src/App.tsx create mode 100644 packages/migrate-tool/src/config.ts create mode 100644 packages/migrate-tool/src/main.tsx create mode 100644 packages/migrate-tool/src/pages/Admin.tsx create mode 100644 packages/migrate-tool/src/pages/Home.tsx create mode 100644 packages/migrate-tool/src/pages/Status.tsx create mode 100644 packages/migrate-tool/src/sdk.ts create mode 100644 packages/migrate-tool/src/styles.css create mode 100644 packages/migrate-tool/src/types.ts create mode 100644 packages/migrate-tool/src/vite-env.d.ts create mode 100644 packages/migrate-tool/supabase/migrations/0001_init.sql create mode 100644 packages/migrate-tool/tsconfig.api.json create mode 100644 packages/migrate-tool/tsconfig.app.json create mode 100644 packages/migrate-tool/tsconfig.json create mode 100644 packages/migrate-tool/vercel.json create mode 100644 packages/migrate-tool/vite.config.ts diff --git a/package-lock.json b/package-lock.json index 81f8a8a919e..6b4fc990512 100644 --- a/package-lock.json +++ b/package-lock.json @@ -165,12 +165,6 @@ "zod": "^3.25.76 || ^4.1.8" } }, - "node_modules/@ai-sdk/provider-utils/node_modules/@standard-schema/spec": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", - "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", - "license": "MIT" - }, "node_modules/@ai-sdk/provider/node_modules/json-schema": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.4.0.tgz", @@ -1383,6 +1377,10 @@ "slide": "^1.1.5" } }, + "node_modules/@audius/migrate-tool": { + "resolved": "packages/migrate-tool", + "link": true + }, "node_modules/@audius/mobile": { "resolved": "packages/mobile", "link": true @@ -7115,6 +7113,59 @@ "@noble/ciphers": "^1.0.0" } }, + "node_modules/@edge-runtime/format": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/@edge-runtime/format/-/format-2.2.1.tgz", + "integrity": "sha512-JQTRVuiusQLNNLe2W9tnzBlV/GvSVcozLl4XZHk5swnRZ/v6jp8TqR8P7sqmJsQqblDZ3EztcWmLDbhRje/+8g==", + "dev": true, + "license": "MPL-2.0", + "engines": { + "node": ">=16" + } + }, + "node_modules/@edge-runtime/node-utils": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@edge-runtime/node-utils/-/node-utils-2.3.0.tgz", + "integrity": "sha512-uUtx8BFoO1hNxtHjp3eqVPC/mWImGb2exOfGjMLUoipuWgjej+f4o/VP4bUI8U40gu7Teogd5VTeZUkGvJSPOQ==", + "dev": true, + "license": "MPL-2.0", + "engines": { + "node": ">=16" + } + }, + "node_modules/@edge-runtime/ponyfill": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/@edge-runtime/ponyfill/-/ponyfill-2.4.2.tgz", + "integrity": "sha512-oN17GjFr69chu6sDLvXxdhg0Qe8EZviGSuqzR9qOiKh4MhFYGdBBcqRNzdmYeAdeRzOW2mM9yil4RftUQ7sUOA==", + "dev": true, + "license": "MPL-2.0", + "engines": { + "node": ">=16" + } + }, + "node_modules/@edge-runtime/primitives": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/@edge-runtime/primitives/-/primitives-4.1.0.tgz", + "integrity": "sha512-Vw0lbJ2lvRUqc7/soqygUX216Xb8T3WBZ987oywz6aJqRxcwSVWwr9e+Nqo2m9bxobA9mdbWNNoRY6S9eko1EQ==", + "dev": true, + "license": "MPL-2.0", + "engines": { + "node": ">=16" + } + }, + "node_modules/@edge-runtime/vm": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/@edge-runtime/vm/-/vm-3.2.0.tgz", + "integrity": "sha512-0dEVyRLM/lG4gp1R/Ik5bfPl/1wX00xFwd5KcNH602tzBa09oF7pbTKETEhR1GjZ75K6OJnYFu8II2dyMhONMw==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "@edge-runtime/primitives": "4.1.0" + }, + "engines": { + "node": ">=16" + } + }, "node_modules/@egjs/hammerjs": { "version": "2.0.17", "license": "MIT", @@ -8340,6 +8391,22 @@ "node": ">=12" } }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", + "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", + "cpu": [ + "loong64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, "node_modules/@esbuild/linux-mips64el": { "version": "0.18.20", "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz", @@ -8754,7 +8821,9 @@ } }, "node_modules/@ethersproject/bignumber": { - "version": "5.7.0", + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/bignumber/-/bignumber-5.8.0.tgz", + "integrity": "sha512-ZyaT24bHaSeJon2tGPKIiHszWjD/54Sz8t57Toch475lCLljC6MgPmxk7Gtzz+ddNN5LuHea9qhAe0x3D+uYPA==", "funding": [ { "type": "individual", @@ -8767,13 +8836,21 @@ ], "license": "MIT", "dependencies": { - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/logger": "^5.7.0", + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/logger": "^5.8.0", "bn.js": "^5.2.1" } }, + "node_modules/@ethersproject/bignumber/node_modules/bn.js": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.3.tgz", + "integrity": "sha512-EAcmnPkxpntVL+DS7bO1zhcZNvCkxqtkd0ZY53h06GNQ3DEkkGZ/gKgmDv6DdZQGj9BgfSPKtJJ7Dp1GPP8f7w==", + "license": "MIT" + }, "node_modules/@ethersproject/bytes": { - "version": "5.7.0", + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/bytes/-/bytes-5.8.0.tgz", + "integrity": "sha512-vTkeohgJVCPVHu5c25XWaWQOZ4v+DkGoC42/TS2ond+PARCxTJvgTFUNDZovyQ/uAQ4EcpqqowKydcdmRKjg7A==", "funding": [ { "type": "individual", @@ -8786,11 +8863,13 @@ ], "license": "MIT", "dependencies": { - "@ethersproject/logger": "^5.7.0" + "@ethersproject/logger": "^5.8.0" } }, "node_modules/@ethersproject/constants": { - "version": "5.7.0", + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/constants/-/constants-5.8.0.tgz", + "integrity": "sha512-wigX4lrf5Vu+axVTIvNsuL6YrV4O5AXl5ubcURKMEME5TnWBouUh0CDTWxZ2GpnRn1kcCgE7l8O5+VbV9QTTcg==", "funding": [ { "type": "individual", @@ -8803,7 +8882,7 @@ ], "license": "MIT", "dependencies": { - "@ethersproject/bignumber": "^5.7.0" + "@ethersproject/bignumber": "^5.8.0" } }, "node_modules/@ethersproject/contracts": { @@ -8918,7 +8997,9 @@ "license": "MIT" }, "node_modules/@ethersproject/keccak256": { - "version": "5.7.0", + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/keccak256/-/keccak256-5.8.0.tgz", + "integrity": "sha512-A1pkKLZSz8pDaQ1ftutZoaN46I6+jvuqugx5KYNeQOPqq+JZ0Txm7dlWesCHB5cndJSu5vP2VKptKf7cksERng==", "funding": [ { "type": "individual", @@ -8931,12 +9012,14 @@ ], "license": "MIT", "dependencies": { - "@ethersproject/bytes": "^5.7.0", + "@ethersproject/bytes": "^5.8.0", "js-sha3": "0.8.0" } }, "node_modules/@ethersproject/logger": { - "version": "5.7.0", + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/logger/-/logger-5.8.0.tgz", + "integrity": "sha512-Qe6knGmY+zPPWTC+wQrpitodgBfH7XoceCGL5bJVejmH+yCS3R8jJm8iiWuvWbG76RUmyEG53oqv6GMVWqunjA==", "funding": [ { "type": "individual", @@ -9096,7 +9179,9 @@ } }, "node_modules/@ethersproject/sha2": { - "version": "5.7.0", + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/sha2/-/sha2-5.8.0.tgz", + "integrity": "sha512-dDOUrXr9wF/YFltgTBYS0tKslPEKr6AekjqDW2dbn1L1xmjGR+9GiKu4ajxovnrDbwxAKdHjW8jNcwfz8PAz4A==", "funding": [ { "type": "individual", @@ -9109,8 +9194,8 @@ ], "license": "MIT", "dependencies": { - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/logger": "^5.7.0", + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/logger": "^5.8.0", "hash.js": "1.1.7" } }, @@ -9148,7 +9233,9 @@ } }, "node_modules/@ethersproject/strings": { - "version": "5.7.0", + "version": "5.8.0", + "resolved": "https://registry.npmjs.org/@ethersproject/strings/-/strings-5.8.0.tgz", + "integrity": "sha512-qWEAk0MAvl0LszjdfnZ2uC8xbR2wdv4cDabyHiBh3Cldq/T8dPH3V4BbBsAYJUeonwD+8afVXld274Ls+Y1xXg==", "funding": [ { "type": "individual", @@ -9161,9 +9248,9 @@ ], "license": "MIT", "dependencies": { - "@ethersproject/bytes": "^5.7.0", - "@ethersproject/constants": "^5.7.0", - "@ethersproject/logger": "^5.7.0" + "@ethersproject/bytes": "^5.8.0", + "@ethersproject/constants": "^5.8.0", + "@ethersproject/logger": "^5.8.0" } }, "node_modules/@ethersproject/transactions": { @@ -15288,6 +15375,128 @@ "version": "4.1.0", "license": "MIT" }, + "node_modules/@mapbox/node-pre-gyp": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", + "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==", + "dev": true, + "license": "BSD-3-Clause", + "dependencies": { + "detect-libc": "^2.0.0", + "https-proxy-agent": "^5.0.0", + "make-dir": "^3.1.0", + "node-fetch": "^2.6.7", + "nopt": "^5.0.0", + "npmlog": "^5.0.1", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.11" + }, + "bin": { + "node-pre-gyp": "bin/node-pre-gyp" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^6.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/make-dir/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "dev": true, + "license": "ISC", + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/semver": { + "version": "7.8.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.0.tgz", + "integrity": "sha512-AcM7dV/5ul4EekoQ29Agm5vri8JNqRyj39o0qpX6vDF2GZrtutZl5RwgD1XnZjiTAfncsJhMI48QQH3sN87YNA==", + "dev": true, + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/@mapbox/node-pre-gyp/node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/@marijn/find-cluster-break": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/@marijn/find-cluster-break/-/find-cluster-break-1.0.2.tgz", @@ -17660,20 +17869,6 @@ "node": ">=10" } }, - "node_modules/@npmcli/move-file/node_modules/rimraf": { - "version": "3.0.2", - "dev": true, - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/@npmcli/name-from-folder": { "version": "2.0.0", "dev": true, @@ -30252,22 +30447,6 @@ "node": ">=10" } }, - "node_modules/@rnx-kit/chromium-edge-launcher/node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/@rolldown/pluginutils": { "version": "1.0.0-beta.47", "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.47.tgz", @@ -34740,6 +34919,12 @@ ], "license": "MIT" }, + "node_modules/@standard-schema/spec": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@standard-schema/spec/-/spec-1.1.0.tgz", + "integrity": "sha512-l2aFy5jALhniG5HgqrD6jXLi/rUWrKvqN/qJx6yoJsgKhblVd+iqqU4RCXavm/jPityDo5TCvKMnpjKnOriy0w==", + "license": "MIT" + }, "node_modules/@stripe/crypto": { "version": "0.0.4", "license": "MIT", @@ -34755,6 +34940,116 @@ "version": "1.0.0", "license": "MIT" }, + "node_modules/@supabase/auth-js": { + "version": "2.99.2", + "resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.99.2.tgz", + "integrity": "sha512-uRGNXMKEw4VhwouNW7N0XDAGqJP9redHNDmWi17dTrcO1lvFfyRiXsqqfgnVC8aqtRn8kLkLPEzHjiRWsni+oQ==", + "license": "MIT", + "dependencies": { + "tslib": "2.8.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@supabase/functions-js": { + "version": "2.99.2", + "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.99.2.tgz", + "integrity": "sha512-xuXQARvjdfB1UPK1yUceZ5EGjOLkVz4rBAaloS9foXiAuseWEdgWBCxkIAFRxGBLGX8Uzo8kseq90jhPb+07Vg==", + "license": "MIT", + "dependencies": { + "tslib": "2.8.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@supabase/postgrest-js": { + "version": "2.99.2", + "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-2.99.2.tgz", + "integrity": "sha512-ueiOVkbkTQ7RskwVmjR8zxWYw3VKOMxo1+qep+Dx/SgApqyhWBGd92waQb45tbLc7ydB5x8El8utXOLQTuTojQ==", + "license": "MIT", + "dependencies": { + "tslib": "2.8.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@supabase/realtime-js": { + "version": "2.99.2", + "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.99.2.tgz", + "integrity": "sha512-J6Jm9601dkpZf3+EJ48ki2pM4sFtCNm/BI0l8iEnrczgg+JSEQkMoOW5VSpM54t0pNs69bsz5PTmYJahDZKiIQ==", + "license": "MIT", + "dependencies": { + "@types/phoenix": "^1.6.6", + "@types/ws": "^8.18.1", + "tslib": "2.8.1", + "ws": "^8.18.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@supabase/realtime-js/node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@supabase/realtime-js/node_modules/ws": { + "version": "8.20.1", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.20.1.tgz", + "integrity": "sha512-It4dO0K5v//JtTXuPkfEOaI3uUN87iYPnqo/ZzqCoG3g8uhA66QUMs/SrM0YK7/NAu+r4LMh/9dq2A7k+rHs+w==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/@supabase/storage-js": { + "version": "2.99.2", + "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.99.2.tgz", + "integrity": "sha512-V/FF8kX8JGSefsVCG1spCLSrHdNR/JFeUMn1jS9KG/Eizjx+evtdKQKLJXFgIylY/bKTXKhc2SYDPIGrIhzsug==", + "license": "MIT", + "dependencies": { + "iceberg-js": "^0.8.1", + "tslib": "2.8.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@supabase/supabase-js": { + "version": "2.99.2", + "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.99.2.tgz", + "integrity": "sha512-179rn5wq0wBAqqGwAwR7TUGg2NOaP+fkd5FCVbYJXby85fsRNPFoNJN8YRBepqX2tN7JJcnTjqaAMXuNjiyisA==", + "license": "MIT", + "dependencies": { + "@supabase/auth-js": "2.99.2", + "@supabase/functions-js": "2.99.2", + "@supabase/postgrest-js": "2.99.2", + "@supabase/realtime-js": "2.99.2", + "@supabase/storage-js": "2.99.2" + }, + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/@svgr/babel-plugin-add-jsx-attribute": { "version": "8.0.0", "license": "MIT", @@ -37726,6 +38021,32 @@ "node": ">=10.13.0" } }, + "node_modules/@ts-morph/common": { + "version": "0.11.1", + "resolved": "https://registry.npmjs.org/@ts-morph/common/-/common-0.11.1.tgz", + "integrity": "sha512-7hWZS0NRpEsNV8vWJzg7FEz6V8MaLNeJOmwmghqUXTpzk16V1LLZhdo+4QvE/+zv4cVci0OviuJFnqhEfoV3+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-glob": "^3.2.7", + "minimatch": "^3.0.4", + "mkdirp": "^1.0.4", + "path-browserify": "^1.0.1" + } + }, + "node_modules/@ts-morph/common/node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "dev": true, + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/@tsconfig/node10": { "version": "1.0.9", "license": "MIT" @@ -38600,6 +38921,12 @@ "pg-types": "^2.2.0" } }, + "node_modules/@types/phoenix": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/@types/phoenix/-/phoenix-1.6.7.tgz", + "integrity": "sha512-oN9ive//QSBkf19rfDv45M7eZPi0eEXylht2OLEXicu5b4KoQ1OzXIw+xDSGWxSxe1JmepRR/ZH283vsu518/Q==", + "license": "MIT" + }, "node_modules/@types/pify": { "version": "5.0.1", "dev": true, @@ -38696,6 +39023,16 @@ "csstype": "^3.0.2" } }, + "node_modules/@types/react-dom": { + "version": "19.0.0", + "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.0.0.tgz", + "integrity": "sha512-1KfiQKsH1o00p9m5ag12axHQSb3FOU9H20UTrujVSkNhuCrRHiQWFqgEnTNK5ZNfnzZv8UWrnXVqCmCF9fgY3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/react": "*" + } + }, "node_modules/@types/react-helmet": { "version": "6.1.6", "dev": true, @@ -40912,6 +41249,220 @@ "vite": "^5.0.0 || ^6.0.0 || ^7.0.0" } }, + "node_modules/@vercel/build-utils": { + "version": "8.7.0", + "resolved": "https://registry.npmjs.org/@vercel/build-utils/-/build-utils-8.7.0.tgz", + "integrity": "sha512-ofZX+ABiW76u5khIyYyH5xK5KSuiAteqRu5hz2k1a2WHLwF7VpeBg8gdFR+HwbVnNkHtkMA64ya5Dd/lNoABkw==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/@vercel/error-utils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@vercel/error-utils/-/error-utils-2.0.3.tgz", + "integrity": "sha512-CqC01WZxbLUxoiVdh9B/poPbNpY9U+tO1N9oWHwTl5YAZxcqXmmWJ8KNMFItJCUUWdY3J3xv8LvAuQv2KZ5YdQ==", + "dev": true, + "license": "Apache-2.0" + }, + "node_modules/@vercel/nft": { + "version": "0.27.3", + "resolved": "https://registry.npmjs.org/@vercel/nft/-/nft-0.27.3.tgz", + "integrity": "sha512-oySTdDSzUAFDXpsSLk9Q943o+/Yu/+TCFxnehpFQEf/3khi2stMpTHPVNwFdvZq/Z4Ky93lE+MGHpXCRpMkSCA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@mapbox/node-pre-gyp": "^1.0.5", + "@rollup/pluginutils": "^4.0.0", + "acorn": "^8.6.0", + "acorn-import-attributes": "^1.9.5", + "async-sema": "^3.1.1", + "bindings": "^1.4.0", + "estree-walker": "2.0.2", + "glob": "^7.1.3", + "graceful-fs": "^4.2.9", + "micromatch": "^4.0.2", + "node-gyp-build": "^4.2.2", + "resolve-from": "^5.0.0" + }, + "bin": { + "nft": "out/cli.js" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@vercel/nft/node_modules/@rollup/pluginutils": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-4.2.1.tgz", + "integrity": "sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "estree-walker": "^2.0.1", + "picomatch": "^2.2.2" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/@vercel/nft/node_modules/acorn": { + "version": "8.16.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", + "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==", + "dev": true, + "license": "MIT", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/@vercel/node": { + "version": "3.2.29", + "resolved": "https://registry.npmjs.org/@vercel/node/-/node-3.2.29.tgz", + "integrity": "sha512-WRVYidBqtRyYUw36v/WyUB2v97PsiV2+LepUiOPWcW9UpszQGGT2DAzsXOYqWveXMJKFhx0aETR6Nn6i+Yps1Q==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "@edge-runtime/node-utils": "2.3.0", + "@edge-runtime/primitives": "4.1.0", + "@edge-runtime/vm": "3.2.0", + "@types/node": "16.18.11", + "@vercel/build-utils": "8.7.0", + "@vercel/error-utils": "2.0.3", + "@vercel/nft": "0.27.3", + "@vercel/static-config": "3.0.0", + "async-listen": "3.0.0", + "cjs-module-lexer": "1.2.3", + "edge-runtime": "2.5.9", + "es-module-lexer": "1.4.1", + "esbuild": "0.14.47", + "etag": "1.8.1", + "node-fetch": "2.6.9", + "path-to-regexp": "6.2.1", + "ts-morph": "12.0.0", + "ts-node": "10.9.1", + "typescript": "4.9.5", + "undici": "5.28.4" + } + }, + "node_modules/@vercel/node/node_modules/@types/node": { + "version": "16.18.11", + "resolved": "https://registry.npmjs.org/@types/node/-/node-16.18.11.tgz", + "integrity": "sha512-3oJbGBUWuS6ahSnEq1eN2XrCyf4YsWI8OyCvo7c64zQJNplk3mO84t53o8lfTk+2ji59g5ycfc6qQ3fdHliHuA==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vercel/node/node_modules/es-module-lexer": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.4.1.tgz", + "integrity": "sha512-cXLGjP0c4T3flZJKQSuziYoq7MlT+rnvfZjfp7h+I7K9BNX54kP9nyWvdbwjQ4u1iWbOL4u96fgeZLToQlZC7w==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vercel/node/node_modules/esbuild": { + "version": "0.14.47", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.14.47.tgz", + "integrity": "sha512-wI4ZiIfFxpkuxB8ju4MHrGwGLyp1+awEHAHVpx6w7a+1pmYIq8T9FGEVVwFo0iFierDoMj++Xq69GXWYn2EiwA==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "esbuild-android-64": "0.14.47", + "esbuild-android-arm64": "0.14.47", + "esbuild-darwin-64": "0.14.47", + "esbuild-darwin-arm64": "0.14.47", + "esbuild-freebsd-64": "0.14.47", + "esbuild-freebsd-arm64": "0.14.47", + "esbuild-linux-32": "0.14.47", + "esbuild-linux-64": "0.14.47", + "esbuild-linux-arm": "0.14.47", + "esbuild-linux-arm64": "0.14.47", + "esbuild-linux-mips64le": "0.14.47", + "esbuild-linux-ppc64le": "0.14.47", + "esbuild-linux-riscv64": "0.14.47", + "esbuild-linux-s390x": "0.14.47", + "esbuild-netbsd-64": "0.14.47", + "esbuild-openbsd-64": "0.14.47", + "esbuild-sunos-64": "0.14.47", + "esbuild-windows-32": "0.14.47", + "esbuild-windows-64": "0.14.47", + "esbuild-windows-arm64": "0.14.47" + } + }, + "node_modules/@vercel/node/node_modules/node-fetch": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.9.tgz", + "integrity": "sha512-DJm/CJkZkRjKKj4Zi4BsKVZh3ValV5IR5s7LVZnW+6YMh0W1BfNA8XSs6DLMGYlId5F3KnA70uu2qepcR08Qqg==", + "dev": true, + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/@vercel/node/node_modules/path-to-regexp": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-6.2.1.tgz", + "integrity": "sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vercel/node/node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vercel/node/node_modules/typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "dev": true, + "license": "Apache-2.0", + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/@vercel/node/node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "dev": true, + "license": "BSD-2-Clause" + }, + "node_modules/@vercel/node/node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, "node_modules/@vercel/oidc": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/@vercel/oidc/-/oidc-3.1.0.tgz", @@ -40921,6 +41472,253 @@ "node": ">= 20" } }, + "node_modules/@vercel/static-config": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@vercel/static-config/-/static-config-3.0.0.tgz", + "integrity": "sha512-2qtvcBJ1bGY0dYGYh3iM7yGKkk971FujLEDXzuW5wcZsPr1GSEjO/w2iSr3qve6nDDtBImsGoDEnus5FI4+fIw==", + "dev": true, + "license": "Apache-2.0", + "dependencies": { + "ajv": "8.6.3", + "json-schema-to-ts": "1.6.4", + "ts-morph": "12.0.0" + } + }, + "node_modules/@vercel/static-config/node_modules/ajv": { + "version": "8.6.3", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.6.3.tgz", + "integrity": "sha512-SMJOdDP6LqTkD0Uq8qLi+gMwSt0imXLSV080qFVwJCpH9U6Mb+SUGHAXM0KNbcBPguytWyvFxcHgMLe2D2XSpw==", + "dev": true, + "license": "MIT", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "json-schema-traverse": "^1.0.0", + "require-from-string": "^2.0.2", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/@vercel/static-config/node_modules/json-schema-traverse": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", + "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", + "dev": true, + "license": "MIT" + }, + "node_modules/@vitejs/plugin-react": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", + "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", + "license": "MIT", + "dependencies": { + "@babel/core": "^7.28.0", + "@babel/plugin-transform-react-jsx-self": "^7.27.1", + "@babel/plugin-transform-react-jsx-source": "^7.27.1", + "@rolldown/pluginutils": "1.0.0-beta.27", + "@types/babel__core": "^7.20.5", + "react-refresh": "^0.17.0" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" + } + }, + "node_modules/@vitejs/plugin-react/node_modules/@babel/compat-data": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", + "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", + "license": "MIT", + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@vitejs/plugin-react/node_modules/@babel/core": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", + "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-compilation-targets": "^7.28.6", + "@babel/helper-module-transforms": "^7.28.6", + "@babel/helpers": "^7.28.6", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/traverse": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/remapping": "^2.3.5", + "convert-source-map": "^2.0.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.2.3", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/babel" + } + }, + "node_modules/@vitejs/plugin-react/node_modules/@babel/generator": { + "version": "7.29.1", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", + "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", + "license": "MIT", + "dependencies": { + "@babel/parser": "^7.29.0", + "@babel/types": "^7.29.0", + "@jridgewell/gen-mapping": "^0.3.12", + "@jridgewell/trace-mapping": "^0.3.28", + "jsesc": "^3.0.2" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@vitejs/plugin-react/node_modules/@babel/helper-compilation-targets": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", + "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", + "license": "MIT", + "dependencies": { + "@babel/compat-data": "^7.28.6", + "@babel/helper-validator-option": "^7.27.1", + "browserslist": "^4.24.0", + "lru-cache": "^5.1.1", + "semver": "^6.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@vitejs/plugin-react/node_modules/@babel/helper-module-transforms": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", + "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", + "license": "MIT", + "dependencies": { + "@babel/helper-module-imports": "^7.28.6", + "@babel/helper-validator-identifier": "^7.28.5", + "@babel/traverse": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + }, + "peerDependencies": { + "@babel/core": "^7.0.0" + } + }, + "node_modules/@vitejs/plugin-react/node_modules/@babel/helpers": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", + "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", + "license": "MIT", + "dependencies": { + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@vitejs/plugin-react/node_modules/@babel/parser": { + "version": "7.29.2", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", + "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", + "license": "MIT", + "dependencies": { + "@babel/types": "^7.29.0" + }, + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@vitejs/plugin-react/node_modules/@babel/template": { + "version": "7.28.6", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", + "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.28.6", + "@babel/parser": "^7.28.6", + "@babel/types": "^7.28.6" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@vitejs/plugin-react/node_modules/@babel/traverse": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", + "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", + "license": "MIT", + "dependencies": { + "@babel/code-frame": "^7.29.0", + "@babel/generator": "^7.29.0", + "@babel/helper-globals": "^7.28.0", + "@babel/parser": "^7.29.0", + "@babel/template": "^7.28.6", + "@babel/types": "^7.29.0", + "debug": "^4.3.1" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@vitejs/plugin-react/node_modules/@babel/types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", + "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", + "license": "MIT", + "dependencies": { + "@babel/helper-string-parser": "^7.27.1", + "@babel/helper-validator-identifier": "^7.28.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@vitejs/plugin-react/node_modules/@rolldown/pluginutils": { + "version": "1.0.0-beta.27", + "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", + "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", + "license": "MIT" + }, + "node_modules/@vitejs/plugin-react/node_modules/convert-source-map": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", + "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", + "license": "MIT" + }, + "node_modules/@vitejs/plugin-react/node_modules/react-refresh": { + "version": "0.17.0", + "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", + "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@vitejs/plugin-react/node_modules/semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + } + }, "node_modules/@vitest/expect": { "version": "0.34.6", "dev": true, @@ -45620,6 +46418,21 @@ "dev": true, "license": "MIT" }, + "node_modules/are-we-there-yet": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", + "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", + "deprecated": "This package is no longer supported.", + "dev": true, + "license": "ISC", + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/arg": { "version": "2.0.0", "dev": true, @@ -45991,6 +46804,16 @@ "version": "1.0.1", "license": "MIT" }, + "node_modules/async-listen": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/async-listen/-/async-listen-3.0.0.tgz", + "integrity": "sha512-V+SsTpDqkrWTimiotsyl33ePSjA5/KrithwupuvJ6ztsqPvGv6ge4OredFhPffVXiLN/QUWvE0XcqJaYgt6fOg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, "node_modules/async-listener": { "version": "0.6.10", "license": "BSD-2-Clause", @@ -46023,6 +46846,13 @@ "retry": "0.13.1" } }, + "node_modules/async-sema": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/async-sema/-/async-sema-3.1.1.tgz", + "integrity": "sha512-tLRNUXati5MFePdAk8dw7Qt7DpxPB60ofAgn8WRhW6a2rcimZnYBP9oxHiv0OHy+Wz7kPMG+t4LGdt31+4EmGg==", + "dev": true, + "license": "MIT" + }, "node_modules/async-stream-emitter": { "version": "4.1.0", "license": "MIT", @@ -49921,22 +50751,6 @@ "node": ">=10" } }, - "node_modules/chromium-edge-launcher/node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/chromium-pickle-js": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/chromium-pickle-js/-/chromium-pickle-js-0.2.0.tgz", @@ -50406,6 +51220,13 @@ "node": ">= 0.12.0" } }, + "node_modules/code-block-writer": { + "version": "10.1.1", + "resolved": "https://registry.npmjs.org/code-block-writer/-/code-block-writer-10.1.1.tgz", + "integrity": "sha512-67ueh2IRGst/51p0n6FvPrnRjAGHY5F8xdjkgrYE7DDzpJe6qA07RYQ9VcoUeo5ATOjSOiWpSL3SWBRRbempMw==", + "dev": true, + "license": "MIT" + }, "node_modules/code-point-at": { "version": "1.1.0", "dev": true, @@ -53498,22 +54319,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/del/node_modules/rimraf": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", - "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", - "deprecated": "Rimraf versions prior to v4 are no longer supported", - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/del/node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -54487,6 +55292,112 @@ "integrity": "sha512-80BnDp2Fn7RxXlEr5HHZblniY4aQ97MOAicdWWpSo0vkQiISSE9wLR4SqxKsu4gCtXFBIPPzy8JMhay4NWRg/Q==", "license": "MIT" }, + "node_modules/edge-runtime": { + "version": "2.5.9", + "resolved": "https://registry.npmjs.org/edge-runtime/-/edge-runtime-2.5.9.tgz", + "integrity": "sha512-pk+k0oK0PVXdlT4oRp4lwh+unuKB7Ng4iZ2HB+EZ7QCEQizX360Rp/F4aRpgpRgdP2ufB35N+1KppHmYjqIGSg==", + "dev": true, + "license": "MPL-2.0", + "dependencies": { + "@edge-runtime/format": "2.2.1", + "@edge-runtime/ponyfill": "2.4.2", + "@edge-runtime/vm": "3.2.0", + "async-listen": "3.0.1", + "mri": "1.2.0", + "picocolors": "1.0.0", + "pretty-ms": "7.0.1", + "signal-exit": "4.0.2", + "time-span": "4.0.0" + }, + "bin": { + "edge-runtime": "dist/cli/index.js" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/edge-runtime/node_modules/async-listen": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/async-listen/-/async-listen-3.0.1.tgz", + "integrity": "sha512-cWMaNwUJnf37C/S5TfCkk/15MwbPRwVYALA2jtjkbHjCmAPiDXyNJy2q3p1KAZzDLHAWyarUWSujUoHR4pEgrA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/edge-runtime/node_modules/convert-hrtime": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/convert-hrtime/-/convert-hrtime-3.0.0.tgz", + "integrity": "sha512-7V+KqSvMiHp8yWDuwfww06XleMWVVB9b9tURBx+G7UTADuo5hYPuowKloz4OzOqbPezxgo+fdQ1522WzPG4OeA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/edge-runtime/node_modules/parse-ms": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/parse-ms/-/parse-ms-2.1.0.tgz", + "integrity": "sha512-kHt7kzLoS9VBZfUsiKjv43mr91ea+U05EyKkEtqp7vNbHxmaVuEqN7XxeEVnGrMtYOAxGrDElSi96K7EgO1zCA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/edge-runtime/node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", + "dev": true, + "license": "ISC" + }, + "node_modules/edge-runtime/node_modules/pretty-ms": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/pretty-ms/-/pretty-ms-7.0.1.tgz", + "integrity": "sha512-973driJZvxiGOQ5ONsFhOF/DtzPMOMtgC11kCpUrPGMTgqp2q/1gwzCquocrN33is0VZ5GFHXZYMM9l6h67v2Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "parse-ms": "^2.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/edge-runtime/node_modules/signal-exit": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.0.2.tgz", + "integrity": "sha512-MY2/qGx4enyjprQnFaZsHib3Yadh3IXyV2C321GY0pjGfVBu4un0uDJkwgdxqO+Rdx8JMT8IfJIRwbYVz3Ob3Q==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/edge-runtime/node_modules/time-span": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/time-span/-/time-span-4.0.0.tgz", + "integrity": "sha512-MyqZCTGLDZ77u4k+jqg4UlrzPTPZ49NDlaekU6uuFaJLzPIN1woaRXCbGeqOfxwc3Y37ZROGAJ614Rdv7Olt+g==", + "dev": true, + "license": "MIT", + "dependencies": { + "convert-hrtime": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/editorconfig": { "version": "1.0.4", "dev": true, @@ -55573,6 +56484,278 @@ "@esbuild/win32-x64": "0.18.20" } }, + "node_modules/esbuild-android-64": { + "version": "0.14.47", + "resolved": "https://registry.npmjs.org/esbuild-android-64/-/esbuild-android-64-0.14.47.tgz", + "integrity": "sha512-R13Bd9+tqLVFndncMHssZrPWe6/0Kpv2/dt4aA69soX4PRxlzsVpCvoJeFE8sOEoeVEiBkI0myjlkDodXlHa0g==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-android-arm64": { + "version": "0.14.47", + "resolved": "https://registry.npmjs.org/esbuild-android-arm64/-/esbuild-android-arm64-0.14.47.tgz", + "integrity": "sha512-OkwOjj7ts4lBp/TL6hdd8HftIzOy/pdtbrNA4+0oVWgGG64HrdVzAF5gxtJufAPOsEjkyh1oIYvKAUinKKQRSQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-darwin-64": { + "version": "0.14.47", + "resolved": "https://registry.npmjs.org/esbuild-darwin-64/-/esbuild-darwin-64-0.14.47.tgz", + "integrity": "sha512-R6oaW0y5/u6Eccti/TS6c/2c1xYTb1izwK3gajJwi4vIfNs1s8B1dQzI1UiC9T61YovOQVuePDcfqHLT3mUZJA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-darwin-arm64": { + "version": "0.14.47", + "resolved": "https://registry.npmjs.org/esbuild-darwin-arm64/-/esbuild-darwin-arm64-0.14.47.tgz", + "integrity": "sha512-seCmearlQyvdvM/noz1L9+qblC5vcBrhUaOoLEDDoLInF/VQ9IkobGiLlyTPYP5dW1YD4LXhtBgOyevoIHGGnw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-freebsd-64": { + "version": "0.14.47", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-64/-/esbuild-freebsd-64-0.14.47.tgz", + "integrity": "sha512-ZH8K2Q8/Ux5kXXvQMDsJcxvkIwut69KVrYQhza/ptkW50DC089bCVrJZZ3sKzIoOx+YPTrmsZvqeZERjyYrlvQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-freebsd-arm64": { + "version": "0.14.47", + "resolved": "https://registry.npmjs.org/esbuild-freebsd-arm64/-/esbuild-freebsd-arm64-0.14.47.tgz", + "integrity": "sha512-ZJMQAJQsIOhn3XTm7MPQfCzEu5b9STNC+s90zMWe2afy9EwnHV7Ov7ohEMv2lyWlc2pjqLW8QJnz2r0KZmeAEQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-32": { + "version": "0.14.47", + "resolved": "https://registry.npmjs.org/esbuild-linux-32/-/esbuild-linux-32-0.14.47.tgz", + "integrity": "sha512-FxZOCKoEDPRYvq300lsWCTv1kcHgiiZfNrPtEhFAiqD7QZaXrad8LxyJ8fXGcWzIFzRiYZVtB3ttvITBvAFhKw==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-64": { + "version": "0.14.47", + "resolved": "https://registry.npmjs.org/esbuild-linux-64/-/esbuild-linux-64-0.14.47.tgz", + "integrity": "sha512-nFNOk9vWVfvWYF9YNYksZptgQAdstnDCMtR6m42l5Wfugbzu11VpMCY9XrD4yFxvPo9zmzcoUL/88y0lfJZJJw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-arm": { + "version": "0.14.47", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm/-/esbuild-linux-arm-0.14.47.tgz", + "integrity": "sha512-ZGE1Bqg/gPRXrBpgpvH81tQHpiaGxa8c9Rx/XOylkIl2ypLuOcawXEAo8ls+5DFCcRGt/o3sV+PzpAFZobOsmA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-arm64": { + "version": "0.14.47", + "resolved": "https://registry.npmjs.org/esbuild-linux-arm64/-/esbuild-linux-arm64-0.14.47.tgz", + "integrity": "sha512-ywfme6HVrhWcevzmsufjd4iT3PxTfCX9HOdxA7Hd+/ZM23Y9nXeb+vG6AyA6jgq/JovkcqRHcL9XwRNpWG6XRw==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-mips64le": { + "version": "0.14.47", + "resolved": "https://registry.npmjs.org/esbuild-linux-mips64le/-/esbuild-linux-mips64le-0.14.47.tgz", + "integrity": "sha512-mg3D8YndZ1LvUiEdDYR3OsmeyAew4MA/dvaEJxvyygahWmpv1SlEEnhEZlhPokjsUMfRagzsEF/d/2XF+kTQGg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-ppc64le": { + "version": "0.14.47", + "resolved": "https://registry.npmjs.org/esbuild-linux-ppc64le/-/esbuild-linux-ppc64le-0.14.47.tgz", + "integrity": "sha512-WER+f3+szmnZiWoK6AsrTKGoJoErG2LlauSmk73LEZFQ/iWC+KhhDsOkn1xBUpzXWsxN9THmQFltLoaFEH8F8w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-riscv64": { + "version": "0.14.47", + "resolved": "https://registry.npmjs.org/esbuild-linux-riscv64/-/esbuild-linux-riscv64-0.14.47.tgz", + "integrity": "sha512-1fI6bP3A3rvI9BsaaXbMoaOjLE3lVkJtLxsgLHqlBhLlBVY7UqffWBvkrX/9zfPhhVMd9ZRFiaqXnB1T7BsL2g==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-linux-s390x": { + "version": "0.14.47", + "resolved": "https://registry.npmjs.org/esbuild-linux-s390x/-/esbuild-linux-s390x-0.14.47.tgz", + "integrity": "sha512-eZrWzy0xFAhki1CWRGnhsHVz7IlSKX6yT2tj2Eg8lhAwlRE5E96Hsb0M1mPSE1dHGpt1QVwwVivXIAacF/G6mw==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-netbsd-64": { + "version": "0.14.47", + "resolved": "https://registry.npmjs.org/esbuild-netbsd-64/-/esbuild-netbsd-64-0.14.47.tgz", + "integrity": "sha512-Qjdjr+KQQVH5Q2Q1r6HBYswFTToPpss3gqCiSw2Fpq/ua8+eXSQyAMG+UvULPqXceOwpnPo4smyZyHdlkcPppQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-openbsd-64": { + "version": "0.14.47", + "resolved": "https://registry.npmjs.org/esbuild-openbsd-64/-/esbuild-openbsd-64-0.14.47.tgz", + "integrity": "sha512-QpgN8ofL7B9z8g5zZqJE+eFvD1LehRlxr25PBkjyyasakm4599iroUpaj96rdqRlO2ShuyqwJdr+oNqWwTUmQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, "node_modules/esbuild-plugin-react-virtualized": { "version": "1.0.4", "resolved": "https://registry.npmjs.org/esbuild-plugin-react-virtualized/-/esbuild-plugin-react-virtualized-1.0.4.tgz", @@ -55582,6 +56765,74 @@ "esbuild": "*" } }, + "node_modules/esbuild-sunos-64": { + "version": "0.14.47", + "resolved": "https://registry.npmjs.org/esbuild-sunos-64/-/esbuild-sunos-64-0.14.47.tgz", + "integrity": "sha512-uOeSgLUwukLioAJOiGYm3kNl+1wJjgJA8R671GYgcPgCx7QR73zfvYqXFFcIO93/nBdIbt5hd8RItqbbf3HtAQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-windows-32": { + "version": "0.14.47", + "resolved": "https://registry.npmjs.org/esbuild-windows-32/-/esbuild-windows-32-0.14.47.tgz", + "integrity": "sha512-H0fWsLTp2WBfKLBgwYT4OTfFly4Im/8B5f3ojDv1Kx//kiubVY0IQunP2Koc/fr/0wI7hj3IiBDbSrmKlrNgLQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-windows-64": { + "version": "0.14.47", + "resolved": "https://registry.npmjs.org/esbuild-windows-64/-/esbuild-windows-64-0.14.47.tgz", + "integrity": "sha512-/Pk5jIEH34T68r8PweKRi77W49KwanZ8X6lr3vDAtOlH5EumPE4pBHqkCUdELanvsT14yMXLQ/C/8XPi1pAtkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/esbuild-windows-arm64": { + "version": "0.14.47", + "resolved": "https://registry.npmjs.org/esbuild-windows-arm64/-/esbuild-windows-arm64-0.14.47.tgz", + "integrity": "sha512-HFSW2lnp62fl86/qPQlqw6asIwCnEsEoNIL1h2uVMgakddf+vUuMcCbtUY1i8sst7KkgHrVKCJQB33YhhOweCQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, "node_modules/esbuild/node_modules/@esbuild/linux-loong64": { "version": "0.18.20", "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz", @@ -59530,20 +60781,6 @@ "node": ">=12.0.0" } }, - "node_modules/flat-cache/node_modules/rimraf": { - "version": "3.0.2", - "dev": true, - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/flatted": { "version": "3.4.2", "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.4.2.tgz", @@ -59974,6 +61211,28 @@ "incremental-encoder": "0.0.1" } }, + "node_modules/gauge": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", + "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", + "deprecated": "This package is no longer supported.", + "dev": true, + "license": "ISC", + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.2", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.1", + "object-assign": "^4.1.1", + "signal-exit": "^3.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.2" + }, + "engines": { + "node": ">=10" + } + }, "node_modules/generic-names": { "version": "4.0.0", "dev": true, @@ -63978,6 +65237,15 @@ "version": "1.0.4", "license": "BSD-3-Clause" }, + "node_modules/iceberg-js": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/iceberg-js/-/iceberg-js-0.8.1.tgz", + "integrity": "sha512-1dhVQZXhcHje7798IVM+xoo/1ZdVfzOMIc8/rgVSijRK38EDqOJoGula9N/8ZI5RD8QTxNQtK/Gozpr+qUqRRA==", + "license": "MIT", + "engines": { + "node": ">=20.0.0" + } + }, "node_modules/iconv-corefoundation": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/iconv-corefoundation/-/iconv-corefoundation-1.1.7.tgz", @@ -67834,6 +69102,17 @@ "lodash": "^4.17.4" } }, + "node_modules/json-schema-to-ts": { + "version": "1.6.4", + "resolved": "https://registry.npmjs.org/json-schema-to-ts/-/json-schema-to-ts-1.6.4.tgz", + "integrity": "sha512-pR4yQ9DHz6itqswtHCm26mw45FSNfQ9rEQjosaZErhn5J3J2sIViQiz8rDaezjKAhFGpmsoczYVBgGHzFw/stA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/json-schema": "^7.0.6", + "ts-toolbelt": "^6.15.5" + } + }, "node_modules/json-schema-traverse": { "version": "0.4.1", "license": "MIT" @@ -69815,59 +71094,6 @@ "node": ">=10" } }, - "node_modules/make-fetch-happen/node_modules/rimraf": { - "version": "3.0.2", - "dev": true, - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/make-fetch-happen/node_modules/rimraf/node_modules/brace-expansion": { - "version": "1.1.11", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^1.0.0", - "concat-map": "0.0.1" - } - }, - "node_modules/make-fetch-happen/node_modules/rimraf/node_modules/glob": { - "version": "7.2.3", - "dev": true, - "license": "ISC", - "dependencies": { - "fs.realpath": "^1.0.0", - "inflight": "^1.0.4", - "inherits": "2", - "minimatch": "^3.1.1", - "once": "^1.3.0", - "path-is-absolute": "^1.0.0" - }, - "engines": { - "node": "*" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/make-fetch-happen/node_modules/rimraf/node_modules/minimatch": { - "version": "3.1.2", - "dev": true, - "license": "ISC", - "dependencies": { - "brace-expansion": "^1.1.7" - }, - "engines": { - "node": "*" - } - }, "node_modules/make-fetch-happen/node_modules/semver": { "version": "7.5.4", "dev": true, @@ -77196,20 +78422,6 @@ "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, - "node_modules/node-gyp/node_modules/rimraf": { - "version": "3.0.2", - "dev": true, - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/node-gyp/node_modules/semver": { "version": "7.5.4", "dev": true, @@ -78594,20 +79806,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/npm-check-updates/node_modules/rimraf": { - "version": "3.0.2", - "dev": true, - "license": "ISC", - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/npm-check-updates/node_modules/semver": { "version": "7.5.4", "dev": true, @@ -79065,6 +80263,20 @@ "node": ">=8" } }, + "node_modules/npmlog": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", + "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", + "deprecated": "This package is no longer supported.", + "dev": true, + "license": "ISC", + "dependencies": { + "are-we-there-yet": "^2.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^3.0.0", + "set-blocking": "^2.0.0" + } + }, "node_modules/nssocket": { "version": "0.6.0", "license": "MIT", @@ -83359,6 +84571,21 @@ "dev": true, "license": "MIT" }, + "node_modules/react-chartjs-2": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-2.10.0.tgz", + "integrity": "sha512-1MjWEkUn8LLFf6GVyYUOrruJTW3yVU5hlEJOwGj3MiokuC+jH/BahjWVGAMonbe9UYbEIUbd2Rn36iVlC0Hb7w==", + "license": "MIT", + "dependencies": { + "lodash": "^4.17.19", + "prop-types": "^15.7.2" + }, + "peerDependencies": { + "chart.js": "^2.3", + "react": "^0.14.0 || ^15.0.0 || ^16.0.0-beta || ^16.0.0", + "react-dom": "^0.14.0 || ^15.0.0 || ^16.0.0-beta || ^16.0.0" + } + }, "node_modules/react-country-flag": { "version": "3.0.2", "license": "MIT", @@ -88073,14 +89300,19 @@ } }, "node_modules/rimraf": { - "version": "3.0.0", - "dev": true, + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", "license": "ISC", "dependencies": { "glob": "^7.1.3" }, "bin": { "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, "node_modules/ripemd160": { @@ -92206,21 +93438,6 @@ "node": "^12.13.0 || ^14.15.0 || >=16.0.0" } }, - "node_modules/sqlite3/node_modules/rimraf": { - "version": "3.0.2", - "dev": true, - "license": "ISC", - "optional": true, - "dependencies": { - "glob": "^7.1.3" - }, - "bin": { - "rimraf": "bin.js" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, "node_modules/sqlite3/node_modules/semver": { "version": "7.6.0", "dev": true, @@ -95615,6 +96832,17 @@ "node": ">=4" } }, + "node_modules/ts-morph": { + "version": "12.0.0", + "resolved": "https://registry.npmjs.org/ts-morph/-/ts-morph-12.0.0.tgz", + "integrity": "sha512-VHC8XgU2fFW7yO1f/b3mxKDje1vmyzFXHWzOYmKEkCEwcLjDtbdLgBQviqj4ZwP4MJkQtRo6Ha2I29lq/B+VxA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@ts-morph/common": "~0.11.0", + "code-block-writer": "^10.1.1" + } + }, "node_modules/ts-node": { "version": "10.9.1", "license": "MIT", @@ -95990,6 +97218,13 @@ "dev": true, "license": "ISC" }, + "node_modules/ts-toolbelt": { + "version": "6.15.5", + "resolved": "https://registry.npmjs.org/ts-toolbelt/-/ts-toolbelt-6.15.5.tgz", + "integrity": "sha512-FZIXf1ksVyLcfr7M317jbB67XFJhOO1YqdTcuGaq9q5jLUoTikukZ+98TPjKiP2jC5CgmYdWWYs0s2nLSU0/1A==", + "dev": true, + "license": "Apache-2.0" + }, "node_modules/tsconfck": { "version": "2.1.2", "dev": true, @@ -107172,23 +108407,6 @@ "node": ">=18" } }, - "packages/distro/node_modules/@esbuild/linux-loong64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", - "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, "packages/distro/node_modules/@esbuild/linux-mips64el": { "version": "0.25.12", "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", @@ -107427,16 +108645,6 @@ "node": ">=18" } }, - "packages/distro/node_modules/@types/react-dom": { - "version": "19.0.0", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.0.0.tgz", - "integrity": "sha512-1KfiQKsH1o00p9m5ag12axHQSb3FOU9H20UTrujVSkNhuCrRHiQWFqgEnTNK5ZNfnzZv8UWrnXVqCmCF9fgY3w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/react": "*" - } - }, "packages/distro/node_modules/@types/semver": { "version": "7.7.0", "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.7.0.tgz", @@ -107902,167 +109110,6 @@ "node": ">=18.0" } }, - "packages/docs/node_modules/@babel/compat-data": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.29.0.tgz", - "integrity": "sha512-T1NCJqT/j9+cn8fvkt7jtwbLBfLC/1y1c7NtCeXFRgzGTsafi68MRv8yzkYSapBnFA6L3U2VSc02ciDzoAJhJg==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "packages/docs/node_modules/@babel/core": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.29.0.tgz", - "integrity": "sha512-CGOfOJqWjg2qW/Mb6zNsDm+u5vFQ8DxXfbM09z69p5Z6+mE1ikP2jUXw+j42Pf1XTYED2Rni5f95npYeuwMDQA==", - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", - "@babel/helper-compilation-targets": "^7.28.6", - "@babel/helper-module-transforms": "^7.28.6", - "@babel/helpers": "^7.28.6", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/traverse": "^7.29.0", - "@babel/types": "^7.29.0", - "@jridgewell/remapping": "^2.3.5", - "convert-source-map": "^2.0.0", - "debug": "^4.1.0", - "gensync": "^1.0.0-beta.2", - "json5": "^2.2.3", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/babel" - } - }, - "packages/docs/node_modules/@babel/generator": { - "version": "7.29.1", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.29.1.tgz", - "integrity": "sha512-qsaF+9Qcm2Qv8SRIMMscAvG4O3lJ0F1GuMo5HR/Bp02LopNgnZBC/EkbevHFeGs4ls/oPz9v+Bsmzbkbe+0dUw==", - "license": "MIT", - "dependencies": { - "@babel/parser": "^7.29.0", - "@babel/types": "^7.29.0", - "@jridgewell/gen-mapping": "^0.3.12", - "@jridgewell/trace-mapping": "^0.3.28", - "jsesc": "^3.0.2" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "packages/docs/node_modules/@babel/helper-compilation-targets": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.28.6.tgz", - "integrity": "sha512-JYtls3hqi15fcx5GaSNL7SCTJ2MNmjrkHXg4FSpOA/grxK8KwyZ5bubHsCq8FXCkua6xhuaaBit+3b7+VZRfcA==", - "license": "MIT", - "dependencies": { - "@babel/compat-data": "^7.28.6", - "@babel/helper-validator-option": "^7.27.1", - "browserslist": "^4.24.0", - "lru-cache": "^5.1.1", - "semver": "^6.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "packages/docs/node_modules/@babel/helper-module-transforms": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.28.6.tgz", - "integrity": "sha512-67oXFAYr2cDLDVGLXTEABjdBJZ6drElUSI7WKp70NrpyISso3plG9SAGEF6y7zbha/wOzUByWWTJvEDVNIUGcA==", - "license": "MIT", - "dependencies": { - "@babel/helper-module-imports": "^7.28.6", - "@babel/helper-validator-identifier": "^7.28.5", - "@babel/traverse": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - }, - "peerDependencies": { - "@babel/core": "^7.0.0" - } - }, - "packages/docs/node_modules/@babel/helpers": { - "version": "7.29.2", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.29.2.tgz", - "integrity": "sha512-HoGuUs4sCZNezVEKdVcwqmZN8GoHirLUcLaYVNBK2J0DadGtdcqgr3BCbvH8+XUo4NGjNl3VOtSjEKNzqfFgKw==", - "license": "MIT", - "dependencies": { - "@babel/template": "^7.28.6", - "@babel/types": "^7.29.0" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "packages/docs/node_modules/@babel/parser": { - "version": "7.29.2", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.29.2.tgz", - "integrity": "sha512-4GgRzy/+fsBa72/RZVJmGKPmZu9Byn8o4MoLpmNe1m8ZfYnz5emHLQz3U4gLud6Zwl0RZIcgiLD7Uq7ySFuDLA==", - "license": "MIT", - "dependencies": { - "@babel/types": "^7.29.0" - }, - "bin": { - "parser": "bin/babel-parser.js" - }, - "engines": { - "node": ">=6.0.0" - } - }, - "packages/docs/node_modules/@babel/template": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.28.6.tgz", - "integrity": "sha512-YA6Ma2KsCdGb+WC6UpBVFJGXL58MDA6oyONbjyF/+5sBgxY/dwkhLogbMT2GXXyU84/IhRw/2D1Os1B/giz+BQ==", - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.28.6", - "@babel/parser": "^7.28.6", - "@babel/types": "^7.28.6" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "packages/docs/node_modules/@babel/traverse": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.29.0.tgz", - "integrity": "sha512-4HPiQr0X7+waHfyXPZpWPfWL/J7dcN1mx9gL6WdQVMbPnF3+ZhSMs8tCxN7oHddJE9fhNE7+lxdnlyemKfJRuA==", - "license": "MIT", - "dependencies": { - "@babel/code-frame": "^7.29.0", - "@babel/generator": "^7.29.0", - "@babel/helper-globals": "^7.28.0", - "@babel/parser": "^7.29.0", - "@babel/template": "^7.28.6", - "@babel/types": "^7.29.0", - "debug": "^4.3.1" - }, - "engines": { - "node": ">=6.9.0" - } - }, - "packages/docs/node_modules/@babel/types": { - "version": "7.29.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.29.0.tgz", - "integrity": "sha512-LwdZHpScM4Qz8Xw2iKSzS+cfglZzJGvofQICy7W7v4caru4EaAmyUuO6BGrbyQ2mYV11W0U8j5mBhd14dd3B0A==", - "license": "MIT", - "dependencies": { - "@babel/helper-string-parser": "^7.27.1", - "@babel/helper-validator-identifier": "^7.28.5" - }, - "engines": { - "node": ">=6.9.0" - } - }, "packages/docs/node_modules/@esbuild/aix-ppc64": { "version": "0.25.12", "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", @@ -108239,22 +109286,6 @@ "node": ">=18" } }, - "packages/docs/node_modules/@esbuild/linux-loong64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", - "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", - "cpu": [ - "loong64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, "packages/docs/node_modules/@esbuild/linux-mips64el": { "version": "0.25.12", "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", @@ -108491,12 +109522,6 @@ "url": "https://paulmillr.com/funding/" } }, - "packages/docs/node_modules/@rolldown/pluginutils": { - "version": "1.0.0-beta.27", - "resolved": "https://registry.npmjs.org/@rolldown/pluginutils/-/pluginutils-1.0.0-beta.27.tgz", - "integrity": "sha512-+d0F4MKMCbeVUJwG96uQ4SgAznZNSq93I3V+9NHA4OpvqG8mRCpGdKmK8l/dl02h2CCDHwW2FqilnTyDcAnqjA==", - "license": "MIT" - }, "packages/docs/node_modules/@scalar/agent-chat": { "version": "0.9.10", "resolved": "https://registry.npmjs.org/@scalar/agent-chat/-/agent-chat-0.9.10.tgz", @@ -109063,26 +110088,6 @@ "integrity": "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q==", "license": "MIT" }, - "packages/docs/node_modules/@vitejs/plugin-react": { - "version": "4.7.0", - "resolved": "https://registry.npmjs.org/@vitejs/plugin-react/-/plugin-react-4.7.0.tgz", - "integrity": "sha512-gUu9hwfWvvEDBBmgtAowQCojwZmJ5mcLn3aufeCsitijs3+f2NsrPtlAWIR6OPiqljl96GVCUbLe0HyqIpVaoA==", - "license": "MIT", - "dependencies": { - "@babel/core": "^7.28.0", - "@babel/plugin-transform-react-jsx-self": "^7.27.1", - "@babel/plugin-transform-react-jsx-source": "^7.27.1", - "@rolldown/pluginutils": "1.0.0-beta.27", - "@types/babel__core": "^7.20.5", - "react-refresh": "^0.17.0" - }, - "engines": { - "node": "^14.18.0 || >=16.0.0" - }, - "peerDependencies": { - "vite": "^4.2.0 || ^5.0.0 || ^6.0.0 || ^7.0.0" - } - }, "packages/docs/node_modules/acorn": { "version": "8.16.0", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", @@ -109193,12 +110198,6 @@ "node": ">=6" } }, - "packages/docs/node_modules/convert-source-map": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-2.0.0.tgz", - "integrity": "sha512-Kvp459HrV2FEJ1CAsi1Ku+MY3kasH19TFykTz2xWmMeq6bk2NU3XXvfJ+Q61m0xktWwt+1HSYf3JZsTms3aRJg==", - "license": "MIT" - }, "packages/docs/node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -110719,15 +111718,6 @@ "url": "https://github.com/sponsors/jonschlinkert" } }, - "packages/docs/node_modules/react-refresh": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/react-refresh/-/react-refresh-0.17.0.tgz", - "integrity": "sha512-z6F7K9bV85EfseRCp2bzrpyQ0Gkw1uLoCel9XBVWPg/TjRj94SkJzUTGfOa4bs7iJvBWtQG0Wq7wnI0syw3EBQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, "packages/docs/node_modules/remark-gfm": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/remark-gfm/-/remark-gfm-4.0.1.tgz", @@ -110807,15 +111797,6 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "packages/docs/node_modules/semver": { - "version": "6.3.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", - "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - } - }, "packages/docs/node_modules/send": { "version": "0.19.2", "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", @@ -111747,23 +112728,6 @@ "node": ">=18" } }, - "packages/embed/node_modules/@esbuild/linux-loong64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", - "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, "packages/embed/node_modules/@esbuild/linux-mips64el": { "version": "0.25.12", "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", @@ -114005,16 +114969,6 @@ "integrity": "sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==", "dev": true }, - "packages/harmony/node_modules/@types/react-dom": { - "version": "19.0.0", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.0.0.tgz", - "integrity": "sha512-1KfiQKsH1o00p9m5ag12axHQSb3FOU9H20UTrujVSkNhuCrRHiQWFqgEnTNK5ZNfnzZv8UWrnXVqCmCF9fgY3w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/react": "*" - } - }, "packages/harmony/node_modules/@types/react-virtualized": { "version": "9.21.30", "dev": true, @@ -117111,9 +118065,691 @@ "node": ">=10" } }, + "packages/migrate-tool": { + "name": "@audius/migrate-tool", + "version": "0.1.0", + "dependencies": { + "@audius/sdk": "file:../sdk", + "@supabase/supabase-js": "^2.45.0", + "react": "19.0.0", + "react-dom": "19.0.0" + }, + "devDependencies": { + "@types/node": "^22.10.0", + "@types/react": "19.0.0", + "@types/react-dom": "19.0.0", + "@vercel/node": "^3.2.0", + "@vitejs/plugin-react": "^4.2.2", + "buffer": "^6.0.3", + "process": "^0.11.10", + "typescript": "^5.0.4", + "vite": "^6.0.0", + "vite-plugin-node-polyfills": "^0.23.0" + } + }, + "packages/migrate-tool/node_modules/@esbuild/aix-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.25.12.tgz", + "integrity": "sha512-Hhmwd6CInZ3dwpuGTF8fJG6yoWmsToE+vYgD4nytZVxcu1ulHpUQRAB1UJ8+N1Am3Mz4+xOByoQoSZf4D+CpkA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=18" + } + }, + "packages/migrate-tool/node_modules/@esbuild/android-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.25.12.tgz", + "integrity": "sha512-VJ+sKvNA/GE7Ccacc9Cha7bpS8nyzVv0jdVgwNDaR4gDMC/2TTRc33Ip8qrNYUcpkOHUT5OZ0bUcNNVZQ9RLlg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "packages/migrate-tool/node_modules/@esbuild/android-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.25.12.tgz", + "integrity": "sha512-6AAmLG7zwD1Z159jCKPvAxZd4y/VTO0VkprYy+3N2FtJ8+BQWFXU+OxARIwA46c5tdD9SsKGZ/1ocqBS/gAKHg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "packages/migrate-tool/node_modules/@esbuild/android-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.25.12.tgz", + "integrity": "sha512-5jbb+2hhDHx5phYR2By8GTWEzn6I9UqR11Kwf22iKbNpYrsmRB18aX/9ivc5cabcUiAT/wM+YIZ6SG9QO6a8kg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=18" + } + }, + "packages/migrate-tool/node_modules/@esbuild/darwin-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.25.12.tgz", + "integrity": "sha512-N3zl+lxHCifgIlcMUP5016ESkeQjLj/959RxxNYIthIg+CQHInujFuXeWbWMgnTo4cp5XVHqFPmpyu9J65C1Yg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "packages/migrate-tool/node_modules/@esbuild/darwin-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.25.12.tgz", + "integrity": "sha512-HQ9ka4Kx21qHXwtlTUVbKJOAnmG1ipXhdWTmNXiPzPfWKpXqASVcWdnf2bnL73wgjNrFXAa3yYvBSd9pzfEIpA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=18" + } + }, + "packages/migrate-tool/node_modules/@esbuild/freebsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.25.12.tgz", + "integrity": "sha512-gA0Bx759+7Jve03K1S0vkOu5Lg/85dou3EseOGUes8flVOGxbhDDh/iZaoek11Y8mtyKPGF3vP8XhnkDEAmzeg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "packages/migrate-tool/node_modules/@esbuild/freebsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.25.12.tgz", + "integrity": "sha512-TGbO26Yw2xsHzxtbVFGEXBFH0FRAP7gtcPE7P5yP7wGy7cXK2oO7RyOhL5NLiqTlBh47XhmIUXuGciXEqYFfBQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=18" + } + }, + "packages/migrate-tool/node_modules/@esbuild/linux-arm": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.25.12.tgz", + "integrity": "sha512-lPDGyC1JPDou8kGcywY0YILzWlhhnRjdof3UlcoqYmS9El818LLfJJc3PXXgZHrHCAKs/Z2SeZtDJr5MrkxtOw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "packages/migrate-tool/node_modules/@esbuild/linux-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.25.12.tgz", + "integrity": "sha512-8bwX7a8FghIgrupcxb4aUmYDLp8pX06rGh5HqDT7bB+8Rdells6mHvrFHHW2JAOPZUbnjUpKTLg6ECyzvas2AQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "packages/migrate-tool/node_modules/@esbuild/linux-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.25.12.tgz", + "integrity": "sha512-0y9KrdVnbMM2/vG8KfU0byhUN+EFCny9+8g202gYqSSVMonbsCfLjUO+rCci7pM0WBEtz+oK/PIwHkzxkyharA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "packages/migrate-tool/node_modules/@esbuild/linux-mips64el": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", + "integrity": "sha512-iyRrM1Pzy9GFMDLsXn1iHUm18nhKnNMWscjmp4+hpafcZjrr2WbT//d20xaGljXDBYHqRcl8HnxbX6uaA/eGVw==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "packages/migrate-tool/node_modules/@esbuild/linux-ppc64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.25.12.tgz", + "integrity": "sha512-9meM/lRXxMi5PSUqEXRCtVjEZBGwB7P/D4yT8UG/mwIdze2aV4Vo6U5gD3+RsoHXKkHCfSxZKzmDssVlRj1QQA==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "packages/migrate-tool/node_modules/@esbuild/linux-riscv64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.25.12.tgz", + "integrity": "sha512-Zr7KR4hgKUpWAwb1f3o5ygT04MzqVrGEGXGLnj15YQDJErYu/BGg+wmFlIDOdJp0PmB0lLvxFIOXZgFRrdjR0w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "packages/migrate-tool/node_modules/@esbuild/linux-s390x": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.25.12.tgz", + "integrity": "sha512-MsKncOcgTNvdtiISc/jZs/Zf8d0cl/t3gYWX8J9ubBnVOwlk65UIEEvgBORTiljloIWnBzLs4qhzPkJcitIzIg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "packages/migrate-tool/node_modules/@esbuild/linux-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.25.12.tgz", + "integrity": "sha512-uqZMTLr/zR/ed4jIGnwSLkaHmPjOjJvnm6TVVitAa08SLS9Z0VM8wIRx7gWbJB5/J54YuIMInDquWyYvQLZkgw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=18" + } + }, + "packages/migrate-tool/node_modules/@esbuild/netbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.25.12.tgz", + "integrity": "sha512-xXwcTq4GhRM7J9A8Gv5boanHhRa/Q9KLVmcyXHCTaM4wKfIpWkdXiMog/KsnxzJ0A1+nD+zoecuzqPmCRyBGjg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "packages/migrate-tool/node_modules/@esbuild/netbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.25.12.tgz", + "integrity": "sha512-Ld5pTlzPy3YwGec4OuHh1aCVCRvOXdH8DgRjfDy/oumVovmuSzWfnSJg+VtakB9Cm0gxNO9BzWkj6mtO1FMXkQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=18" + } + }, + "packages/migrate-tool/node_modules/@esbuild/openbsd-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.25.12.tgz", + "integrity": "sha512-fF96T6KsBo/pkQI950FARU9apGNTSlZGsv1jZBAlcLL1MLjLNIWPBkj5NlSz8aAzYKg+eNqknrUJ24QBybeR5A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "packages/migrate-tool/node_modules/@esbuild/openbsd-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.25.12.tgz", + "integrity": "sha512-MZyXUkZHjQxUvzK7rN8DJ3SRmrVrke8ZyRusHlP+kuwqTcfWLyqMOE3sScPPyeIXN/mDJIfGXvcMqCgYKekoQw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=18" + } + }, + "packages/migrate-tool/node_modules/@esbuild/openharmony-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.25.12.tgz", + "integrity": "sha512-rm0YWsqUSRrjncSXGA7Zv78Nbnw4XL6/dzr20cyrQf7ZmRcsovpcRBdhD43Nuk3y7XIoW2OxMVvwuRvk9XdASg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ], + "engines": { + "node": ">=18" + } + }, + "packages/migrate-tool/node_modules/@esbuild/sunos-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.25.12.tgz", + "integrity": "sha512-3wGSCDyuTHQUzt0nV7bocDy72r2lI33QL3gkDNGkod22EsYl04sMf0qLb8luNKTOmgF/eDEDP5BFNwoBKH441w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=18" + } + }, + "packages/migrate-tool/node_modules/@esbuild/win32-arm64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.25.12.tgz", + "integrity": "sha512-rMmLrur64A7+DKlnSuwqUdRKyd3UE7oPJZmnljqEptesKM8wx9J8gx5u0+9Pq0fQQW8vqeKebwNXdfOyP+8Bsg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "packages/migrate-tool/node_modules/@esbuild/win32-ia32": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.25.12.tgz", + "integrity": "sha512-HkqnmmBoCbCwxUKKNPBixiWDGCpQGVsrQfJoVGYLPT41XWF8lHuE5N6WhVia2n4o5QK5M4tYr21827fNhi4byQ==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "packages/migrate-tool/node_modules/@esbuild/win32-x64": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.25.12.tgz", + "integrity": "sha512-alJC0uCZpTFrSL0CCDjcgleBXPnCrEAhTBILpeAp7M/OFgoqtAetfBzX0xM00MUsVVPpVjlPuMbREqnZCXaTnA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=18" + } + }, + "packages/migrate-tool/node_modules/@rollup/plugin-inject": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/@rollup/plugin-inject/-/plugin-inject-5.0.5.tgz", + "integrity": "sha512-2+DEJbNBoPROPkgTDNe8/1YXWcqxbN5DTjASVIOx8HS+pITXushyNiBV56RB08zuptzz8gT3YfkqriTBVycepg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/pluginutils": "^5.0.1", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.3" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "packages/migrate-tool/node_modules/@rollup/pluginutils": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.3.0.tgz", + "integrity": "sha512-5EdhGZtnu3V88ces7s53hhfK5KSASnJZv8Lulpc04cWO3REESroJXg73DFsOmgbU2BhwV0E20bu2IDZb3VKW4Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0", + "estree-walker": "^2.0.2", + "picomatch": "^4.0.2" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0" + }, + "peerDependenciesMeta": { + "rollup": { + "optional": true + } + } + }, + "packages/migrate-tool/node_modules/@types/estree": { + "version": "1.0.9", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz", + "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==", + "dev": true, + "license": "MIT" + }, + "packages/migrate-tool/node_modules/@types/node": { + "version": "22.19.19", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.19.19.tgz", + "integrity": "sha512-dyh/xO2Fh5bYrfWaaqGrRQQGkNdmYw6AmaAUvYeUMNTWQtvb796ikLdmTchRmOlOiIJ1TDXfWgVx1QkUlQ6Hew==", + "dev": true, + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "packages/migrate-tool/node_modules/esbuild": { + "version": "0.25.12", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.25.12.tgz", + "integrity": "sha512-bbPBYYrtZbkt6Os6FiTLCTFxvq4tt3JKall1vRwshA3fdVztsLAatFaZobhkBC8/BrPetoa0oksYoKXoG4ryJg==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=18" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.25.12", + "@esbuild/android-arm": "0.25.12", + "@esbuild/android-arm64": "0.25.12", + "@esbuild/android-x64": "0.25.12", + "@esbuild/darwin-arm64": "0.25.12", + "@esbuild/darwin-x64": "0.25.12", + "@esbuild/freebsd-arm64": "0.25.12", + "@esbuild/freebsd-x64": "0.25.12", + "@esbuild/linux-arm": "0.25.12", + "@esbuild/linux-arm64": "0.25.12", + "@esbuild/linux-ia32": "0.25.12", + "@esbuild/linux-loong64": "0.25.12", + "@esbuild/linux-mips64el": "0.25.12", + "@esbuild/linux-ppc64": "0.25.12", + "@esbuild/linux-riscv64": "0.25.12", + "@esbuild/linux-s390x": "0.25.12", + "@esbuild/linux-x64": "0.25.12", + "@esbuild/netbsd-arm64": "0.25.12", + "@esbuild/netbsd-x64": "0.25.12", + "@esbuild/openbsd-arm64": "0.25.12", + "@esbuild/openbsd-x64": "0.25.12", + "@esbuild/openharmony-arm64": "0.25.12", + "@esbuild/sunos-x64": "0.25.12", + "@esbuild/win32-arm64": "0.25.12", + "@esbuild/win32-ia32": "0.25.12", + "@esbuild/win32-x64": "0.25.12" + } + }, + "packages/migrate-tool/node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "packages/migrate-tool/node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, + "packages/migrate-tool/node_modules/picomatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-4.0.4.tgz", + "integrity": "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "packages/migrate-tool/node_modules/vite": { + "version": "6.4.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-6.4.2.tgz", + "integrity": "sha512-2N/55r4JDJ4gdrCvGgINMy+HH3iRpNIz8K6SFwVsA+JbQScLiC+clmAxBgwiSPgcG9U15QmvqCGWzMbqda5zGQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.25.0", + "fdir": "^6.4.4", + "picomatch": "^4.0.2", + "postcss": "^8.5.3", + "rollup": "^4.34.9", + "tinyglobby": "^0.2.13" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || ^20.0.0 || >=22.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", + "jiti": ">=1.21.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.16.0", + "tsx": "^4.8.1", + "yaml": "^2.4.2" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "jiti": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + }, + "tsx": { + "optional": true + }, + "yaml": { + "optional": true + } + } + }, + "packages/migrate-tool/node_modules/vite-plugin-node-polyfills": { + "version": "0.23.0", + "resolved": "https://registry.npmjs.org/vite-plugin-node-polyfills/-/vite-plugin-node-polyfills-0.23.0.tgz", + "integrity": "sha512-4n+Ys+2bKHQohPBKigFlndwWQ5fFKwaGY6muNDMTb0fSQLyBzS+jjUNRZG9sKF0S/Go4ApG6LFnUGopjkILg3w==", + "dev": true, + "license": "MIT", + "dependencies": { + "@rollup/plugin-inject": "^5.0.5", + "node-stdlib-browser": "^1.2.0" + }, + "funding": { + "url": "https://github.com/sponsors/davidmyersdev" + }, + "peerDependencies": { + "vite": "^2.0.0 || ^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0" + } + }, "packages/mobile": { "name": "@audius/mobile", - "version": "1.5.183", + "version": "1.5.184", "dependencies": { "@amplitude/analytics-react-native": "1.4.11", "@audius/common": "*", @@ -120301,23 +121937,6 @@ "node": ">=18" } }, - "packages/protocol-dashboard/node_modules/@esbuild/linux-loong64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", - "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, "packages/protocol-dashboard/node_modules/@esbuild/linux-mips64el": { "version": "0.25.12", "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", @@ -120696,16 +122315,6 @@ "integrity": "sha512-Sjsy10w6XFHDktJJdXzBJmoondAKW+LcGpRFH+9+zXEDj0cOH8BxJuZA9vUDSMAzU1YRJlsPKmZEEiTYDlICLw==", "dev": true }, - "packages/protocol-dashboard/node_modules/@types/react-dom": { - "version": "19.0.0", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.0.0.tgz", - "integrity": "sha512-1KfiQKsH1o00p9m5ag12axHQSb3FOU9H20UTrujVSkNhuCrRHiQWFqgEnTNK5ZNfnzZv8UWrnXVqCmCF9fgY3w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/react": "*" - } - }, "packages/protocol-dashboard/node_modules/@types/react-helmet": { "version": "5.0.16", "resolved": "https://registry.npmjs.org/@types/react-helmet/-/react-helmet-5.0.16.tgz", @@ -121954,20 +123563,6 @@ "integrity": "sha512-RofWgt/7fL5wP1Y7fxE7/EmTLzQVnB0ycyibJ0OOHIlJqTNzglYFxVwETOcIoJqJmpDXJ9xImDv+Fq34F/d4Dw==", "dev": true }, - "packages/protocol-dashboard/node_modules/react-chartjs-2": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/react-chartjs-2/-/react-chartjs-2-2.10.0.tgz", - "integrity": "sha512-1MjWEkUn8LLFf6GVyYUOrruJTW3yVU5hlEJOwGj3MiokuC+jH/BahjWVGAMonbe9UYbEIUbd2Rn36iVlC0Hb7w==", - "dependencies": { - "lodash": "^4.17.19", - "prop-types": "^15.7.2" - }, - "peerDependencies": { - "chart.js": "^2.3", - "react": "^0.14.0 || ^15.0.0 || ^16.0.0-beta || ^16.0.0", - "react-dom": "^0.14.0 || ^15.0.0 || ^16.0.0-beta || ^16.0.0" - } - }, "packages/protocol-dashboard/node_modules/react-markdown": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/react-markdown/-/react-markdown-10.1.0.tgz", @@ -126382,23 +127977,6 @@ "node": ">=18" } }, - "packages/web/node_modules/@esbuild/linux-loong64": { - "version": "0.25.12", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.25.12.tgz", - "integrity": "sha512-h///Lr5a9rib/v1GGqXVGzjL4TMvVTv+s1DPoxQdz7l/AYv6LDSxdIwzxkrPW438oUXiDtwM10o9PmwS/6Z0Ng==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, "packages/web/node_modules/@esbuild/linux-mips64el": { "version": "0.25.12", "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.25.12.tgz", @@ -127487,16 +129065,6 @@ "dev": true, "license": "MIT" }, - "packages/web/node_modules/@types/react-dom": { - "version": "19.0.0", - "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.0.0.tgz", - "integrity": "sha512-1KfiQKsH1o00p9m5ag12axHQSb3FOU9H20UTrujVSkNhuCrRHiQWFqgEnTNK5ZNfnzZv8UWrnXVqCmCF9fgY3w==", - "dev": true, - "license": "MIT", - "dependencies": { - "@types/react": "*" - } - }, "packages/web/node_modules/@vitejs/plugin-react-swc": { "version": "4.2.2", "resolved": "https://registry.npmjs.org/@vitejs/plugin-react-swc/-/plugin-react-swc-4.2.2.tgz", diff --git a/packages/migrate-tool/.env.example b/packages/migrate-tool/.env.example new file mode 100644 index 00000000000..b0403e548a0 --- /dev/null +++ b/packages/migrate-tool/.env.example @@ -0,0 +1,25 @@ +# ----- Frontend (Vite — must be prefixed VITE_) ----- + +# Audius developer app API key (safe to expose in browser). +# Get one at audius.co/settings → Developer Apps. +VITE_AUDIUS_API_KEY= + +# "production" (default) or "development" to point at a local protocol stack. +VITE_AUDIUS_ENVIRONMENT=production + +# ----- Backend (Vercel functions — never expose in browser) ----- + +# Audius bearer token. Backend only. Used with the API key above so the +# server can act on behalf of users who have authorized the developer app. +AUDIUS_API_KEY= +AUDIUS_BEARER_TOKEN= + +# Bearer secret that admin endpoints (list/approve/reject) require in the +# Authorization header. Pick a long random string and share it only with +# Audius team members authorized to approve migrations. +ADMIN_BEARER_TOKEN= + +# Supabase connection. The service role key bypasses RLS and must stay +# backend-only — never expose it. +SUPABASE_URL= +SUPABASE_SERVICE_ROLE_KEY= diff --git a/packages/migrate-tool/.gitignore b/packages/migrate-tool/.gitignore new file mode 100644 index 00000000000..913fe16ffb5 --- /dev/null +++ b/packages/migrate-tool/.gitignore @@ -0,0 +1,7 @@ +node_modules +dist +.vercel +.env +.env.local +*.log +.turbo diff --git a/packages/migrate-tool/README.md b/packages/migrate-tool/README.md new file mode 100644 index 00000000000..74bebf9eba5 --- /dev/null +++ b/packages/migrate-tool/README.md @@ -0,0 +1,120 @@ +# @audius/migrate-tool + +A small web tool for moving an artist's tracks from an old Audius account to a +new one — for cases where the artist has lost access to their original account +(forgotten password, lost email) and has created a new account. + +Designed to be deployed to **migrate.audius.co** on Vercel with a Supabase +database backing the request queue. + +## How it works + +1. The artist signs in with their new Audius account (OAuth via the Audius + developer app). +2. They enter the handle of their old account. The tool previews the tracks + that would be migrated. +3. They submit a migration request. The request is stored in Supabase with + `status = 'pending'`. +4. An Audius team member opens `/admin`, unlocks with the admin bearer token, + and reviews the request. Identity verification (confirming the requester + actually owns the old account) happens **out-of-band** via the usual + support channel — the tool does not enforce it. +5. On approval the backend pulls each old track's audio + artwork via the SDK + and re-uploads it on the new account using the developer app's bearer + token. Per-track results are written back to the DB and shown on the + status page. + +## Limitations + +- **Original masters**: only tracks the artist marked as **downloadable** expose + the original audio file via the public API. Other tracks migrate with the + transcoded MP3 stream, which is a lossy re-encoding rather than a bit-for-bit + copy. The track preview shows which of these applies per track. +- **No identity verification in-tool**: anyone signed in can request migration + of any handle. The approver is responsible for verifying the requester owns + the old account before approving. Don't approve a request without + confirming identity through a separate channel. +- **Old account is not modified**: the tracks are re-created on the new + account. The originals on the old account are untouched (the tool has no + authority over the old account). +- **No social-graph preservation**: plays, favorites, reposts, and comments + on the old tracks do not carry over. + +## Deploy + +### 1. Supabase + +Create a project, then run the SQL in `supabase/migrations/0001_init.sql`. + +You'll need: + +- `SUPABASE_URL` — project URL +- `SUPABASE_SERVICE_ROLE_KEY` — backend-only key (do not expose to the browser) + +### 2. Audius developer app + +Create a developer app at → Developer Apps. You'll +get an **API Key** and a **Bearer Token**. + +- `VITE_AUDIUS_API_KEY` — the API key (safe in the browser; baked into the build) +- `AUDIUS_API_KEY` — same API key, for the backend +- `AUDIUS_BEARER_TOKEN` — backend-only; grants the app permission to act on + behalf of users who have authorized it via OAuth + +You'll also need to whitelist the deployment's OAuth redirect URI in the dev +app's settings (e.g. `https://migrate.audius.co/`). + +### 3. Admin token + +- `ADMIN_BEARER_TOKEN` — pick a long random string. Share it only with team + members authorized to approve migrations. + +### 4. Vercel + +```sh +cd packages/migrate-tool +npx vercel link +npx vercel env add VITE_AUDIUS_API_KEY +npx vercel env add AUDIUS_API_KEY +npx vercel env add AUDIUS_BEARER_TOKEN +npx vercel env add ADMIN_BEARER_TOKEN +npx vercel env add SUPABASE_URL +npx vercel env add SUPABASE_SERVICE_ROLE_KEY +npx vercel --prod +``` + +Then add `migrate.audius.co` as a domain in the Vercel project. + +## Local development + +```sh +cp .env.example .env.local +# Fill in the values, then: +npm install +npm run dev +``` + +The Vite dev server runs at `http://localhost:5180`. To exercise the API +functions locally, run them with `npx vercel dev` instead. + +## Approving a request + +1. Go to `https://migrate.audius.co/admin`. +2. Paste the `ADMIN_BEARER_TOKEN` to unlock. +3. Review the request — especially the old handle and the track list. +4. **Verify the requester owns the old account through your usual support + channel.** This is the only safeguard against migration abuse. +5. Click **Approve & execute**. The backend runs the migration synchronously + (Vercel function timeout is set to 5 minutes in `vercel.json`). + +## Files + +- `src/` — Vite + React SPA (home / status / admin pages) +- `api/` — Vercel serverless functions + - `api/requests/index.ts` — `POST /api/requests` (create) + - `api/requests/[id].ts` — `GET /api/requests/:id` (status) + - `api/admin/requests.ts` — `GET /api/admin/requests` (list, bearer-gated) + - `api/admin/approve.ts` — `POST /api/admin/approve?id=…` (bearer-gated) + - `api/admin/reject.ts` — `POST /api/admin/reject?id=…` (bearer-gated) + - `api/_lib/migrate.ts` — migration worker +- `supabase/migrations/0001_init.sql` — DB schema diff --git a/packages/migrate-tool/api/_lib/audius.ts b/packages/migrate-tool/api/_lib/audius.ts new file mode 100644 index 00000000000..62da71f84cd --- /dev/null +++ b/packages/migrate-tool/api/_lib/audius.ts @@ -0,0 +1,37 @@ +import { + createSdkWithServices, + type AudiusSdkWithServices +} from '@audius/sdk' + +let serverSdk: AudiusSdkWithServices | null = null + +/** + * Server-side SDK initialized with the developer app's API key + bearer + * token. The bearer token grants the app permission to act on behalf of + * any user who has authorized it via OAuth. + * + * Per the SDK README: "Bearer Token — backend only. Grants your app the + * ability to act on behalf of users who have authorized it. Never expose + * this in browser or mobile code." + * + * We use createSdkWithServices (rather than the public sdk() factory) so + * sdk.tracks is the wrapped TracksApi with friendly helpers like + * getTrackStreamUrl, getTrackDownloadUrl, and the createTrack overload + * that handles audio + image upload + trackCid in one call. + */ +export function getServerSDK(): AudiusSdkWithServices { + if (serverSdk) return serverSdk + const apiKey = process.env.AUDIUS_API_KEY + const bearerToken = process.env.AUDIUS_BEARER_TOKEN + if (!apiKey || !bearerToken) { + throw new Error( + 'AUDIUS_API_KEY and AUDIUS_BEARER_TOKEN must be set on the server.' + ) + } + serverSdk = createSdkWithServices({ + apiKey, + bearerToken, + appName: 'AudiusTrackMigration' + }) + return serverSdk +} diff --git a/packages/migrate-tool/api/_lib/auth.ts b/packages/migrate-tool/api/_lib/auth.ts new file mode 100644 index 00000000000..576b4535a9a --- /dev/null +++ b/packages/migrate-tool/api/_lib/auth.ts @@ -0,0 +1,34 @@ +import type { VercelRequest, VercelResponse } from '@vercel/node' + +/** + * Constant-time check that the incoming Authorization header matches the + * admin bearer token. Returns true if authorized, otherwise writes a 401 + * response and returns false — callers can early-return. + */ +export function requireAdmin( + req: VercelRequest, + res: VercelResponse +): boolean { + const expected = process.env.ADMIN_BEARER_TOKEN + if (!expected) { + res.status(500).json({ error: 'ADMIN_BEARER_TOKEN not configured.' }) + return false + } + const header = req.headers.authorization ?? '' + const match = /^Bearer\s+(.+)$/.exec(header) + const token = match?.[1] ?? '' + if (!constantTimeEquals(token, expected)) { + res.status(401).json({ error: 'Unauthorized.' }) + return false + } + return true +} + +function constantTimeEquals(a: string, b: string): boolean { + if (a.length !== b.length) return false + let mismatch = 0 + for (let i = 0; i < a.length; i++) { + mismatch |= a.charCodeAt(i) ^ b.charCodeAt(i) + } + return mismatch === 0 +} diff --git a/packages/migrate-tool/api/_lib/migrate.ts b/packages/migrate-tool/api/_lib/migrate.ts new file mode 100644 index 00000000000..5746bebdcb6 --- /dev/null +++ b/packages/migrate-tool/api/_lib/migrate.ts @@ -0,0 +1,145 @@ +import type { Genre, Mood } from '@audius/sdk' + +import { getServerSDK } from './audius' +import { getSupabase, TABLE } from './supabase' +import type { DbRow, TrackResult } from './types' + +/** + * Fetch a URL into a Blob with a reasonable timeout. Used for pulling the + * old track's audio and artwork before re-uploading them to the new owner. + */ +async function fetchBlob(url: string, timeoutMs = 90_000): Promise { + const controller = new AbortController() + const timer = setTimeout(() => controller.abort(), timeoutMs) + try { + const res = await fetch(url, { signal: controller.signal }) + if (!res.ok) throw new Error(`Fetch ${res.status} ${res.statusText} (${url})`) + return await res.blob() + } finally { + clearTimeout(timer) + } +} + +function filenameFromUrl(url: string, fallback: string): string { + try { + const u = new URL(url) + const last = u.pathname.split('/').filter(Boolean).pop() + return last || fallback + } catch { + return fallback + } +} + +function blobToFile(blob: Blob, name: string): File { + return new File([blob], name, { type: blob.type || 'application/octet-stream' }) +} + +/** + * Run the migration for one DB row. Updates the row in-place with per-track + * results as it goes, and sets the final status when done. + * + * Limitation: only tracks flagged as downloadable expose the original + * audio file. Other tracks fall back to the transcoded mp3 stream, which + * is a lossy re-encoding rather than a bit-for-bit copy. + */ +export async function executeMigration(row: DbRow): Promise { + const supabase = getSupabase() + const sdk = getServerSDK() + + await supabase + .from(TABLE) + .update({ status: 'running' }) + .eq('id', row.id) + + const results: TrackResult[] = row.tracks.map((t) => ({ + oldTrackId: t.trackId, + status: 'pending' + })) + + const persistResults = async () => { + await supabase + .from(TABLE) + .update({ results }) + .eq('id', row.id) + } + + let anyFailed = false + + for (let i = 0; i < row.tracks.length; i++) { + const preview = row.tracks[i]! + try { + const trackRes = await sdk.tracks.getTrack({ trackId: preview.trackId }) + const track = trackRes.data + if (!track) throw new Error('Track not found on source account.') + + const audioUrl = preview.isDownloadable + ? await sdk.tracks.getTrackDownloadUrl({ trackId: preview.trackId }) + : await sdk.tracks.getTrackStreamUrl({ trackId: preview.trackId }) + + const audioBlob = await fetchBlob(audioUrl) + const audioFile = blobToFile( + audioBlob, + filenameFromUrl(audioUrl, `${preview.trackId}.mp3`) + ) + + let imageFile: File | undefined + const artworkUrl = + track.artwork?._1000x1000 ?? + track.artwork?._480x480 ?? + track.artwork?._150x150 + if (artworkUrl) { + const imageBlob = await fetchBlob(artworkUrl) + imageFile = blobToFile( + imageBlob, + filenameFromUrl(artworkUrl, 'artwork.jpg') + ) + } + + const upload = await sdk.tracks.createTrack({ + userId: row.new_user_id, + audioFile, + imageFile, + // The generated type requires trackCid here, but the wrapped + // createTrack populates it from the audio upload response. See + // TracksApi.createTrack → populateTrackMetadataWithUploadResponseV2. + // @ts-expect-error trackCid is set by the SDK after audio upload + metadata: { + title: track.title, + genre: track.genre as Genre, + description: track.description ?? undefined, + mood: (track.mood as Mood | undefined) ?? undefined, + tags: track.tags ?? undefined, + isrc: track.isrc ?? undefined, + iswc: track.iswc ?? undefined, + license: track.license ?? undefined + } + }) + + results[i] = { + oldTrackId: preview.trackId, + newTrackId: upload.trackId, + status: 'success' + } + } catch (e) { + anyFailed = true + results[i] = { + oldTrackId: preview.trackId, + status: 'failed', + error: e instanceof Error ? e.message : String(e) + } + } + await persistResults() + } + + await supabase + .from(TABLE) + .update({ + status: anyFailed ? 'failed' : 'completed', + results, + completed_at: new Date().toISOString(), + failure_reason: anyFailed + ? 'One or more tracks failed to migrate. See per-track results.' + : null + }) + .eq('id', row.id) +} diff --git a/packages/migrate-tool/api/_lib/serialize.ts b/packages/migrate-tool/api/_lib/serialize.ts new file mode 100644 index 00000000000..5a3c0239936 --- /dev/null +++ b/packages/migrate-tool/api/_lib/serialize.ts @@ -0,0 +1,18 @@ +import type { DbRow } from './types' + +export function rowToResponse(row: DbRow) { + return { + id: row.id, + newUserId: row.new_user_id, + newUserHandle: row.new_user_handle, + oldHandle: row.old_handle, + status: row.status, + tracks: row.tracks, + results: row.results ?? [], + rejectionReason: row.rejection_reason, + failureReason: row.failure_reason, + createdAt: row.created_at, + approvedAt: row.approved_at, + completedAt: row.completed_at + } +} diff --git a/packages/migrate-tool/api/_lib/supabase.ts b/packages/migrate-tool/api/_lib/supabase.ts new file mode 100644 index 00000000000..d73bb00db77 --- /dev/null +++ b/packages/migrate-tool/api/_lib/supabase.ts @@ -0,0 +1,20 @@ +import { createClient, type SupabaseClient } from '@supabase/supabase-js' + +let client: SupabaseClient | null = null + +export function getSupabase(): SupabaseClient { + if (client) return client + const url = process.env.SUPABASE_URL + const key = process.env.SUPABASE_SERVICE_ROLE_KEY + if (!url || !key) { + throw new Error( + 'SUPABASE_URL and SUPABASE_SERVICE_ROLE_KEY must be set on the server.' + ) + } + client = createClient(url, key, { + auth: { persistSession: false, autoRefreshToken: false } + }) + return client +} + +export const TABLE = 'migration_requests' diff --git a/packages/migrate-tool/api/_lib/types.ts b/packages/migrate-tool/api/_lib/types.ts new file mode 100644 index 00000000000..38c43ececae --- /dev/null +++ b/packages/migrate-tool/api/_lib/types.ts @@ -0,0 +1,38 @@ +export type RequestStatus = + | 'pending' + | 'approved' + | 'running' + | 'completed' + | 'failed' + | 'rejected' + +export type TrackPreview = { + trackId: string + title: string + genre?: string | null + durationSec?: number | null + artworkUrl?: string | null + isDownloadable: boolean +} + +export type TrackResult = { + oldTrackId: string + newTrackId?: string + status: 'pending' | 'success' | 'failed' + error?: string +} + +export type DbRow = { + id: string + new_user_id: string + new_user_handle: string + old_handle: string + status: RequestStatus + tracks: TrackPreview[] + results: TrackResult[] | null + rejection_reason: string | null + failure_reason: string | null + created_at: string + approved_at: string | null + completed_at: string | null +} diff --git a/packages/migrate-tool/api/admin/approve.ts b/packages/migrate-tool/api/admin/approve.ts new file mode 100644 index 00000000000..f73143f2332 --- /dev/null +++ b/packages/migrate-tool/api/admin/approve.ts @@ -0,0 +1,64 @@ +import type { VercelRequest, VercelResponse } from '@vercel/node' + +import { requireAdmin } from '../_lib/auth' +import { executeMigration } from '../_lib/migrate' +import { getSupabase, TABLE } from '../_lib/supabase' +import type { DbRow } from '../_lib/types' + +export default async function handler(req: VercelRequest, res: VercelResponse) { + if (req.method !== 'POST') { + res.setHeader('Allow', 'POST') + res.status(405).json({ error: 'Method not allowed' }) + return + } + if (!requireAdmin(req, res)) return + + const id = String(req.query.id ?? '').trim() + if (!id) { + res.status(400).json({ error: 'id is required.' }) + return + } + + const supabase = getSupabase() + + // Conditionally flip pending → approved so a double-approval is a no-op. + const { data: claimed, error: claimError } = await supabase + .from(TABLE) + .update({ + status: 'approved', + approved_at: new Date().toISOString() + }) + .eq('id', id) + .eq('status', 'pending') + .select('*') + .maybeSingle() + + if (claimError) { + res.status(500).json({ error: claimError.message }) + return + } + if (!claimed) { + res.status(409).json({ error: 'Request is not pending.' }) + return + } + + try { + await executeMigration(claimed) + } catch (e) { + await supabase + .from(TABLE) + .update({ + status: 'failed', + failure_reason: e instanceof Error ? e.message : String(e), + completed_at: new Date().toISOString() + }) + .eq('id', id) + res.status(500).json({ + error: 'Migration failed.', + message: e instanceof Error ? e.message : String(e) + }) + return + } + + res.status(200).json({ ok: true }) +} diff --git a/packages/migrate-tool/api/admin/reject.ts b/packages/migrate-tool/api/admin/reject.ts new file mode 100644 index 00000000000..2beb4b00dae --- /dev/null +++ b/packages/migrate-tool/api/admin/reject.ts @@ -0,0 +1,39 @@ +import type { VercelRequest, VercelResponse } from '@vercel/node' + +import { requireAdmin } from '../_lib/auth' +import { getSupabase, TABLE } from '../_lib/supabase' + +export default async function handler(req: VercelRequest, res: VercelResponse) { + if (req.method !== 'POST') { + res.setHeader('Allow', 'POST') + res.status(405).json({ error: 'Method not allowed' }) + return + } + if (!requireAdmin(req, res)) return + + const id = String(req.query.id ?? '').trim() + if (!id) { + res.status(400).json({ error: 'id is required.' }) + return + } + + const body = (req.body ?? {}) as { reason?: string } + const reason = String(body.reason ?? '').trim() || null + + const supabase = getSupabase() + const { error } = await supabase + .from(TABLE) + .update({ + status: 'rejected', + rejection_reason: reason + }) + .eq('id', id) + .eq('status', 'pending') + + if (error) { + res.status(500).json({ error: error.message }) + return + } + + res.status(200).json({ ok: true }) +} diff --git a/packages/migrate-tool/api/admin/requests.ts b/packages/migrate-tool/api/admin/requests.ts new file mode 100644 index 00000000000..acc0c536e01 --- /dev/null +++ b/packages/migrate-tool/api/admin/requests.ts @@ -0,0 +1,31 @@ +import type { VercelRequest, VercelResponse } from '@vercel/node' + +import { requireAdmin } from '../_lib/auth' +import { rowToResponse } from '../_lib/serialize' +import { getSupabase, TABLE } from '../_lib/supabase' +import type { DbRow } from '../_lib/types' + +export default async function handler(req: VercelRequest, res: VercelResponse) { + if (req.method !== 'GET') { + res.setHeader('Allow', 'GET') + res.status(405).json({ error: 'Method not allowed' }) + return + } + if (!requireAdmin(req, res)) return + + const supabase = getSupabase() + const { data, error } = await supabase + .from(TABLE) + .select('*') + .order('created_at', { ascending: false }) + .limit(200) + + if (error) { + res.status(500).json({ error: error.message }) + return + } + + res.status(200).json({ + requests: ((data ?? []) as DbRow[]).map(rowToResponse) + }) +} diff --git a/packages/migrate-tool/api/requests/[id].ts b/packages/migrate-tool/api/requests/[id].ts new file mode 100644 index 00000000000..a391540e4d9 --- /dev/null +++ b/packages/migrate-tool/api/requests/[id].ts @@ -0,0 +1,37 @@ +import type { VercelRequest, VercelResponse } from '@vercel/node' + +import { rowToResponse } from '../_lib/serialize' +import { getSupabase, TABLE } from '../_lib/supabase' +import type { DbRow } from '../_lib/types' + +export default async function handler(req: VercelRequest, res: VercelResponse) { + if (req.method !== 'GET') { + res.setHeader('Allow', 'GET') + res.status(405).json({ error: 'Method not allowed' }) + return + } + + const id = String(req.query.id ?? '').trim() + if (!id) { + res.status(400).json({ error: 'id is required.' }) + return + } + + const supabase = getSupabase() + const { data, error } = await supabase + .from(TABLE) + .select('*') + .eq('id', id) + .maybeSingle() + + if (error) { + res.status(500).json({ error: error.message }) + return + } + if (!data) { + res.status(404).json({ error: 'Request not found.' }) + return + } + + res.status(200).json(rowToResponse(data)) +} diff --git a/packages/migrate-tool/api/requests/index.ts b/packages/migrate-tool/api/requests/index.ts new file mode 100644 index 00000000000..b1f19204381 --- /dev/null +++ b/packages/migrate-tool/api/requests/index.ts @@ -0,0 +1,52 @@ +import type { VercelRequest, VercelResponse } from '@vercel/node' + +import { getSupabase, TABLE } from '../_lib/supabase' +import type { TrackPreview } from '../_lib/types' + +type Body = { + newUserId?: string + newUserHandle?: string + oldHandle?: string + tracks?: TrackPreview[] +} + +export default async function handler(req: VercelRequest, res: VercelResponse) { + if (req.method !== 'POST') { + res.setHeader('Allow', 'POST') + res.status(405).json({ error: 'Method not allowed' }) + return + } + + const body = (req.body ?? {}) as Body + const newUserId = String(body.newUserId ?? '').trim() + const newUserHandle = String(body.newUserHandle ?? '').trim() + const oldHandle = String(body.oldHandle ?? '').trim().replace(/^@/, '') + const tracks = Array.isArray(body.tracks) ? body.tracks : [] + + if (!newUserId || !newUserHandle || !oldHandle || tracks.length === 0) { + res.status(400).json({ + error: 'newUserId, newUserHandle, oldHandle, and tracks are required.' + }) + return + } + + const supabase = getSupabase() + const { data, error } = await supabase + .from(TABLE) + .insert({ + new_user_id: newUserId, + new_user_handle: newUserHandle, + old_handle: oldHandle, + tracks, + status: 'pending' + }) + .select('id') + .single() + + if (error || !data) { + res.status(500).json({ error: error?.message ?? 'Insert failed.' }) + return + } + + res.status(201).json({ id: data.id }) +} diff --git a/packages/migrate-tool/index.html b/packages/migrate-tool/index.html new file mode 100644 index 00000000000..152560ef2de --- /dev/null +++ b/packages/migrate-tool/index.html @@ -0,0 +1,12 @@ + + + + + + Audius Track Migration + + +
+ + + diff --git a/packages/migrate-tool/package.json b/packages/migrate-tool/package.json new file mode 100644 index 00000000000..d22888023fa --- /dev/null +++ b/packages/migrate-tool/package.json @@ -0,0 +1,33 @@ +{ + "name": "@audius/migrate-tool", + "version": "0.1.0", + "private": true, + "type": "module", + "description": "Track migration tool — moves an artist's tracks from an old Audius account to a new one. Designed to be deployed to migrate.audius.co.", + "scripts": { + "dev": "vite", + "build": "tsc -b && vite build", + "preview": "vite preview", + "typecheck": "tsc -b --noEmit", + "lint": "echo 'no lint configured yet'", + "verify": "npm run typecheck" + }, + "dependencies": { + "@audius/sdk": "file:../sdk", + "@supabase/supabase-js": "^2.45.0", + "react": "19.0.0", + "react-dom": "19.0.0" + }, + "devDependencies": { + "@types/node": "^22.10.0", + "@types/react": "19.0.0", + "@types/react-dom": "19.0.0", + "@vercel/node": "^3.2.0", + "@vitejs/plugin-react": "^4.2.2", + "buffer": "^6.0.3", + "process": "^0.11.10", + "typescript": "^5.0.4", + "vite": "^6.0.0", + "vite-plugin-node-polyfills": "^0.23.0" + } +} diff --git a/packages/migrate-tool/src/App.tsx b/packages/migrate-tool/src/App.tsx new file mode 100644 index 00000000000..d051ddc971e --- /dev/null +++ b/packages/migrate-tool/src/App.tsx @@ -0,0 +1,67 @@ +import { useEffect, useState } from 'react' + +import { Admin } from './pages/Admin' +import { Home } from './pages/Home' +import { Status } from './pages/Status' + +type Route = + | { page: 'home' } + | { page: 'status'; requestId: string } + | { page: 'admin' } + +function parseRoute(): Route { + const url = new URL(window.location.href) + if (url.pathname.startsWith('/admin')) return { page: 'admin' } + if (url.pathname.startsWith('/status')) { + return { page: 'status', requestId: url.searchParams.get('id') ?? '' } + } + return { page: 'home' } +} + +export function App() { + const [route, setRoute] = useState(() => parseRoute()) + + useEffect(() => { + const handler = () => setRoute(parseRoute()) + window.addEventListener('popstate', handler) + return () => window.removeEventListener('popstate', handler) + }, []) + + const navigate = (path: string) => { + window.history.pushState({}, '', path) + setRoute(parseRoute()) + } + + return ( +
+
+

+ { e.preventDefault(); navigate('/') }}> + Audius Track Migration + +

+

+ Move tracks from an old Audius account to a new one. Each migration + requires approval from an Audius team member. +

+
+ +
+ {route.page === 'home' && } + {route.page === 'status' && ( + + )} + {route.page === 'admin' && } +
+ + +
+ ) +} diff --git a/packages/migrate-tool/src/config.ts b/packages/migrate-tool/src/config.ts new file mode 100644 index 00000000000..905fb375508 --- /dev/null +++ b/packages/migrate-tool/src/config.ts @@ -0,0 +1,17 @@ +const apiKey = + typeof import.meta !== 'undefined' && + import.meta.env?.VITE_AUDIUS_API_KEY != null + ? String(import.meta.env.VITE_AUDIUS_API_KEY).trim() + : undefined + +const environment = + typeof import.meta !== 'undefined' && + import.meta.env?.VITE_AUDIUS_ENVIRONMENT === 'development' + ? ('development' as const) + : ('production' as const) + +export const config = { + apiKey, + environment, + isConfigured: Boolean(apiKey) +} diff --git a/packages/migrate-tool/src/main.tsx b/packages/migrate-tool/src/main.tsx new file mode 100644 index 00000000000..12ab679c9fc --- /dev/null +++ b/packages/migrate-tool/src/main.tsx @@ -0,0 +1,14 @@ +import { StrictMode } from 'react' +import { createRoot } from 'react-dom/client' + +import { App } from './App' +import './styles.css' + +const root = document.getElementById('root') +if (!root) throw new Error('Missing #root element') + +createRoot(root).render( + + + +) diff --git a/packages/migrate-tool/src/pages/Admin.tsx b/packages/migrate-tool/src/pages/Admin.tsx new file mode 100644 index 00000000000..09451520665 --- /dev/null +++ b/packages/migrate-tool/src/pages/Admin.tsx @@ -0,0 +1,211 @@ +import { useCallback, useEffect, useState } from 'react' + +import type { MigrationRequest } from '../types' + +const TOKEN_STORAGE_KEY = 'audius-migrate-admin-token' + +export function Admin() { + const [token, setToken] = useState( + () => sessionStorage.getItem(TOKEN_STORAGE_KEY) ?? '' + ) + const [authenticated, setAuthenticated] = useState(false) + const [requests, setRequests] = useState(null) + const [loading, setLoading] = useState(false) + const [error, setError] = useState(null) + const [busyId, setBusyId] = useState(null) + + const fetchList = useCallback( + async (bearerToken: string) => { + setLoading(true) + setError(null) + try { + const res = await fetch('/api/admin/requests', { + headers: { Authorization: `Bearer ${bearerToken}` } + }) + if (res.status === 401) { + throw new Error('Invalid admin token.') + } + if (!res.ok) { + throw new Error(`Server returned ${res.status}`) + } + const body = (await res.json()) as { requests: MigrationRequest[] } + setRequests(body.requests) + setAuthenticated(true) + sessionStorage.setItem(TOKEN_STORAGE_KEY, bearerToken) + } catch (e) { + setError(e instanceof Error ? e.message : 'Failed to load.') + setAuthenticated(false) + } finally { + setLoading(false) + } + }, + [] + ) + + useEffect(() => { + if (token) fetchList(token) + // Run once on mount with the persisted token (if any). Subsequent + // refreshes happen through the explicit "Refresh" button. + // eslint-disable-next-line react-hooks/exhaustive-deps + }, []) + + const handleUnlock = useCallback( + (e: React.FormEvent) => { + e.preventDefault() + fetchList(token) + }, + [token, fetchList] + ) + + const handleApprove = useCallback( + async (id: string) => { + if (!confirm('Approve this migration? The tracks will be re-uploaded on the new account.')) return + setBusyId(id) + try { + const res = await fetch( + `/api/admin/approve?id=${encodeURIComponent(id)}`, + { method: 'POST', headers: { Authorization: `Bearer ${token}` } } + ) + if (!res.ok) throw new Error(`Approve failed: ${res.status}`) + await fetchList(token) + } catch (e) { + setError(e instanceof Error ? e.message : 'Approve failed.') + } finally { + setBusyId(null) + } + }, + [token, fetchList] + ) + + const handleReject = useCallback( + async (id: string) => { + const reason = prompt('Rejection reason (shown to the requester):') + if (reason == null) return + setBusyId(id) + try { + const res = await fetch( + `/api/admin/reject?id=${encodeURIComponent(id)}`, + { + method: 'POST', + headers: { + Authorization: `Bearer ${token}`, + 'Content-Type': 'application/json' + }, + body: JSON.stringify({ reason }) + } + ) + if (!res.ok) throw new Error(`Reject failed: ${res.status}`) + await fetchList(token) + } catch (e) { + setError(e instanceof Error ? e.message : 'Reject failed.') + } finally { + setBusyId(null) + } + }, + [token, fetchList] + ) + + if (!authenticated) { + return ( +
+

Admin

+ {error &&
{error}
} +
+ + setToken(e.target.value)} + autoComplete="off" + /> +
+ +
+
+
+ ) + } + + return ( + <> + {error &&
{error}
} +
+
+

Requests

+ +
+
+ + {(requests ?? []).length === 0 && ( +
No requests yet.
+ )} + + {(requests ?? []).map((req) => ( +
+
+
+
+ @{req.oldHandle} → @{req.newUserHandle} +
+
+ {req.tracks.length} track{req.tracks.length === 1 ? '' : 's'} + {' · '} + {req.id} + {' · '} + {new Date(req.createdAt).toLocaleString()} +
+
+ {req.status} +
+ +
    + {req.tracks.slice(0, 5).map((t) => ( +
  • + {t.artworkUrl ? ( + + ) : ( +
    + )} +
    +
    {t.title}
    +
    + {t.genre ?? 'Unknown'} + {' · '} + {t.isDownloadable ? 'original audio' : 'transcoded mp3'} +
    +
    +
  • + ))} + {req.tracks.length > 5 && ( +
  • …and {req.tracks.length - 5} more
  • + )} +
+ + {req.status === 'pending' && ( +
+ + +
+ )} +
+ ))} + + ) +} diff --git a/packages/migrate-tool/src/pages/Home.tsx b/packages/migrate-tool/src/pages/Home.tsx new file mode 100644 index 00000000000..2d39d1ada98 --- /dev/null +++ b/packages/migrate-tool/src/pages/Home.tsx @@ -0,0 +1,255 @@ +import { useCallback, useEffect, useState } from 'react' + +import type { User } from '@audius/sdk' + +import { config } from '../config' +import { getSDK } from '../sdk' +import type { TrackPreview } from '../types' + +type Props = { + navigate: (path: string) => void +} + +async function formatApiError(reason: unknown): Promise { + if (reason != null && typeof reason === 'object' && 'response' in reason) { + const res = (reason as { response: Response }).response + if (res != null && typeof res.text === 'function') { + try { + const body = await res.text() + return `API ${res.status}: ${body || res.statusText || 'Unknown'}` + } catch { + return `API ${res.status}` + } + } + } + return reason instanceof Error ? reason.message : 'Request failed' +} + +export function Home({ navigate }: Props) { + const [profile, setProfile] = useState(null) + const [oldHandle, setOldHandle] = useState('') + const [tracks, setTracks] = useState(null) + const [loading, setLoading] = useState(false) + const [submitting, setSubmitting] = useState(false) + const [error, setError] = useState(null) + + useEffect(() => { + const sdkInstance = getSDK() + + if (sdkInstance.oauth.hasRedirectResult()) { + setLoading(true) + sdkInstance.oauth + .handleRedirect() + .then(() => sdkInstance.oauth.getUser()) + .then((user) => setProfile(user)) + .catch(async (e) => setError(await formatApiError(e))) + .finally(() => setLoading(false)) + return + } + + sdkInstance.oauth.isAuthenticated().then((authenticated) => { + if (!authenticated) return + sdkInstance.oauth + .getUser() + .then((user) => setProfile(user)) + .catch(() => { + // expired session — fall back to sign-in screen + }) + }) + }, []) + + const handleSignIn = useCallback(async () => { + setError(null) + setLoading(true) + try { + const sdkInstance = getSDK() + await sdkInstance.oauth.login({ scope: 'write', display: 'popup' }) + const user = await sdkInstance.oauth.getUser() + setProfile(user) + } catch (e) { + setError(await formatApiError(e)) + } finally { + setLoading(false) + } + }, []) + + const handleSignOut = useCallback(async () => { + await getSDK().oauth.logout() + setProfile(null) + setTracks(null) + setOldHandle('') + }, []) + + const handlePreview = useCallback(async () => { + setError(null) + setTracks(null) + const handle = oldHandle.trim().replace(/^@/, '') + if (!handle) { + setError('Enter the old account handle.') + return + } + setLoading(true) + try { + const sdkInstance = getSDK() + const res = await sdkInstance.users.getTracksByUserHandle({ + handle, + limit: 100 + }) + const data = res.data ?? [] + const previews: TrackPreview[] = data.map((t) => ({ + trackId: t.id, + title: t.title, + genre: t.genre ?? null, + durationSec: t.duration ?? null, + artworkUrl: t.artwork?._150x150 ?? t.artwork?._480x480 ?? null, + isDownloadable: Boolean(t.isDownloadable) + })) + if (previews.length === 0) { + setError(`No tracks found for @${handle}.`) + } + setTracks(previews) + } catch (e) { + setError(await formatApiError(e)) + } finally { + setLoading(false) + } + }, [oldHandle]) + + const handleSubmit = useCallback(async () => { + if (!profile || !tracks || tracks.length === 0) return + setSubmitting(true) + setError(null) + try { + const res = await fetch('/api/requests', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ + newUserId: profile.id, + newUserHandle: profile.handle, + oldHandle: oldHandle.trim().replace(/^@/, ''), + tracks + }) + }) + if (!res.ok) { + throw new Error(`Server returned ${res.status}: ${await res.text()}`) + } + const body = (await res.json()) as { id: string } + navigate(`/status?id=${encodeURIComponent(body.id)}`) + } catch (e) { + setError(e instanceof Error ? e.message : 'Submission failed.') + } finally { + setSubmitting(false) + } + }, [profile, tracks, oldHandle, navigate]) + + if (!config.isConfigured) { + return ( +
+

Setup required

+

+ VITE_AUDIUS_API_KEY is not set. Create an Audius + developer app at + audius.co/settings → Developer Apps and add the key to your + environment. +

+
+ ) + } + + return ( + <> + {error &&
{error}
} + +
+

Step 1 — Sign in with your new account

+ {profile ? ( +
+
+ Signed in as @{profile.handle} +
+ user_id: {profile.id} +
+
+ +
+ ) : ( + + )} +
+ + {profile && ( +
+

Step 2 — Old account handle

+

+ The handle of the account whose tracks you want migrated. +

+ + setOldHandle(e.target.value)} + disabled={loading || submitting} + /> +
+ +
+
+ )} + + {tracks && tracks.length > 0 && ( +
+

Step 3 — Review & submit

+
+ Heads up: tracks marked downloadable will migrate + with the original audio file. Tracks not flagged as downloadable + will migrate with the transcoded MP3 stream only — the original + master isn't accessible to the developer app. +
+

+ {tracks.length} track{tracks.length === 1 ? '' : 's'} will be + re-created on @{profile?.handle} once an Audius team member + approves this request. +

+
    + {tracks.map((t) => ( +
  • + {t.artworkUrl ? ( + + ) : ( +
    + )} +
    +
    {t.title}
    +
    + {t.genre ?? 'Unknown genre'} + {' · '} + {t.isDownloadable ? 'original audio' : 'transcoded mp3'} +
    +
    +
  • + ))} +
+
+ +
+
+ )} + + ) +} diff --git a/packages/migrate-tool/src/pages/Status.tsx b/packages/migrate-tool/src/pages/Status.tsx new file mode 100644 index 00000000000..731cd84383a --- /dev/null +++ b/packages/migrate-tool/src/pages/Status.tsx @@ -0,0 +1,168 @@ +import { useEffect, useState } from 'react' + +import type { MigrationRequest } from '../types' + +type Props = { + requestId: string + navigate: (path: string) => void +} + +const POLL_INTERVAL_MS = 5000 + +export function Status({ requestId, navigate }: Props) { + const [request, setRequest] = useState(null) + const [error, setError] = useState(null) + + useEffect(() => { + if (!requestId) return + let cancelled = false + + const load = async () => { + try { + const res = await fetch( + `/api/requests/${encodeURIComponent(requestId)}` + ) + if (!res.ok) { + throw new Error(`Server returned ${res.status}`) + } + const body = (await res.json()) as MigrationRequest + if (!cancelled) { + setRequest(body) + setError(null) + } + } catch (e) { + if (!cancelled) { + setError(e instanceof Error ? e.message : 'Failed to load.') + } + } + } + + load() + const id = setInterval(load, POLL_INTERVAL_MS) + return () => { + cancelled = true + clearInterval(id) + } + }, [requestId]) + + if (!requestId) { + return ( +
+

Missing request id

+ +
+ ) + } + + if (error && !request) { + return ( +
+
{error}
+ +
+ ) + } + + if (!request) { + return
Loading…
+ } + + return ( + <> +
+
+
+
+ Request id +
+ {request.id} +
+ {request.status} +
+

+ Migrating from @{request.oldHandle} to{' '} + @{request.newUserHandle} +

+
+ + {request.status === 'pending' && ( +
+

+ Waiting for an Audius team member to review. You can leave this + page open — it polls every few seconds. The team will reach out + via your usual support channel if they need to verify your + identity before approving. +

+
+ )} + + {request.status === 'rejected' && ( +
+

+ Rejected.{' '} + {request.rejectionReason ?? 'No reason given.'} +

+ +
+ )} + + {request.status === 'failed' && ( +
+

+ Migration failed.{' '} + {request.failureReason ?? 'See per-track results below.'} +

+
+ )} + +
+

Tracks ({request.tracks.length})

+
    + {request.tracks.map((t) => { + const result = request.results?.find( + (r) => r.oldTrackId === t.trackId + ) + return ( +
  • + {t.artworkUrl ? ( + + ) : ( +
    + )} +
    +
    {t.title}
    +
    + {t.genre ?? 'Unknown genre'} + {result?.newTrackId && ( + <> + {' · '} + new id: {result.newTrackId} + + )} + {result?.error && ( + <> + {' · '} + {result.error} + + )} +
    +
    + {result && ( + + {result.status} + + )} +
  • + ) + })} +
+
+ + ) +} diff --git a/packages/migrate-tool/src/sdk.ts b/packages/migrate-tool/src/sdk.ts new file mode 100644 index 00000000000..af5c841ff51 --- /dev/null +++ b/packages/migrate-tool/src/sdk.ts @@ -0,0 +1,22 @@ +import type { AudiusSdk } from '@audius/sdk' +import { sdk } from '@audius/sdk' + +import { config } from './config' + +const APP_NAME = 'AudiusTrackMigration' + +let sdkInstance: AudiusSdk | null = null + +export function getSDK(): AudiusSdk { + if (sdkInstance) return sdkInstance + const redirectUri = window.location.origin + window.location.pathname + sdkInstance = config.apiKey + ? sdk({ + appName: APP_NAME, + apiKey: config.apiKey, + redirectUri, + environment: config.environment + }) + : sdk({ appName: APP_NAME, redirectUri, environment: config.environment }) + return sdkInstance +} diff --git a/packages/migrate-tool/src/styles.css b/packages/migrate-tool/src/styles.css new file mode 100644 index 00000000000..431826dc0f6 --- /dev/null +++ b/packages/migrate-tool/src/styles.css @@ -0,0 +1,190 @@ +:root { + color-scheme: light; + font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; + --color-bg: #f7f7fa; + --color-card: #ffffff; + --color-border: #e1e1e8; + --color-text: #1d1d28; + --color-muted: #6b6b78; + --color-primary: #7e1bcc; + --color-primary-hover: #6a18ad; + --color-danger: #c43c3c; + --color-success: #2e8b57; +} + +* { box-sizing: border-box; } + +body { + margin: 0; + background: var(--color-bg); + color: var(--color-text); +} + +.app { + max-width: 820px; + margin: 0 auto; + padding: 32px 20px 64px; +} + +.header h1 { + margin: 0 0 8px; + font-size: 28px; +} + +.header h1 a { + color: inherit; + text-decoration: none; +} + +.tagline { + margin: 0 0 32px; + color: var(--color-muted); +} + +.card { + background: var(--color-card); + border: 1px solid var(--color-border); + border-radius: 12px; + padding: 24px; + margin-bottom: 20px; +} + +.card h2 { margin-top: 0; } + +label { + display: block; + font-weight: 600; + margin-bottom: 8px; +} + +input[type='text'], +input[type='password'] { + width: 100%; + padding: 10px 12px; + border: 1px solid var(--color-border); + border-radius: 8px; + font-size: 15px; + background: white; +} + +button { + background: var(--color-primary); + color: white; + border: none; + padding: 10px 18px; + border-radius: 8px; + font-size: 15px; + font-weight: 600; + cursor: pointer; +} + +button:hover:not(:disabled) { background: var(--color-primary-hover); } +button:disabled { opacity: 0.5; cursor: not-allowed; } + +button.secondary { + background: transparent; + color: var(--color-primary); + border: 1px solid var(--color-primary); +} + +button.danger { background: var(--color-danger); } +button.danger:hover:not(:disabled) { background: #a82c2c; } + +.error { + background: #fbe5e5; + color: var(--color-danger); + padding: 12px 14px; + border-radius: 8px; + margin-bottom: 16px; +} + +.note { + background: #fff8e1; + border-left: 4px solid #f6c343; + padding: 12px 14px; + border-radius: 4px; + font-size: 14px; + margin-bottom: 16px; +} + +.track-list { + list-style: none; + padding: 0; + margin: 16px 0 0; +} + +.track-list li { + display: flex; + gap: 12px; + align-items: center; + padding: 10px 0; + border-bottom: 1px solid var(--color-border); +} + +.track-list li:last-child { border-bottom: none; } + +.track-art { + width: 48px; + height: 48px; + border-radius: 6px; + background: #ddd; + object-fit: cover; + flex-shrink: 0; +} + +.track-meta { + flex: 1; + min-width: 0; +} + +.track-title { + font-weight: 600; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +.track-sub { + color: var(--color-muted); + font-size: 13px; +} + +.badge { + display: inline-block; + padding: 2px 8px; + border-radius: 4px; + font-size: 12px; + font-weight: 600; + background: #eaeaea; + color: var(--color-muted); +} + +.badge.pending { background: #fff3cd; color: #856404; } +.badge.approved { background: #d4edda; color: #155724; } +.badge.running { background: #d1ecf1; color: #0c5460; } +.badge.completed { background: #d4edda; color: #155724; } +.badge.failed { background: #f8d7da; color: #721c24; } +.badge.rejected { background: #f8d7da; color: #721c24; } +.badge.success { background: #d4edda; color: #155724; } + +.row { + display: flex; + gap: 12px; + align-items: center; + justify-content: space-between; +} + +.muted { color: var(--color-muted); } + +.footer { + margin-top: 48px; + padding-top: 16px; + border-top: 1px solid var(--color-border); + text-align: center; + font-size: 13px; +} + +.footer a { + color: var(--color-muted); + text-decoration: none; +} diff --git a/packages/migrate-tool/src/types.ts b/packages/migrate-tool/src/types.ts new file mode 100644 index 00000000000..ca78625a8ab --- /dev/null +++ b/packages/migrate-tool/src/types.ts @@ -0,0 +1,38 @@ +export type RequestStatus = + | 'pending' + | 'approved' + | 'running' + | 'completed' + | 'failed' + | 'rejected' + +export type TrackPreview = { + trackId: string + title: string + genre?: string | null + durationSec?: number | null + artworkUrl?: string | null + isDownloadable: boolean +} + +export type TrackResult = { + oldTrackId: string + newTrackId?: string + status: 'pending' | 'success' | 'failed' + error?: string +} + +export type MigrationRequest = { + id: string + newUserId: string + newUserHandle: string + oldHandle: string + status: RequestStatus + tracks: TrackPreview[] + results?: TrackResult[] + createdAt: string + approvedAt?: string | null + completedAt?: string | null + rejectionReason?: string | null + failureReason?: string | null +} diff --git a/packages/migrate-tool/src/vite-env.d.ts b/packages/migrate-tool/src/vite-env.d.ts new file mode 100644 index 00000000000..88a0543401d --- /dev/null +++ b/packages/migrate-tool/src/vite-env.d.ts @@ -0,0 +1,10 @@ +/// + +interface ImportMetaEnv { + readonly VITE_AUDIUS_API_KEY?: string + readonly VITE_AUDIUS_ENVIRONMENT?: 'development' | 'production' +} + +interface ImportMeta { + readonly env: ImportMetaEnv +} diff --git a/packages/migrate-tool/supabase/migrations/0001_init.sql b/packages/migrate-tool/supabase/migrations/0001_init.sql new file mode 100644 index 00000000000..4fa6f4142c5 --- /dev/null +++ b/packages/migrate-tool/supabase/migrations/0001_init.sql @@ -0,0 +1,31 @@ +-- Migration requests for the Audius track migration tool. +-- One row per submitted migration; status drives the approval workflow. + +create extension if not exists "pgcrypto"; + +create table if not exists migration_requests ( + id uuid primary key default gen_random_uuid(), + new_user_id text not null, + new_user_handle text not null, + old_handle text not null, + status text not null default 'pending' + check (status in ('pending','approved','running','completed','failed','rejected')), + tracks jsonb not null, + results jsonb, + rejection_reason text, + failure_reason text, + created_at timestamptz not null default now(), + approved_at timestamptz, + completed_at timestamptz +); + +create index if not exists migration_requests_status_idx + on migration_requests (status, created_at desc); + +create index if not exists migration_requests_new_user_idx + on migration_requests (new_user_id); + +-- RLS stays off: every API call hits the service role key from a Vercel +-- function. Browsers never touch this table directly. Re-enable RLS and +-- write policies if that ever changes. +alter table migration_requests disable row level security; diff --git a/packages/migrate-tool/tsconfig.api.json b/packages/migrate-tool/tsconfig.api.json new file mode 100644 index 00000000000..10018e14887 --- /dev/null +++ b/packages/migrate-tool/tsconfig.api.json @@ -0,0 +1,19 @@ +{ + "compilerOptions": { + "composite": true, + "target": "ES2022", + "lib": ["ES2022"], + "module": "ESNext", + "moduleResolution": "bundler", + "skipLibCheck": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true, + "types": ["node"] + }, + "include": ["api"] +} diff --git a/packages/migrate-tool/tsconfig.app.json b/packages/migrate-tool/tsconfig.app.json new file mode 100644 index 00000000000..e2397ee341e --- /dev/null +++ b/packages/migrate-tool/tsconfig.app.json @@ -0,0 +1,21 @@ +{ + "compilerOptions": { + "composite": true, + "target": "ES2020", + "useDefineForClassFields": true, + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "module": "ESNext", + "skipLibCheck": true, + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "react-jsx", + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src"] +} diff --git a/packages/migrate-tool/tsconfig.json b/packages/migrate-tool/tsconfig.json new file mode 100644 index 00000000000..0bbb4ce2393 --- /dev/null +++ b/packages/migrate-tool/tsconfig.json @@ -0,0 +1,7 @@ +{ + "files": [], + "references": [ + { "path": "./tsconfig.app.json" }, + { "path": "./tsconfig.api.json" } + ] +} diff --git a/packages/migrate-tool/vercel.json b/packages/migrate-tool/vercel.json new file mode 100644 index 00000000000..e61b33bd813 --- /dev/null +++ b/packages/migrate-tool/vercel.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://openapi.vercel.sh/vercel.json", + "buildCommand": "npm run build", + "outputDirectory": "dist", + "rewrites": [ + { "source": "/((?!api/).*)", "destination": "/index.html" } + ], + "functions": { + "api/admin/approve.ts": { + "maxDuration": 300 + } + } +} diff --git a/packages/migrate-tool/vite.config.ts b/packages/migrate-tool/vite.config.ts new file mode 100644 index 00000000000..09faa669d66 --- /dev/null +++ b/packages/migrate-tool/vite.config.ts @@ -0,0 +1,25 @@ +import react from '@vitejs/plugin-react' +import { defineConfig } from 'vite' +import { nodePolyfills } from 'vite-plugin-node-polyfills' + +export default defineConfig({ + plugins: [ + react(), + // The SDK references Buffer / process at runtime — keep these polyfilled + // so it works in the browser without a separate global shim. + nodePolyfills({ + include: ['buffer', 'process'], + globals: { Buffer: true, process: true } + }) + ], + server: { + port: 5180, + open: true + }, + build: { + outDir: 'dist', + // The SDK pulls in heavy deps (viem, solana). Pin the chunk limit a bit + // higher so the build doesn't spam warnings — it's a leaf app, not a lib. + chunkSizeWarningLimit: 1500 + } +}) From da1756bee3358a337dd156c172dc606b31cba68a Mon Sep 17 00:00:00 2001 From: Dylan Audius Date: Tue, 26 May 2026 15:56:52 -0700 Subject: [PATCH 2/2] fix(migrate-tool): migrate originals via orig_file_cid with mp3 fallback MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Audio source resolution now walks a prioritized candidate list per track (raw orig_file_cid → CID mirror → gated download URL → transcoded mp3 stream), trying each until one fetch succeeds. Original masters now flow through for every indexed track that still has its bytes on the network, not just the ones the artist toggled downloadable. The stream fallback guarantees every approved request migrates at least the lossy mp3 so a single pruned original never aborts the run. Preview UI relabeled to "mp3 only" for the rare tracks where no original is on file. Co-Authored-By: Claude Opus 4.7 (1M context) --- packages/migrate-tool/README.md | 17 ++- packages/migrate-tool/api/_lib/migrate.ts | 126 +++++++++++++++++++--- packages/migrate-tool/api/_lib/types.ts | 1 + packages/migrate-tool/src/pages/Admin.tsx | 2 +- packages/migrate-tool/src/pages/Home.tsx | 14 +-- packages/migrate-tool/src/types.ts | 1 + 6 files changed, 138 insertions(+), 23 deletions(-) diff --git a/packages/migrate-tool/README.md b/packages/migrate-tool/README.md index 74bebf9eba5..3fef40ae196 100644 --- a/packages/migrate-tool/README.md +++ b/packages/migrate-tool/README.md @@ -26,10 +26,19 @@ database backing the request queue. ## Limitations -- **Original masters**: only tracks the artist marked as **downloadable** expose - the original audio file via the public API. Other tracks migrate with the - transcoded MP3 stream, which is a lossy re-encoding rather than a bit-for-bit - copy. The track preview shows which of these applies per track. +- **Audio source selection**: the worker tries sources in priority order + for each track and uses the first one that succeeds: + 1. Raw `orig_file_cid` fetched from the rendezvous-primary validator node + (`/content/{cid}`) — bit-for-bit copy of the original master, + regardless of whether the artist marked the track downloadable. + 2. Same CID, mirror node — covers the case where the primary node is + unhealthy. + 3. Gated download URL — original master, only available when the track + is flagged downloadable. + 4. Transcoded MP3 stream — lossy, but always reachable. Acts as the + last-resort fallback so every approved request gets a content copy. + Sources 1 and 2 are skipped when `orig_file_cid` is missing or + `is_original_available` is false (legacy uploads or pruned originals). - **No identity verification in-tool**: anyone signed in can request migration of any handle. The approver is responsible for verifying the requester owns the old account before approving. Don't approve a request without diff --git a/packages/migrate-tool/api/_lib/migrate.ts b/packages/migrate-tool/api/_lib/migrate.ts index 5746bebdcb6..4506bd511cf 100644 --- a/packages/migrate-tool/api/_lib/migrate.ts +++ b/packages/migrate-tool/api/_lib/migrate.ts @@ -1,4 +1,4 @@ -import type { Genre, Mood } from '@audius/sdk' +import type { AudiusSdkWithServices, Genre, Mood, Track } from '@audius/sdk' import { getServerSDK } from './audius' import { getSupabase, TABLE } from './supabase' @@ -34,13 +34,115 @@ function blobToFile(blob: Blob, name: string): File { return new File([blob], name, { type: blob.type || 'application/octet-stream' }) } +type AudioCandidate = { + label: string + resolve: () => Promise<{ url: string; filename: string }> +} + +/** + * Build a prioritized list of source URLs for the track's audio. Each + * candidate is tried in order, so a hard failure on the original (pruned + * bytes, unhealthy node, missing index) silently falls through to the + * next best source. The MP3 stream is always last so every track ends up + * with at least *some* content copy. + */ +function buildAudioCandidates( + sdk: AudiusSdkWithServices, + track: Track, + trackId: string, + isDownloadablePreview: boolean +): AudioCandidate[] { + const candidates: AudioCandidate[] = [] + + // 1. Original master via raw CID. Validator nodes serve content-addressed + // files at /content/{cid} with no gating, so this works regardless of + // isDownloadable. Tried first because it's a bit-for-bit copy. + if (track.origFileCid && track.isOriginalAvailable !== false) { + const cid = track.origFileCid + candidates.push({ + label: `orig-cid:${cid}`, + resolve: async () => { + const nodes = sdk.services.storageNodeSelector.getNodes(cid) + if (nodes.length === 0) { + throw new Error('No storage node available for original file CID.') + } + // Pick the rendezvous-primary; the mirror candidate below handles + // failover when this fetch throws. + return { + url: `${nodes[0]}/content/${cid}`, + filename: track.origFilename ?? cid + } + } + }) + + // Same CID, mirrors. Each mirror becomes its own candidate so a single + // unhealthy node doesn't take down the migration. + candidates.push({ + label: `orig-cid-mirrors:${cid}`, + resolve: async () => { + const nodes = sdk.services.storageNodeSelector.getNodes(cid) + const mirror = nodes[1] ?? nodes[2] + if (!mirror) { + throw new Error('No mirror available for original file CID.') + } + return { + url: `${mirror}/content/${cid}`, + filename: track.origFilename ?? cid + } + } + }) + } + + // 2. Gated download URL — only works for tracks the artist flagged as + // downloadable, but the bytes are still the original master. + if (isDownloadablePreview) { + candidates.push({ + label: 'download-url', + resolve: async () => { + const url = await sdk.tracks.getTrackDownloadUrl({ trackId }) + return { url, filename: filenameFromUrl(url, `${trackId}.audio`) } + } + }) + } + + // 3. Transcoded MP3 stream — always available, lossy. Ensures every + // track migrates with *something* even if the original is gone. + candidates.push({ + label: 'stream-url', + resolve: async () => { + const url = await sdk.tracks.getTrackStreamUrl({ trackId }) + return { url, filename: filenameFromUrl(url, `${trackId}.mp3`) } + } + }) + + return candidates +} + +async function fetchAudio( + candidates: AudioCandidate[] +): Promise<{ blob: Blob; filename: string; source: string }> { + const errors: string[] = [] + for (const candidate of candidates) { + try { + const { url, filename } = await candidate.resolve() + const blob = await fetchBlob(url) + return { blob, filename, source: candidate.label } + } catch (e) { + errors.push(`${candidate.label}: ${e instanceof Error ? e.message : String(e)}`) + } + } + throw new Error(`No audio source succeeded. Tried: ${errors.join(' | ')}`) +} + /** * Run the migration for one DB row. Updates the row in-place with per-track * results as it goes, and sets the final status when done. * - * Limitation: only tracks flagged as downloadable expose the original - * audio file. Other tracks fall back to the transcoded mp3 stream, which - * is a lossy re-encoding rather than a bit-for-bit copy. + * Audio source order (see buildAudioCandidates): the original master via + * raw CID, then mirrors, then the gated download URL (downloadable tracks + * only), then the transcoded MP3 stream. Each candidate is tried until + * one succeeds — guarantees every track migrates with the highest-fidelity + * copy that's still reachable. */ export async function executeMigration(row: DbRow): Promise { const supabase = getSupabase() @@ -72,15 +174,15 @@ export async function executeMigration(row: DbRow): Promise { const track = trackRes.data if (!track) throw new Error('Track not found on source account.') - const audioUrl = preview.isDownloadable - ? await sdk.tracks.getTrackDownloadUrl({ trackId: preview.trackId }) - : await sdk.tracks.getTrackStreamUrl({ trackId: preview.trackId }) - - const audioBlob = await fetchBlob(audioUrl) - const audioFile = blobToFile( - audioBlob, - filenameFromUrl(audioUrl, `${preview.trackId}.mp3`) + const candidates = buildAudioCandidates( + sdk, + track, + preview.trackId, + preview.isDownloadable ) + const { blob: audioBlob, filename: audioFilename } = + await fetchAudio(candidates) + const audioFile = blobToFile(audioBlob, audioFilename) let imageFile: File | undefined const artworkUrl = diff --git a/packages/migrate-tool/api/_lib/types.ts b/packages/migrate-tool/api/_lib/types.ts index 38c43ececae..90e7647a57f 100644 --- a/packages/migrate-tool/api/_lib/types.ts +++ b/packages/migrate-tool/api/_lib/types.ts @@ -13,6 +13,7 @@ export type TrackPreview = { durationSec?: number | null artworkUrl?: string | null isDownloadable: boolean + hasOriginal: boolean } export type TrackResult = { diff --git a/packages/migrate-tool/src/pages/Admin.tsx b/packages/migrate-tool/src/pages/Admin.tsx index 09451520665..49bb50c5dca 100644 --- a/packages/migrate-tool/src/pages/Admin.tsx +++ b/packages/migrate-tool/src/pages/Admin.tsx @@ -177,7 +177,7 @@ export function Admin() {
{t.genre ?? 'Unknown'} {' · '} - {t.isDownloadable ? 'original audio' : 'transcoded mp3'} + {t.hasOriginal ? 'original audio' : 'mp3 only'}
diff --git a/packages/migrate-tool/src/pages/Home.tsx b/packages/migrate-tool/src/pages/Home.tsx index 2d39d1ada98..44bd6bb4328 100644 --- a/packages/migrate-tool/src/pages/Home.tsx +++ b/packages/migrate-tool/src/pages/Home.tsx @@ -102,7 +102,8 @@ export function Home({ navigate }: Props) { genre: t.genre ?? null, durationSec: t.duration ?? null, artworkUrl: t.artwork?._150x150 ?? t.artwork?._480x480 ?? null, - isDownloadable: Boolean(t.isDownloadable) + isDownloadable: Boolean(t.isDownloadable), + hasOriginal: Boolean(t.origFileCid) && t.isOriginalAvailable !== false })) if (previews.length === 0) { setError(`No tracks found for @${handle}.`) @@ -211,10 +212,11 @@ export function Home({ navigate }: Props) {

Step 3 — Review & submit

- Heads up: tracks marked downloadable will migrate - with the original audio file. Tracks not flagged as downloadable - will migrate with the transcoded MP3 stream only — the original - master isn't accessible to the developer app. + Heads up: the worker migrates the original + uploaded master whenever it's still on the network, regardless + of whether the track is marked downloadable. The few tracks + tagged below as mp3 only have no original on file and + will migrate with the transcoded MP3 stream.

{tracks.length} track{tracks.length === 1 ? '' : 's'} will be @@ -234,7 +236,7 @@ export function Home({ navigate }: Props) {

{t.genre ?? 'Unknown genre'} {' · '} - {t.isDownloadable ? 'original audio' : 'transcoded mp3'} + {t.hasOriginal ? 'original audio' : 'mp3 only'}
diff --git a/packages/migrate-tool/src/types.ts b/packages/migrate-tool/src/types.ts index ca78625a8ab..a0eae2a13ac 100644 --- a/packages/migrate-tool/src/types.ts +++ b/packages/migrate-tool/src/types.ts @@ -13,6 +13,7 @@ export type TrackPreview = { durationSec?: number | null artworkUrl?: string | null isDownloadable: boolean + hasOriginal: boolean } export type TrackResult = {