diff --git a/CLAUDE.md b/CLAUDE.md index 985e4a3..9b32b1f 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -39,7 +39,7 @@ Top-level `defineCommand` in `src/index.ts` wires eleven subcommands (`cloud`, ` **Auth.** Every command calls `resolveAuth({ apiKeyFlag })` (`src/utils/auth.ts`) once and threads the returned `AuthContext` into gateways/services. `ApiGateway` and `fetchCompatibilityData` spread `auth.headers` into fetch headers — they no longer accept a raw api key. Precedence: `--api-key` flag > `DEVICE_CLOUD_API_KEY` env > stored session from `dcd login`. `resolveAuth` refreshes expiring Supabase sessions via `CliAuthGateway.refresh` and rewrites the config atomically. -**Config store.** `dcd login` writes `$XDG_CONFIG_HOME/dcd/config.json` (fallback `~/.dcd/config.json`, 0600). Shape: `{ version, env, api_url, supabase_url, session: { access_token, refresh_token, expires_at, user_email, user_id }, current_org_id, current_org_name }`. `DCD_CONFIG_DIR` overrides the directory (used by tests). The login command itself (`src/commands/login.ts`) uses PKCE (S256) with a server rendezvous — no loopback server: it mints `state`, `code_verifier`, and `code_challenge`, opens `/cli-login?state=...&code_challenge=...`, then polls the dcd API's `POST /cli-login/claim` with `{state, code_verifier}` while the frontend POSTs the session to `POST /cli-login/handoff`; the API verifies `sha256(verifier) === challenge` and returns the session. After claiming, the CLI fetches `/me/orgs` and prompts for an org (the same picker `dcd switch-org` uses). The frontend lives in `../dcd/frontend/app/features/cli-login/CliLoginScreen.tsx`. +**Config store.** `dcd login` writes `$XDG_CONFIG_HOME/dcd/config.json` (fallback `~/.dcd/config.json`, 0600). Shape: `{ version, env, api_url, supabase_url, session: { access_token, refresh_token, expires_at, user_email, user_id }, current_org_id, current_org_name }`. `DCD_CONFIG_DIR` overrides the directory (used by tests). The login command itself (`src/commands/login.ts`) uses PKCE (S256) with a server rendezvous — no loopback server: it mints `state`, `code_verifier`, and `code_challenge`, opens `/cli-login?state=...&code_challenge=...`, then polls the dcd API's `POST /cli-login/claim` with `{state, code_verifier}` while the frontend POSTs proof of identity (the browser's access token) to `POST /cli-login/handoff`; the API verifies the token, **mints a dedicated Supabase session for the CLI** (its own refresh-token family — sharing the browser's tokens caused "Invalid Refresh Token: Already Used" whenever either client rotated them), stores it keyed by state, then on claim verifies `sha256(verifier) === challenge` and returns it. After claiming, the CLI fetches `/me/orgs` and prompts for an org (the same picker `dcd switch-org` uses). The frontend lives in `../dcd/frontend/app/features/cli-login/CliLoginScreen.tsx`. **Cross-repo auth surface.** The dcd API's `ApiKeyGuard` accepts either `x-app-api-key` (existing) or `Authorization: Bearer ` + `x-dcd-org: `. For Bearer it verifies the JWT, checks `user_org_profile` membership, and injects the org's api_key back into the request headers so existing `@Headers(APP_API_KEY_HEADER)` controller code keeps working unchanged. `dcd switch-org` calls `GET /me/orgs`, a JWT-only endpoint at `../dcd/api/src/apps/me/me.controller.ts`. diff --git a/package.json b/package.json index a724be3..9467a5f 100644 --- a/package.json +++ b/package.json @@ -32,7 +32,7 @@ "eslint": "^10.5.0", "eslint-config-prettier": "^10.1.8", "eslint-plugin-import": "^2.32.0", - "eslint-plugin-unicorn": "^68.0.0", + "eslint-plugin-unicorn": "^69.0.0", "husky": "^9.1.7", "mocha": "^11.7.6", "prettier": "^3.8.4", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5db7755..43ccd66 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -37,13 +37,13 @@ importers: dependencies: '@clack/prompts': specifier: ^1.6.0 - version: 1.6.0 + version: 1.7.0 '@modelcontextprotocol/sdk': specifier: ^1.29.0 version: 1.29.0(zod@4.4.3) '@supabase/supabase-js': specifier: ^2.108.2 - version: 2.108.2 + version: 2.110.2 bplist-parser: specifier: ^0.3.2 version: 0.3.2 @@ -55,7 +55,7 @@ importers: version: 0.2.2 js-yaml: specifier: ^5.0.0 - version: 5.0.0 + version: 5.2.1 node-apk: specifier: ^1.2.1 version: 1.2.1 @@ -67,7 +67,7 @@ importers: version: 5.0.0 tar: specifier: ^7.5.16 - version: 7.5.16 + version: 7.5.19 tus-js-client: specifier: ^4.3.1 version: 4.3.1 @@ -80,7 +80,7 @@ importers: devDependencies: '@eslint/js': specifier: ^10.0.1 - version: 10.0.1(eslint@10.5.0) + version: 10.0.1(eslint@10.6.0) '@types/chai': specifier: ^5.2.3 version: 5.2.3 @@ -92,7 +92,7 @@ importers: version: 10.0.10 '@types/node': specifier: ^26.0.0 - version: 26.0.0 + version: 26.1.1 '@types/yazl': specifier: ^3.3.1 version: 3.3.1 @@ -101,16 +101,16 @@ importers: version: 6.2.2 eslint: specifier: ^10.5.0 - version: 10.5.0 + version: 10.6.0 eslint-config-prettier: specifier: ^10.1.8 - version: 10.1.8(eslint@10.5.0) + version: 10.1.8(eslint@10.6.0) eslint-plugin-import: specifier: ^2.32.0 - version: 2.32.0(@typescript-eslint/parser@8.61.1(eslint@10.5.0)(typescript@6.0.3))(eslint@10.5.0) + version: 2.32.0(@typescript-eslint/parser@8.63.0(eslint@10.6.0)(typescript@6.0.3))(eslint@10.6.0) eslint-plugin-unicorn: - specifier: ^68.0.0 - version: 68.0.0(eslint@10.5.0) + specifier: ^69.0.0 + version: 69.0.0(eslint@10.6.0) husky: specifier: ^9.1.7 version: 9.1.7 @@ -119,19 +119,19 @@ importers: version: 11.7.6 prettier: specifier: ^3.8.4 - version: 3.8.4 + version: 3.9.5 shx: specifier: ^0.4.0 version: 0.4.0 tsx: specifier: ^4.22.4 - version: 4.22.4 + version: 4.23.0 typescript: specifier: ^6.0.3 version: 6.0.3 typescript-eslint: specifier: ^8.61.1 - version: 8.61.1(eslint@10.5.0)(typescript@6.0.3) + version: 8.63.0(eslint@10.6.0)(typescript@6.0.3) packages: @@ -139,12 +139,12 @@ packages: resolution: {integrity: sha512-qehxGkRj55h/ff8EMaJ+cYhyaKlHIxqYDn682wQD7RNp9UujOQsHog2uS0r2vzr4pW+sXf90NeeayjcNaX3fFg==} engines: {node: '>=6.9.0'} - '@clack/core@1.4.2': - resolution: {integrity: sha512-0Ty/1Gfm+Kb07sXcuESjyKfwEhSy4Ns1AgeEisHb/bDY5fWme0tTeTkU14T1Gmcs17YIjB/teiDe4uaCghbYqQ==} + '@clack/core@1.4.3': + resolution: {integrity: sha512-/kr3UWNtdJfxZtPgDqUOmG2pvwlmcLGheex5yiZKdwbzZJxhV+HMNR9QNmyY5cGwTNV6LrR7Jtp+KjhUAP1qBQ==} engines: {node: '>= 20.12.0'} - '@clack/prompts@1.6.0': - resolution: {integrity: sha512-EYlRokl8szrP9Z25qT5aepMdBjzBvHF9ZEhzIiUBc9guz/T31EqRgvD0QSgZcpE93xiwrr+OkB4nz0BZyF6fSA==} + '@clack/prompts@1.7.0': + resolution: {integrity: sha512-y7/yvZ2TPAnR9+jnc00klvNNLkJiXFFrQA/hlLCcxA9a2A4zQIOimyFQ9XfwYKiGD1fb5GY8vbKIIgO8d5Tb2A==} engines: {node: '>= 20.12.0'} '@esbuild/aix-ppc64@0.28.1': @@ -405,32 +405,32 @@ packages: '@rtsao/scc@1.1.0': resolution: {integrity: sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==} - '@supabase/auth-js@2.108.2': - resolution: {integrity: sha512-tNaQmBgodDZwgB40mRwVbxFy8IDYwjdpcZ0BYrWiwlULCSQoJj4QoG4zgJT7QRPXcqipefNOzvO/qAu4dF98ag==} - engines: {node: '>=20.0.0'} + '@supabase/auth-js@2.110.2': + resolution: {integrity: sha512-Qj7a6EDP+AMMQFWqGv+qFa8r6re//dk+qQI5bA0KK+PZmnI3JPu97TDeNt6SMiQ2FkklP79hP2yDFYSnA989OA==} + engines: {node: '>=22.0.0'} - '@supabase/functions-js@2.108.2': - resolution: {integrity: sha512-RNUX8EiBy3iLwAX19jtRzLyePnl11/fHcgwDHLnpKcDSXt/5qBnh3LUwAtIjT21Q66QsmNUR2esrHziLCpNubw==} - engines: {node: '>=20.0.0'} + '@supabase/functions-js@2.110.2': + resolution: {integrity: sha512-ZjjqrXpxM9/rE+eAtZxiK45EWy9EBoJQ322Q5Y75LccYQNh212neHTgXP/o4MIzmH0LNXT8UzvTZtQOfOzyoeQ==} + engines: {node: '>=22.0.0'} '@supabase/phoenix@0.4.4': resolution: {integrity: sha512-Gt0pqoXuIqX/8dvG0OKp/wMCobXNH3klNbUPBNyOfN0YA1IswrM3HyWFMOPk1Jy+BRaIyDPcFx4jLBwHNmlyfQ==} - '@supabase/postgrest-js@2.108.2': - resolution: {integrity: sha512-GQ28/Y8hk3CFmkb3kXH1h/AQx6JIYSQfO0CJMRVBcEKZoNy6C45cXAZ4fcJvRC5Id0cs6xnkUV0+c0rIocigsw==} - engines: {node: '>=20.0.0'} + '@supabase/postgrest-js@2.110.2': + resolution: {integrity: sha512-++LBmcIMwCtgO4tISQUmo9+2xkRwHQqS8ZKMCnhXLe9P8k8YQRXuMoh/RiSzQSoev8gqet0W7yOboW0cUxnt0Q==} + engines: {node: '>=22.0.0'} - '@supabase/realtime-js@2.108.2': - resolution: {integrity: sha512-aAGxCSUemZvQIibnCdvNvgaKib28I4rfrNjKbQ9cG1uBLwUsI7hVpGXgEbypCCDhLjQlDTAiJlu7rgljYUT73g==} - engines: {node: '>=20.0.0'} + '@supabase/realtime-js@2.110.2': + resolution: {integrity: sha512-z3jTOTPgyn6E3r6dVOOQ10He4yAMB2czjFw7xVdX3s16MHElna5rY1gVaePs0NIo6xvtMYbtmOXlFaFt/ePLpg==} + engines: {node: '>=22.0.0'} - '@supabase/storage-js@2.108.2': - resolution: {integrity: sha512-TVZPQxXGxY2+A6yTtm77zUHsh70lBhYUEaJL8RQC+BghcX/ygiMG/rmXrNVBce30/WAeNPa8FiG8HbqlGeV05g==} - engines: {node: '>=20.0.0'} + '@supabase/storage-js@2.110.2': + resolution: {integrity: sha512-EhsRSwSnmQefKJsAxoRUZ0hvHr92ECM8DDGAKR5z0HdoJx4heI60PjHUTruVNZxKX6XeobLGDyLud020Bw1iwg==} + engines: {node: '>=22.0.0'} - '@supabase/supabase-js@2.108.2': - resolution: {integrity: sha512-hFhnPveb5JQg4a0QYicM0swT253YHMdfeRAl2BKHOlI5VAzuHxUGSr8RbwNLYNPauWOgQMS1H8sz8bvYlgwUfQ==} - engines: {node: '>=20.0.0'} + '@supabase/supabase-js@2.110.2': + resolution: {integrity: sha512-r9q9w4ZQ6mOjh36aqUNFSisBF611vzpO8JphBESr2Q1SWvmGFQeI7Jq7Y+PaNMZ6Zszz+S2yTlJStCpnaMSnQg==} + engines: {node: '>=22.0.0'} '@types/chai@5.2.3': resolution: {integrity: sha512-Mw558oeA9fFbv65/y4mHtXDs9bPnFMZAL/jxdPFUpOHHIXX91mcgEHbS5Lahr+pwZFR8A7GQleRWeI6cGFC2UA==} @@ -456,69 +456,69 @@ packages: '@types/mocha@10.0.10': resolution: {integrity: sha512-xPyYSz1cMPnJQhl0CLMH68j3gprKZaTjG3s5Vi+fDgx+uhG9NOXwbVt52eFS8ECyXhyKcjDLCBEqBExKuiZb7Q==} - '@types/node@26.0.0': - resolution: {integrity: sha512-vf2YFi1iY9lHGwNJMs01biZFbKJkrZR1T6/MlzjhJLPdntOHLhTrDSnSVcdtvjihi4VQNlrFRIxLsDBlQpAipA==} + '@types/node@26.1.1': + resolution: {integrity: sha512-nxAkRSVkN1Y0JC1W8ky/fTfkGsMmcrRsbx+3XoZE+rMOX71kLYTV7fLXpqud1GpbpP5TuffXFqfX7fH2GgZREw==} '@types/yazl@3.3.1': resolution: {integrity: sha512-DIWfCKpsTp6hE5BDBHV3+fIL/bLUF9Bv13iDrWnMlmhQpH67buNvI291ZauQ1xcccxK3FqQ9honnXpq4R8NMuQ==} - '@typescript-eslint/eslint-plugin@8.61.1': - resolution: {integrity: sha512-ZPlVl3PB3et/59Ne0fv/sci6ZXz4T4Hp4nTJ56i/Y0gR89ARb+KphojTq6j+56E5PIezmOIOOWyY+aWQFd+IkQ==} + '@typescript-eslint/eslint-plugin@8.63.0': + resolution: {integrity: sha512-rvwSgqT+DHpWdzfSzPatRLm02a0GlESt++9iy3hLCDY4BgkaLcl8LBi9Yh7XGFBpwcBE/K3024QuXWTpbz4FfQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: - '@typescript-eslint/parser': ^8.61.1 + '@typescript-eslint/parser': ^8.63.0 eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/parser@8.61.1': - resolution: {integrity: sha512-PJ5vePq5/ognBbrIcoC5+SHO5dfpeLPzP9FpLkzWrguoYQEeeSjlJpVwOpo1JRSTEi7dRcwNy4h4dzV70PqHcg==} + '@typescript-eslint/parser@8.63.0': + resolution: {integrity: sha512-gwh4gvvlaVDKKxyfxMG+Gnu1u9X0OQBwyGLkbwB65dIzBKnxeRiJlNFqlI3zwVhNXJIs6qV7mlFCn/BIajlVig==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/project-service@8.61.1': - resolution: {integrity: sha512-PrC4JYGmR241lYnfhmKGTXkFqv8+ymbTFgSAY0fVXpY82/QkMw5TZPl+vGzuDDU2QYJk9fIDOBTntF+yDv9LEA==} + '@typescript-eslint/project-service@8.63.0': + resolution: {integrity: sha512-e5dh0/UI0ok53AlZ5wRkXCB32z/f2jUZqPR/ygAw5WYaSw8j9EoJWlS7wQjr/dmOaqWjnPIn2m+HhVPCMWGZVQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/scope-manager@8.61.1': - resolution: {integrity: sha512-L2bdIeoQS8FlKAvONAr20w6OcLXeB+qiDKbAooS9A0Ben+iSIkBef0FxqwKWYqt5sa0i4KJtxVyVmhMylKzF5w==} + '@typescript-eslint/scope-manager@8.63.0': + resolution: {integrity: sha512-uUyfMWCnDSN8bCpcrY8nGP2BLkQ9Xn0GsipcONcpIDWhwhO4ZSyHvyS14U3X75mzxWxL3I2UZIrenTzdzcJO8A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/tsconfig-utils@8.61.1': - resolution: {integrity: sha512-UN/H4di+OO7EWx2ovME+8t31YO+KVnK0RRKEHR3kOt21/Ay8BOq3M1OMvWs5vNiqcFCYGYoxK3MXPZzmMUE+yg==} + '@typescript-eslint/tsconfig-utils@8.63.0': + resolution: {integrity: sha512-sUAbkulqBAsncKnbRP3+7CtQFRKicexnj7ZwNC6ddCR7EmrXvjvdCYMJbUIqMd6lwoEriZjwLo08aS5tSjVMHg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/type-utils@8.61.1': - resolution: {integrity: sha512-GYRicKmVK0C4fsKgaACaknOUAq9Oa2kwsjnpFhFcS/5p4Ht5IP9OVLbgIgcK4SRk92nVHFluurg1lumD9dBcLw==} + '@typescript-eslint/type-utils@8.63.0': + resolution: {integrity: sha512-Nzzh/OGxVCOjObjaj1CQF2RUasyYy2Jfuh+zZ3PjLzG2fYRriAiZLib9UKtO+CpQAS3YHiAS+ckZDclwqI1TPA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/types@8.61.1': - resolution: {integrity: sha512-G+CRlPqLv7Bz1IZVs03x5K59F1veqL0EJUROAdGhKsEq8qOiRiZbI+HUojPq5l0fEGOKModD9br6lObhB8zkoA==} + '@typescript-eslint/types@8.63.0': + resolution: {integrity: sha512-xyLtl9DUBBFrcJS4x2pIqGLH68/tC2uOa4Z7pUteW09D3bXnnXUom4dyPikzWgB7llmIc1zoeI3aoUdC4rPK/Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@typescript-eslint/typescript-estree@8.61.1': - resolution: {integrity: sha512-u+oQD3BqYWPc8YV9Zab4vaJElJuwOLPRc10Jm1o/qS+6Qwen14HCWwx0Seo4LnSn2wxea2Ik8DxPt2/FHmuhrg==} + '@typescript-eslint/typescript-estree@8.63.0': + resolution: {integrity: sha512-ygBkU+B7ex5UI/gKhaqexWev79uISfIv7XQCRNYO/jmD8rGLPyWLAb3KMRT6nd8Gt9bmUBi9+iX6tBdYfOY81Q==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/utils@8.61.1': - resolution: {integrity: sha512-1+P/3Dj6jvtybE1q0HQ6yBt/gq+oKJyLdEv4HdnqasaEXRSYCAsD59mXEVQnM/ULNdQxbX77tdG4jPRjIS6knA==} + '@typescript-eslint/utils@8.63.0': + resolution: {integrity: sha512-fUKaeAvrTuQg/Tgt3nliAUSZHJM6DlCcfyEmxCvlX8kieWSStBX+5O5Fnidtc3i2JrH+9c/GL4RY2iasd/GPTA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 typescript: '>=4.8.4 <6.1.0' - '@typescript-eslint/visitor-keys@8.61.1': - resolution: {integrity: sha512-6fJ9MHWtK14C1DSkiMlHUSOmrVebL7150xZJBlJiL62jjhIA4JmOq6flwBgDxIdBKKdoiZRel+dfPD5MLfny3w==} + '@typescript-eslint/visitor-keys@8.63.0': + resolution: {integrity: sha512-UexrHGnGTpbuQHct2ExOc2ZcFbGUS9FOesCxxqdBGcpI1BxYu/LZ6U8Aq6/72XtF/qRBk9nhuGHFJIXXMhPMdw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@xmldom/xmldom@0.9.10': @@ -615,8 +615,8 @@ packages: resolution: {integrity: sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==} engines: {node: 18 || 20 || >=22} - baseline-browser-mapping@2.10.38: - resolution: {integrity: sha512-31/02mVB4yuQU6adKk5SlY6m+mxDwUq5KZkyYgnLrrKl7TEm1+3PyDtDBz2kOv/wxZz41GHsvV1A/u6RmiyBvw==} + baseline-browser-mapping@2.10.40: + resolution: {integrity: sha512-BSSLZ9/Cjjv7Gtj5B68ZzXcXUg8iOf3fme+FCuh8rC/Go+Kmh8cox7M3A8dolou16s64QjLPOSdngh7GxXvkSw==} engines: {node: '>=6.0.0'} hasBin: true @@ -658,8 +658,8 @@ packages: buffer-from@1.1.2: resolution: {integrity: sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==} - builtin-modules@5.2.0: - resolution: {integrity: sha512-02yxLeyxF4dNl6SlY6/5HfRSrSdZ/sCPoxy2kZNP5dZZX8LSAD9aE2gtJIUgWrsQTiMPl3mxESyrobSwvRGisQ==} + builtin-modules@5.3.0: + resolution: {integrity: sha512-hMQUl2bUFG339QygPM97E+mc8OY1IAchORZxm4a/frcYwKzozMzRVDBwHW0NjOqGElLm2O37AVQE8ikxlZHrMQ==} engines: {node: '>=18.20'} bytes@3.1.2: @@ -840,8 +840,8 @@ packages: ee-first@1.1.1: resolution: {integrity: sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==} - electron-to-chromium@1.5.376: - resolution: {integrity: sha512-cUVA7/RvbFTEuw/i3obUwDTRIXojaxkResf+ibByPFxjc6XK3VNtcQXV0NSbAlJ0FMjcJGgftVVB4Qo184EXvA==} + electron-to-chromium@1.5.381: + resolution: {integrity: sha512-n9Wa6yB+vDsGuA8AKbl/0z7HbvWqt5jxIdvr1IUicd0ryPrk7/xzwqLv8D9AbbvZ6avVNtXYLTfmgFHkwkyelg==} emoji-regex@8.0.0: resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==} @@ -944,8 +944,8 @@ packages: '@typescript-eslint/parser': optional: true - eslint-plugin-unicorn@68.0.0: - resolution: {integrity: sha512-mHYWvX948Q4H3bGc39bsNMxD/leOuNI+Iws9NVsoSz5VA7EGP86wnz7mZ/SPSvRhefT8L4hd8DHfDuGC+lIoCQ==} + eslint-plugin-unicorn@69.0.0: + resolution: {integrity: sha512-ZN/KtHr9hQ6AOByANSNJpsDbo/+Nn+EyQ6blK4w+dcmS/xpYkqLLfrUc+NA/wOK6vF5uEUvhn8my5B/3sruB9g==} engines: {node: '>=22'} peerDependencies: eslint: '>=10.4' @@ -962,8 +962,8 @@ packages: resolution: {integrity: sha512-tD40eHxA35h0PEIZNeIjkHoDR4YjjJp34biM0mDvplBe//mB+IHCqHDGV7pxF+7MklTvighcCPPZC7ynWyjdTA==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} - eslint@10.5.0: - resolution: {integrity: sha512-1y+7C+vi12bUK1IpZeaV3gsH9fHLBmPvYmPx42pvT/E9yG0IC8g3PUZZgp0+JLJl7ZDK0flc2gc+Aw9dpCvIsQ==} + eslint@10.6.0: + resolution: {integrity: sha512-6lVbcqSodALYo+4ELD0heG6lFiFxnLMuLkiMi2qV8LMp54N8tE8FT1GMH+ev4Ti00nFjNze2+Su6DsV5OQW3Dg==} engines: {node: ^20.19.0 || ^22.13.0 || >=24} hasBin: true peerDependencies: @@ -1154,8 +1154,8 @@ packages: deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me hasBin: true - globals@17.6.0: - resolution: {integrity: sha512-sepffkT8stwnIYbsMBpoCHJuJM5l98FUF2AnE07hfvE0m/qp3R586hw4jF4uadbhvg1ooIdzuu7CsfD2jzCaNA==} + globals@17.7.0: + resolution: {integrity: sha512-Czmyns5dUsq4seFBR/Kdydhmo8y9kC79hiSkPn0YcGtNnYWnrgt0vjrSjx9tspoDGWm2CMarffRuLjM4xUz8xg==} engines: {node: '>=18'} globalthis@1.0.4: @@ -1225,8 +1225,8 @@ packages: resolution: {integrity: sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==} engines: {node: '>= 4'} - ignore@7.0.5: - resolution: {integrity: sha512-Hs59xBNfUIunMFgWAbGX5cq6893IbWg4KnrjbYwX3tx0ztorVgTDA6B2sxf8ejHJ4wz8BqGUMYlnzNBer5NvGg==} + ignore@7.0.6: + resolution: {integrity: sha512-BAg6QkE8W+TuQLrrw0Ugr7HegXduRuuj8/ti2kSOc+jz1dmx8/WNcjr6XGnq5YpDWxFwwaavqD0+jIUOKelTsw==} engines: {node: '>= 4'} imurmurhash@0.1.4: @@ -1406,8 +1406,8 @@ packages: js-base64@3.7.8: resolution: {integrity: sha512-hNngCeKxIUQiEUN3GPJOkz4wF/YvdUdbNL9hsBcMQTkKzboD7T/q3OYOuuPZLUE6dBxSGpwhk5mwuDud7JVAow==} - js-yaml@5.0.0: - resolution: {integrity: sha512-GSvaPUbk1U+FMZ7rJzF+F8e5YVtu7KnD40et/5rBXXRBv2jCO9L3qCewvIDDdudC0QycTFlf6EAA+h3kxBsuUw==} + js-yaml@5.2.1: + resolution: {integrity: sha512-zfLtNfQqxVqq3uaTqSkh4x4hZw3KHobGUA0fJUj4wawW8bsQLTVqpHdXSIzidh7o+4lEW36tANuAGdaFx6Zgnw==} hasBin: true jsesc@3.1.0: @@ -1559,8 +1559,8 @@ packages: resolution: {integrity: sha512-LarFH0+6VfriEhqMMcLX2F7SwSXeWwnEAJEsYm5QKWchiVYVvJyV9v7UDvUv+w5HO23ZpQTXDv/GxdDdMyOuoQ==} engines: {node: '>= 6.13.0'} - node-releases@2.0.48: - resolution: {integrity: sha512-1uz8041X6LoI6ZSdZacM9lVY28vuzDlSKitnpbSNK0RfKoIJkX29NBPVEFXhnuSuEOA9Ww0xnPJ+ILWbGAv8DA==} + node-releases@2.0.50: + resolution: {integrity: sha512-J6l92tKHX6w8Jy5nO1Vuc01NoIiRGi/d6qBKVxh+IQ8Cr3b6HbVNfKiF8ZpFKufTwpwxMmce2W3iQZ861ZRyTg==} engines: {node: '>=18'} node-stream-zip@1.15.0: @@ -1666,8 +1666,8 @@ packages: resolution: {integrity: sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==} engines: {node: '>=8.6'} - picomatch@4.0.4: - resolution: {integrity: sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A==} + picomatch@4.0.5: + resolution: {integrity: sha512-RvwwcruNjI1ncT5xRakeyS9Lf8lcItv34KD+aif+VH9kduAyfYBipGh12274xtenIPZ119/R9BdTBa8gAwSh0A==} engines: {node: '>=12'} pkce-challenge@5.0.1: @@ -1690,8 +1690,8 @@ packages: resolution: {integrity: sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==} engines: {node: '>= 0.8.0'} - prettier@3.8.4: - resolution: {integrity: sha512-N2MylSdi48+5N/6S5j+maeHbUSIzzZ5uOcX5Hm4QpV8Dkb1HFjfAKTKX6yNPJQD9AhcT3ifHNB66tWTTJDi11Q==} + prettier@3.9.5: + resolution: {integrity: sha512-/FVl766LpUfB5vXgCYOYa0MeV/441Ia99AeICQIQFTY/Nw0roZwULcXpku5i1/m5kt/baz+s4Zogspd839HSMg==} engines: {node: '>=14'} hasBin: true @@ -1954,8 +1954,8 @@ packages: resolution: {integrity: sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==} engines: {node: '>= 0.4'} - tar@7.5.16: - resolution: {integrity: sha512-56adEpPMouktRlBLXiaYFFzZ/3+JXa8P9n7WbR+ibIjtviN55mEaOkiysCnPnWm+7kkui1Dn8J9l+g6zV8731w==} + tar@7.5.19: + resolution: {integrity: sha512-4LeEWl96twnS2Q7Bz4MGqgazLqO+hJN63GZxXoIqh1T3VweYD997gbU1ItNsQafqqXTXd5WFyFdReLtwvRBNiw==} engines: {node: '>=18'} tinyglobby@0.2.17: @@ -1982,8 +1982,8 @@ packages: tslib@2.8.1: resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} - tsx@4.22.4: - resolution: {integrity: sha512-X8EX+XV4QR5xCsrgxaED954zTDfY8KqlDtskKEL0cHhyS/P8b4IFOvGDQpsC9Q1XnLq915wEfwwY/zzskCtmhg==} + tsx@4.23.0: + resolution: {integrity: sha512-eUdUIaCr963q2h5u3+QwvYp0+eqPvn+egeqZUm0hwERCqqx1E3kK5ehbGCvqSE5MQAULr67ww0cA3jKc3YkM1w==} engines: {node: '>=18.0.0'} hasBin: true @@ -2015,8 +2015,8 @@ packages: resolution: {integrity: sha512-phPGCwqr2+Qo0fwniCE8e4pKnGu/yFb5nD5Y8bf0EEeiI5GklnACYA9GFy/DrAeRrKHXvHn+1SUsOWgJp6RO+g==} engines: {node: '>= 0.4'} - typescript-eslint@8.61.1: - resolution: {integrity: sha512-V7PayAfJokV3pEHgN7/v03D1SpujhRfQtYLbLIiBfDDncdg4PAiRBfoS4cnCANK4jmAPncczi59QO3afiXUlNw==} + typescript-eslint@8.63.0: + resolution: {integrity: sha512-xgwXyzG4sK9ALkBxbyGkTMMOS+imnW65iPhxCQMK83KhxyoDNW7l+IDqEf9vMdoUidHpOoS967RCq4eMiTexwQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} peerDependencies: eslint: ^8.57.0 || ^9.0.0 || ^10.0.0 @@ -2140,14 +2140,14 @@ snapshots: '@babel/helper-validator-identifier@7.29.7': {} - '@clack/core@1.4.2': + '@clack/core@1.4.3': dependencies: fast-wrap-ansi: 0.2.2 sisteransi: 1.0.5 - '@clack/prompts@1.6.0': + '@clack/prompts@1.7.0': dependencies: - '@clack/core': 1.4.2 + '@clack/core': 1.4.3 fast-string-width: 3.0.2 fast-wrap-ansi: 0.2.2 sisteransi: 1.0.5 @@ -2230,9 +2230,9 @@ snapshots: '@esbuild/win32-x64@0.28.1': optional: true - '@eslint-community/eslint-utils@4.9.1(eslint@10.5.0)': + '@eslint-community/eslint-utils@4.9.1(eslint@10.6.0)': dependencies: - eslint: 10.5.0 + eslint: 10.6.0 eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.12.2': {} @@ -2253,9 +2253,9 @@ snapshots: dependencies: '@types/json-schema': 7.0.15 - '@eslint/js@10.0.1(eslint@10.5.0)': + '@eslint/js@10.0.1(eslint@10.6.0)': optionalDependencies: - eslint: 10.5.0 + eslint: 10.6.0 '@eslint/object-schema@3.0.5': {} @@ -2336,37 +2336,37 @@ snapshots: '@rtsao/scc@1.1.0': {} - '@supabase/auth-js@2.108.2': + '@supabase/auth-js@2.110.2': dependencies: tslib: 2.8.1 - '@supabase/functions-js@2.108.2': + '@supabase/functions-js@2.110.2': dependencies: tslib: 2.8.1 '@supabase/phoenix@0.4.4': {} - '@supabase/postgrest-js@2.108.2': + '@supabase/postgrest-js@2.110.2': dependencies: tslib: 2.8.1 - '@supabase/realtime-js@2.108.2': + '@supabase/realtime-js@2.110.2': dependencies: '@supabase/phoenix': 0.4.4 tslib: 2.8.1 - '@supabase/storage-js@2.108.2': + '@supabase/storage-js@2.110.2': dependencies: iceberg-js: 0.8.1 tslib: 2.8.1 - '@supabase/supabase-js@2.108.2': + '@supabase/supabase-js@2.110.2': dependencies: - '@supabase/auth-js': 2.108.2 - '@supabase/functions-js': 2.108.2 - '@supabase/postgrest-js': 2.108.2 - '@supabase/realtime-js': 2.108.2 - '@supabase/storage-js': 2.108.2 + '@supabase/auth-js': 2.110.2 + '@supabase/functions-js': 2.110.2 + '@supabase/postgrest-js': 2.110.2 + '@supabase/realtime-js': 2.110.2 + '@supabase/storage-js': 2.110.2 '@types/chai@5.2.3': dependencies: @@ -2387,80 +2387,80 @@ snapshots: '@types/mocha@10.0.10': {} - '@types/node@26.0.0': + '@types/node@26.1.1': dependencies: undici-types: 8.3.0 '@types/yazl@3.3.1': dependencies: - '@types/node': 26.0.0 + '@types/node': 26.1.1 - '@typescript-eslint/eslint-plugin@8.61.1(@typescript-eslint/parser@8.61.1(eslint@10.5.0)(typescript@6.0.3))(eslint@10.5.0)(typescript@6.0.3)': + '@typescript-eslint/eslint-plugin@8.63.0(@typescript-eslint/parser@8.63.0(eslint@10.6.0)(typescript@6.0.3))(eslint@10.6.0)(typescript@6.0.3)': dependencies: '@eslint-community/regexpp': 4.12.2 - '@typescript-eslint/parser': 8.61.1(eslint@10.5.0)(typescript@6.0.3) - '@typescript-eslint/scope-manager': 8.61.1 - '@typescript-eslint/type-utils': 8.61.1(eslint@10.5.0)(typescript@6.0.3) - '@typescript-eslint/utils': 8.61.1(eslint@10.5.0)(typescript@6.0.3) - '@typescript-eslint/visitor-keys': 8.61.1 - eslint: 10.5.0 - ignore: 7.0.5 + '@typescript-eslint/parser': 8.63.0(eslint@10.6.0)(typescript@6.0.3) + '@typescript-eslint/scope-manager': 8.63.0 + '@typescript-eslint/type-utils': 8.63.0(eslint@10.6.0)(typescript@6.0.3) + '@typescript-eslint/utils': 8.63.0(eslint@10.6.0)(typescript@6.0.3) + '@typescript-eslint/visitor-keys': 8.63.0 + eslint: 10.6.0 + ignore: 7.0.6 natural-compare: 1.4.0 ts-api-utils: 2.5.0(typescript@6.0.3) typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.61.1(eslint@10.5.0)(typescript@6.0.3)': + '@typescript-eslint/parser@8.63.0(eslint@10.6.0)(typescript@6.0.3)': dependencies: - '@typescript-eslint/scope-manager': 8.61.1 - '@typescript-eslint/types': 8.61.1 - '@typescript-eslint/typescript-estree': 8.61.1(typescript@6.0.3) - '@typescript-eslint/visitor-keys': 8.61.1 + '@typescript-eslint/scope-manager': 8.63.0 + '@typescript-eslint/types': 8.63.0 + '@typescript-eslint/typescript-estree': 8.63.0(typescript@6.0.3) + '@typescript-eslint/visitor-keys': 8.63.0 debug: 4.4.3(supports-color@8.1.1) - eslint: 10.5.0 + eslint: 10.6.0 typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/project-service@8.61.1(typescript@6.0.3)': + '@typescript-eslint/project-service@8.63.0(typescript@6.0.3)': dependencies: - '@typescript-eslint/tsconfig-utils': 8.61.1(typescript@6.0.3) - '@typescript-eslint/types': 8.61.1 + '@typescript-eslint/tsconfig-utils': 8.63.0(typescript@6.0.3) + '@typescript-eslint/types': 8.63.0 debug: 4.4.3(supports-color@8.1.1) typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/scope-manager@8.61.1': + '@typescript-eslint/scope-manager@8.63.0': dependencies: - '@typescript-eslint/types': 8.61.1 - '@typescript-eslint/visitor-keys': 8.61.1 + '@typescript-eslint/types': 8.63.0 + '@typescript-eslint/visitor-keys': 8.63.0 - '@typescript-eslint/tsconfig-utils@8.61.1(typescript@6.0.3)': + '@typescript-eslint/tsconfig-utils@8.63.0(typescript@6.0.3)': dependencies: typescript: 6.0.3 - '@typescript-eslint/type-utils@8.61.1(eslint@10.5.0)(typescript@6.0.3)': + '@typescript-eslint/type-utils@8.63.0(eslint@10.6.0)(typescript@6.0.3)': dependencies: - '@typescript-eslint/types': 8.61.1 - '@typescript-eslint/typescript-estree': 8.61.1(typescript@6.0.3) - '@typescript-eslint/utils': 8.61.1(eslint@10.5.0)(typescript@6.0.3) + '@typescript-eslint/types': 8.63.0 + '@typescript-eslint/typescript-estree': 8.63.0(typescript@6.0.3) + '@typescript-eslint/utils': 8.63.0(eslint@10.6.0)(typescript@6.0.3) debug: 4.4.3(supports-color@8.1.1) - eslint: 10.5.0 + eslint: 10.6.0 ts-api-utils: 2.5.0(typescript@6.0.3) typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/types@8.61.1': {} + '@typescript-eslint/types@8.63.0': {} - '@typescript-eslint/typescript-estree@8.61.1(typescript@6.0.3)': + '@typescript-eslint/typescript-estree@8.63.0(typescript@6.0.3)': dependencies: - '@typescript-eslint/project-service': 8.61.1(typescript@6.0.3) - '@typescript-eslint/tsconfig-utils': 8.61.1(typescript@6.0.3) - '@typescript-eslint/types': 8.61.1 - '@typescript-eslint/visitor-keys': 8.61.1 + '@typescript-eslint/project-service': 8.63.0(typescript@6.0.3) + '@typescript-eslint/tsconfig-utils': 8.63.0(typescript@6.0.3) + '@typescript-eslint/types': 8.63.0 + '@typescript-eslint/visitor-keys': 8.63.0 debug: 4.4.3(supports-color@8.1.1) minimatch: 10.2.3 semver: 7.8.5 @@ -2470,20 +2470,20 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.61.1(eslint@10.5.0)(typescript@6.0.3)': + '@typescript-eslint/utils@8.63.0(eslint@10.6.0)(typescript@6.0.3)': dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@10.5.0) - '@typescript-eslint/scope-manager': 8.61.1 - '@typescript-eslint/types': 8.61.1 - '@typescript-eslint/typescript-estree': 8.61.1(typescript@6.0.3) - eslint: 10.5.0 + '@eslint-community/eslint-utils': 4.9.1(eslint@10.6.0) + '@typescript-eslint/scope-manager': 8.63.0 + '@typescript-eslint/types': 8.63.0 + '@typescript-eslint/typescript-estree': 8.63.0(typescript@6.0.3) + eslint: 10.6.0 typescript: 6.0.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/visitor-keys@8.61.1': + '@typescript-eslint/visitor-keys@8.63.0': dependencies: - '@typescript-eslint/types': 8.61.1 + '@typescript-eslint/types': 8.63.0 eslint-visitor-keys: 5.0.1 '@xmldom/xmldom@0.9.10': {} @@ -2591,7 +2591,7 @@ snapshots: balanced-match@4.0.4: {} - baseline-browser-mapping@2.10.38: {} + baseline-browser-mapping@2.10.40: {} big-integer@1.6.52: {} @@ -2630,17 +2630,17 @@ snapshots: browserslist@4.28.4: dependencies: - baseline-browser-mapping: 2.10.38 + baseline-browser-mapping: 2.10.40 caniuse-lite: 1.0.30001799 - electron-to-chromium: 1.5.376 - node-releases: 2.0.48 + electron-to-chromium: 1.5.381 + node-releases: 2.0.50 update-browserslist-db: 1.2.3(browserslist@4.28.4) buffer-crc32@1.0.0: {} buffer-from@1.1.2: {} - builtin-modules@5.2.0: {} + builtin-modules@5.3.0: {} bytes@3.1.2: {} @@ -2804,7 +2804,7 @@ snapshots: ee-first@1.1.1: {} - electron-to-chromium@1.5.376: {} + electron-to-chromium@1.5.381: {} emoji-regex@8.0.0: {} @@ -2942,9 +2942,9 @@ snapshots: escape-string-regexp@4.0.0: {} - eslint-config-prettier@10.1.8(eslint@10.5.0): + eslint-config-prettier@10.1.8(eslint@10.6.0): dependencies: - eslint: 10.5.0 + eslint: 10.6.0 eslint-import-resolver-node@0.3.10: dependencies: @@ -2954,17 +2954,17 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.13.0(@typescript-eslint/parser@8.61.1(eslint@10.5.0)(typescript@6.0.3))(eslint-import-resolver-node@0.3.10)(eslint@10.5.0): + eslint-module-utils@2.13.0(@typescript-eslint/parser@8.63.0(eslint@10.6.0)(typescript@6.0.3))(eslint-import-resolver-node@0.3.10)(eslint@10.6.0): dependencies: debug: 3.2.7 optionalDependencies: - '@typescript-eslint/parser': 8.61.1(eslint@10.5.0)(typescript@6.0.3) - eslint: 10.5.0 + '@typescript-eslint/parser': 8.63.0(eslint@10.6.0)(typescript@6.0.3) + eslint: 10.6.0 eslint-import-resolver-node: 0.3.10 transitivePeerDependencies: - supports-color - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.61.1(eslint@10.5.0)(typescript@6.0.3))(eslint@10.5.0): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.63.0(eslint@10.6.0)(typescript@6.0.3))(eslint@10.6.0): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 @@ -2973,9 +2973,9 @@ snapshots: array.prototype.flatmap: 1.3.3 debug: 3.2.7 doctrine: 2.1.0 - eslint: 10.5.0 + eslint: 10.6.0 eslint-import-resolver-node: 0.3.10 - eslint-module-utils: 2.13.0(@typescript-eslint/parser@8.61.1(eslint@10.5.0)(typescript@6.0.3))(eslint-import-resolver-node@0.3.10)(eslint@10.5.0) + eslint-module-utils: 2.13.0(@typescript-eslint/parser@8.63.0(eslint@10.6.0)(typescript@6.0.3))(eslint-import-resolver-node@0.3.10)(eslint@10.6.0) hasown: 2.0.4 is-core-module: 2.16.2 is-glob: 4.0.3 @@ -2987,24 +2987,24 @@ snapshots: string.prototype.trimend: 1.0.10 tsconfig-paths: 3.15.0 optionalDependencies: - '@typescript-eslint/parser': 8.61.1(eslint@10.5.0)(typescript@6.0.3) + '@typescript-eslint/parser': 8.63.0(eslint@10.6.0)(typescript@6.0.3) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack - supports-color - eslint-plugin-unicorn@68.0.0(eslint@10.5.0): + eslint-plugin-unicorn@69.0.0(eslint@10.6.0): dependencies: '@babel/helper-validator-identifier': 7.29.7 - '@eslint-community/eslint-utils': 4.9.1(eslint@10.5.0) + '@eslint-community/eslint-utils': 4.9.1(eslint@10.6.0) browserslist: 4.28.4 change-case: 5.4.4 ci-info: 4.4.0 core-js-compat: 3.49.0 detect-indent: 7.0.2 - eslint: 10.5.0 + eslint: 10.6.0 find-up-simple: 1.0.1 - globals: 17.6.0 + globals: 17.7.0 indent-string: 5.0.0 is-builtin-module: 5.0.0 jsesc: 3.1.0 @@ -3024,9 +3024,9 @@ snapshots: eslint-visitor-keys@5.0.1: {} - eslint@10.5.0: + eslint@10.6.0: dependencies: - '@eslint-community/eslint-utils': 4.9.1(eslint@10.5.0) + '@eslint-community/eslint-utils': 4.9.1(eslint@10.6.0) '@eslint-community/regexpp': 4.12.2 '@eslint/config-array': 0.23.5 '@eslint/config-helpers': 0.6.0 @@ -3163,9 +3163,9 @@ snapshots: dependencies: reusify: 1.1.0 - fdir@6.5.0(picomatch@4.0.4): + fdir@6.5.0(picomatch@4.0.5): optionalDependencies: - picomatch: 4.0.4 + picomatch: 4.0.5 file-entry-cache@8.0.0: dependencies: @@ -3283,7 +3283,7 @@ snapshots: package-json-from-dist: 1.0.1 path-scurry: 1.11.1 - globals@17.6.0: {} + globals@17.7.0: {} globalthis@1.0.4: dependencies: @@ -3338,7 +3338,7 @@ snapshots: ignore@5.3.2: {} - ignore@7.0.5: {} + ignore@7.0.6: {} imurmurhash@0.1.4: {} @@ -3383,7 +3383,7 @@ snapshots: is-builtin-module@5.0.0: dependencies: - builtin-modules: 5.2.0 + builtin-modules: 5.3.0 is-callable@1.2.7: {} @@ -3502,7 +3502,7 @@ snapshots: js-base64@3.7.8: {} - js-yaml@5.0.0: + js-yaml@5.2.1: dependencies: argparse: 2.0.1 @@ -3622,7 +3622,7 @@ snapshots: glob: 10.5.0 he: 1.2.0 is-path-inside: 3.0.3 - js-yaml: 5.0.0 + js-yaml: 5.2.1 log-symbols: 4.1.0 minimatch: 9.0.7 ms: 2.1.3 @@ -3656,7 +3656,7 @@ snapshots: node-forge@1.4.0: {} - node-releases@2.0.48: {} + node-releases@2.0.50: {} node-stream-zip@1.15.0: {} @@ -3762,7 +3762,7 @@ snapshots: picomatch@2.3.2: {} - picomatch@4.0.4: {} + picomatch@4.0.5: {} pkce-challenge@5.0.1: {} @@ -3777,7 +3777,7 @@ snapshots: prelude-ls@1.2.1: {} - prettier@3.8.4: {} + prettier@3.9.5: {} proper-lockfile@4.1.2: dependencies: @@ -4089,7 +4089,7 @@ snapshots: supports-preserve-symlinks-flag@1.0.0: {} - tar@7.5.16: + tar@7.5.19: dependencies: '@isaacs/fs-minipass': 4.0.1 chownr: 3.0.0 @@ -4099,8 +4099,8 @@ snapshots: tinyglobby@0.2.17: dependencies: - fdir: 6.5.0(picomatch@4.0.4) - picomatch: 4.0.4 + fdir: 6.5.0(picomatch@4.0.5) + picomatch: 4.0.5 to-regex-range@5.0.1: dependencies: @@ -4121,7 +4121,7 @@ snapshots: tslib@2.8.1: {} - tsx@4.22.4: + tsx@4.23.0: dependencies: esbuild: 0.28.1 optionalDependencies: @@ -4180,13 +4180,13 @@ snapshots: possible-typed-array-names: 1.1.0 reflect.getprototypeof: 1.0.10 - typescript-eslint@8.61.1(eslint@10.5.0)(typescript@6.0.3): + typescript-eslint@8.63.0(eslint@10.6.0)(typescript@6.0.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.61.1(@typescript-eslint/parser@8.61.1(eslint@10.5.0)(typescript@6.0.3))(eslint@10.5.0)(typescript@6.0.3) - '@typescript-eslint/parser': 8.61.1(eslint@10.5.0)(typescript@6.0.3) - '@typescript-eslint/typescript-estree': 8.61.1(typescript@6.0.3) - '@typescript-eslint/utils': 8.61.1(eslint@10.5.0)(typescript@6.0.3) - eslint: 10.5.0 + '@typescript-eslint/eslint-plugin': 8.63.0(@typescript-eslint/parser@8.63.0(eslint@10.6.0)(typescript@6.0.3))(eslint@10.6.0)(typescript@6.0.3) + '@typescript-eslint/parser': 8.63.0(eslint@10.6.0)(typescript@6.0.3) + '@typescript-eslint/typescript-estree': 8.63.0(typescript@6.0.3) + '@typescript-eslint/utils': 8.63.0(eslint@10.6.0)(typescript@6.0.3) + eslint: 10.6.0 typescript: 6.0.3 transitivePeerDependencies: - supports-color diff --git a/src/commands/login.ts b/src/commands/login.ts index b451813..ba6e9dc 100644 --- a/src/commands/login.ts +++ b/src/commands/login.ts @@ -10,8 +10,11 @@ * 2. CLI opens /cli-login?state=...&code_challenge=... in the browser. * 3. User signs in (OTP or SSO) and explicitly authorizes the handoff on * the frontend. - * 4. Frontend POSTs {state, code_challenge, session...} to the dcd api at - * POST /cli-login/handoff. The api stores a short-TTL row keyed by state. + * 4. Frontend POSTs {state, code_challenge, access_token} to the dcd api at + * POST /cli-login/handoff — the access token is proof of identity only. + * The api verifies it, mints a *dedicated* Supabase session for the CLI + * (its own refresh-token family, so browser token rotation can't + * invalidate it), and stores it in a short-TTL row keyed by state. * 5. Meanwhile, the CLI polls POST /cli-login/claim with {state, code_verifier}. * Once the api has the row, it verifies sha256(verifier) === challenge, * deletes the row, and returns the session. @@ -81,20 +84,32 @@ export const loginCommand = defineCommand({ // If there's an existing stored session, make the user confirm before we // overwrite it. Silent clobber is fine for power users but surprising if - // someone runs `dcd login` by mistake while already authenticated. + // someone runs `dcd login` by mistake while already authenticated. An + // already-expired session gets no confirm: the user is here because a + // command told them to re-login, and "Already logged in… keep session?" + // would dead-end them on a session that no longer works. const existing = readConfig(); if (existing?.session) { - const currentOrg = existing.current_org_name ?? existing.current_org_id; - const ok = await p.confirm({ - message: - `Already logged in as ${existing.session.user_email}` + - (currentOrg ? ` (org ${currentOrg})` : '') + - `. Sign out and log in again?`, - initialValue: false, - }); - if (p.isCancel(ok) || !ok) { - logger.log(ui.info('Keeping existing session.')); - return; + const now = Math.floor(Date.now() / 1000); + if (existing.session.expires_at <= now) { + logger.log( + ui.info( + `Your session for ${existing.session.user_email} has expired — signing in again.`, + ), + ); + } else { + const currentOrg = existing.current_org_name ?? existing.current_org_id; + const ok = await p.confirm({ + message: + `Already logged in as ${existing.session.user_email}` + + (currentOrg ? ` (org ${currentOrg})` : '') + + `. Sign out and log in again?`, + initialValue: true, + }); + if (p.isCancel(ok) || !ok) { + logger.log(ui.info('Keeping existing session.')); + return; + } } } diff --git a/src/gateways/cli-auth-gateway.ts b/src/gateways/cli-auth-gateway.ts index 247609a..35b10cf 100644 --- a/src/gateways/cli-auth-gateway.ts +++ b/src/gateways/cli-auth-gateway.ts @@ -3,12 +3,28 @@ * and sign-out (best-effort revocation on the Supabase side). * * Does not talk to the dcd API — the dcd-side session exchange lives in the - * login command's loopback flow, where the frontend POSTs ciphertext back. + * login command's PKCE rendezvous flow (see src/commands/login.ts). */ -import { createClient } from '@supabase/supabase-js'; +import { createClient, isAuthApiError } from '@supabase/supabase-js'; import type { StoredSession } from '../utils/config-store.js'; +/** + * Thrown when a session refresh fails. `definitive` distinguishes "Supabase + * rejected this refresh token" (revoked, already used, malformed — re-login + * is the only fix) from transient failures (network, GoTrue 5xx) where the + * stored session may still be good on the next attempt. + */ +export class SessionRefreshError extends Error { + constructor( + message: string, + readonly definitive: boolean, + ) { + super(message); + this.name = 'SessionRefreshError'; + } +} + export interface RefreshedSession { access_token: string; refresh_token: string; @@ -35,9 +51,17 @@ export const CliAuthGateway = { refresh_token: session.refresh_token, }); if (error || !data.session || !data.user) { - throw new Error( + // 4xx from GoTrue means the token itself was rejected; anything else + // (fetch failure, 5xx) could succeed on retry with the same token. + const definitive = + error != null && + isAuthApiError(error) && + error.status >= 400 && + error.status < 500; + throw new SessionRefreshError( `Failed to refresh session: ${error?.message ?? 'no session returned'}. ` + `Run \`dcd login\` again.`, + definitive, ); } const s = data.session; diff --git a/src/utils/auth.ts b/src/utils/auth.ts index 62a376f..21aecac 100644 --- a/src/utils/auth.ts +++ b/src/utils/auth.ts @@ -10,7 +10,10 @@ import { closeSync, openSync, rmSync, statSync } from 'node:fs'; import { ENVIRONMENTS } from '../config/environments.js'; -import { CliAuthGateway } from '../gateways/cli-auth-gateway.js'; +import { + CliAuthGateway, + SessionRefreshError, +} from '../gateways/cli-auth-gateway.js'; import { telemetry } from '../services/telemetry.service.js'; import type { AuthContext } from '../types/domain/auth.types.js'; @@ -124,11 +127,19 @@ async function refreshSessionWithLock( } const { anonKey } = ENVIRONMENTS[current.env].supabase; - const refreshed = await CliAuthGateway.refresh( - current.supabase_url, - anonKey, - session, - ); + let refreshed; + try { + refreshed = await CliAuthGateway.refresh( + current.supabase_url, + anonKey, + session, + ); + } catch (error) { + if (error instanceof SessionRefreshError && error.definitive) { + dropStoredSession(); + } + throw error; + } // Re-read again and merge only `session` so a concurrent `switch-org` // write (org fields) isn't reverted by our pre-refresh snapshot. const merged: StoredConfig = { ...(readConfig() ?? current), session: refreshed }; @@ -139,6 +150,20 @@ async function refreshSessionWithLock( } } +/** + * Remove only the (definitively dead) session from the stored config, keeping + * env/api_url/org so a re-login lands back in the same environment. With the + * dead session gone, subsequent commands report "Not authenticated" instead + * of failing the same refresh, and `dcd login` skips its "already logged in" + * confirm. Exported for tests. + */ +export function dropStoredSession(): void { + const config = readConfig(); + if (!config?.session) return; + delete config.session; + writeConfig(config); +} + async function acquireRefreshLock(lockPath: string): Promise { const deadline = Date.now() + LOCK_WAIT_MS; for (;;) { diff --git a/test/unit/auth.test.ts b/test/unit/auth.test.ts index 6140715..89ed4dd 100644 --- a/test/unit/auth.test.ts +++ b/test/unit/auth.test.ts @@ -3,7 +3,8 @@ import * as fs from 'node:fs'; import * as os from 'node:os'; import * as path from 'node:path'; -import { resolveAuth } from '../../src/utils/auth.js'; +import { SessionRefreshError } from '../../src/gateways/cli-auth-gateway.js'; +import { dropStoredSession, resolveAuth } from '../../src/utils/auth.js'; import { clearConfig, configFileMode, @@ -181,6 +182,48 @@ describe('resolveAuth precedence', () => { }); }); + it('surfaces definitive vs transient refresh failures via SessionRefreshError', () => { + const dead = new SessionRefreshError('Invalid Refresh Token: Already Used', true); + const blip = new SessionRefreshError('fetch failed', false); + expect(dead.definitive).to.equal(true); + expect(blip.definitive).to.equal(false); + expect(dead).to.be.instanceOf(Error); + }); + + it('dropStoredSession removes only the session, keeping env/org fields', async () => { + await withTempConfigDir(() => { + writeConfig({ + version: 1, + env: 'dev', + api_url: 'https://api.dev.devicecloud.dev', + supabase_url: 'https://lbmsowehtjwnqlurpemb.supabase.co', + session: { + access_token: 'a', + refresh_token: 'consumed-by-another-client', + expires_at: Math.floor(Date.now() / 1000) - 60, + user_email: 'u@example.com', + user_id: 'u1', + }, + current_org_id: '42', + current_org_name: 'Acme', + }); + + dropStoredSession(); + + const after = readConfig(); + expect(after).to.not.equal(null); + expect(after!.session).to.equal(undefined); + expect(after!.env).to.equal('dev'); + expect(after!.api_url).to.equal('https://api.dev.devicecloud.dev'); + expect(after!.current_org_id).to.equal('42'); + expect(after!.current_org_name).to.equal('Acme'); + + // Idempotent when there's no session to drop. + dropStoredSession(); + expect(readConfig()!.session).to.equal(undefined); + }); + }); + it('uses a non-expired stored session as a last resort', async () => { await withTempConfigDir(async () => { writeConfig({