From ede071510e21a789ba32cfed30fdd0b42e733710 Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Fri, 19 Jun 2026 10:07:12 +0530 Subject: [PATCH 1/2] chore: update Command Line SDK to 22.1.0 --- CHANGELOG.md | 11 + README.md | 4 +- bun.lock | 155 +-- bunfig.toml | 4 +- cli.ts | 2 + docs/examples/account/create.md | 2 +- docs/examples/account/update-password.md | 2 +- docs/examples/account/update-recovery.md | 2 +- .../examples/messaging/create-ses-provider.md | 5 + .../examples/messaging/update-ses-provider.md | 4 + .../migrations/create-n-host-migration.md | 2 +- .../migrations/create-supabase-migration.md | 2 +- docs/examples/migrations/get-n-host-report.md | 2 +- .../migrations/get-supabase-report.md | 2 +- docs/examples/oauth2/approve.md | 4 + docs/examples/oauth2/authorize.md | 7 + .../oauth2/create-device-authorization.md | 3 + docs/examples/oauth2/create-grant.md | 4 + docs/examples/oauth2/create-token.md | 4 + docs/examples/oauth2/get-grant.md | 4 + docs/examples/oauth2/logout.md | 3 + docs/examples/oauth2/reject.md | 4 + docs/examples/oauth2/revoke.md | 4 + .../update-deny-corporate-email-policy.md | 4 + .../project/update-o-auth-2-server.md | 5 + .../update-password-strength-policy.md | 3 + docs/examples/users/update-password.md | 2 +- install.ps1 | 4 +- install.sh | 2 +- lib/auth/login.ts | 470 +++++++++ lib/auth/oauth.ts | 120 +++ lib/auth/session.ts | 206 ++++ lib/commands/generic.ts | 489 +--------- lib/commands/init.ts | 7 +- lib/commands/push.ts | 52 +- lib/commands/services/account.ts | 2 +- lib/commands/services/messaging.ts | 52 + lib/commands/services/oauth-2.ts | 164 ++++ lib/commands/services/organizations.ts | 21 +- lib/commands/services/project.ts | 91 +- lib/commands/services/storage.ts | 4 +- lib/commands/services/tables-db.ts | 5 +- lib/commands/utils/deployment.ts | 17 +- lib/config.ts | 52 + lib/constants.ts | 6 +- lib/flags.ts | 21 + lib/questions.ts | 54 +- lib/sdks.ts | 104 +- lib/services.ts | 6 + lib/types.ts | 5 + lib/utils.ts | 36 +- package-lock.json | 904 +++++------------- package.json | 9 +- scoop/appwrite.config.json | 6 +- 54 files changed, 1851 insertions(+), 1308 deletions(-) create mode 100644 docs/examples/messaging/create-ses-provider.md create mode 100644 docs/examples/messaging/update-ses-provider.md create mode 100644 docs/examples/oauth2/approve.md create mode 100644 docs/examples/oauth2/authorize.md create mode 100644 docs/examples/oauth2/create-device-authorization.md create mode 100644 docs/examples/oauth2/create-grant.md create mode 100644 docs/examples/oauth2/create-token.md create mode 100644 docs/examples/oauth2/get-grant.md create mode 100644 docs/examples/oauth2/logout.md create mode 100644 docs/examples/oauth2/reject.md create mode 100644 docs/examples/oauth2/revoke.md create mode 100644 docs/examples/project/update-deny-corporate-email-policy.md create mode 100644 docs/examples/project/update-o-auth-2-server.md create mode 100644 docs/examples/project/update-password-strength-policy.md create mode 100644 lib/auth/login.ts create mode 100644 lib/auth/oauth.ts create mode 100644 lib/auth/session.ts create mode 100644 lib/commands/services/oauth-2.ts create mode 100644 lib/flags.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 3ab4fb07..6556000f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Change Log +## 22.1.0 + +* Fixed: `organization get-invoice-download` and `get-invoice-view` now save the invoice to disk via `--destination` +* Added: `oauth2` command group (`authorize`, `create-token`, `create-grant`, `get-grant`, `approve`, `reject`, `revoke`, `logout`, `create-device-authorization`) +* Added: `messaging create-ses-provider` and `update-ses-provider` commands for Amazon SES +* Added: `project update-o-auth-2-server` command to configure the OIDC provider +* Added: `project update-deny-corporate-email-policy` command +* Added: `project update-password-strength-policy` command +* Added: `--dedicated-database-id` option to `tables-db create` +* Updated: Bumped `@appwrite.io/console` dependency to `^15.0.0` + ## 22.0.0 * Breaking: Renamed `project update-deny-canonical-email-policy` to `update-deny-aliased-email-policy` diff --git a/README.md b/README.md index 3da5b4a5..06d1bff9 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ Once the installation is complete, you can verify the install using ```sh $ appwrite -v -22.0.0 +22.1.0 ``` ### Install using prebuilt binaries @@ -83,7 +83,7 @@ $ scoop install https://raw.githubusercontent.com/appwrite/sdk-for-cli/master/sc Once the installation completes, you can verify your install using ``` $ appwrite -v -22.0.0 +22.1.0 ``` ## Getting Started diff --git a/bun.lock b/bun.lock index 851dc4a7..c5131f0f 100644 --- a/bun.lock +++ b/bun.lock @@ -5,7 +5,7 @@ "": { "name": "appwrite-cli", "dependencies": { - "@appwrite.io/console": "13.1.0", + "@appwrite.io/console": "^15.0.0", "chalk": "4.1.2", "chokidar": "^3.6.0", "cli-progress": "^3.12.0", @@ -34,7 +34,7 @@ "@types/tar": "^6.1.13", "@typescript-eslint/eslint-plugin": "^8.0.0", "@typescript-eslint/parser": "^8.0.0", - "esbuild": "^0.27.2", + "esbuild": "^0.28.1", "eslint": "^9.0.0", "eslint-plugin-unused-imports": "^4.0.0", "prettier": "^3.7.4", @@ -46,11 +46,12 @@ }, "overrides": { "@xmldom/xmldom": "^0.9.10", + "esbuild": "^0.28.1", "phin": "3.7.1", "tmp": "^0.2.6", }, "packages": { - "@appwrite.io/console": ["@appwrite.io/console@13.1.0", "", { "dependencies": { "json-bigint": "1.0.0" } }, "sha512-fInc7p11A7kGF5Y+FsWBr2NGeu8GUcA0/OCuZGo4eKVOGO7Wy0OAWLBfwmfHXG7Dx/R9SiW7rNLpK7+taN76tA=="], + "@appwrite.io/console": ["@appwrite.io/console@15.0.0", "", { "dependencies": { "json-bigint": "1.0.0" } }, "sha512-yjCjsUTcOUacR/O0/XEm9ax+EpcxxG3+h7TsY0YEd2fW32T2AimASOVMcRoEb8DrSHWqCn5kH1VRL/U6ZfAoJw=="], "@babel/runtime": ["@babel/runtime@7.29.7", "", {}, "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw=="], @@ -58,57 +59,57 @@ "@colors/colors": ["@colors/colors@1.5.0", "", {}, "sha512-ooWCrlZP11i8GImSjTHYHLkvFDP48nS4+204nGb1RiX/WXYHmJA2III9/e2DWVabCESdW7hBAEzHRqUn9OUVvQ=="], - "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.27.7", "", { "os": "aix", "cpu": "ppc64" }, "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg=="], + "@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.28.1", "", { "os": "aix", "cpu": "ppc64" }, "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ=="], - "@esbuild/android-arm": ["@esbuild/android-arm@0.27.7", "", { "os": "android", "cpu": "arm" }, "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ=="], + "@esbuild/android-arm": ["@esbuild/android-arm@0.28.1", "", { "os": "android", "cpu": "arm" }, "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ=="], - "@esbuild/android-arm64": ["@esbuild/android-arm64@0.27.7", "", { "os": "android", "cpu": "arm64" }, "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ=="], + "@esbuild/android-arm64": ["@esbuild/android-arm64@0.28.1", "", { "os": "android", "cpu": "arm64" }, "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg=="], - "@esbuild/android-x64": ["@esbuild/android-x64@0.27.7", "", { "os": "android", "cpu": "x64" }, "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg=="], + "@esbuild/android-x64": ["@esbuild/android-x64@0.28.1", "", { "os": "android", "cpu": "x64" }, "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng=="], - "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.27.7", "", { "os": "darwin", "cpu": "arm64" }, "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw=="], + "@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.28.1", "", { "os": "darwin", "cpu": "arm64" }, "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q=="], - "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.27.7", "", { "os": "darwin", "cpu": "x64" }, "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ=="], + "@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.28.1", "", { "os": "darwin", "cpu": "x64" }, "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ=="], - "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.27.7", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w=="], + "@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.28.1", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw=="], - "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.27.7", "", { "os": "freebsd", "cpu": "x64" }, "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ=="], + "@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.28.1", "", { "os": "freebsd", "cpu": "x64" }, "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ=="], - "@esbuild/linux-arm": ["@esbuild/linux-arm@0.27.7", "", { "os": "linux", "cpu": "arm" }, "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA=="], + "@esbuild/linux-arm": ["@esbuild/linux-arm@0.28.1", "", { "os": "linux", "cpu": "arm" }, "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ=="], - "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.27.7", "", { "os": "linux", "cpu": "arm64" }, "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A=="], + "@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.28.1", "", { "os": "linux", "cpu": "arm64" }, "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g=="], - "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.27.7", "", { "os": "linux", "cpu": "ia32" }, "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg=="], + "@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.28.1", "", { "os": "linux", "cpu": "ia32" }, "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w=="], - "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.27.7", "", { "os": "linux", "cpu": "none" }, "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q=="], + "@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.28.1", "", { "os": "linux", "cpu": "none" }, "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg=="], - "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.27.7", "", { "os": "linux", "cpu": "none" }, "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw=="], + "@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.28.1", "", { "os": "linux", "cpu": "none" }, "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ=="], - "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.27.7", "", { "os": "linux", "cpu": "ppc64" }, "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ=="], + "@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.28.1", "", { "os": "linux", "cpu": "ppc64" }, "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ=="], - "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.27.7", "", { "os": "linux", "cpu": "none" }, "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ=="], + "@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.28.1", "", { "os": "linux", "cpu": "none" }, "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ=="], - "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.27.7", "", { "os": "linux", "cpu": "s390x" }, "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw=="], + "@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.28.1", "", { "os": "linux", "cpu": "s390x" }, "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag=="], - "@esbuild/linux-x64": ["@esbuild/linux-x64@0.27.7", "", { "os": "linux", "cpu": "x64" }, "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA=="], + "@esbuild/linux-x64": ["@esbuild/linux-x64@0.28.1", "", { "os": "linux", "cpu": "x64" }, "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA=="], - "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.27.7", "", { "os": "none", "cpu": "arm64" }, "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w=="], + "@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.28.1", "", { "os": "none", "cpu": "arm64" }, "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw=="], - "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.27.7", "", { "os": "none", "cpu": "x64" }, "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw=="], + "@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.28.1", "", { "os": "none", "cpu": "x64" }, "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg=="], - "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.27.7", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A=="], + "@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.28.1", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q=="], - "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.27.7", "", { "os": "openbsd", "cpu": "x64" }, "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg=="], + "@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.28.1", "", { "os": "openbsd", "cpu": "x64" }, "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw=="], - "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.27.7", "", { "os": "none", "cpu": "arm64" }, "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw=="], + "@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.28.1", "", { "os": "none", "cpu": "arm64" }, "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg=="], - "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.27.7", "", { "os": "sunos", "cpu": "x64" }, "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA=="], + "@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.28.1", "", { "os": "sunos", "cpu": "x64" }, "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ=="], - "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.27.7", "", { "os": "win32", "cpu": "arm64" }, "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA=="], + "@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.28.1", "", { "os": "win32", "cpu": "arm64" }, "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA=="], - "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.27.7", "", { "os": "win32", "cpu": "ia32" }, "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw=="], + "@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.28.1", "", { "os": "win32", "cpu": "ia32" }, "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg=="], - "@esbuild/win32-x64": ["@esbuild/win32-x64@0.27.7", "", { "os": "win32", "cpu": "x64" }, "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg=="], + "@esbuild/win32-x64": ["@esbuild/win32-x64@0.28.1", "", { "os": "win32", "cpu": "x64" }, "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A=="], "@eslint-community/eslint-utils": ["@eslint-community/eslint-utils@4.9.1", "", { "dependencies": { "eslint-visitor-keys": "^3.4.3" }, "peerDependencies": { "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" } }, "sha512-phrYmNiYppR7znFEdqgfWHXR6NCkZEK7hwWDHZUjit/2/U0r6XvkDl0SYnoM51Hq7FhCGdLDT6zxCCOY1hexsQ=="], @@ -232,7 +233,7 @@ "@types/estree": ["@types/estree@1.0.9", "", {}, "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg=="], - "@types/inquirer": ["@types/inquirer@8.2.12", "", { "dependencies": { "@types/through": "*", "rxjs": "^7.2.0" } }, "sha512-YxURZF2ZsSjU5TAe06tW0M3sL4UI9AMPA6dd8I72uOtppzNafcY38xkYgCZ/vsVOAyNdzHmvtTpLWilOrbP0dQ=="], + "@types/inquirer": ["@types/inquirer@8.2.13", "", { "dependencies": { "@types/through": "*", "rxjs": "^7.2.0" } }, "sha512-shSvl3mn4Z8AK627kA1vx8PYkyH6CdIjV5NYYj7a0xIxzmG3ZgzEpzCi3CWfktjAlq+0Z0wHJGtWNiACaYpeOg=="], "@types/json-bigint": ["@types/json-bigint@1.0.4", "", {}, "sha512-ydHooXLbOmxBbubnA7Eh+RpBzuaIiQjh8WGJYQB50JFGFrdxW7JzVlyEV7fAXw0T2sqJ1ysTneJbiyNLqZRAag=="], @@ -244,29 +245,29 @@ "@types/through": ["@types/through@0.0.33", "", { "dependencies": { "@types/node": "*" } }, "sha512-HsJ+z3QuETzP3cswwtzt2vEIiHBk/dCcHGhbmG5X3ecnwFD/lPrMpliGXxSCg03L9AhrdwA4Oz/qfspkDW+xGQ=="], - "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.60.0", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.60.0", "@typescript-eslint/type-utils": "8.60.0", "@typescript-eslint/utils": "8.60.0", "@typescript-eslint/visitor-keys": "8.60.0", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.60.0", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-QYb/sa74/s7OKMbACMjrYnGspj9Hs5YI5aaffSL65UfeBUzVzBJfVo3oWSpbzPurvm7yaCCo2Lk7lVj610HqKw=="], + "@typescript-eslint/eslint-plugin": ["@typescript-eslint/eslint-plugin@8.61.1", "", { "dependencies": { "@eslint-community/regexpp": "^4.12.2", "@typescript-eslint/scope-manager": "8.61.1", "@typescript-eslint/type-utils": "8.61.1", "@typescript-eslint/utils": "8.61.1", "@typescript-eslint/visitor-keys": "8.61.1", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "@typescript-eslint/parser": "^8.61.1", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-ZPlVl3PB3et/59Ne0fv/sci6ZXz4T4Hp4nTJ56i/Y0gR89ARb+KphojTq6j+56E5PIezmOIOOWyY+aWQFd+IkQ=="], - "@typescript-eslint/parser": ["@typescript-eslint/parser@8.60.0", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.60.0", "@typescript-eslint/types": "8.60.0", "@typescript-eslint/typescript-estree": "8.60.0", "@typescript-eslint/visitor-keys": "8.60.0", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-fcqpj/MyK4sxDPcbe7STNPbpQL4RLZOPWuaTmwZYuc+hJKzRf58yRxfhqGpc6PIq9ZyfSBpfHgmUHmHs0KwHwg=="], + "@typescript-eslint/parser": ["@typescript-eslint/parser@8.61.1", "", { "dependencies": { "@typescript-eslint/scope-manager": "8.61.1", "@typescript-eslint/types": "8.61.1", "@typescript-eslint/typescript-estree": "8.61.1", "@typescript-eslint/visitor-keys": "8.61.1", "debug": "^4.4.3" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-PJ5vePq5/ognBbrIcoC5+SHO5dfpeLPzP9FpLkzWrguoYQEeeSjlJpVwOpo1JRSTEi7dRcwNy4h4dzV70PqHcg=="], - "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.60.0", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.60.0", "@typescript-eslint/types": "^8.60.0", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-aZu74NNKJeUWqCjDddzdiKaS82dgYgV/vmf+Ui3ZdZejmgfXR/q+pRumgobnQ2cCJTgGTWp4ypiwsuofFubavg=="], + "@typescript-eslint/project-service": ["@typescript-eslint/project-service@8.61.1", "", { "dependencies": { "@typescript-eslint/tsconfig-utils": "^8.61.1", "@typescript-eslint/types": "^8.61.1", "debug": "^4.4.3" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-PrC4JYGmR241lYnfhmKGTXkFqv8+ymbTFgSAY0fVXpY82/QkMw5TZPl+vGzuDDU2QYJk9fIDOBTntF+yDv9LEA=="], - "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.60.0", "", { "dependencies": { "@typescript-eslint/types": "8.60.0", "@typescript-eslint/visitor-keys": "8.60.0" } }, "sha512-pFzqhllJMs+jghLQWzV00ds39xLzuyqPSev5pd8f4Ir0rtKR3ZLUB4/4dhjOFighWb9larvtfJvqL+4yKDI3Xw=="], + "@typescript-eslint/scope-manager": ["@typescript-eslint/scope-manager@8.61.1", "", { "dependencies": { "@typescript-eslint/types": "8.61.1", "@typescript-eslint/visitor-keys": "8.61.1" } }, "sha512-L2bdIeoQS8FlKAvONAr20w6OcLXeB+qiDKbAooS9A0Ben+iSIkBef0FxqwKWYqt5sa0i4KJtxVyVmhMylKzF5w=="], - "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.60.0", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-BZPR3RGYlAXnly6ymAxfkVn5rCbZzQNou0rxv3GfWZ8cTQp+hhVd73khbGLAd8k1TlAPLISH337M+tAgAnaJDQ=="], + "@typescript-eslint/tsconfig-utils": ["@typescript-eslint/tsconfig-utils@8.61.1", "", { "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-UN/H4di+OO7EWx2ovME+8t31YO+KVnK0RRKEHR3kOt21/Ay8BOq3M1OMvWs5vNiqcFCYGYoxK3MXPZzmMUE+yg=="], - "@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.60.0", "", { "dependencies": { "@typescript-eslint/types": "8.60.0", "@typescript-eslint/typescript-estree": "8.60.0", "@typescript-eslint/utils": "8.60.0", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-SX46wEUtitCpq7AN38HkUU/+zvUpdKf7ephtWAFgckH8O7PQIyL5gvrhQgBLuEYgLfuKWOVvWVskMbuFHAz5xg=="], + "@typescript-eslint/type-utils": ["@typescript-eslint/type-utils@8.61.1", "", { "dependencies": { "@typescript-eslint/types": "8.61.1", "@typescript-eslint/typescript-estree": "8.61.1", "@typescript-eslint/utils": "8.61.1", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-GYRicKmVK0C4fsKgaACaknOUAq9Oa2kwsjnpFhFcS/5p4Ht5IP9OVLbgIgcK4SRk92nVHFluurg1lumD9dBcLw=="], - "@typescript-eslint/types": ["@typescript-eslint/types@8.60.0", "", {}, "sha512-AsE7x2XaAK+CVbeih0Fvbn+r1qHxtpLDJ3XUuFcIinT318T90yHMJC+Zgv+jUuDjQQd06HKwxnDu6sz1IcTilA=="], + "@typescript-eslint/types": ["@typescript-eslint/types@8.61.1", "", {}, "sha512-G+CRlPqLv7Bz1IZVs03x5K59F1veqL0EJUROAdGhKsEq8qOiRiZbI+HUojPq5l0fEGOKModD9br6lObhB8zkoA=="], - "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.60.0", "", { "dependencies": { "@typescript-eslint/project-service": "8.60.0", "@typescript-eslint/tsconfig-utils": "8.60.0", "@typescript-eslint/types": "8.60.0", "@typescript-eslint/visitor-keys": "8.60.0", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-3AcZNBGMClm6CXDyo8kYvVGT/sx29sS0oBsIb9oZI2gunA4Vm2M3YHzRLPvsUBBsl+yB5FPtltq7gGH0iTlp9g=="], + "@typescript-eslint/typescript-estree": ["@typescript-eslint/typescript-estree@8.61.1", "", { "dependencies": { "@typescript-eslint/project-service": "8.61.1", "@typescript-eslint/tsconfig-utils": "8.61.1", "@typescript-eslint/types": "8.61.1", "@typescript-eslint/visitor-keys": "8.61.1", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", "tinyglobby": "^0.2.15", "ts-api-utils": "^2.5.0" }, "peerDependencies": { "typescript": ">=4.8.4 <6.1.0" } }, "sha512-u+oQD3BqYWPc8YV9Zab4vaJElJuwOLPRc10Jm1o/qS+6Qwen14HCWwx0Seo4LnSn2wxea2Ik8DxPt2/FHmuhrg=="], - "@typescript-eslint/utils": ["@typescript-eslint/utils@8.60.0", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.60.0", "@typescript-eslint/types": "8.60.0", "@typescript-eslint/typescript-estree": "8.60.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-HtXuPfrHTyBDkameWpl+vJb1Uevu2tznAyahM1Oc4AENidCLTPiZDWIo4GfcxNdC/RcfGcadzzkqbRG87dUrQA=="], + "@typescript-eslint/utils": ["@typescript-eslint/utils@8.61.1", "", { "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", "@typescript-eslint/scope-manager": "8.61.1", "@typescript-eslint/types": "8.61.1", "@typescript-eslint/typescript-estree": "8.61.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-1+P/3Dj6jvtybE1q0HQ6yBt/gq+oKJyLdEv4HdnqasaEXRSYCAsD59mXEVQnM/ULNdQxbX77tdG4jPRjIS6knA=="], - "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.60.0", "", { "dependencies": { "@typescript-eslint/types": "8.60.0", "eslint-visitor-keys": "^5.0.0" } }, "sha512-9WI52t8ZGLVGrPMBet25yAftqY/n95+zmoUUtJBBQTKDSKUu7OsPTroT2op7U9JatkoRccL0YkWDNMFfC4Sjxg=="], + "@typescript-eslint/visitor-keys": ["@typescript-eslint/visitor-keys@8.61.1", "", { "dependencies": { "@typescript-eslint/types": "8.61.1", "eslint-visitor-keys": "^5.0.0" } }, "sha512-6fJ9MHWtK14C1DSkiMlHUSOmrVebL7150xZJBlJiL62jjhIA4JmOq6flwBgDxIdBKKdoiZRel+dfPD5MLfny3w=="], "@xmldom/xmldom": ["@xmldom/xmldom@0.9.10", "", {}, "sha512-A9gOqLdi6cV4ibazAjcQufGj0B1y/vDqYrcuP6d/6x8P27gRS8643Dj9o1dEKtB6O7fwxb2FgBmJS2mX7gpvdw=="], - "acorn": ["acorn@8.16.0", "", { "bin": "bin/acorn" }, "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw=="], + "acorn": ["acorn@8.17.0", "", { "bin": "bin/acorn" }, "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg=="], "acorn-jsx": ["acorn-jsx@5.3.2", "", { "peerDependencies": { "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ=="], @@ -372,7 +373,7 @@ "environment": ["environment@1.1.0", "", {}, "sha512-xUtoPkMggbz0MPyPiIWr1Kp4aeWJjDZ6SMvURhimjdZgsRuDplF5/s9hcgGhyXMhs+6vpnuoiZ2kFiu3FMnS8Q=="], - "esbuild": ["esbuild@0.27.7", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.27.7", "@esbuild/android-arm": "0.27.7", "@esbuild/android-arm64": "0.27.7", "@esbuild/android-x64": "0.27.7", "@esbuild/darwin-arm64": "0.27.7", "@esbuild/darwin-x64": "0.27.7", "@esbuild/freebsd-arm64": "0.27.7", "@esbuild/freebsd-x64": "0.27.7", "@esbuild/linux-arm": "0.27.7", "@esbuild/linux-arm64": "0.27.7", "@esbuild/linux-ia32": "0.27.7", "@esbuild/linux-loong64": "0.27.7", "@esbuild/linux-mips64el": "0.27.7", "@esbuild/linux-ppc64": "0.27.7", "@esbuild/linux-riscv64": "0.27.7", "@esbuild/linux-s390x": "0.27.7", "@esbuild/linux-x64": "0.27.7", "@esbuild/netbsd-arm64": "0.27.7", "@esbuild/netbsd-x64": "0.27.7", "@esbuild/openbsd-arm64": "0.27.7", "@esbuild/openbsd-x64": "0.27.7", "@esbuild/openharmony-arm64": "0.27.7", "@esbuild/sunos-x64": "0.27.7", "@esbuild/win32-arm64": "0.27.7", "@esbuild/win32-ia32": "0.27.7", "@esbuild/win32-x64": "0.27.7" }, "bin": "bin/esbuild" }, "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w=="], + "esbuild": ["esbuild@0.28.1", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.28.1", "@esbuild/android-arm": "0.28.1", "@esbuild/android-arm64": "0.28.1", "@esbuild/android-x64": "0.28.1", "@esbuild/darwin-arm64": "0.28.1", "@esbuild/darwin-x64": "0.28.1", "@esbuild/freebsd-arm64": "0.28.1", "@esbuild/freebsd-x64": "0.28.1", "@esbuild/linux-arm": "0.28.1", "@esbuild/linux-arm64": "0.28.1", "@esbuild/linux-ia32": "0.28.1", "@esbuild/linux-loong64": "0.28.1", "@esbuild/linux-mips64el": "0.28.1", "@esbuild/linux-ppc64": "0.28.1", "@esbuild/linux-riscv64": "0.28.1", "@esbuild/linux-s390x": "0.28.1", "@esbuild/linux-x64": "0.28.1", "@esbuild/netbsd-arm64": "0.28.1", "@esbuild/netbsd-x64": "0.28.1", "@esbuild/openbsd-arm64": "0.28.1", "@esbuild/openbsd-x64": "0.28.1", "@esbuild/openharmony-arm64": "0.28.1", "@esbuild/sunos-x64": "0.28.1", "@esbuild/win32-arm64": "0.28.1", "@esbuild/win32-ia32": "0.28.1", "@esbuild/win32-x64": "0.28.1" }, "bin": "bin/esbuild" }, "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw=="], "escape-string-regexp": ["escape-string-regexp@4.0.0", "", {}, "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="], @@ -496,7 +497,7 @@ "jpeg-js": ["jpeg-js@0.4.4", "", {}, "sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg=="], - "js-yaml": ["js-yaml@4.1.1", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": "bin/js-yaml.js" }, "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA=="], + "js-yaml": ["js-yaml@4.2.0", "", { "dependencies": { "argparse": "^2.0.1" }, "bin": "bin/js-yaml.js" }, "sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw=="], "json-bigint": ["json-bigint@1.0.0", "", { "dependencies": { "bignumber.js": "^9.0.0" } }, "sha512-SiPv/8VpZuWbvLSMtTDU8hEfrZWg/mH/nV/b4o0CYbSxu1UIQPLdwKOCIyLQX+VIPO5vrLX3i8qtqFyhdPSUSQ=="], @@ -596,7 +597,7 @@ "prelude-ls": ["prelude-ls@1.2.1", "", {}, "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="], - "prettier": ["prettier@3.8.3", "", { "bin": "bin/prettier.cjs" }, "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw=="], + "prettier": ["prettier@3.8.4", "", { "bin": "bin/prettier.cjs" }, "sha512-N2MylSdi48+5N/6S5j+maeHbUSIzzZ5uOcX5Hm4QpV8Dkb1HFjfAKTKX6yNPJQD9AhcT3ifHNB66tWTTJDi11Q=="], "process": ["process@0.11.10", "", {}, "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A=="], @@ -628,7 +629,7 @@ "sax": ["sax@1.6.0", "", {}, "sha512-6R3J5M4AcbtLUdZmRv2SygeVaM7IhrLXu9BmnOGmmACak8fiUtOsYNWUS4uK7upbmHIBbLBeFeI//477BKLBzA=="], - "semver": ["semver@7.8.1", "", { "bin": "bin/semver.js" }, "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg=="], + "semver": ["semver@7.8.4", "", { "bin": "bin/semver.js" }, "sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA=="], "shebang-command": ["shebang-command@2.0.0", "", { "dependencies": { "shebang-regex": "^3.0.0" } }, "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA=="], @@ -658,7 +659,7 @@ "tail": ["tail@2.2.6", "", {}, "sha512-IQ6G4wK/t8VBauYiGPLx+d3fA5XjSVagjWV5SIYzvEvglbQjwEcukeYI68JOPpdydjxhZ9sIgzRlSmwSpphHyw=="], - "tar": ["tar@7.5.15", "", { "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", "minipass": "^7.1.2", "minizlib": "^3.1.0", "yallist": "^5.0.0" } }, "sha512-dzGK0boVlC4W5QFuQN1EFSl3bIDYsk7Tj40U6eIBnK2k/8ml7TZ5agbI5j5+qnoVcAA+rNtBml8SEiLxZpNqRQ=="], + "tar": ["tar@7.5.16", "", { "dependencies": { "@isaacs/fs-minipass": "^4.0.0", "chownr": "^3.0.0", "minipass": "^7.1.2", "minizlib": "^3.1.0", "yallist": "^5.0.0" } }, "sha512-56adEpPMouktRlBLXiaYFFzZ/3+JXa8P9n7WbR+ibIjtviN55mEaOkiysCnPnWm+7kkui1Dn8J9l+g6zV8731w=="], "term-img": ["term-img@7.1.0", "", { "dependencies": { "ansi-escapes": "^7.1.1", "iterm2-version": "^5.0.0" } }, "sha512-au++khgSDly2KXNhC6BOU3mLi2v+Dk5mChYKDcpB5xYwhlwqYQtj0z59dIqFEmr+w7ndZaNqurHapkGc6/hprQ=="], @@ -670,7 +671,7 @@ "tinycolor2": ["tinycolor2@1.6.0", "", {}, "sha512-XPaBkWQJdsf3pLKJV9p4qN/S+fm2Oj8AIPo1BTUhg5oxkvm9+SVEGFdhyOz7tTdUTfvxMiAs4sp6/eZO2Ew+pw=="], - "tinyglobby": ["tinyglobby@0.2.16", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg=="], + "tinyglobby": ["tinyglobby@0.2.17", "", { "dependencies": { "fdir": "^6.5.0", "picomatch": "^4.0.4" } }, "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g=="], "tmp": ["tmp@0.2.7", "", {}, "sha512-e0votIpp4Uo2AJYSzVHV6xCcawuiez3DzqDAbrTc3YxBkplN6e+dM13ZeIcZnDg/QpSuU2zfZ3rzwY8ukEnaXw=="], @@ -682,7 +683,7 @@ "tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - "tsx": ["tsx@4.22.3", "", { "dependencies": { "esbuild": "~0.28.0" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "bin": "dist/cli.mjs" }, "sha512-mdoNxBC/cSQObGGVQ5Bpn5i+yv7j68gk3Nfm3wFjcJg3Z0Mix9jzAFfP12prmm5eVGmDKtp0yyArrs0Q+8gZHg=="], + "tsx": ["tsx@4.22.4", "", { "dependencies": { "esbuild": "~0.28.0" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "bin": "dist/cli.mjs" }, "sha512-X8EX+XV4QR5xCsrgxaED954zTDfY8KqlDtskKEL0cHhyS/P8b4IFOvGDQpsC9Q1XnLq915wEfwwY/zzskCtmhg=="], "type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="], @@ -690,13 +691,13 @@ "typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="], - "typescript-eslint": ["typescript-eslint@8.60.0", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.60.0", "@typescript-eslint/parser": "8.60.0", "@typescript-eslint/typescript-estree": "8.60.0", "@typescript-eslint/utils": "8.60.0" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-9f65qWLZdAW9m1JaxBDUHcqRUfL8bkxxXL7XxEfI+F09q56PkBvIfCjLF3yInsDM/BBmwkqmCQdCZe/RYlIWEw=="], + "typescript-eslint": ["typescript-eslint@8.61.1", "", { "dependencies": { "@typescript-eslint/eslint-plugin": "8.61.1", "@typescript-eslint/parser": "8.61.1", "@typescript-eslint/typescript-estree": "8.61.1", "@typescript-eslint/utils": "8.61.1" }, "peerDependencies": { "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "sha512-V7PayAfJokV3pEHgN7/v03D1SpujhRfQtYLbLIiBfDDncdg4PAiRBfoS4cnCANK4jmAPncczi59QO3afiXUlNw=="], "uglify-js": ["uglify-js@3.19.3", "", { "bin": { "uglifyjs": "bin/uglifyjs" } }, "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ=="], "uint8array-extras": ["uint8array-extras@1.5.0", "", {}, "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A=="], - "undici": ["undici@6.26.0", "", {}, "sha512-4yqz8a3n5HmGTlsbADNtr/dJlhkh/55Rq798G6ibiULcXbDtaLpTl1pvdqcbFfeoj3iSi52lePFM7h9H21cw/A=="], + "undici": ["undici@6.27.0", "", {}, "sha512-YmfV3YnEDzXRC5lZ2jWtWWHKGUm1zIt8AhesR1tens+HTNv+YZlN/dp6G727LOvMJ8xjP9Be7Y2Sdr96LDm+pg=="], "undici-types": ["undici-types@5.26.5", "", {}, "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA=="], @@ -896,8 +897,6 @@ "tinyglobby/picomatch": ["picomatch@4.0.4", "", {}, "sha512-QP88BAKvMam/3NxH6vj2o21R6MjxZUAd6nlwAS/pnGvN9IVLocLHxGYIzFhg6fUQ+5th6P4dv4eW9jX3DSIj7A=="], - "tsx/esbuild": ["esbuild@0.28.0", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.28.0", "@esbuild/android-arm": "0.28.0", "@esbuild/android-arm64": "0.28.0", "@esbuild/android-x64": "0.28.0", "@esbuild/darwin-arm64": "0.28.0", "@esbuild/darwin-x64": "0.28.0", "@esbuild/freebsd-arm64": "0.28.0", "@esbuild/freebsd-x64": "0.28.0", "@esbuild/linux-arm": "0.28.0", "@esbuild/linux-arm64": "0.28.0", "@esbuild/linux-ia32": "0.28.0", "@esbuild/linux-loong64": "0.28.0", "@esbuild/linux-mips64el": "0.28.0", "@esbuild/linux-ppc64": "0.28.0", "@esbuild/linux-riscv64": "0.28.0", "@esbuild/linux-s390x": "0.28.0", "@esbuild/linux-x64": "0.28.0", "@esbuild/netbsd-arm64": "0.28.0", "@esbuild/netbsd-x64": "0.28.0", "@esbuild/openbsd-arm64": "0.28.0", "@esbuild/openbsd-x64": "0.28.0", "@esbuild/openharmony-arm64": "0.28.0", "@esbuild/sunos-x64": "0.28.0", "@esbuild/win32-arm64": "0.28.0", "@esbuild/win32-ia32": "0.28.0", "@esbuild/win32-x64": "0.28.0" }, "bin": "bin/esbuild" }, "sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw=="], - "wrap-ansi/string-width": ["string-width@4.2.3", "", { "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" } }, "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g=="], "xml2js/xmlbuilder": ["xmlbuilder@11.0.1", "", {}, "sha512-fDlsI/kFEx7gLvbecc0/ohLG50fugQp8ryHzMTuW9vSa1GJ0XYWKnhsUx7oie3G98+r56aTQIUB4kht42R3JvA=="], @@ -988,58 +987,6 @@ "string-width/strip-ansi/ansi-regex": ["ansi-regex@6.2.2", "", {}, "sha512-Bq3SmSpyFHaWjPk8If9yc6svM8c56dB5BAtW4Qbw5jHTwwXXcTLoRMkpDJp6VL0XzlWaCHTXrkFURMYmD0sLqg=="], - "tsx/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.28.0", "", { "os": "aix", "cpu": "ppc64" }, "sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA=="], - - "tsx/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.28.0", "", { "os": "android", "cpu": "arm" }, "sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ=="], - - "tsx/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.28.0", "", { "os": "android", "cpu": "arm64" }, "sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw=="], - - "tsx/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.28.0", "", { "os": "android", "cpu": "x64" }, "sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA=="], - - "tsx/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.28.0", "", { "os": "darwin", "cpu": "arm64" }, "sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q=="], - - "tsx/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.28.0", "", { "os": "darwin", "cpu": "x64" }, "sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ=="], - - "tsx/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.28.0", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q=="], - - "tsx/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.28.0", "", { "os": "freebsd", "cpu": "x64" }, "sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw=="], - - "tsx/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.28.0", "", { "os": "linux", "cpu": "arm" }, "sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw=="], - - "tsx/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.28.0", "", { "os": "linux", "cpu": "arm64" }, "sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A=="], - - "tsx/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.28.0", "", { "os": "linux", "cpu": "ia32" }, "sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ=="], - - "tsx/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.28.0", "", { "os": "linux", "cpu": "none" }, "sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg=="], - - "tsx/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.28.0", "", { "os": "linux", "cpu": "none" }, "sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w=="], - - "tsx/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.28.0", "", { "os": "linux", "cpu": "ppc64" }, "sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg=="], - - "tsx/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.28.0", "", { "os": "linux", "cpu": "none" }, "sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ=="], - - "tsx/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.28.0", "", { "os": "linux", "cpu": "s390x" }, "sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q=="], - - "tsx/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.28.0", "", { "os": "linux", "cpu": "x64" }, "sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ=="], - - "tsx/esbuild/@esbuild/netbsd-arm64": ["@esbuild/netbsd-arm64@0.28.0", "", { "os": "none", "cpu": "arm64" }, "sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw=="], - - "tsx/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.28.0", "", { "os": "none", "cpu": "x64" }, "sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw=="], - - "tsx/esbuild/@esbuild/openbsd-arm64": ["@esbuild/openbsd-arm64@0.28.0", "", { "os": "openbsd", "cpu": "arm64" }, "sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g=="], - - "tsx/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.28.0", "", { "os": "openbsd", "cpu": "x64" }, "sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA=="], - - "tsx/esbuild/@esbuild/openharmony-arm64": ["@esbuild/openharmony-arm64@0.28.0", "", { "os": "none", "cpu": "arm64" }, "sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w=="], - - "tsx/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.28.0", "", { "os": "sunos", "cpu": "x64" }, "sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw=="], - - "tsx/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.28.0", "", { "os": "win32", "cpu": "arm64" }, "sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA=="], - - "tsx/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.28.0", "", { "os": "win32", "cpu": "ia32" }, "sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA=="], - - "tsx/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.28.0", "", { "os": "win32", "cpu": "x64" }, "sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw=="], - "wrap-ansi/string-width/emoji-regex": ["emoji-regex@8.0.0", "", {}, "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="], "wrap-ansi/string-width/is-fullwidth-code-point": ["is-fullwidth-code-point@3.0.0", "", {}, "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="], diff --git a/bunfig.toml b/bunfig.toml index 1821408e..5ab2520f 100644 --- a/bunfig.toml +++ b/bunfig.toml @@ -6,4 +6,6 @@ minimumReleaseAge = 604800 # 7d # Exempt `tmp` from the release-age policy so the security fix for # GHSA-ph9p-34f9-6g65 (landed in tmp@0.2.6) can be installed before the # 7d window elapses. Remove this entry once 0.2.6+ ages out. -minimumReleaseAgeExcludes = ["tmp"] +# `@appwrite.io/console` is Appwrite's first-party SDK, released in lockstep +# with this CLI, so it is exempt from the third-party release-age window. +minimumReleaseAgeExcludes = ["tmp", "@appwrite.io/console"] diff --git a/cli.ts b/cli.ts index 140c54ec..6e740361 100644 --- a/cli.ts +++ b/cli.ts @@ -44,6 +44,7 @@ import { health } from './lib/commands/services/health.js'; import { locale } from './lib/commands/services/locale.js'; import { messaging } from './lib/commands/services/messaging.js'; import { migrations } from './lib/commands/services/migrations.js'; +import { oauth2 } from './lib/commands/services/oauth-2.js'; import { organization } from './lib/commands/services/organization.js'; import { organizations } from './lib/commands/services/organizations.js'; import { presences } from './lib/commands/services/presences.js'; @@ -216,6 +217,7 @@ if (process.argv.includes('-v') || process.argv.includes('--version')) { .addCommand(locale) .addCommand(messaging) .addCommand(migrations) + .addCommand(oauth2) .addCommand(organization) .addCommand(organizations) .addCommand(presences) diff --git a/docs/examples/account/create.md b/docs/examples/account/create.md index f4fd9643..f2450ef8 100644 --- a/docs/examples/account/create.md +++ b/docs/examples/account/create.md @@ -2,5 +2,5 @@ appwrite account create \ --user-id \ --email email@example.com \ - --password '' + --password password ``` diff --git a/docs/examples/account/update-password.md b/docs/examples/account/update-password.md index db0c1764..b7df4864 100644 --- a/docs/examples/account/update-password.md +++ b/docs/examples/account/update-password.md @@ -1,4 +1,4 @@ ```bash appwrite account update-password \ - --password '' + --password password ``` diff --git a/docs/examples/account/update-recovery.md b/docs/examples/account/update-recovery.md index d4f70b2c..5873ac86 100644 --- a/docs/examples/account/update-recovery.md +++ b/docs/examples/account/update-recovery.md @@ -2,5 +2,5 @@ appwrite account update-recovery \ --user-id \ --secret \ - --password '' + --password password ``` diff --git a/docs/examples/messaging/create-ses-provider.md b/docs/examples/messaging/create-ses-provider.md new file mode 100644 index 00000000..b8b1b6a8 --- /dev/null +++ b/docs/examples/messaging/create-ses-provider.md @@ -0,0 +1,5 @@ +```bash +appwrite messaging create-ses-provider \ + --provider-id \ + --name +``` diff --git a/docs/examples/messaging/update-ses-provider.md b/docs/examples/messaging/update-ses-provider.md new file mode 100644 index 00000000..101a8e87 --- /dev/null +++ b/docs/examples/messaging/update-ses-provider.md @@ -0,0 +1,4 @@ +```bash +appwrite messaging update-ses-provider \ + --provider-id +``` diff --git a/docs/examples/migrations/create-n-host-migration.md b/docs/examples/migrations/create-n-host-migration.md index 703d3ec0..bf2f92c3 100644 --- a/docs/examples/migrations/create-n-host-migration.md +++ b/docs/examples/migrations/create-n-host-migration.md @@ -6,5 +6,5 @@ appwrite migrations create-n-host-migration \ --admin-secret \ --database \ --username \ - --password + --password password ``` diff --git a/docs/examples/migrations/create-supabase-migration.md b/docs/examples/migrations/create-supabase-migration.md index d89211c4..76cae2ac 100644 --- a/docs/examples/migrations/create-supabase-migration.md +++ b/docs/examples/migrations/create-supabase-migration.md @@ -5,5 +5,5 @@ appwrite migrations create-supabase-migration \ --api-key \ --database-host \ --username \ - --password + --password password ``` diff --git a/docs/examples/migrations/get-n-host-report.md b/docs/examples/migrations/get-n-host-report.md index 8e472c6e..03bee64c 100644 --- a/docs/examples/migrations/get-n-host-report.md +++ b/docs/examples/migrations/get-n-host-report.md @@ -6,5 +6,5 @@ appwrite migrations get-n-host-report \ --admin-secret \ --database \ --username \ - --password + --password password ``` diff --git a/docs/examples/migrations/get-supabase-report.md b/docs/examples/migrations/get-supabase-report.md index 9aabb714..d728cb3d 100644 --- a/docs/examples/migrations/get-supabase-report.md +++ b/docs/examples/migrations/get-supabase-report.md @@ -5,5 +5,5 @@ appwrite migrations get-supabase-report \ --api-key \ --database-host \ --username \ - --password + --password password ``` diff --git a/docs/examples/oauth2/approve.md b/docs/examples/oauth2/approve.md new file mode 100644 index 00000000..a5b255f8 --- /dev/null +++ b/docs/examples/oauth2/approve.md @@ -0,0 +1,4 @@ +```bash +appwrite oauth-2 approve \ + --grant-_id +``` diff --git a/docs/examples/oauth2/authorize.md b/docs/examples/oauth2/authorize.md new file mode 100644 index 00000000..e0427643 --- /dev/null +++ b/docs/examples/oauth2/authorize.md @@ -0,0 +1,7 @@ +```bash +appwrite oauth-2 authorize \ + --client-_id \ + --redirect-_uri https://example.com \ + --response-_type code \ + --scope +``` diff --git a/docs/examples/oauth2/create-device-authorization.md b/docs/examples/oauth2/create-device-authorization.md new file mode 100644 index 00000000..81655435 --- /dev/null +++ b/docs/examples/oauth2/create-device-authorization.md @@ -0,0 +1,3 @@ +```bash +appwrite oauth-2 create-device-authorization +``` diff --git a/docs/examples/oauth2/create-grant.md b/docs/examples/oauth2/create-grant.md new file mode 100644 index 00000000..5d234708 --- /dev/null +++ b/docs/examples/oauth2/create-grant.md @@ -0,0 +1,4 @@ +```bash +appwrite oauth-2 create-grant \ + --user-_code +``` diff --git a/docs/examples/oauth2/create-token.md b/docs/examples/oauth2/create-token.md new file mode 100644 index 00000000..a8fcc0a8 --- /dev/null +++ b/docs/examples/oauth2/create-token.md @@ -0,0 +1,4 @@ +```bash +appwrite oauth-2 create-token \ + --grant-_type +``` diff --git a/docs/examples/oauth2/get-grant.md b/docs/examples/oauth2/get-grant.md new file mode 100644 index 00000000..53ce6635 --- /dev/null +++ b/docs/examples/oauth2/get-grant.md @@ -0,0 +1,4 @@ +```bash +appwrite oauth-2 get-grant \ + --grant-_id +``` diff --git a/docs/examples/oauth2/logout.md b/docs/examples/oauth2/logout.md new file mode 100644 index 00000000..ccb37f26 --- /dev/null +++ b/docs/examples/oauth2/logout.md @@ -0,0 +1,3 @@ +```bash +appwrite oauth-2 logout +``` diff --git a/docs/examples/oauth2/reject.md b/docs/examples/oauth2/reject.md new file mode 100644 index 00000000..941b4815 --- /dev/null +++ b/docs/examples/oauth2/reject.md @@ -0,0 +1,4 @@ +```bash +appwrite oauth-2 reject \ + --grant-_id +``` diff --git a/docs/examples/oauth2/revoke.md b/docs/examples/oauth2/revoke.md new file mode 100644 index 00000000..d83cc9d0 --- /dev/null +++ b/docs/examples/oauth2/revoke.md @@ -0,0 +1,4 @@ +```bash +appwrite oauth-2 revoke \ + --token +``` diff --git a/docs/examples/project/update-deny-corporate-email-policy.md b/docs/examples/project/update-deny-corporate-email-policy.md new file mode 100644 index 00000000..ddbfc636 --- /dev/null +++ b/docs/examples/project/update-deny-corporate-email-policy.md @@ -0,0 +1,4 @@ +```bash +appwrite project update-deny-corporate-email-policy \ + --enabled false +``` diff --git a/docs/examples/project/update-o-auth-2-server.md b/docs/examples/project/update-o-auth-2-server.md new file mode 100644 index 00000000..a2a3596d --- /dev/null +++ b/docs/examples/project/update-o-auth-2-server.md @@ -0,0 +1,5 @@ +```bash +appwrite project update-o-auth-2-server \ + --enabled false \ + --authorization-url https://example.com +``` diff --git a/docs/examples/project/update-password-strength-policy.md b/docs/examples/project/update-password-strength-policy.md new file mode 100644 index 00000000..baa7ca9f --- /dev/null +++ b/docs/examples/project/update-password-strength-policy.md @@ -0,0 +1,3 @@ +```bash +appwrite project update-password-strength-policy +``` diff --git a/docs/examples/users/update-password.md b/docs/examples/users/update-password.md index 1298b26e..84c6374c 100644 --- a/docs/examples/users/update-password.md +++ b/docs/examples/users/update-password.md @@ -1,5 +1,5 @@ ```bash appwrite users update-password \ --user-id \ - --password '' + --password password ``` diff --git a/install.ps1 b/install.ps1 index 7ce4ec5a..c237e67b 100644 --- a/install.ps1 +++ b/install.ps1 @@ -13,8 +13,8 @@ # You can use "View source" of this page to see the full script. # REPO -$GITHUB_x64_URL = "https://github.com/appwrite/sdk-for-cli/releases/download/22.0.0/appwrite-cli-win-x64.exe" -$GITHUB_arm64_URL = "https://github.com/appwrite/sdk-for-cli/releases/download/22.0.0/appwrite-cli-win-arm64.exe" +$GITHUB_x64_URL = "https://github.com/appwrite/sdk-for-cli/releases/download/22.1.0/appwrite-cli-win-x64.exe" +$GITHUB_arm64_URL = "https://github.com/appwrite/sdk-for-cli/releases/download/22.1.0/appwrite-cli-win-arm64.exe" $APPWRITE_BINARY_NAME = "appwrite.exe" diff --git a/install.sh b/install.sh index 70e9c414..784c57f6 100644 --- a/install.sh +++ b/install.sh @@ -120,7 +120,7 @@ verifyMacOSCodeSignature() { downloadBinary() { echo "[2/5] Downloading executable for $OS ($ARCH) ..." - GITHUB_LATEST_VERSION="22.0.0" + GITHUB_LATEST_VERSION="22.1.0" GITHUB_FILE="appwrite-cli-${OS}-${ARCH}" GITHUB_URL="https://github.com/$GITHUB_REPOSITORY_NAME/releases/download/$GITHUB_LATEST_VERSION/$GITHUB_FILE" diff --git a/lib/auth/login.ts b/lib/auth/login.ts new file mode 100644 index 00000000..82be961f --- /dev/null +++ b/lib/auth/login.ts @@ -0,0 +1,470 @@ +import inquirer from "inquirer"; +import { Account, type Models } from "@appwrite.io/console"; +import { sdkForConsole } from "../sdks.js"; +import { globalConfig, normalizeCloudConsoleEndpoint } from "../config.js"; +import { + EXECUTABLE_NAME, + OAUTH2_CLIENT_ID, + OAUTH2_SCOPES, +} from "../constants.js"; +import { success, hint, warn, log } from "../parser.js"; +import { isCloudLoginEndpoint, isRegionalCloudEndpoint } from "../utils.js"; +import { isFlagEnabled } from "../flags.js"; +import ID from "../id.js"; +import { + questionsListFactors, + questionsLogin, + questionsMFAChallenge, + questionsSwitchAccount, +} from "../questions.js"; +import ClientLegacy from "../client.js"; +import { decodeIdToken, pollForDeviceToken } from "./oauth.js"; +import { getOauth2Service } from "../services.js"; +import { + createLegacyConsoleClient, + hasAuthSession, + removeCurrentSession, + removeLegacySessionsExcept, + restoreCurrentSession, + deleteServerSession, +} from "./session.js"; + +const DEFAULT_ENDPOINT = "https://cloud.appwrite.io/v1"; + +interface AppwriteError { + type?: string; + response?: string; +} + +const isGuestUnauthorizedError = (err: unknown): err is AppwriteError => + (err as AppwriteError)?.type === "general_unauthorized_scope" || + (err as AppwriteError)?.response === "general_unauthorized_scope"; + +const isMfaRequiredError = (err: unknown): err is AppwriteError => + (err as AppwriteError)?.type === "user_more_factors_required" || + (err as AppwriteError)?.response === "user_more_factors_required"; + +const startWaitingForApprovalSpinner = (): (() => void) => { + const message = "Waiting for approval..."; + const frames = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"]; + + if (!process.stdout.isTTY) { + process.stdout.write(`${message}\n`); + return () => {}; + } + + let frame = 0; + process.stdout.write(`${frames[frame]} ${message}`); + + const interval = setInterval(() => { + frame = (frame + 1) % frames.length; + process.stdout.write(`\r${frames[frame]} ${message}`); + }, 80); + + return () => { + clearInterval(interval); + process.stdout.write("\r\x1b[K"); + }; +}; + +export const getCurrentAccount = async (): Promise => { + if (globalConfig.getEndpoint() === "" || !hasAuthSession()) { + return null; + } + + // sdkForConsole normalizes the endpoint when building the console client, so + // we must not persist it back into the session here — that would overwrite a + // regional Cloud endpoint and route later project calls to the generic host. + const client = await sdkForConsole(); + const accountClient = new Account(client); + + try { + const account = await accountClient.get(); + globalConfig.setEmail(account.email); + return account; + } catch (err) { + if (isGuestUnauthorizedError(err)) { + removeCurrentSession(); + } + return null; + } +}; + +const completeMfaLogin = async ({ + client, + legacyClient, + mfa, + code, +}: { + client: Awaited>; + legacyClient: ClientLegacy; + mfa?: string; + code?: string; +}): Promise => { + let accountClient = new Account(client); + + const savedCookie = globalConfig.getCookie(); + if (savedCookie) { + legacyClient.setCookie(savedCookie); + client.setCookie(savedCookie); + } + + const { factor } = mfa + ? { factor: mfa } + : await inquirer.prompt(questionsListFactors); + const challenge = await accountClient.createMfaChallenge(factor); + + const { otp } = code + ? { otp: code } + : await inquirer.prompt(questionsMFAChallenge); + await legacyClient.call( + "PUT", + "/account/mfa/challenges", + { + "content-type": "application/json", + }, + { + challengeId: challenge.$id, + otp, + }, + ); + + const updatedCookie = globalConfig.getCookie(); + if (updatedCookie) { + client.setCookie(updatedCookie); + } + + accountClient = new Account(client); + return accountClient.get(); +}; + +const loginWithEmailPassword = async ({ + id, + oldCurrent, + configEndpoint, + email, + password, + endpoint, + mfa, + code, +}: { + id: string; + oldCurrent: string; + configEndpoint: string; + email: string; + password: string; + endpoint?: string; + mfa?: string; + code?: string; +}): Promise => { + globalConfig.addSession(id, { endpoint: configEndpoint }); + globalConfig.setCurrentSession(id); + globalConfig.setEndpoint(configEndpoint); + globalConfig.setEmail(email); + + const legacyClient = createLegacyConsoleClient(configEndpoint); + const client = await sdkForConsole({ requiresAuth: false }); + let accountClient = new Account(client); + let account: Models.User; + + try { + await legacyClient.call( + "POST", + "/account/sessions/email", + { + "content-type": "application/json", + }, + { email, password }, + ); + + const savedCookie = globalConfig.getCookie(); + if (savedCookie) { + legacyClient.setCookie(savedCookie); + client.setCookie(savedCookie); + } + + accountClient = new Account(client); + account = await accountClient.get(); + } catch (err) { + if (isMfaRequiredError(err)) { + try { + account = await completeMfaLogin({ + client, + legacyClient, + mfa, + code, + }); + } catch (mfaErr) { + globalConfig.removeSession(id); + restoreCurrentSession(oldCurrent); + throw mfaErr; + } + } else { + globalConfig.removeSession(id); + globalConfig.setCurrentSession(oldCurrent); + + if ( + endpoint !== DEFAULT_ENDPOINT && + ((err as AppwriteError)?.type === "user_invalid_credentials" || + (err as AppwriteError)?.response === "user_invalid_credentials") + ) { + log("Use the --endpoint option for self-hosted instances"); + } + + throw err; + } + } + + globalConfig.setEmail(account.email); + success("Successfully signed in as " + account.email); + hint( + "Next you can create or link to your project using 'appwrite init project'", + ); +}; + +const switchToAccount = async ({ + oldCurrent, + accountId, +}: { + oldCurrent: string; + accountId: string; +}): Promise => { + if (!globalConfig.getSessionIds().includes(accountId)) { + throw Error("Session ID not found"); + } + + if (accountId === oldCurrent) { + const account = await getCurrentAccount(); + if (account) { + success(`Already using ${account.email}`); + return; + } + throw new Error( + `Selected account session is no longer valid. Run '${EXECUTABLE_NAME} login --switch' again.`, + ); + } + + globalConfig.setCurrentSession(accountId); + + let account: Models.User | null = null; + try { + account = await getCurrentAccount(); + } catch (err) { + restoreCurrentSession(oldCurrent); + throw err; + } + + if (!account) { + restoreCurrentSession(oldCurrent); + throw new Error( + `Selected account session is no longer valid. Run '${EXECUTABLE_NAME} login --switch' again.`, + ); + } + + success(`Switched to ${account.email}`); +}; + +const loginWithOAuthDevice = async ({ + id, + oldCurrent, + configEndpoint, +}: { + id: string; + oldCurrent: string; + configEndpoint: string; +}): Promise => { + const clientId = OAUTH2_CLIENT_ID; + const oauth2 = await getOauth2Service( + await sdkForConsole({ requiresAuth: false, endpointOverride: configEndpoint }), + ); + + globalConfig.addSession(id, { endpoint: configEndpoint, clientId }); + globalConfig.setCurrentSession(id); + globalConfig.setEndpoint(configEndpoint); + + let deviceAuth; + try { + deviceAuth = await oauth2.createDeviceAuthorization({ + clientId, + scope: OAUTH2_SCOPES, + }); + } catch (err) { + globalConfig.removeSession(id); + globalConfig.setCurrentSession(oldCurrent); + throw err; + } + + const verificationUri = + deviceAuth.verification_uri_complete || deviceAuth.verification_uri; + process.stdout.write( + "\nTo sign in, confirm the code below in your browser:\n\n" + + ` Code: ${deviceAuth.user_code}\n` + + ` URL: ${verificationUri}\n\n`, + ); + + let token: Models.Oauth2Token | null = null; + const stopWaitingForApprovalSpinner = startWaitingForApprovalSpinner(); + + try { + token = await pollForDeviceToken(oauth2, deviceAuth, clientId); + } catch (err) { + globalConfig.removeSession(id); + globalConfig.setCurrentSession(oldCurrent); + throw err; + } finally { + stopWaitingForApprovalSpinner(); + } + + if (!token || !token.access_token) { + globalConfig.removeSession(id); + globalConfig.setCurrentSession(oldCurrent); + throw new Error("Device authorization timed out or was denied."); + } + + const tokenExpiry = Date.now() + token.expires_in * 1000; + globalConfig.setAccessToken(token.access_token); + globalConfig.setRefreshToken(token.refresh_token || ""); + globalConfig.setTokenExpiry(tokenExpiry); + + let tokenEmail = ""; + if (token.id_token) { + tokenEmail = decodeIdToken(token.id_token).email || ""; + } + + if (tokenEmail) { + globalConfig.setEmail(tokenEmail); + } + + let account: Models.User | null = null; + try { + account = await getCurrentAccount(); + if (!account?.email) { + throw new Error("Unable to verify the new session."); + } + } catch (err) { + await deleteServerSession(id); + globalConfig.removeSession(id); + restoreCurrentSession(oldCurrent); + throw err; + } + + globalConfig.setEmail(account.email); + + const { removed: removedLegacySessions, failed: failedLegacySessions } = + await removeLegacySessionsExcept(id); + + success("Successfully signed in as " + globalConfig.getEmail()); + if (removedLegacySessions > 0) { + hint("Removed legacy cookie session data from this CLI configuration."); + } + if (failedLegacySessions > 0) { + warn("Could not revoke all legacy sessions; kept them in local config."); + } + hint( + "Next you can create or link to your project using 'appwrite init project'", + ); +}; + +export const loginCommand = async ({ + email, + password, + endpoint, + mfa, + code, + switch: switchAccount, + new: newAccount, +}: { + email?: string; + password?: string; + endpoint?: string; + mfa?: string; + code?: string; + switch?: boolean; + new?: boolean; +}): Promise => { + let oldCurrent = globalConfig.getCurrentSession(); + + if (switchAccount && newAccount) { + throw new Error("Use either --switch or --new, not both."); + } + + if (endpoint && isRegionalCloudEndpoint(endpoint)) { + throw new Error( + `Cloud login uses ${DEFAULT_ENDPOINT}. Regional Cloud endpoints are for project API calls, not account login.`, + ); + } + + const configEndpoint = normalizeCloudConsoleEndpoint( + (endpoint ?? globalConfig.getEndpoint()) || DEFAULT_ENDPOINT, + ); + const shouldUseCloudLogin = + isFlagEnabled("oauthLogin") && isCloudLoginEndpoint(configEndpoint); + + oldCurrent = globalConfig.getCurrentSession(); + + if (oldCurrent !== "" && !newAccount) { + let account: Models.User | null = null; + try { + account = await getCurrentAccount(); + } catch (_err) { + account = null; + } + oldCurrent = globalConfig.getCurrentSession(); + + if (account) { + if ( + isFlagEnabled("oauthLogin") && + !globalConfig.getAccessToken() && + globalConfig.getCookie() + ) { + warn( + `You are using a legacy cookie session. Run '${EXECUTABLE_NAME} login --new' to switch to the new browser-based login flow.`, + ); + } + + if (!email && !password && !endpoint && !switchAccount && !newAccount) { + success("Already logged in as " + account.email); + hint(`Use '${EXECUTABLE_NAME} login --new' to add another account`); + return; + } + } + } + + let answers; + if (switchAccount) { + if (!globalConfig.getSessions().some((session) => session.email)) { + throw new Error( + `No signed-in accounts found. Run '${EXECUTABLE_NAME} login' to sign in.`, + ); + } + answers = await inquirer.prompt(questionsSwitchAccount); + } else if (!shouldUseCloudLogin) { + answers = + email && password + ? { email, password } + : await inquirer.prompt(questionsLogin); + } + + if (switchAccount && answers?.accountId) { + await switchToAccount({ oldCurrent, accountId: answers.accountId }); + return; + } + + const id = ID.unique(); + + if (!shouldUseCloudLogin) { + await loginWithEmailPassword({ + id, + oldCurrent, + configEndpoint, + email: answers.email, + password: answers.password, + endpoint, + mfa, + code, + }); + return; + } + + await loginWithOAuthDevice({ id, oldCurrent, configEndpoint }); +}; diff --git a/lib/auth/oauth.ts b/lib/auth/oauth.ts new file mode 100644 index 00000000..94f9fb2d --- /dev/null +++ b/lib/auth/oauth.ts @@ -0,0 +1,120 @@ +import { AppwriteException, Oauth2, type Models } from "@appwrite.io/console"; +import { sdkForConsole } from "../sdks.js"; +import { getOauth2Service } from "../services.js"; + +export type DeviceAuthorization = Awaited< + ReturnType +>; + +export const decodeIdToken = ( + idToken: string, +): { email?: string; name?: string; sub?: string } => { + try { + const payload = idToken.split(".")[1]; + if (!payload) return {}; + const decoded = JSON.parse( + Buffer.from(payload, "base64url").toString("utf-8"), + ); + return { + email: decoded.email, + name: decoded.name, + sub: decoded.sub, + }; + } catch (_error) { + return {}; + } +}; + +const matchesOAuthError = (err: unknown, code: string): boolean => { + if (!(err instanceof AppwriteException)) { + return false; + } + + const response = typeof err.response === "string" ? err.response : ""; + + return err.type === code || err.message === code || response.includes(code); +}; + +export const isAuthorizationPendingError = (err: unknown): boolean => + matchesOAuthError(err, "authorization_pending") || + matchesOAuthError(err, "slow_down"); + +const isSlowDownError = (err: unknown): boolean => + matchesOAuthError(err, "slow_down"); + +// An empty/unrecognized error body during polling (e.g. a 400 with no type, +// message, or response) is treated as a transient pending response rather than +// aborting the device flow. +const isEmptyDevicePollError = (err: unknown): boolean => { + if (!(err instanceof AppwriteException)) { + return false; + } + + const type = typeof err.type === "string" ? err.type.trim() : ""; + const message = typeof err.message === "string" ? err.message.trim() : ""; + const response = typeof err.response === "string" ? err.response.trim() : ""; + + return type === "" && message === "" && response === ""; +}; + +/** + * Poll the token endpoint until the device authorization is approved. + * Returns the token, or `null` if the authorization window expires. + * Pending and empty-body responses are retried; `slow_down` additionally + * increases the polling interval by 5s (RFC 8628 §3.5); any other error throws. + */ +export const pollForDeviceToken = async ( + oauth2: Oauth2, + deviceAuth: DeviceAuthorization, + clientId: string, +): Promise => { + const expiresAt = Date.now() + deviceAuth.expires_in * 1000; + // Default to a 5s interval when the server omits one (RFC 8628 §3.5); + // an omitted interval would otherwise be NaN and busy-poll the endpoint. + let intervalMs = + (Number.isFinite(deviceAuth.interval) && deviceAuth.interval >= 0 + ? deviceAuth.interval + : 5) * 1000; + + while (Date.now() < expiresAt) { + await new Promise((resolve) => setTimeout(resolve, intervalMs)); + + let token: Models.Oauth2Token; + try { + token = await oauth2.createToken({ + grantType: "urn:ietf:params:oauth:grant-type:device_code", + deviceCode: deviceAuth.device_code, + clientId, + }); + } catch (err) { + if (isAuthorizationPendingError(err) || isEmptyDevicePollError(err)) { + if (isSlowDownError(err)) { + intervalMs += 5000; + } + continue; + } + throw err; + } + + if (token) { + return token; + } + } + + return null; +}; + +export const revokeRefreshToken = async ( + endpoint: string, + refreshToken: string, + clientId: string, +): Promise => { + const oauth2 = await getOauth2Service( + await sdkForConsole({ requiresAuth: false, endpointOverride: endpoint }), + ); + await oauth2.revoke({ + token: refreshToken, + tokenTypeHint: "refresh_token", + clientId, + }); +}; diff --git a/lib/auth/session.ts b/lib/auth/session.ts new file mode 100644 index 00000000..ca8bf489 --- /dev/null +++ b/lib/auth/session.ts @@ -0,0 +1,206 @@ +import { globalConfig, normalizeCloudConsoleEndpoint } from "../config.js"; +import type { SessionData } from "../types.js"; +import ClientLegacy from "../client.js"; +import { OAUTH2_CLIENT_ID } from "../constants.js"; +import { revokeRefreshToken } from "./oauth.js"; + +/** + * Typed accessor for a stored session, avoiding repeated inline casts. + */ +export const getSession = (sessionId: string): SessionData | undefined => + globalConfig.get(sessionId) as SessionData | undefined; + +export const createLegacyConsoleClient = ( + endpoint: string, + selfSigned: boolean = globalConfig.getSelfSigned(), +): ClientLegacy => { + const legacyClient = new ClientLegacy(); + legacyClient.setEndpoint(endpoint); + legacyClient.setProject("console"); + if (selfSigned) { + legacyClient.setSelfSigned(true); + } + return legacyClient; +}; + +export const hasAuthSession = (): boolean => + globalConfig.getAccessToken() !== "" || globalConfig.getCookie() !== ""; + +/** + * A session that exists only in local config (no server-side credential to + * revoke), e.g. an endpoint/key-only entry created by `client --endpoint`. + */ +export const isLocalOnlySession = (sessionId: string): boolean => { + const session = getSession(sessionId); + return Boolean(session && !session.refreshToken && !session.cookie); +}; + +/** + * A legacy cookie session that predates the OAuth device-login flow. + */ +export const isLegacySession = (sessionId: string): boolean => { + const session = getSession(sessionId); + return Boolean(session?.cookie && !session?.accessToken); +}; + +export const getSessionAccountKey = (sessionId: string): string | undefined => { + const session = getSession(sessionId); + if (!session) return undefined; + return `${session.email ?? ""}|${normalizeCloudConsoleEndpoint( + session.endpoint ?? "", + )}`; +}; + +export const restoreCurrentSession = (sessionId: string): void => { + globalConfig.setCurrentSession( + globalConfig.getSessionIds().includes(sessionId) ? sessionId : "", + ); +}; + +export const restoreCurrentSessionFallback = ( + preferredSessionId: string, + fallbackSessionIds: string[], +): void => { + const sessionIds = globalConfig.getSessionIds(); + globalConfig.setCurrentSession( + [preferredSessionId, ...fallbackSessionIds].find((sessionId) => + sessionIds.includes(sessionId), + ) ?? "", + ); +}; + +export const removeCurrentSession = (): void => { + const current = globalConfig.getCurrentSession(); + globalConfig.setCurrentSession(""); + globalConfig.removeSession(current); +}; + +/** + * Revoke a session on the server. OAuth sessions revoke their refresh token; + * legacy cookie sessions delete the current server session. Returns whether + * the server-side cleanup succeeded. + */ +export const deleteServerSession = async ( + sessionId: string, +): Promise => { + const session = getSession(sessionId); + if (!session?.endpoint) { + return false; + } + + try { + if (session.refreshToken) { + await revokeRefreshToken( + session.endpoint, + session.refreshToken, + session.clientId || OAUTH2_CLIENT_ID, + ); + return true; + } + + if (session.cookie) { + // Use the target session's own self-signed setting, not the current + // session's, so revoking a self-signed legacy session works even when a + // different (e.g. new OAuth) session is current. + const legacyClient = createLegacyConsoleClient( + session.endpoint, + Boolean(session.selfSigned), + ); + legacyClient.setCookie(session.cookie); + await legacyClient.call("DELETE", "/account/sessions/current", { + "content-type": "application/json", + }); + return true; + } + + return false; + } catch (_e) { + return false; + } +}; + +/** + * Log out a set of session IDs: local-only sessions are removed locally; + * the rest are revoked server-side and removed locally on success. Returns + * the IDs that could not be revoked so callers can restore/warn as needed. + */ +export const logoutSessions = async ( + sessionIds: string[], +): Promise<{ failed: number; failedIds: string[] }> => { + let failed = 0; + const failedIds: string[] = []; + + for (const sessionId of sessionIds) { + if (isLocalOnlySession(sessionId)) { + globalConfig.removeSession(sessionId); + continue; + } + + globalConfig.setCurrentSession(sessionId); + const serverDeleted = await deleteServerSession(sessionId); + if (serverDeleted) { + globalConfig.removeSession(sessionId); + } else { + failed++; + failedIds.push(sessionId); + } + } + + return { failed, failedIds }; +}; + +export const removeLegacySessionsExcept = async ( + sessionIdToKeep: string, +): Promise<{ removed: number; failed: number }> => { + let removed = 0; + let failed = 0; + + for (const sessionId of globalConfig.getSessionIds()) { + if (sessionId === sessionIdToKeep || !isLegacySession(sessionId)) { + continue; + } + + const serverDeleted = await deleteServerSession(sessionId); + if (serverDeleted) { + globalConfig.removeSession(sessionId); + removed++; + } else { + failed++; + } + } + + return { removed, failed }; +}; + +/** + * Given selected session IDs, determine which local sessions belong to the + * selected accounts and should be individually logged out from the server. + * + * @param selectedSessionIds Array of session IDs to process for logout. + * @returns Session IDs that belong to selected account groups. + */ +export const planSessionLogout = (selectedSessionIds: string[]): string[] => { + // Map to group all session IDs by their unique account key (email+endpoint) + const sessionIdsByAccount = new Map(); + for (const sessionId of globalConfig.getSessionIds()) { + const key = getSessionAccountKey(sessionId); + if (!key) continue; // Skip sessions without proper account key + + // For each account key, gather all associated session IDs + const ids = sessionIdsByAccount.get(key) ?? []; + ids.push(sessionId); + sessionIdsByAccount.set(key, ids); + } + + // Map to store one selected session ID per unique account for server logout + const selectedByAccount = new Map(); + for (const selectedSessionId of selectedSessionIds) { + const key = getSessionAccountKey(selectedSessionId); + if (!key || selectedByAccount.has(key)) continue; // Skip if key missing or already considered for this account + selectedByAccount.set(key, selectedSessionId); + } + + return Array.from(selectedByAccount.keys()).flatMap( + (accountKey) => sessionIdsByAccount.get(accountKey) ?? [], + ); +}; diff --git a/lib/commands/generic.ts b/lib/commands/generic.ts index 2e4e6883..9e88e20f 100644 --- a/lib/commands/generic.ts +++ b/lib/commands/generic.ts @@ -1,12 +1,7 @@ import inquirer from "inquirer"; import { Command } from "commander"; import { Client } from "@appwrite.io/console"; -import { sdkForConsole } from "../sdks.js"; -import { - globalConfig, - localConfig, - normalizeCloudConsoleEndpoint, -} from "../config.js"; +import { globalConfig, localConfig } from "../config.js"; import { EXECUTABLE_NAME } from "../constants.js"; import { actionRunner, @@ -20,397 +15,23 @@ import { drawTable, cliConfig, } from "../parser.js"; -import { isCloudHostname } from "../utils.js"; import ID from "../id.js"; +import { questionsLogout } from "../questions.js"; +import { getCurrentAccount, loginCommand } from "../auth/login.js"; import { - questionsLogin, - questionsLogout, - questionsListFactors, - questionsMFAChallenge, - questionsSwitchAccount, -} from "../questions.js"; -import { - Account, - Client as ConsoleClient, - type Models, -} from "@appwrite.io/console"; -import ClientLegacy from "../client.js"; - -const DEFAULT_ENDPOINT = "https://cloud.appwrite.io/v1"; - -interface AppwriteError { - type?: string; - response?: string; -} - -const isMfaRequiredError = (err: unknown): err is AppwriteError => - (err as AppwriteError)?.type === "user_more_factors_required" || - (err as AppwriteError)?.response === "user_more_factors_required"; + hasAuthSession, + logoutSessions, + planSessionLogout, + restoreCurrentSessionFallback, +} from "../auth/session.js"; -const isGuestUnauthorizedError = (err: unknown): err is AppwriteError => - (err as AppwriteError)?.type === "general_unauthorized_scope" || - (err as AppwriteError)?.response === "general_unauthorized_scope"; - -const isRegionalCloudEndpoint = (endpoint: string): boolean => { - try { - const hostname = new URL(endpoint).hostname; - return isCloudHostname(hostname) && hostname !== "cloud.appwrite.io"; - } catch (_error) { - return false; - } -}; - -const restoreCurrentSession = (sessionId: string): void => { - globalConfig.setCurrentSession( - globalConfig.getSessionIds().includes(sessionId) ? sessionId : "", - ); -}; - -const removeCurrentSession = (): void => { - const current = globalConfig.getCurrentSession(); - globalConfig.setCurrentSession(""); - globalConfig.removeSession(current); -}; - -const getCurrentAccount = async (): Promise => { - if (globalConfig.getEndpoint() === "" || globalConfig.getCookie() === "") { - return null; - } - - const endpoint = normalizeCloudConsoleEndpoint(globalConfig.getEndpoint()); - if (endpoint !== globalConfig.getEndpoint()) { - globalConfig.setEndpoint(endpoint); - } - - const client = await sdkForConsole({ requiresAuth: false }); - const accountClient = new Account(client); - - try { - const account = await accountClient.get(); - globalConfig.setEmail(account.email); - return account; - } catch (err) { - if (isGuestUnauthorizedError(err)) { - removeCurrentSession(); - } - return null; - } -}; - -const createLegacyConsoleClient = (endpoint: string): ClientLegacy => { - const legacyClient = new ClientLegacy(); - legacyClient.setEndpoint(endpoint); - legacyClient.setProject("console"); - if (globalConfig.getSelfSigned()) { - legacyClient.setSelfSigned(true); - } - return legacyClient; -}; - -const completeMfaLogin = async ({ - client, - legacyClient, - mfa, - code, -}: { - client: ConsoleClient; - legacyClient: ClientLegacy; - mfa?: string; - code?: string; -}): Promise => { - let accountClient = new Account(client); - - const savedCookie = globalConfig.getCookie(); - if (savedCookie) { - legacyClient.setCookie(savedCookie); - client.setCookie(savedCookie); - } - - const { factor } = mfa - ? { factor: mfa } - : await inquirer.prompt(questionsListFactors); - const challenge = await accountClient.createMfaChallenge(factor); - - const { otp } = code - ? { otp: code } - : await inquirer.prompt(questionsMFAChallenge); - await legacyClient.call( - "PUT", - "/account/mfa/challenges", - { - "content-type": "application/json", - }, - { - challengeId: challenge.$id, - otp, - }, - ); - - const updatedCookie = globalConfig.getCookie(); - if (updatedCookie) { - client.setCookie(updatedCookie); - } - - accountClient = new Account(client); - return accountClient.get(); -}; - -const deleteServerSession = async (sessionId: string): Promise => { - try { - const client = await sdkForConsole(); - const accountClient = new Account(client); - await accountClient.deleteSession(sessionId); - return true; - } catch (_e) { - return false; - } -}; - -const deleteLocalSession = (accountId: string): void => { - globalConfig.removeSession(accountId); -}; - -const getSessionAccountKey = (sessionId: string): string | undefined => { - const session = globalConfig.get(sessionId) as - | { email?: string; endpoint?: string } - | undefined; - if (!session) return undefined; - return `${session.email ?? ""}|${normalizeCloudConsoleEndpoint( - session.endpoint ?? "", - )}`; -}; - -/** - * Given selected session IDs, determine which sessions should be logged out - * from the server (one per unique account) and which should be removed locally (all sessions for those accounts). - * - * @param selectedSessionIds Array of session IDs to process for logout. - * @returns Object containing `serverTargets` (sessions to logout from server) - * and `localTargets` (sessions to remove locally). - */ -const planSessionLogout = ( - selectedSessionIds: string[], -): { serverTargets: string[]; localTargets: string[] } => { - // Map to group all session IDs by their unique account key (email+endpoint) - const sessionIdsByAccount = new Map(); - for (const sessionId of globalConfig.getSessionIds()) { - const key = getSessionAccountKey(sessionId); - if (!key) continue; // Skip sessions without proper account key - - // For each account key, gather all associated session IDs - const ids = sessionIdsByAccount.get(key) ?? []; - ids.push(sessionId); - sessionIdsByAccount.set(key, ids); - } - - // Map to store one selected session ID per unique account for server logout - const selectedByAccount = new Map(); - for (const selectedSessionId of selectedSessionIds) { - const key = getSessionAccountKey(selectedSessionId); - if (!key || selectedByAccount.has(key)) continue; // Skip if key missing or already considered for this account - selectedByAccount.set(key, selectedSessionId); - } - - // Sessions to target for server logout: one per unique account - const serverTargets = Array.from(selectedByAccount.values()); - // Sessions to remove locally: all sessions under selected accounts - const localTargets = Array.from(selectedByAccount.keys()).flatMap( - (accountKey) => sessionIdsByAccount.get(accountKey) ?? [], - ); - - return { serverTargets, localTargets }; -}; - -export const loginCommand = async ({ - email, - password, - endpoint, - mfa, - code, - switch: switchAccount, - new: newAccount, -}: { - email?: string; - password?: string; - endpoint?: string; - mfa?: string; - code?: string; - switch?: boolean; - new?: boolean; -}): Promise => { - let oldCurrent = globalConfig.getCurrentSession(); - - if (switchAccount && newAccount) { - throw new Error("Use either --switch or --new, not both."); - } - - if (endpoint && isRegionalCloudEndpoint(endpoint)) { - throw new Error( - `Cloud login uses ${DEFAULT_ENDPOINT}. Regional Cloud endpoints are for project API calls, not account login.`, - ); - } - - const configEndpoint = normalizeCloudConsoleEndpoint( - (endpoint ?? globalConfig.getEndpoint()) || DEFAULT_ENDPOINT, - ); - - if (globalConfig.getCurrentSession() !== "") { - const account = await getCurrentAccount(); - oldCurrent = globalConfig.getCurrentSession(); - - if (account) { - if (!email && !password && !endpoint && !switchAccount && !newAccount) { - success("Already logged in as " + account.email); - hint(`Use '${EXECUTABLE_NAME} login --new' to add another account`); - return; - } - } - } - - let answers; - if (switchAccount) { - if (!globalConfig.getSessions().some((session) => session.email)) { - throw new Error( - `No signed-in accounts found. Run '${EXECUTABLE_NAME} login' to sign in.`, - ); - } - answers = await inquirer.prompt(questionsSwitchAccount); - } else if (email && password) { - answers = { email, password }; - } else { - answers = await inquirer.prompt(questionsLogin); - } - - if (!answers.method) { - answers.method = switchAccount ? "select" : "login"; - } - - if (answers.method === "select") { - const accountId = answers.accountId; - - if (!globalConfig.getSessionIds().includes(accountId)) { - throw Error("Session ID not found"); - } - - if (accountId === oldCurrent) { - const account = await getCurrentAccount(); - if (account) { - success(`Already using ${account.email}`); - return; - } - throw new Error( - `Selected account session is no longer valid. Run '${EXECUTABLE_NAME} login --switch' again.`, - ); - } - - globalConfig.setCurrentSession(accountId); - globalConfig.setEndpoint( - normalizeCloudConsoleEndpoint(globalConfig.getEndpoint()), - ); - - const client = await sdkForConsole({ requiresAuth: false }); - const accountClient = new Account(client); - const legacyClient = createLegacyConsoleClient( - globalConfig.getEndpoint() || DEFAULT_ENDPOINT, - ); - - try { - await accountClient.get(); - } catch (err) { - if (!isMfaRequiredError(err)) { - if (isGuestUnauthorizedError(err)) { - globalConfig.removeSession(accountId); - } - restoreCurrentSession(oldCurrent); - throw err; - } - - await completeMfaLogin({ - client, - legacyClient, - mfa, - code, - }); - } - - success(`Switched to ${globalConfig.getEmail()}`); - - return; - } - - const id = ID.unique(); - - globalConfig.addSession(id, { endpoint: configEndpoint }); - globalConfig.setCurrentSession(id); - globalConfig.setEndpoint(configEndpoint); - globalConfig.setEmail(answers.email); - - // Use legacy client for login to extract cookies from response - const legacyClient = createLegacyConsoleClient(configEndpoint); - - const client = await sdkForConsole({ requiresAuth: false }); - let accountClient = new Account(client); - - let account; - - try { - await legacyClient.call( - "POST", - "/account/sessions/email", - { - "content-type": "application/json", - }, - { - email: answers.email, - password: answers.password, - }, - ); - - const savedCookie = globalConfig.getCookie(); - - if (savedCookie) { - legacyClient.setCookie(savedCookie); - client.setCookie(savedCookie); - } - - accountClient = new Account(client); - account = await accountClient.get(); - } catch (err) { - if (isMfaRequiredError(err)) { - account = await completeMfaLogin({ - client, - legacyClient, - mfa, - code, - }); - } else { - globalConfig.removeSession(id); - globalConfig.setCurrentSession(oldCurrent); - if ( - endpoint !== DEFAULT_ENDPOINT && - (err.type === "user_invalid_credentials" || - err.response === "user_invalid_credentials") - ) { - log("Use the --endpoint option for self-hosted instances"); - } - throw err; - } - } - - success("Successfully signed in as " + account.email); - hint( - "Next you can create or link to your project using 'appwrite init project'", - ); -}; +export { loginCommand }; export const whoami = new Command("whoami") .description(commandDescriptions["whoami"]) .action( actionRunner(async () => { - if ( - globalConfig.getEndpoint() === "" || - globalConfig.getCookie() === "" - ) { + if (globalConfig.getEndpoint() === "" || !hasAuthSession()) { error("No user is signed in. To sign in, run 'appwrite login'"); return; } @@ -450,17 +71,17 @@ export const register = new Command("register") export const login = new Command("login") .description(commandDescriptions["login"]) - .option(`--email [email]`, `User email`) - .option(`--password [password]`, `User password`) + .option(`--email [email]`, `Email`) + .option(`--password [password]`, `Password`) .option( - `--endpoint [endpoint]`, - `Appwrite endpoint for self hosted instances`, + `--mfa [factor]`, + `Factor used for MFA. Must be one of: email, phone, totp, recoveryCode`, ) + .option(`--code [code]`, `Code used for MFA`) .option( - `--mfa [factor]`, - `Multi-factor authentication login factor: totp, email, phone or recoveryCode`, + `--endpoint [endpoint]`, + `Appwrite endpoint for self hosted instances`, ) - .option(`--code [code]`, `Multi-factor code`) .option(`--switch`, `Switch to another signed-in account`) .option(`--new`, `Sign in to another account`) .configureHelp({ @@ -484,16 +105,17 @@ export const logout = new Command("logout") return; } if (sessions.length === 1) { - // Try to delete from server, then remove locally - const serverDeleted = await deleteServerSession(current); - // Remove all local sessions with the same email+endpoint - const allSessionIds = globalConfig.getSessionIds(); - for (const sessId of allSessionIds) { - deleteLocalSession(sessId); - } - globalConfig.setCurrentSession(""); - if (!serverDeleted) { - hint("Could not reach server, removed local session data"); + const { failed, failedIds } = await logoutSessions( + planSessionLogout([current]), + ); + + if (failed > 0) { + restoreCurrentSessionFallback(originalCurrent, failedIds); + hint( + "Could not reach server for all sessions; kept local session data", + ); + } else { + globalConfig.setCurrentSession(""); } success("Logged out successfully"); @@ -503,17 +125,14 @@ export const logout = new Command("logout") const answers = await inquirer.prompt(questionsLogout); if (answers.accounts?.length) { - const { serverTargets, localTargets } = planSessionLogout( - answers.accounts as string[], + const { failed } = await logoutSessions( + planSessionLogout(answers.accounts as string[]), ); - for (const sessionId of serverTargets) { - globalConfig.setCurrentSession(sessionId); - await deleteServerSession(sessionId); - } - - for (const sessionId of localTargets) { - deleteLocalSession(sessionId); + if (failed > 0) { + hint( + "Could not reach server for all sessions; kept local session data", + ); } } @@ -600,21 +219,14 @@ export const client = new Command("client") ? "********" : ""; const project = localConfig.getProject(); - const cookie = globalConfig.getCookie(); - let maskedCookie = ""; - if (cookie) { - const [cookieName, cookieValueAndRest = ""] = cookie.split("=", 2); - const cookieValue = cookieValueAndRest.split(";")[0] ?? ""; - const tail = - cookieValue.length > 8 - ? cookieValue.slice(-8) - : cookieValue || "********"; - maskedCookie = `${cookieName}=...${tail}`; - } + const accessToken = globalConfig.getAccessToken(); + const maskedAccessToken = accessToken + ? `${accessToken.slice(0, 8)}...${accessToken.slice(-8)}` + : ""; const config = { endpoint: globalConfig.getEndpoint(), key: maskedKey, - cookie: maskedCookie, + accessToken: maskedAccessToken, selfSigned: globalConfig.getSelfSigned(), projectId: project.projectId ?? "", projectName: project.projectName ?? "", @@ -675,18 +287,19 @@ export const client = new Command("client") } if (reset !== undefined) { - const sessions = globalConfig.getSessions(); - - for (const sessionId of sessions.map((session) => session.id)) { - globalConfig.setCurrentSession(sessionId); - await deleteServerSession(sessionId); - } - - for (const sessionId of globalConfig.getSessionIds()) { - deleteLocalSession(sessionId); + const originalCurrent = globalConfig.getCurrentSession(); + const { failed, failedIds } = await logoutSessions( + globalConfig.getSessionIds(), + ); + + if (failed > 0) { + restoreCurrentSessionFallback(originalCurrent, failedIds); + hint( + "Could not reach server for all sessions; kept local session data", + ); + } else { + globalConfig.setCurrentSession(""); } - - globalConfig.setCurrentSession(""); } if (!debug) { diff --git a/lib/commands/init.ts b/lib/commands/init.ts index 19117e53..5c174b1e 100644 --- a/lib/commands/init.ts +++ b/lib/commands/init.ts @@ -290,9 +290,12 @@ const initProject = async ({ } try { - if (globalConfig.getEndpoint() === "" || globalConfig.getCookie() === "") { + if ( + globalConfig.getEndpoint() === "" || + (globalConfig.getAccessToken() === "" && globalConfig.getCookie() === "") + ) { throw new Error( - `Missing endpoint or cookie configuration. Please run '${EXECUTABLE_NAME} login' first.`, + `Missing endpoint or session configuration. Please run '${EXECUTABLE_NAME} login' first.`, ); } const client = await sdkForConsole(); diff --git a/lib/commands/push.ts b/lib/commands/push.ts index 8cdcdde4..36dec604 100644 --- a/lib/commands/push.ts +++ b/lib/commands/push.ts @@ -34,6 +34,7 @@ import { arrayEqualsUnordered, getFunctionDeploymentConsoleUrl, getSiteDeploymentConsoleUrl, + isCloudHostname, siteRequiresBuildCommand, } from "../utils.js"; import { Spinner, SPINNER_DOTS } from "../spinner.js"; @@ -699,6 +700,7 @@ export class Push { private projectClient: Client; private consoleClient: Client; private silent: boolean; + private projectRegionCache = new Map>(); constructor(projectClient: Client, consoleClient: Client, silent = false) { this.projectClient = projectClient; @@ -742,6 +744,43 @@ export class Push { } } + private async getConsoleUrlProjectRegion( + endpoint: string, + projectId: string, + ): Promise { + try { + if (isCloudHostname(new URL(endpoint).hostname)) { + return undefined; + } + } catch { + return undefined; + } + + const cached = this.projectRegionCache.get(projectId); + if (cached) { + return cached; + } + + const region = (async () => { + try { + const consoleClient = await sdkForConsole({ + requiresAuth: true, + organizationId: localConfig.getProject().organizationId, + }); + const organizationService = await getOrganizationService(consoleClient); + const project = await organizationService.getProject({ + projectId, + }); + return project.region || undefined; + } catch { + return undefined; + } + })(); + this.projectRegionCache.set(projectId, region); + + return region; + } + public async pushResources( config: ConfigType, options: PushOptions = { all: true, skipDeprecated: true }, @@ -1651,11 +1690,16 @@ export class Push { const endpoint = localConfig.getEndpoint() || globalConfig.getEndpoint(); const projectId = localConfig.getProject().projectId; + const projectRegion = await this.getConsoleUrlProjectRegion( + endpoint, + projectId, + ); const consoleUrl = getFunctionDeploymentConsoleUrl( endpoint, projectId, func["$id"], deploymentId, + projectRegion, ); let waitingSince: number | null = null; const deploymentTimeoutTracker = @@ -2159,11 +2203,16 @@ export class Push { const endpoint = localConfig.getEndpoint() || globalConfig.getEndpoint(); const projectId = localConfig.getProject().projectId; + const projectRegion = await this.getConsoleUrlProjectRegion( + endpoint, + projectId, + ); const consoleUrl = getSiteDeploymentConsoleUrl( endpoint, projectId, site["$id"], deploymentId, + projectRegion, ); let waitingSince: number | null = null; let readyWithoutScreenshotsSince: number | null = null; @@ -2981,8 +3030,7 @@ const pushSettings = async (): Promise => { config: project, consoleClient, }); - resolvedOrganizationId = - consoleClient.headers["X-Appwrite-Organization"]; + resolvedOrganizationId = consoleClient.headers["X-Appwrite-Organization"]; const organizationService = await getOrganizationService(consoleClient); const projectService = await getProjectService(); const projectId = project.projectId; diff --git a/lib/commands/services/account.ts b/lib/commands/services/account.ts index 8f181998..5948a335 100644 --- a/lib/commands/services/account.ts +++ b/lib/commands/services/account.ts @@ -353,7 +353,7 @@ const accountUpdatePasswordCommand = account .command(`update-password`) .description(`Update currently logged in user password. For validation, user is required to pass in the new password, and the old password. For users created with OAuth, Team Invites and Magic URL, oldPassword is optional.`) .requiredOption(`--password `, `New user password. Must be at least 8 chars.`) - .option(`--old-password `, `Current user password. Must be at least 8 chars.`) + .option(`--old-password `, `Current user password. Max length: 256 chars.`) .action( actionRunner( async ({ password, oldPassword }) => diff --git a/lib/commands/services/messaging.ts b/lib/commands/services/messaging.ts index 652bfca9..e7c2863d 100644 --- a/lib/commands/services/messaging.ts +++ b/lib/commands/services/messaging.ts @@ -665,6 +665,58 @@ const messagingUpdateSendgridProviderCommand = messaging ); +const messagingCreateSesProviderCommand = messaging + .command(`create-ses-provider`) + .description(`Create a new Amazon SES provider.`) + .requiredOption(`--provider-id `, `Provider ID. Choose a custom ID or generate a random ID with \`ID.unique()\`. Valid chars are a-z, A-Z, 0-9, period, hyphen, and underscore. Can't start with a special char. Max length is 36 chars.`) + .requiredOption(`--name `, `Provider name.`) + .option(`--access-key `, `AWS access key ID.`) + .option(`--secret-key `, `AWS secret access key.`) + .option(`--region `, `AWS region, for example us-east-1.`) + .option(`--from-name `, `Sender Name.`) + .option(`--from-email `, `Sender email address.`) + .option(`--reply-to-name `, `Name set in the reply to field for the mail. Default value is sender name.`) + .option(`--reply-to-email `, `Email set in the reply to field for the mail. Default value is sender email.`) + .option( + `--enabled [value]`, + `Set as enabled.`, + (value: string | undefined) => + value === undefined ? true : parseBool(value), + ) + .action( + actionRunner( + async ({ providerId, name, accessKey, secretKey, region, fromName, fromEmail, replyToName, replyToEmail, enabled }) => + parse(await (await getMessagingClient()).createSesProvider(providerId, name, accessKey, secretKey, region, fromName, fromEmail, replyToName, replyToEmail, enabled)), + ), + ); + + +const messagingUpdateSesProviderCommand = messaging + .command(`update-ses-provider`) + .description(`Update an Amazon SES provider by its unique ID.`) + .requiredOption(`--provider-id `, `Provider ID.`) + .option(`--name `, `Provider name.`) + .option( + `--enabled [value]`, + `Set as enabled.`, + (value: string | undefined) => + value === undefined ? true : parseBool(value), + ) + .option(`--access-key `, `AWS access key ID.`) + .option(`--secret-key `, `AWS secret access key.`) + .option(`--region `, `AWS region, for example us-east-1.`) + .option(`--from-name `, `Sender Name.`) + .option(`--from-email `, `Sender email address.`) + .option(`--reply-to-name `, `Name set in the Reply To field for the mail. Default value is Sender Name.`) + .option(`--reply-to-email `, `Email set in the Reply To field for the mail. Default value is Sender Email.`) + .action( + actionRunner( + async ({ providerId, name, enabled, accessKey, secretKey, region, fromName, fromEmail, replyToName, replyToEmail }) => + parse(await (await getMessagingClient()).updateSesProvider(providerId, name, enabled, accessKey, secretKey, region, fromName, fromEmail, replyToName, replyToEmail)), + ), + ); + + const messagingCreateSmtpProviderCommand = messaging .command(`create-smtp-provider`) .description(`Create a new SMTP provider.`) diff --git a/lib/commands/services/oauth-2.ts b/lib/commands/services/oauth-2.ts new file mode 100644 index 00000000..3b028db5 --- /dev/null +++ b/lib/commands/services/oauth-2.ts @@ -0,0 +1,164 @@ +import { Command } from "commander"; +import { sdkForProject } from "../../sdks.js"; +import { + actionRunner, + commandDescriptions, + success, + parse, + parseBool, + parseInteger, +} from "../../parser.js"; +import { Oauth2 } from "@appwrite.io/console"; + +let oauth2Client: Oauth2 | null = null; + +const getOauth2Client = async (): Promise => { + if (!oauth2Client) { + const sdkClient = await sdkForProject(); + oauth2Client = new Oauth2(sdkClient); + } + return oauth2Client; +}; + +export const oauth2 = new Command("oauth-2") + .description(commandDescriptions["oauth2"] ?? "") + .configureHelp({ + helpWidth: process.stdout.columns || 80, + }); + +const oauth2ApproveCommand = oauth2 + .command(`approve`) + .description(`Approve an OAuth2 grant after the user gives consent. Returns the \`redirectUrl\` the end user should be sent to. The consent screen may optionally pass enriched \`authorization_details\` to record the concrete resources the user selected. You can pass Accept header of \`application/json\` to receive a JSON response instead of a redirect.`) + .requiredOption(`--grant-_id `, `Grant ID made during authorization, provided to consent screen in URL search params.`) + .option(`--authorization-_details `, `Enriched \`authorization_details\` the user consented to, replacing what the client requested. Each entry must use a \`type\` the project accepts. Optional; omit to keep the originally requested details.`) + .action( + actionRunner( + async ({ grant_id, authorization_details }) => + parse(await (await getOauth2Client()).approve(grant_id, authorization_details)), + ), + ); + + +const oauth2AuthorizeCommand = oauth2 + .command(`authorize`) + .description(`Begin the OAuth2 authorization flow. When called without a session, the user is redirected to the consent screen without grant ID. When called with a session, the redirect URL includes param for grant ID. You can pass Accept header of \`application/json\` to receive a JSON response instead of a redirect.`) + .requiredOption(`--client-_id `, `OAuth2 client ID.`) + .requiredOption(`--redirect-_uri `, `Redirect URI where visitor will be redirected after authorization, whether successful or not.`) + .requiredOption(`--response-_type `, `OAuth2 / OIDC response type. One of \`code\` (Authorization Code Flow), \`id_token\` (Implicit Flow, OIDC login only), or \`code id_token\` (Hybrid Flow).`) + .requiredOption(`--scope `, `Space-separated OAuth2 scopes. Can include project scopes, and built-in scopes: \`openid\`, \`email\`, \`profile\`.`) + .option(`--state `, `OAuth2 state. You receive this back in the redirect URI.`) + .option(`--nonce `, `OIDC nonce parameter to prevent replay attacks. Required when response_type includes \`id_token\`.`) + .option(`--code-_challenge `, `PKCE code challenge. Required when OAuth2 app is public.`) + .option(`--code-_challenge-_method `, `PKCE code challenge method. Required when OAuth2 app is public.`) + .option(`--prompt `, `OIDC prompt parameter for customization of consent screen. Space-separated list of: none, login, consent, select_account.`) + .option(`--max-_age `, `OIDC max_age paraleter for customization of consent screen. Maximum allowable elapsed time in seconds since the user last authenticated. If exceeded, re-authentication is required.`, parseInteger) + .option(`--authorization-_details `, `Rich authorization request. JSON array of objects, each with a \`type\` and project-defined fields`) + .action( + actionRunner( + async ({ client_id, redirect_uri, response_type, scope, state, nonce, code_challenge, code_challenge_method, prompt, max_age, authorization_details }) => + parse(await (await getOauth2Client()).authorize(client_id, redirect_uri, response_type, scope, state, nonce, code_challenge, code_challenge_method, prompt, max_age, authorization_details)), + ), + ); + + +const oauth2CreateDeviceAuthorizationCommand = oauth2 + .command(`create-device-authorization`) + .description(`Start the OAuth2 Device Authorization Grant. Returns the device code, user code, verification URL, expiration, and polling interval.`) + .option(`--client-_id `, `OAuth2 client ID.`) + .option(`--scope `, `Space-separated OAuth2 scopes. Can include project scopes, and built-in scopes: \`openid\`, \`email\`, \`profile\`.`) + .option(`--authorization-_details `, `Rich authorization request. JSON array of objects, each with a \`type\` and project-defined fields`) + .action( + actionRunner( + async ({ client_id, scope, authorization_details }) => + parse(await (await getOauth2Client()).createDeviceAuthorization(client_id, scope, authorization_details)), + ), + ); + + +const oauth2CreateGrantCommand = oauth2 + .command(`create-grant`) + .description(`Exchange a device flow user code for an OAuth2 grant. The authenticated user is bound to the pending grant. Pass the returned grant ID to the get grant endpoint to render the consent screen, then to the approve or reject endpoint to complete the flow.`) + .requiredOption(`--user-_code `, `User code displayed on the device.`) + .action( + actionRunner( + async ({ user_code }) => + parse(await (await getOauth2Client()).createGrant(user_code)), + ), + ); + + +const oauth2GetGrantCommand = oauth2 + .command(`get-grant`) + .description(`Get an OAuth2 grant by its ID. Used by the consent screen to display the details of the authorization the user is being asked to approve. A grant can only be read by the user it belongs to, or by server SDK.`) + .requiredOption(`--grant-_id `, `Grant ID made during authorization, provided to consent screen in URL search params.`) + .action( + actionRunner( + async ({ grant_id }) => + parse(await (await getOauth2Client()).getGrant(grant_id)), + ), + ); + + +const oauth2LogoutCommand = oauth2 + .command(`logout`) + .description(`OpenID Connect RP-Initiated Logout. Ends the user session and revokes the tokens issued to the app identified by the \`id_token_hint\`, then redirects the user to \`post_logout_redirect_uri\` when it matches a URI registered on the app.`) + .option(`--id-_token-_hint `, `ID Token previously issued to the app, used as proof of the logout request. Required to end the session; signature and issuer are validated while expiry is ignored.`) + .option(`--logout-_hint `, `Hint about the user that is logging out. Accepted for OIDC compatibility.`) + .option(`--client-_id `, `OAuth2 client ID. When both \`client_id\` and \`id_token_hint\` are provided, they must identify the same app.`) + .option(`--post-_logout-_redirect-_uri `, `URI to redirect the user to after logout. Must exactly match a URI registered in the app's \`postLogoutRedirectUris\`.`) + .option(`--state `, `Opaque value passed back unchanged in the \`state\` query param of the post-logout redirect.`) + .option(`--ui-_locales `, `Preferred languages for any logout UI, as space-separated BCP47 tags. Accepted for OIDC compatibility.`) + .action( + actionRunner( + async ({ id_token_hint, logout_hint, client_id, post_logout_redirect_uri, state, ui_locales }) => + parse(await (await getOauth2Client()).logout(id_token_hint, logout_hint, client_id, post_logout_redirect_uri, state, ui_locales)), + ), + ); + + +const oauth2RejectCommand = oauth2 + .command(`reject`) + .description(`Reject an OAuth2 grant when the user denies consent. Returns the \`redirectUrl\` the end user should be sent to with an \`access_denied\` error. You can pass Accept header of \`application/json\` to receive a JSON response instead of a redirect.`) + .requiredOption(`--grant-_id `, `Grant ID made during authorization, provided to consent screen in URL search params.`) + .action( + actionRunner( + async ({ grant_id }) => + parse(await (await getOauth2Client()).reject(grant_id)), + ), + ); + + +const oauth2RevokeCommand = oauth2 + .command(`revoke`) + .description(`Revoke an OAuth2 access token or refresh token.`) + .requiredOption(`--token `, `The access or refresh token to revoke.`) + .option(`--token-_type-_hint `, `Type of token to revoke (access_token or refresh_token).`) + .option(`--client-_id `, `OAuth2 client ID.`) + .option(`--client-_secret `, `OAuth2 client secret. Required for confidential apps; omitted for public apps.`) + .action( + actionRunner( + async ({ token, token_type_hint, client_id, client_secret }) => + parse(await (await getOauth2Client()).revoke(token, token_type_hint, client_id, client_secret)), + ), + ); + + +const oauth2CreateTokenCommand = oauth2 + .command(`create-token`) + .description(`Exchange an OAuth2 authorization code, refresh token, or device code for access and refresh tokens.`) + .requiredOption(`--grant-_type `, `OAuth2 grant type. Can be one of: \`authorization_code\`, \`refresh_token\`, \`urn:ietf:params:oauth:grant-type:device_code\`.`) + .option(`--code `, `Authorization code to be exchanged for access and refresh tokens. Required for \`authorization_code\` grant type.`) + .option(`--refresh-_token `, `Refresh token to be exchanged for a new access and refresh tokens. Required for \`refresh_token\` grant type.`) + .option(`--device-_code `, `Device code obtained from the device authorization endpoint. Required for \`urn:ietf:params:oauth:grant-type:device_code\` grant type.`) + .option(`--client-_id `, `OAuth2 client ID.`) + .option(`--client-_secret `, `OAuth2 client secret. Required for confidential apps.`) + .option(`--code-_verifier `, `PKCE code verifier. Required for public apps.`) + .option(`--redirect-_uri `, `Redirect URI. Required for \`authorization_code\` grant type.`) + .action( + actionRunner( + async ({ grant_type, code, refresh_token, device_code, client_id, client_secret, code_verifier, redirect_uri }) => + parse(await (await getOauth2Client()).createToken(grant_type, code, refresh_token, device_code, client_id, client_secret, code_verifier, redirect_uri)), + ), + ); + + diff --git a/lib/commands/services/organizations.ts b/lib/commands/services/organizations.ts index 1df97536..2c9719c1 100644 --- a/lib/commands/services/organizations.ts +++ b/lib/commands/services/organizations.ts @@ -1,4 +1,5 @@ import { Command } from "commander"; +import fs from "fs"; import { buildQueries, collectQueryValue, @@ -383,10 +384,16 @@ const organizationsGetInvoiceDownloadCommand = organizations .description(`Download invoice in PDF`) .requiredOption(`--organization-id `, `Organization ID`) .requiredOption(`--invoice-id `, `Invoice unique ID`) + .requiredOption(`--destination `, `Path to save the file to.`) .action( actionRunner( - async ({ organizationId, invoiceId }) => - parse(await (await getOrganizationsClient()).getInvoiceDownload(organizationId, invoiceId)), + async ({ organizationId, invoiceId, destination }) => { + const url = await (await getOrganizationsClient()).getInvoiceDownload(organizationId, invoiceId); + const response = await fetch(url); + const buffer = Buffer.from(await response.arrayBuffer()); + fs.writeFileSync(destination, buffer); + success(`File saved to ${destination}`); + }, ), ); @@ -423,10 +430,16 @@ const organizationsGetInvoiceViewCommand = organizations .description(`View invoice in PDF`) .requiredOption(`--organization-id `, `Organization ID`) .requiredOption(`--invoice-id `, `Invoice unique ID`) + .requiredOption(`--destination `, `Path to save the file to.`) .action( actionRunner( - async ({ organizationId, invoiceId }) => - parse(await (await getOrganizationsClient()).getInvoiceView(organizationId, invoiceId)), + async ({ organizationId, invoiceId, destination }) => { + const url = await (await getOrganizationsClient()).getInvoiceView(organizationId, invoiceId); + const response = await fetch(url); + const buffer = Buffer.from(await response.arrayBuffer()); + fs.writeFileSync(destination, buffer); + success(`File saved to ${destination}`); + }, ), ); diff --git a/lib/commands/services/project.ts b/lib/commands/services/project.ts index 59f0c45e..a0900db8 100644 --- a/lib/commands/services/project.ts +++ b/lib/commands/services/project.ts @@ -264,6 +264,35 @@ const projectListOAuth2ProvidersCommand = project ); +const projectUpdateOAuth2ServerCommand = project + .command(`update-o-auth-2-server`) + .description(`Update the OAuth2 server (OIDC provider) configuration.`) + .requiredOption(`--enabled `, `Enable or disable the OAuth2 server.`, parseBool) + .requiredOption(`--authorization-url `, `URL to your application with consent screen.`) + .option(`--scopes [scopes...]`, `List of allowed OAuth2 scopes. Maximum of 100 scopes are allowed, each up to 128 characters long.`) + .option(`--authorization-details-types [authorization-details-types...]`, `List of accepted \`authorization_details\` types. Maximum of 100 types are allowed, each up to 128 characters long.`) + .option(`--access-token-duration `, `Access token duration in seconds for confidential clients (server-side apps that authenticate with a client secret). Leave empty to use default 8 hours.`, parseInteger) + .option(`--refresh-token-duration `, `Refresh token duration in seconds for confidential clients (server-side apps that authenticate with a client secret). Leave empty to use default 1 year.`, parseInteger) + .option(`--public-access-token-duration `, `Access token duration in seconds for public clients (SPAs, mobile, and native apps that cannot keep a client secret). Leave empty to use default 1 hour.`, parseInteger) + .option(`--public-refresh-token-duration `, `Refresh token duration in seconds for public clients (SPAs, mobile, and native apps that cannot keep a client secret). Leave empty to use default 30 days.`, parseInteger) + .option( + `--confidential-pkce [value]`, + `When enabled, PKCE is required for confidential clients (server-side flows using client_secret). PKCE is always required for public clients regardless of this setting.`, + (value: string | undefined) => + value === undefined ? true : parseBool(value), + ) + .option(`--verification-url `, `URL to your application page where users enter the device flow user code. Required to enable the Device Authorization Grant.`) + .option(`--user-code-length `, `Number of characters in the device flow user code, excluding the formatting separator. Shorter codes are easier to type but weaker; pair short codes with short expiry. Leave empty to use default 8.`, parseInteger) + .option(`--user-code-format `, `Character set for device flow user codes: \`numeric\` (digits only — best for numeric keypads and TV remotes), \`alphabetic\` (letters only), or \`alphanumeric\` (letters and digits — highest entropy per character). Defaults to \`alphanumeric\`.`) + .option(`--device-code-duration `, `Lifetime in seconds of device flow device codes and user codes. Device codes are intentionally short-lived. Leave empty to use default 600.`, parseInteger) + .action( + actionRunner( + async ({ enabled, authorizationUrl, scopes, authorizationDetailsTypes, accessTokenDuration, refreshTokenDuration, publicAccessTokenDuration, publicRefreshTokenDuration, confidentialPkce, verificationUrl, userCodeLength, userCodeFormat, deviceCodeDuration }) => + parse(await (await getProjectClient()).updateOAuth2Server(enabled, authorizationUrl, scopes, authorizationDetailsTypes, accessTokenDuration, refreshTokenDuration, publicAccessTokenDuration, publicRefreshTokenDuration, confidentialPkce, verificationUrl, userCodeLength, userCodeFormat, deviceCodeDuration)), + ), + ); + + const projectUpdateOAuth2AmazonCommand = project .command(`update-o-auth-2-amazon`) .description(`Update the project OAuth2 Amazon configuration.`) @@ -1312,6 +1341,18 @@ const projectUpdateDenyAliasedEmailPolicyCommand = project ); +const projectUpdateDenyCorporateEmailPolicyCommand = project + .command(`update-deny-corporate-email-policy`) + .description(`Configures if only corporate email addresses (non-free and non-disposable domains) are allowed during new user sign-ups and email updates.`) + .requiredOption(`--enabled `, `Set whether or not to restrict sign-ups and email updates to corporate email addresses only.`, parseBool) + .action( + actionRunner( + async ({ enabled }) => + parse(await (await getProjectClient()).updateDenyCorporateEmailPolicy(enabled)), + ), + ); + + const projectUpdateDenyDisposableEmailPolicyCommand = project .command(`update-deny-disposable-email-policy`) .description(`Configures if disposable emails from known temporary domains are denied during new users sign-ups and email updates.`) @@ -1369,10 +1410,16 @@ const projectUpdateMembershipPrivacyPolicyCommand = project (value: string | undefined) => value === undefined ? true : parseBool(value), ) + .option( + `--user-accessed-at [value]`, + `Set to true if you want make user last access time visible to all team members, or false to hide it.`, + (value: string | undefined) => + value === undefined ? true : parseBool(value), + ) .action( actionRunner( - async ({ userId, userEmail, userPhone, userName, userMfa }) => - parse(await (await getProjectClient()).updateMembershipPrivacyPolicy(userId, userEmail, userPhone, userName, userMfa)), + async ({ userId, userEmail, userPhone, userName, userMfa, userAccessedAt }) => + parse(await (await getProjectClient()).updateMembershipPrivacyPolicy(userId, userEmail, userPhone, userName, userMfa, userAccessedAt)), ), ); @@ -1415,6 +1462,42 @@ const projectUpdatePasswordPersonalDataPolicyCommand = project ); +const projectUpdatePasswordStrengthPolicyCommand = project + .command(`update-password-strength-policy`) + .description(`Update the password strength requirements for users in the project.`) + .option(`--min `, `Minimum password length. Value must be between 8 and 256. Default is 8.`, parseInteger) + .option( + `--uppercase [value]`, + `Whether passwords must include at least one uppercase letter.`, + (value: string | undefined) => + value === undefined ? true : parseBool(value), + ) + .option( + `--lowercase [value]`, + `Whether passwords must include at least one lowercase letter.`, + (value: string | undefined) => + value === undefined ? true : parseBool(value), + ) + .option( + `--number [value]`, + `Whether passwords must include at least one number.`, + (value: string | undefined) => + value === undefined ? true : parseBool(value), + ) + .option( + `--symbols [value]`, + `Whether passwords must include at least one symbol.`, + (value: string | undefined) => + value === undefined ? true : parseBool(value), + ) + .action( + actionRunner( + async ({ min, uppercase, lowercase, number, symbols }) => + parse(await (await getProjectClient()).updatePasswordStrengthPolicy(min, uppercase, lowercase, number, symbols)), + ), + ); + + const projectUpdateSessionAlertPolicyCommand = project .command(`update-session-alert-policy`) .description(`Updating this policy allows you to control if email alert is sent upon session creation. When enabled, and user signs into their account, they will be sent an email notification. There is an exception, the first session after a new sign up does not trigger an alert, even if the policy is enabled.`) @@ -1478,7 +1561,7 @@ const projectUpdateUserLimitPolicyCommand = project const projectGetPolicyCommand = project .command(`get-policy`) .description(`Get a policy by its unique ID. This endpoint returns the current configuration for the requested project policy.`) - .requiredOption(`--policy-id `, `Policy ID. Can be one of: password-dictionary, password-history, password-personal-data, session-alert, session-duration, session-invalidation, session-limit, user-limit, membership-privacy.`) + .requiredOption(`--policy-id `, `Policy ID. Can be one of: password-dictionary, password-history, password-strength, password-personal-data, session-alert, session-duration, session-invalidation, session-limit, user-limit, membership-privacy, deny-aliased-email, deny-disposable-email, deny-free-email, deny-corporate-email.`) .action( actionRunner( async ({ policyId }) => @@ -1503,7 +1586,7 @@ const projectUpdateProtocolCommand = project const projectUpdateServiceCommand = project .command(`update-service`) .description(`Update properties of a specific service. Use this endpoint to enable or disable a service in your project. `) - .requiredOption(`--service-id `, `Service name. Can be one of: account, avatars, databases, tablesdb, locale, health, project, storage, teams, users, vcs, sites, functions, proxy, graphql, migrations, messaging, advisor`) + .requiredOption(`--service-id `, `Service name. Can be one of: account, avatars, databases, tablesdb, locale, health, project, storage, teams, users, vcs, sites, functions, proxy, graphql, migrations, messaging, advisor, oauth2`) .requiredOption(`--enabled `, `Service status.`, parseBool) .action( actionRunner( diff --git a/lib/commands/services/storage.ts b/lib/commands/services/storage.ts index 1842fb62..05bafc49 100644 --- a/lib/commands/services/storage.ts +++ b/lib/commands/services/storage.ts @@ -79,7 +79,7 @@ const storageCreateBucketCommand = storage (value: string | undefined) => value === undefined ? true : parseBool(value), ) - .option(`--maximum-file-size `, `Maximum file size allowed in bytes. Maximum allowed value is 0B.`, parseInteger) + .option(`--maximum-file-size `, `Maximum file size allowed in bytes. Maximum allowed value is 5GB.`, parseInteger) .option(`--allowed-file-extensions [allowed-file-extensions...]`, `Allowed file extensions. Maximum of 100 extensions are allowed, each 64 characters long.`) .option(`--compression `, `Compression algorithm chosen for compression. Can be one of none, gzip (https://en.wikipedia.org/wiki/Gzip), or zstd (https://en.wikipedia.org/wiki/Zstd), For file size above 20MB compression is skipped even if it's enabled`) .option( @@ -138,7 +138,7 @@ const storageUpdateBucketCommand = storage (value: string | undefined) => value === undefined ? true : parseBool(value), ) - .option(`--maximum-file-size `, `Maximum file size allowed in bytes. Maximum allowed value is 0B.`, parseInteger) + .option(`--maximum-file-size `, `Maximum file size allowed in bytes. Maximum allowed value is 5GB.`, parseInteger) .option(`--allowed-file-extensions [allowed-file-extensions...]`, `Allowed file extensions. Maximum of 100 extensions are allowed, each 64 characters long.`) .option(`--compression `, `Compression algorithm chosen for compression. Can be one of none, gzip (https://en.wikipedia.org/wiki/Gzip), or zstd (https://en.wikipedia.org/wiki/Zstd), For file size above 20MB compression is skipped even if it's enabled`) .option( diff --git a/lib/commands/services/tables-db.ts b/lib/commands/services/tables-db.ts index 632d3de9..32e390b7 100644 --- a/lib/commands/services/tables-db.ts +++ b/lib/commands/services/tables-db.ts @@ -72,10 +72,11 @@ const tablesDBCreateCommand = tablesDB (value: string | undefined) => value === undefined ? true : parseBool(value), ) + .option(`--dedicated-database-id `, `Optional dedicated database (compute) ID to attach this database to. Leave empty to create a database on the shared pool.`) .action( actionRunner( - async ({ databaseId, name, enabled }) => - parse(await (await getTablesDBClient()).create(databaseId, name, enabled)), + async ({ databaseId, name, enabled, dedicatedDatabaseId }) => + parse(await (await getTablesDBClient()).create(databaseId, name, enabled, dedicatedDatabaseId)), ), ); diff --git a/lib/commands/utils/deployment.ts b/lib/commands/utils/deployment.ts index d97eb0f6..394aa241 100644 --- a/lib/commands/utils/deployment.ts +++ b/lib/commands/utils/deployment.ts @@ -8,6 +8,7 @@ import { Agent, WebSocket } from "undici"; import { Client, AppwriteException } from "@appwrite.io/console"; import { error } from "../../parser.js"; import { globalConfig } from "../../config.js"; +import { getValidAccessToken } from "../../sdks.js"; import { Spinner } from "../../spinner.js"; const ignore: typeof ignoreModule = @@ -273,7 +274,8 @@ export async function watchDeploymentUpdates( params: WatchDeploymentUpdatesParams, ): Promise { const cookieHeader = getCookieHeader(globalConfig.getCookie()); - if (!cookieHeader) { + const hasAccessToken = globalConfig.getAccessToken() !== ""; + if (!cookieHeader && !hasAccessToken) { return null; } @@ -287,10 +289,17 @@ export async function watchDeploymentUpdates( let socket: WebSocket; try { + const headers: Record = {}; + if (cookieHeader) { + headers.cookie = cookieHeader; + } + if (hasAccessToken) { + const accessToken = await getValidAccessToken(params.endpoint); + headers.authorization = `Bearer ${accessToken}`; + } + socket = new WebSocket(getRealtimeUrl(params.endpoint), { - headers: { - cookie: cookieHeader, - }, + headers, dispatcher, }); } catch { diff --git a/lib/config.ts b/lib/config.ts index 3fcbaf5a..bd2933ec 100644 --- a/lib/config.ts +++ b/lib/config.ts @@ -1206,6 +1206,10 @@ class Global extends Config { static PREFERENCE_KEY = "key" as const; static PREFERENCE_LOCALE = "locale" as const; static PREFERENCE_MODE = "mode" as const; + static PREFERENCE_ACCESS_TOKEN = "accessToken" as const; + static PREFERENCE_REFRESH_TOKEN = "refreshToken" as const; + static PREFERENCE_TOKEN_EXPIRY = "tokenExpiry" as const; + static PREFERENCE_CLIENT_ID = "clientId" as const; static IGNORE_ATTRIBUTES: readonly string[] = [ Global.PREFERENCE_CURRENT, @@ -1216,6 +1220,10 @@ class Global extends Config { Global.PREFERENCE_KEY, Global.PREFERENCE_LOCALE, Global.PREFERENCE_MODE, + Global.PREFERENCE_ACCESS_TOKEN, + Global.PREFERENCE_REFRESH_TOKEN, + Global.PREFERENCE_TOKEN_EXPIRY, + Global.PREFERENCE_CLIENT_ID, ]; static MODE_ADMIN = "admin"; @@ -1359,6 +1367,50 @@ class Global extends Config { this.setTo(Global.PREFERENCE_KEY, key); } + getAccessToken(): string { + if (!this.hasFrom(Global.PREFERENCE_ACCESS_TOKEN)) { + return ""; + } + return this.getFrom(Global.PREFERENCE_ACCESS_TOKEN); + } + + setAccessToken(accessToken: string): void { + this.setTo(Global.PREFERENCE_ACCESS_TOKEN, accessToken); + } + + getRefreshToken(): string { + if (!this.hasFrom(Global.PREFERENCE_REFRESH_TOKEN)) { + return ""; + } + return this.getFrom(Global.PREFERENCE_REFRESH_TOKEN); + } + + setRefreshToken(refreshToken: string): void { + this.setTo(Global.PREFERENCE_REFRESH_TOKEN, refreshToken); + } + + getTokenExpiry(): number { + if (!this.hasFrom(Global.PREFERENCE_TOKEN_EXPIRY)) { + return 0; + } + return this.getFrom(Global.PREFERENCE_TOKEN_EXPIRY); + } + + setTokenExpiry(tokenExpiry: number): void { + this.setTo(Global.PREFERENCE_TOKEN_EXPIRY, tokenExpiry); + } + + getClientId(): string { + if (!this.hasFrom(Global.PREFERENCE_CLIENT_ID)) { + return ""; + } + return this.getFrom(Global.PREFERENCE_CLIENT_ID); + } + + setClientId(clientId: string): void { + this.setTo(Global.PREFERENCE_CLIENT_ID, clientId); + } + hasFrom(key: string): boolean { const current = this.getCurrentSession(); diff --git a/lib/constants.ts b/lib/constants.ts index 3503445a..0d3e9404 100644 --- a/lib/constants.ts +++ b/lib/constants.ts @@ -1,7 +1,7 @@ // SDK export const SDK_TITLE = 'Appwrite'; export const SDK_TITLE_LOWER = 'appwrite'; -export const SDK_VERSION = '22.0.0'; +export const SDK_VERSION = '22.1.0'; export const SDK_NAME = 'Command Line'; export const SDK_PLATFORM = 'console'; export const SDK_LANGUAGE = 'cli'; @@ -27,6 +27,10 @@ export const GITHUB_RELEASES_URL = `https://github.com/${GITHUB_REPO}/releases`; // API export const DEFAULT_ENDPOINT = 'https://cloud.appwrite.io/v1'; +// OAuth2 +export const OAUTH2_CLIENT_ID = "appwrite-cli"; +export const OAUTH2_SCOPES = "openid email profile"; + // Config resources export const CONFIG_RESOURCE_KEYS = [ "databases", diff --git a/lib/flags.ts b/lib/flags.ts new file mode 100644 index 00000000..44217f25 --- /dev/null +++ b/lib/flags.ts @@ -0,0 +1,21 @@ +/** + * Central registry of CLI feature flags. + * + * Each flag is an opt-in environment variable, disabled unless set to a truthy + * value ("1", "true", or "yes"). To add a flag, add one entry to FLAG_ENV_VARS + * and read it anywhere with isFlagEnabled(""). + */ +const FLAG_ENV_VARS = { + // Browser-based OAuth2 device login for Cloud (otherwise email/password). + oauthLogin: "APPWRITE_CLI_OAUTH_LOGIN", + // Treat localhost/loopback endpoints as Cloud for OAuth login testing. + devCloudLogin: "APPWRITE_CLI_DEV_CLOUD_LOGIN", +} as const; + +export type FlagName = keyof typeof FLAG_ENV_VARS; + +const isTruthy = (value: string | undefined): boolean => + ["1", "true", "yes"].includes((value ?? "").toLowerCase()); + +export const isFlagEnabled = (flag: FlagName): boolean => + isTruthy(process.env[FLAG_ENV_VARS[flag]]); diff --git a/lib/questions.ts b/lib/questions.ts index c036e1ed..4bfc202a 100644 --- a/lib/questions.ts +++ b/lib/questions.ts @@ -822,32 +822,6 @@ export const questionsPullCollection: Question[] = [ }, ]; -export const questionsLogin: Question[] = [ - { - type: "input", - name: "email", - message: "Enter your email", - validate(value: string) { - if (!value) { - return "Please enter your email"; - } - return true; - }, - }, - { - type: "password", - name: "password", - message: "Enter your password", - mask: "*", - validate(value: string) { - if (!value) { - return "Please enter your password"; - } - return true; - }, - }, -]; - export const questionsSwitchAccount: Question[] = [ { type: "list", @@ -906,6 +880,32 @@ export const questionGetEndpoint: Question[] = [ }, ]; +export const questionsLogin: Question[] = [ + { + type: "input", + name: "email", + message: "Enter your email", + validate(value: string) { + if (!value) { + return "Please enter your email"; + } + return true; + }, + }, + { + type: "password", + name: "password", + message: "Enter your password", + mask: "*", + validate(value: string) { + if (!value) { + return "Please enter your password"; + } + return true; + }, + }, +]; + export const questionsLogout: Question[] = [ { type: "checkbox", @@ -1199,7 +1199,7 @@ export const questionsListFactors: Question[] = [ message: "Your account is protected by multi-factor authentication. Please choose one for verification.", choices: async () => { - const client = await sdkForConsole({ requiresAuth: false }); + const client = await sdkForConsole(); const accountClient = new Account(client); const factors = await accountClient.listMfaFactors(); diff --git a/lib/sdks.ts b/lib/sdks.ts index 393cd6a7..1eca90ee 100644 --- a/lib/sdks.ts +++ b/lib/sdks.ts @@ -1,12 +1,73 @@ -import { globalConfig, localConfig } from "./config.js"; -import { Client } from "@appwrite.io/console"; +import { + globalConfig, + localConfig, + normalizeCloudConsoleEndpoint, +} from "./config.js"; +import { Client, Oauth2 } from "@appwrite.io/console"; import os from "os"; import { DEFAULT_ENDPOINT, EXECUTABLE_NAME, + OAUTH2_CLIENT_ID, SDK_TITLE, SDK_VERSION, } from "./constants.js"; +import { warn } from "./parser.js"; +import { isCloudHostname } from "./utils.js"; +import { isFlagEnabled } from "./flags.js"; + +export const getValidAccessToken = async ( + endpoint: string, +): Promise => { + const accessToken = globalConfig.getAccessToken(); + const refreshToken = globalConfig.getRefreshToken(); + const tokenExpiry = globalConfig.getTokenExpiry(); + const clientId = globalConfig.getClientId() || OAUTH2_CLIENT_ID; + + if (accessToken && tokenExpiry > Date.now() + 60_000) { + return accessToken; + } + + if (!refreshToken) { + throw new Error( + `Session expired. Please run \`${EXECUTABLE_NAME} login\` to create a new session.`, + ); + } + + const oauth2 = new Oauth2( + new Client() + .setEndpoint(normalizeCloudConsoleEndpoint(endpoint)) + .setProject("console") + .setSelfSigned(globalConfig.getSelfSigned()), + ); + const token = await oauth2.createToken({ + grantType: "refresh_token", + refreshToken, + clientId, + }); + const newExpiry = Date.now() + token.expires_in * 1000; + globalConfig.setAccessToken(token.access_token); + if (token.refresh_token) { + globalConfig.setRefreshToken(token.refresh_token); + } + globalConfig.setTokenExpiry(newExpiry); + + return token.access_token; +}; + +let legacySessionWarningShown = false; + +const warnLegacySession = (): void => { + // Only nudge toward OAuth login when the feature is enabled. + if (legacySessionWarningShown || !isFlagEnabled("oauthLogin")) { + return; + } + + legacySessionWarningShown = true; + warn( + `This CLI is using a legacy cookie session. Run \`${EXECUTABLE_NAME} login --new\` to switch to the new browser-based login flow.`, + ); +}; export const sdkForConsole = async ({ requiresAuth = true, @@ -18,12 +79,16 @@ export const sdkForConsole = async ({ organizationId?: string; } = {}): Promise => { const client = new Client(); - const endpoint = - endpointOverride || globalConfig.getEndpoint() || DEFAULT_ENDPOINT; - const cookie = globalConfig.getCookie(); + const endpoint = normalizeCloudConsoleEndpoint( + endpointOverride || globalConfig.getEndpoint() || DEFAULT_ENDPOINT, + ); + const isCloudEndpoint = isCloudHostname(new URL(endpoint).hostname); const selfSigned = globalConfig.getSelfSigned(); - if (requiresAuth && cookie === "") { + const accessToken = globalConfig.getAccessToken(); + const cookie = globalConfig.getCookie(); + + if (requiresAuth && !accessToken && !cookie) { throw new Error( `Session not found. Please run \`${EXECUTABLE_NAME} login\` to create a session`, ); @@ -41,10 +106,21 @@ export const sdkForConsole = async ({ client .setEndpoint(endpoint) .setProject("console") - .setCookie(cookie) .setSelfSigned(selfSigned) .setLocale("en-US"); + if (requiresAuth) { + if (accessToken) { + const validAccessToken = await getValidAccessToken(endpoint); + client.headers["Authorization"] = `Bearer ${validAccessToken}`; + } else if (cookie) { + if (isCloudEndpoint) { + warnLegacySession(); + } + client.setCookie(cookie); + } + } + if (organizationId) { client.headers["X-Appwrite-Organization"] = organizationId; } @@ -57,12 +133,14 @@ export const sdkForProject = async (): Promise => { const endpoint = localConfig.getEndpoint() || globalConfig.getEndpoint() || DEFAULT_ENDPOINT; + const isCloudEndpoint = isCloudHostname(new URL(endpoint).hostname); const project = localConfig.getProject().projectId ? localConfig.getProject().projectId : globalConfig.getProject(); const key = globalConfig.getKey(); + const accessToken = globalConfig.getAccessToken(); const cookie = globalConfig.getCookie(); const selfSigned = globalConfig.getSelfSigned(); @@ -87,8 +165,18 @@ export const sdkForProject = async (): Promise => { .setSelfSigned(selfSigned) .setLocale("en-US"); + if (accessToken) { + const validAccessToken = await getValidAccessToken(endpoint); + client.headers["Authorization"] = `Bearer ${validAccessToken}`; + return client.setMode("admin"); + } + if (cookie) { - return client.setCookie(cookie).setMode("admin"); + if (isCloudEndpoint) { + warnLegacySession(); + } + client.setCookie(cookie); + return client.setMode("admin"); } if (key) { diff --git a/lib/services.ts b/lib/services.ts index 74962328..e2342c1d 100644 --- a/lib/services.ts +++ b/lib/services.ts @@ -5,6 +5,7 @@ import { Databases, Functions, Messaging, + Oauth2, Organization, Organizations, Project, @@ -37,6 +38,11 @@ export const getMessagingService = async (sdk?: Client): Promise => { return new Messaging(client); }; +export const getOauth2Service = async (sdk?: Client): Promise => { + const client = !sdk ? await sdkForConsole() : sdk; + return new Oauth2(client); +}; + export const getOrganizationsService = async ( sdk?: Client, ): Promise => { diff --git a/lib/types.ts b/lib/types.ts index 3e1e9144..0407261d 100644 --- a/lib/types.ts +++ b/lib/types.ts @@ -67,6 +67,11 @@ export interface SessionData { email?: string; phone?: string; cookie?: string; + accessToken?: string; + refreshToken?: string; + tokenExpiry?: number; + clientId?: string; + selfSigned?: boolean; } export interface GlobalConfigData extends ConfigData { diff --git a/lib/utils.ts b/lib/utils.ts index 86929f48..20a4d55b 100644 --- a/lib/utils.ts +++ b/lib/utils.ts @@ -7,6 +7,7 @@ import type { Models } from "@appwrite.io/console"; import { ProjectPolicyId } from "@appwrite.io/console"; import { z } from "zod"; import { globalConfig } from "./config.js"; +import { isFlagEnabled } from "./flags.js"; import type { SettingsType } from "./commands/config.js"; import { NPM_REGISTRY_URL, @@ -376,6 +377,30 @@ export const isCloudHostname = (hostname: string): boolean => { return CLOUD_REGION_CODES.has(hostname.split(".")[0]); }; +export const isRegionalCloudEndpoint = (endpoint: string): boolean => { + try { + const hostname = new URL(endpoint).hostname; + return isCloudHostname(hostname) && hostname !== "cloud.appwrite.io"; + } catch (_error) { + return false; + } +}; + +export const isLocalhostHostname = (hostname: string): boolean => + hostname === "localhost" || hostname === "127.0.0.1" || hostname === "[::1]"; + +export const isCloudLoginEndpoint = (endpoint: string): boolean => { + try { + const hostname = new URL(endpoint).hostname; + return ( + isCloudHostname(hostname) || + (isFlagEnabled("devCloudLogin") && isLocalhostHostname(hostname)) + ); + } catch (_error) { + return false; + } +}; + export const getConsoleBaseUrl = (endpoint: string): string => { try { const url = new URL(endpoint); @@ -397,11 +422,16 @@ export const getConsoleBaseUrl = (endpoint: string): string => { export const getConsoleProjectSlug = ( endpoint: string, projectId: string, + projectRegion?: string, ): string => { try { const hostname = new URL(endpoint).hostname; if (!isCloudHostname(hostname)) { + if (projectRegion) { + return `project-${projectRegion}-${projectId}`; + } + return `project-${projectId}`; } @@ -419,8 +449,9 @@ export const getFunctionDeploymentConsoleUrl = ( projectId: string, functionId: string, deploymentId: string, + projectRegion?: string, ): string => { - const projectSlug = getConsoleProjectSlug(endpoint, projectId); + const projectSlug = getConsoleProjectSlug(endpoint, projectId, projectRegion); return `${getConsoleBaseUrl(endpoint)}/console/${projectSlug}/functions/function-${functionId}/deployment-${deploymentId}`; }; @@ -429,8 +460,9 @@ export const getSiteDeploymentConsoleUrl = ( projectId: string, siteId: string, deploymentId: string, + projectRegion?: string, ): string => { - const projectSlug = getConsoleProjectSlug(endpoint, projectId); + const projectSlug = getConsoleProjectSlug(endpoint, projectId, projectRegion); return `${getConsoleBaseUrl(endpoint)}/console/${projectSlug}/sites/site-${siteId}/deployments/deployment-${deploymentId}`; }; diff --git a/package-lock.json b/package-lock.json index 6c351470..a8c26d9d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,15 +1,15 @@ { "name": "appwrite-cli", - "version": "22.0.0", + "version": "22.1.0", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "appwrite-cli", - "version": "22.0.0", + "version": "22.1.0", "license": "BSD-3-Clause", "dependencies": { - "@appwrite.io/console": "13.1.0", + "@appwrite.io/console": "^15.0.0", "chalk": "4.1.2", "chokidar": "^3.6.0", "cli-progress": "^3.12.0", @@ -41,19 +41,25 @@ "@types/tar": "^6.1.13", "@typescript-eslint/eslint-plugin": "^8.0.0", "@typescript-eslint/parser": "^8.0.0", - "esbuild": "^0.27.2", + "esbuild": "^0.28.1", "eslint": "^9.0.0", "eslint-plugin-unused-imports": "^4.0.0", "prettier": "^3.7.4", "tsx": "^4.21.0", "typescript": "^5.3.3", "typescript-eslint": "^8.0.0" + }, + "overrides": { + "phin": "3.7.1", + "@xmldom/xmldom": "^0.9.10", + "tmp": "^0.2.6", + "esbuild": "^0.28.1" } }, "node_modules/@appwrite.io/console": { - "version": "13.1.0", - "resolved": "https://registry.npmjs.org/@appwrite.io/console/-/console-13.1.0.tgz", - "integrity": "sha512-fInc7p11A7kGF5Y+FsWBr2NGeu8GUcA0/OCuZGo4eKVOGO7Wy0OAWLBfwmfHXG7Dx/R9SiW7rNLpK7+taN76tA==", + "version": "15.0.0", + "resolved": "https://registry.npmjs.org/@appwrite.io/console/-/console-15.0.0.tgz", + "integrity": "sha512-yjCjsUTcOUacR/O0/XEm9ax+EpcxxG3+h7TsY0YEd2fW32T2AimASOVMcRoEb8DrSHWqCn5kH1VRL/U6ZfAoJw==", "license": "BSD-3-Clause", "dependencies": { "json-bigint": "1.0.0" @@ -92,9 +98,9 @@ } }, "node_modules/@esbuild/aix-ppc64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.27.7.tgz", - "integrity": "sha512-EKX3Qwmhz1eMdEJokhALr0YiD0lhQNwDqkPYyPhiSwKrh7/4KRjQc04sZ8db+5DVVnZ1LmbNDI1uAMPEUBnQPg==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.1.tgz", + "integrity": "sha512-Svl7tq8k/08+p6CXPpRjQ1fKX+1odH/BQbb48fV6fj3CWHhsoIOoY87w1oHXm0qEpkIK3ZfVgp0hed3XBXzXMQ==", "cpu": [ "ppc64" ], @@ -109,9 +115,9 @@ } }, "node_modules/@esbuild/android-arm": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.27.7.tgz", - "integrity": "sha512-jbPXvB4Yj2yBV7HUfE2KHe4GJX51QplCN1pGbYjvsyCZbQmies29EoJbkEc+vYuU5o45AfQn37vZlyXy4YJ8RQ==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.1.tgz", + "integrity": "sha512-0k2F129Xdio1TdJfzJ8sy1Q47vUD2NnwdhiAf7drUN1EBTfPf4hsFCtmMgu/6m8JSzsBrlmVjudMBQqOfG8usQ==", "cpu": [ "arm" ], @@ -126,9 +132,9 @@ } }, "node_modules/@esbuild/android-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.27.7.tgz", - "integrity": "sha512-62dPZHpIXzvChfvfLJow3q5dDtiNMkwiRzPylSCfriLvZeq0a1bWChrGx/BbUbPwOrsWKMn8idSllklzBy+dgQ==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.1.tgz", + "integrity": "sha512-34EGEbCIAgosYz6goLcopX6Mo7NyGv9tfwEM2/7Ce2VcVRk568iSvniGWcUXIy7wEDR1wzolcxcriFVrWYcwBg==", "cpu": [ "arm64" ], @@ -143,9 +149,9 @@ } }, "node_modules/@esbuild/android-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.27.7.tgz", - "integrity": "sha512-x5VpMODneVDb70PYV2VQOmIUUiBtY3D3mPBG8NxVk5CogneYhkR7MmM3yR/uMdITLrC1ml/NV1rj4bMJuy9MCg==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.1.tgz", + "integrity": "sha512-dbwY7ltSMDWsRatcRpCnES4F+im88OCUgGZjy52shC7GqHRE/cYlxNbB4Z4UpJswpcc4Qxd2oE/ufM0p61IKng==", "cpu": [ "x64" ], @@ -160,9 +166,9 @@ } }, "node_modules/@esbuild/darwin-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.27.7.tgz", - "integrity": "sha512-5lckdqeuBPlKUwvoCXIgI2D9/ABmPq3Rdp7IfL70393YgaASt7tbju3Ac+ePVi3KDH6N2RqePfHnXkaDtY9fkw==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.1.tgz", + "integrity": "sha512-TZbWkQY7kvTAXbXUT7uVACR5cMHsDiSz9z7ZKAX/RTq/WJEk3QyRr0wZpNhBDX+/0CtdqUIJlOiodQcta6tY3Q==", "cpu": [ "arm64" ], @@ -177,9 +183,9 @@ } }, "node_modules/@esbuild/darwin-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.27.7.tgz", - "integrity": "sha512-rYnXrKcXuT7Z+WL5K980jVFdvVKhCHhUwid+dDYQpH+qu+TefcomiMAJpIiC2EM3Rjtq0sO3StMV/+3w3MyyqQ==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.1.tgz", + "integrity": "sha512-zfdzgK9ACBNZLI/CyHTOx81SyNbM6YXn7rxSgX97VjyiPl9W1i4Ka4fgKECEoFCKGpvBj5qArWIGgQjOwkgskQ==", "cpu": [ "x64" ], @@ -194,9 +200,9 @@ } }, "node_modules/@esbuild/freebsd-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.27.7.tgz", - "integrity": "sha512-B48PqeCsEgOtzME2GbNM2roU29AMTuOIN91dsMO30t+Ydis3z/3Ngoj5hhnsOSSwNzS+6JppqWsuhTp6E82l2w==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.1.tgz", + "integrity": "sha512-wG2EA8ENdEI0qhkSZMjfqrdY+ziCYCPMmtZjjIwOmXFjmyzEHn+UUxk5of+SYsjtfs3VpnlC7QLzSI5hY/rOAw==", "cpu": [ "arm64" ], @@ -211,9 +217,9 @@ } }, "node_modules/@esbuild/freebsd-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.27.7.tgz", - "integrity": "sha512-jOBDK5XEjA4m5IJK3bpAQF9/Lelu/Z9ZcdhTRLf4cajlB+8VEhFFRjWgfy3M1O4rO2GQ/b2dLwCUGpiF/eATNQ==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.1.tgz", + "integrity": "sha512-i7dZ9vQgnvSCzi/rYCXNgtF/U+eKZNJBzu3eTQbRgHnM7tNSizLOkRFAl3qzVc/Op/u5YkHHa4pf/3DOYHthLQ==", "cpu": [ "x64" ], @@ -228,9 +234,9 @@ } }, "node_modules/@esbuild/linux-arm": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.27.7.tgz", - "integrity": "sha512-RkT/YXYBTSULo3+af8Ib0ykH8u2MBh57o7q/DAs3lTJlyVQkgQvlrPTnjIzzRPQyavxtPtfg0EopvDyIt0j1rA==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.1.tgz", + "integrity": "sha512-qVXBOHQS+d5Y722GwJzJUtOLlX7km3CraOaGormF1pDtPd2C/l1SHRPgjLunLGe51Sh5YYWKMFDyV4SxgMQYTQ==", "cpu": [ "arm" ], @@ -245,9 +251,9 @@ } }, "node_modules/@esbuild/linux-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.27.7.tgz", - "integrity": "sha512-RZPHBoxXuNnPQO9rvjh5jdkRmVizktkT7TCDkDmQ0W2SwHInKCAV95GRuvdSvA7w4VMwfCjUiPwDi0ZO6Nfe9A==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.1.tgz", + "integrity": "sha512-yHs+0uc8+nvEAfAfxrWQKK5peSNzBc4PegcMO0EJ2hT71uA7vB8Ihg2e77R2P7SG5uYjPbHlLLmve4LLLRCf0g==", "cpu": [ "arm64" ], @@ -262,9 +268,9 @@ } }, "node_modules/@esbuild/linux-ia32": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.27.7.tgz", - "integrity": "sha512-GA48aKNkyQDbd3KtkplYWT102C5sn/EZTY4XROkxONgruHPU72l+gW+FfF8tf2cFjeHaRbWpOYa/uRBz/Xq1Pg==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.1.tgz", + "integrity": "sha512-d1z4ZuP0ajrfz/FhGT4vv278rX8KnPPJx8i5+AtK7TYbx9Le9F1hyzurZpkEyjkGa9dUGhQow4C1NmeGvqxN2w==", "cpu": [ "ia32" ], @@ -279,9 +285,9 @@ } }, "node_modules/@esbuild/linux-loong64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.27.7.tgz", - "integrity": "sha512-a4POruNM2oWsD4WKvBSEKGIiWQF8fZOAsycHOt6JBpZ+JN2n2JH9WAv56SOyu9X5IqAjqSIPTaJkqN8F7XOQ5Q==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.1.tgz", + "integrity": "sha512-M5sRjUVZrkm1OAPR3dlOYzNmN+loZKGVi1VUQGrwuqLcbR6qeAz+famMhjASeH3YVKvZz+zT1jlh/keC3Rj/lg==", "cpu": [ "loong64" ], @@ -296,9 +302,9 @@ } }, "node_modules/@esbuild/linux-mips64el": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.27.7.tgz", - "integrity": "sha512-KabT5I6StirGfIz0FMgl1I+R1H73Gp0ofL9A3nG3i/cYFJzKHhouBV5VWK1CSgKvVaG4q1RNpCTR2LuTVB3fIw==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.1.tgz", + "integrity": "sha512-mRObBZeHh2OxcBFPWE/FjylkRgZdYuiTR3vaTozquCGOH14iP9oN4x4Ge81CoIDYQrXmIxpFumJBu5MtZpnQJQ==", "cpu": [ "mips64el" ], @@ -313,9 +319,9 @@ } }, "node_modules/@esbuild/linux-ppc64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.27.7.tgz", - "integrity": "sha512-gRsL4x6wsGHGRqhtI+ifpN/vpOFTQtnbsupUF5R5YTAg+y/lKelYR1hXbnBdzDjGbMYjVJLJTd2OFmMewAgwlQ==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.1.tgz", + "integrity": "sha512-slScBsMAb3GFDcdrCgLwZtPYRoH2H/youv10QiZyRjmsP48fznoveWytSgCI/R0ZcUgpc0ZhIUEx6LHts8yrfQ==", "cpu": [ "ppc64" ], @@ -330,9 +336,9 @@ } }, "node_modules/@esbuild/linux-riscv64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.27.7.tgz", - "integrity": "sha512-hL25LbxO1QOngGzu2U5xeXtxXcW+/GvMN3ejANqXkxZ/opySAZMrc+9LY/WyjAan41unrR3YrmtTsUpwT66InQ==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.1.tgz", + "integrity": "sha512-kw0owk1o0GFETUJyW0jc0G4Yzs0BHZn0JDZ8JRT088vjJYX777BAs1fDGxAC+q831qOs2DTC96mNsG2opdfyyQ==", "cpu": [ "riscv64" ], @@ -347,9 +353,9 @@ } }, "node_modules/@esbuild/linux-s390x": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.27.7.tgz", - "integrity": "sha512-2k8go8Ycu1Kb46vEelhu1vqEP+UeRVj2zY1pSuPdgvbd5ykAw82Lrro28vXUrRmzEsUV0NzCf54yARIK8r0fdw==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.1.tgz", + "integrity": "sha512-/lAIjX8aYFRByhh6L5rYtPEDRqa9de/4V/juOXcta5frjvzXO4/sqEtyytse0g3zZFuWu5cDN0MkLz2qRDD2Ag==", "cpu": [ "s390x" ], @@ -364,9 +370,9 @@ } }, "node_modules/@esbuild/linux-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.27.7.tgz", - "integrity": "sha512-hzznmADPt+OmsYzw1EE33ccA+HPdIqiCRq7cQeL1Jlq2gb1+OyWBkMCrYGBJ+sxVzve2ZJEVeePbLM2iEIZSxA==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.1.tgz", + "integrity": "sha512-u/anNYF2mmVOEDwLtnQ1wOr3EZ9sTNGLWrsYGYwHWzGA3Si84IOkHXlbWTD1NB+9/1lcnweYKO54uhxZydNzfA==", "cpu": [ "x64" ], @@ -381,9 +387,9 @@ } }, "node_modules/@esbuild/netbsd-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.27.7.tgz", - "integrity": "sha512-b6pqtrQdigZBwZxAn1UpazEisvwaIDvdbMbmrly7cDTMFnw/+3lVxxCTGOrkPVnsYIosJJXAsILG9XcQS+Yu6w==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.1.tgz", + "integrity": "sha512-oks0DYbLwWMmaakTsCb+zL4E+aHRVLom9IJZOAthMQEPiQmydXHkziYEsGYRx0uNV/IjEKGAV941JzH02pflqw==", "cpu": [ "arm64" ], @@ -398,9 +404,9 @@ } }, "node_modules/@esbuild/netbsd-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.27.7.tgz", - "integrity": "sha512-OfatkLojr6U+WN5EDYuoQhtM+1xco+/6FSzJJnuWiUw5eVcicbyK3dq5EeV/QHT1uy6GoDhGbFpprUiHUYggrw==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.1.tgz", + "integrity": "sha512-aeL6lAnN89Hz43Mlh1G8ARasbuoYvSITDEx0tHh5b7jJnHcssqgjy9Yx430GDpmCa6OyrKoS0aNRjKundRizGg==", "cpu": [ "x64" ], @@ -415,9 +421,9 @@ } }, "node_modules/@esbuild/openbsd-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.27.7.tgz", - "integrity": "sha512-AFuojMQTxAz75Fo8idVcqoQWEHIXFRbOc1TrVcFSgCZtQfSdc1RXgB3tjOn/krRHENUB4j00bfGjyl2mJrU37A==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.1.tgz", + "integrity": "sha512-MEFJe5C3R8pwXdZ5Y21oo6m7ePiS0d9pWucn99O/wvyJZChoIQKrQDxKrGeW8F5+T0okTHesAmDeiHDTIq0V/Q==", "cpu": [ "arm64" ], @@ -432,9 +438,9 @@ } }, "node_modules/@esbuild/openbsd-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.27.7.tgz", - "integrity": "sha512-+A1NJmfM8WNDv5CLVQYJ5PshuRm/4cI6WMZRg1by1GwPIQPCTs1GLEUHwiiQGT5zDdyLiRM/l1G0Pv54gvtKIg==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.1.tgz", + "integrity": "sha512-i/ZLIOafE0Z8cI/XANJAixoJL/uRAoS2xOA3rb0xN+KK0K177cMAsQYkzHtBrtMXAKuAc7HGgcWiZ/sRC1Nxgw==", "cpu": [ "x64" ], @@ -449,9 +455,9 @@ } }, "node_modules/@esbuild/openharmony-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.27.7.tgz", - "integrity": "sha512-+KrvYb/C8zA9CU/g0sR6w2RBw7IGc5J2BPnc3dYc5VJxHCSF1yNMxTV5LQ7GuKteQXZtspjFbiuW5/dOj7H4Yw==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.1.tgz", + "integrity": "sha512-ge+Z7EXFNt2BO1oAMsVpiQ8EwndV9i1xXerAeTIK7AtPs3bKFXQM7nlRxDSIUIMeueR1CNXxqztLzdNeReKBJg==", "cpu": [ "arm64" ], @@ -466,9 +472,9 @@ } }, "node_modules/@esbuild/sunos-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.27.7.tgz", - "integrity": "sha512-ikktIhFBzQNt/QDyOL580ti9+5mL/YZeUPKU2ivGtGjdTYoqz6jObj6nOMfhASpS4GU4Q/Clh1QtxWAvcYKamA==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.1.tgz", + "integrity": "sha512-BEjgtECkL3vY+SaSQ6nzVfiALUeFxpawyp8Jmf5PtYhf1Ug40N1h/hxlhts+f1FvSvarEigdxS3BlSMI2PJLcQ==", "cpu": [ "x64" ], @@ -483,9 +489,9 @@ } }, "node_modules/@esbuild/win32-arm64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.27.7.tgz", - "integrity": "sha512-7yRhbHvPqSpRUV7Q20VuDwbjW5kIMwTHpptuUzV+AA46kiPze5Z7qgt6CLCK3pWFrHeNfDd1VKgyP4O+ng17CA==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.1.tgz", + "integrity": "sha512-lCv9eK/H6ZJWbE7bh2nw54CZ9M2nupBxJcTsdk/QQnWkdSjKGuxmmH8/GWrlT1eMmZfn4dGcCjRte397WqfQXA==", "cpu": [ "arm64" ], @@ -500,9 +506,9 @@ } }, "node_modules/@esbuild/win32-ia32": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.27.7.tgz", - "integrity": "sha512-SmwKXe6VHIyZYbBLJrhOoCJRB/Z1tckzmgTLfFYOfpMAx63BJEaL9ExI8x7v0oAO3Zh6D/Oi1gVxEYr5oUCFhw==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.1.tgz", + "integrity": "sha512-zvb/mB2bSCoJOpoCBgYKKpX6YM6mJBlBUVUtVj41DlZJVEB6/0CKlRYxP5wWl1C1ILiCoAU5wZZ4q1P3qeS6Eg==", "cpu": [ "ia32" ], @@ -517,9 +523,9 @@ } }, "node_modules/@esbuild/win32-x64": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.27.7.tgz", - "integrity": "sha512-56hiAJPhwQ1R4i+21FVF7V8kSD5zZTdHcVuRFMW0hn753vVfQN8xlx4uOPT4xoGH0Z/oVATuR82AiqSTDIpaHg==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.1.tgz", + "integrity": "sha512-bm4Mowrv+GXMlpWX++EcXw/iLyd1o3+bJkC2DkWXYVvgZCqD/bSj9ctZeAMC3cIxgjRVR2Dufaiu4YPxr5gW1A==", "cpu": [ "x64" ], @@ -1905,9 +1911,9 @@ "license": "MIT" }, "node_modules/@types/inquirer": { - "version": "8.2.12", - "resolved": "https://registry.npmjs.org/@types/inquirer/-/inquirer-8.2.12.tgz", - "integrity": "sha512-YxURZF2ZsSjU5TAe06tW0M3sL4UI9AMPA6dd8I72uOtppzNafcY38xkYgCZ/vsVOAyNdzHmvtTpLWilOrbP0dQ==", + "version": "8.2.13", + "resolved": "https://registry.npmjs.org/@types/inquirer/-/inquirer-8.2.13.tgz", + "integrity": "sha512-shSvl3mn4Z8AK627kA1vx8PYkyH6CdIjV5NYYj7a0xIxzmG3ZgzEpzCi3CWfktjAlq+0Z0wHJGtWNiACaYpeOg==", "dev": true, "license": "MIT", "dependencies": { @@ -1961,17 +1967,17 @@ } }, "node_modules/@typescript-eslint/eslint-plugin": { - "version": "8.60.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.60.0.tgz", - "integrity": "sha512-QYb/sa74/s7OKMbACMjrYnGspj9Hs5YI5aaffSL65UfeBUzVzBJfVo3oWSpbzPurvm7yaCCo2Lk7lVj610HqKw==", + "version": "8.61.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.61.1.tgz", + "integrity": "sha512-ZPlVl3PB3et/59Ne0fv/sci6ZXz4T4Hp4nTJ56i/Y0gR89ARb+KphojTq6j+56E5PIezmOIOOWyY+aWQFd+IkQ==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/regexpp": "^4.12.2", - "@typescript-eslint/scope-manager": "8.60.0", - "@typescript-eslint/type-utils": "8.60.0", - "@typescript-eslint/utils": "8.60.0", - "@typescript-eslint/visitor-keys": "8.60.0", + "@typescript-eslint/scope-manager": "8.61.1", + "@typescript-eslint/type-utils": "8.61.1", + "@typescript-eslint/utils": "8.61.1", + "@typescript-eslint/visitor-keys": "8.61.1", "ignore": "^7.0.5", "natural-compare": "^1.4.0", "ts-api-utils": "^2.5.0" @@ -1984,22 +1990,22 @@ "url": "https://opencollective.com/typescript-eslint" }, "peerDependencies": { - "@typescript-eslint/parser": "^8.60.0", + "@typescript-eslint/parser": "^8.61.1", "eslint": "^8.57.0 || ^9.0.0 || ^10.0.0", "typescript": ">=4.8.4 <6.1.0" } }, "node_modules/@typescript-eslint/parser": { - "version": "8.60.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.60.0.tgz", - "integrity": "sha512-fcqpj/MyK4sxDPcbe7STNPbpQL4RLZOPWuaTmwZYuc+hJKzRf58yRxfhqGpc6PIq9ZyfSBpfHgmUHmHs0KwHwg==", + "version": "8.61.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-8.61.1.tgz", + "integrity": "sha512-PJ5vePq5/ognBbrIcoC5+SHO5dfpeLPzP9FpLkzWrguoYQEeeSjlJpVwOpo1JRSTEi7dRcwNy4h4dzV70PqHcg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/scope-manager": "8.60.0", - "@typescript-eslint/types": "8.60.0", - "@typescript-eslint/typescript-estree": "8.60.0", - "@typescript-eslint/visitor-keys": "8.60.0", + "@typescript-eslint/scope-manager": "8.61.1", + "@typescript-eslint/types": "8.61.1", + "@typescript-eslint/typescript-estree": "8.61.1", + "@typescript-eslint/visitor-keys": "8.61.1", "debug": "^4.4.3" }, "engines": { @@ -2015,14 +2021,14 @@ } }, "node_modules/@typescript-eslint/project-service": { - "version": "8.60.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.60.0.tgz", - "integrity": "sha512-aZu74NNKJeUWqCjDddzdiKaS82dgYgV/vmf+Ui3ZdZejmgfXR/q+pRumgobnQ2cCJTgGTWp4ypiwsuofFubavg==", + "version": "8.61.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/project-service/-/project-service-8.61.1.tgz", + "integrity": "sha512-PrC4JYGmR241lYnfhmKGTXkFqv8+ymbTFgSAY0fVXpY82/QkMw5TZPl+vGzuDDU2QYJk9fIDOBTntF+yDv9LEA==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/tsconfig-utils": "^8.60.0", - "@typescript-eslint/types": "^8.60.0", + "@typescript-eslint/tsconfig-utils": "^8.61.1", + "@typescript-eslint/types": "^8.61.1", "debug": "^4.4.3" }, "engines": { @@ -2037,14 +2043,14 @@ } }, "node_modules/@typescript-eslint/scope-manager": { - "version": "8.60.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.60.0.tgz", - "integrity": "sha512-pFzqhllJMs+jghLQWzV00ds39xLzuyqPSev5pd8f4Ir0rtKR3ZLUB4/4dhjOFighWb9larvtfJvqL+4yKDI3Xw==", + "version": "8.61.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-8.61.1.tgz", + "integrity": "sha512-L2bdIeoQS8FlKAvONAr20w6OcLXeB+qiDKbAooS9A0Ben+iSIkBef0FxqwKWYqt5sa0i4KJtxVyVmhMylKzF5w==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.60.0", - "@typescript-eslint/visitor-keys": "8.60.0" + "@typescript-eslint/types": "8.61.1", + "@typescript-eslint/visitor-keys": "8.61.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2055,9 +2061,9 @@ } }, "node_modules/@typescript-eslint/tsconfig-utils": { - "version": "8.60.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.60.0.tgz", - "integrity": "sha512-BZPR3RGYlAXnly6ymAxfkVn5rCbZzQNou0rxv3GfWZ8cTQp+hhVd73khbGLAd8k1TlAPLISH337M+tAgAnaJDQ==", + "version": "8.61.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/tsconfig-utils/-/tsconfig-utils-8.61.1.tgz", + "integrity": "sha512-UN/H4di+OO7EWx2ovME+8t31YO+KVnK0RRKEHR3kOt21/Ay8BOq3M1OMvWs5vNiqcFCYGYoxK3MXPZzmMUE+yg==", "dev": true, "license": "MIT", "engines": { @@ -2072,15 +2078,15 @@ } }, "node_modules/@typescript-eslint/type-utils": { - "version": "8.60.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.60.0.tgz", - "integrity": "sha512-SX46wEUtitCpq7AN38HkUU/+zvUpdKf7ephtWAFgckH8O7PQIyL5gvrhQgBLuEYgLfuKWOVvWVskMbuFHAz5xg==", + "version": "8.61.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-8.61.1.tgz", + "integrity": "sha512-GYRicKmVK0C4fsKgaACaknOUAq9Oa2kwsjnpFhFcS/5p4Ht5IP9OVLbgIgcK4SRk92nVHFluurg1lumD9dBcLw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.60.0", - "@typescript-eslint/typescript-estree": "8.60.0", - "@typescript-eslint/utils": "8.60.0", + "@typescript-eslint/types": "8.61.1", + "@typescript-eslint/typescript-estree": "8.61.1", + "@typescript-eslint/utils": "8.61.1", "debug": "^4.4.3", "ts-api-utils": "^2.5.0" }, @@ -2097,9 +2103,9 @@ } }, "node_modules/@typescript-eslint/types": { - "version": "8.60.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.60.0.tgz", - "integrity": "sha512-AsE7x2XaAK+CVbeih0Fvbn+r1qHxtpLDJ3XUuFcIinT318T90yHMJC+Zgv+jUuDjQQd06HKwxnDu6sz1IcTilA==", + "version": "8.61.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-8.61.1.tgz", + "integrity": "sha512-G+CRlPqLv7Bz1IZVs03x5K59F1veqL0EJUROAdGhKsEq8qOiRiZbI+HUojPq5l0fEGOKModD9br6lObhB8zkoA==", "dev": true, "license": "MIT", "engines": { @@ -2111,16 +2117,16 @@ } }, "node_modules/@typescript-eslint/typescript-estree": { - "version": "8.60.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.60.0.tgz", - "integrity": "sha512-3AcZNBGMClm6CXDyo8kYvVGT/sx29sS0oBsIb9oZI2gunA4Vm2M3YHzRLPvsUBBsl+yB5FPtltq7gGH0iTlp9g==", + "version": "8.61.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-8.61.1.tgz", + "integrity": "sha512-u+oQD3BqYWPc8YV9Zab4vaJElJuwOLPRc10Jm1o/qS+6Qwen14HCWwx0Seo4LnSn2wxea2Ik8DxPt2/FHmuhrg==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/project-service": "8.60.0", - "@typescript-eslint/tsconfig-utils": "8.60.0", - "@typescript-eslint/types": "8.60.0", - "@typescript-eslint/visitor-keys": "8.60.0", + "@typescript-eslint/project-service": "8.61.1", + "@typescript-eslint/tsconfig-utils": "8.61.1", + "@typescript-eslint/types": "8.61.1", + "@typescript-eslint/visitor-keys": "8.61.1", "debug": "^4.4.3", "minimatch": "^10.2.2", "semver": "^7.7.3", @@ -2139,16 +2145,16 @@ } }, "node_modules/@typescript-eslint/utils": { - "version": "8.60.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.60.0.tgz", - "integrity": "sha512-HtXuPfrHTyBDkameWpl+vJb1Uevu2tznAyahM1Oc4AENidCLTPiZDWIo4GfcxNdC/RcfGcadzzkqbRG87dUrQA==", + "version": "8.61.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.61.1.tgz", + "integrity": "sha512-1+P/3Dj6jvtybE1q0HQ6yBt/gq+oKJyLdEv4HdnqasaEXRSYCAsD59mXEVQnM/ULNdQxbX77tdG4jPRjIS6knA==", "dev": true, "license": "MIT", "dependencies": { "@eslint-community/eslint-utils": "^4.9.1", - "@typescript-eslint/scope-manager": "8.60.0", - "@typescript-eslint/types": "8.60.0", - "@typescript-eslint/typescript-estree": "8.60.0" + "@typescript-eslint/scope-manager": "8.61.1", + "@typescript-eslint/types": "8.61.1", + "@typescript-eslint/typescript-estree": "8.61.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -2163,13 +2169,13 @@ } }, "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.60.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.60.0.tgz", - "integrity": "sha512-9WI52t8ZGLVGrPMBet25yAftqY/n95+zmoUUtJBBQTKDSKUu7OsPTroT2op7U9JatkoRccL0YkWDNMFfC4Sjxg==", + "version": "8.61.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.61.1.tgz", + "integrity": "sha512-6fJ9MHWtK14C1DSkiMlHUSOmrVebL7150xZJBlJiL62jjhIA4JmOq6flwBgDxIdBKKdoiZRel+dfPD5MLfny3w==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.60.0", + "@typescript-eslint/types": "8.61.1", "eslint-visitor-keys": "^5.0.0" }, "engines": { @@ -2203,9 +2209,9 @@ } }, "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==", + "version": "8.17.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.17.0.tgz", + "integrity": "sha512-xRQbDb9BnwDafYNn6Vwl839DYVjqXYb1XVGtWAZ1kcDc6iwAL4hg3B1dZlRiuENFeO2H53gFG3in621AdERVAg==", "dev": true, "license": "MIT", "bin": { @@ -2858,9 +2864,9 @@ } }, "node_modules/esbuild": { - "version": "0.27.7", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.27.7.tgz", - "integrity": "sha512-IxpibTjyVnmrIQo5aqNpCgoACA/dTKLTlhMHihVHhdkxKyPO1uBBthumT0rdHmcsk9uMonIWS0m4FljWzILh3w==", + "version": "0.28.1", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.1.tgz", + "integrity": "sha512-HrJrvZv5ayxBzPfwphOoNzkzOIIlifzk0KJrGK2c8R4+LKpMtpYLQeUdjnwjWv/LZlkH2laZk+4w78pi99D4Vw==", "dev": true, "hasInstallScript": true, "license": "MIT", @@ -2871,32 +2877,32 @@ "node": ">=18" }, "optionalDependencies": { - "@esbuild/aix-ppc64": "0.27.7", - "@esbuild/android-arm": "0.27.7", - "@esbuild/android-arm64": "0.27.7", - "@esbuild/android-x64": "0.27.7", - "@esbuild/darwin-arm64": "0.27.7", - "@esbuild/darwin-x64": "0.27.7", - "@esbuild/freebsd-arm64": "0.27.7", - "@esbuild/freebsd-x64": "0.27.7", - "@esbuild/linux-arm": "0.27.7", - "@esbuild/linux-arm64": "0.27.7", - "@esbuild/linux-ia32": "0.27.7", - "@esbuild/linux-loong64": "0.27.7", - "@esbuild/linux-mips64el": "0.27.7", - "@esbuild/linux-ppc64": "0.27.7", - "@esbuild/linux-riscv64": "0.27.7", - "@esbuild/linux-s390x": "0.27.7", - "@esbuild/linux-x64": "0.27.7", - "@esbuild/netbsd-arm64": "0.27.7", - "@esbuild/netbsd-x64": "0.27.7", - "@esbuild/openbsd-arm64": "0.27.7", - "@esbuild/openbsd-x64": "0.27.7", - "@esbuild/openharmony-arm64": "0.27.7", - "@esbuild/sunos-x64": "0.27.7", - "@esbuild/win32-arm64": "0.27.7", - "@esbuild/win32-ia32": "0.27.7", - "@esbuild/win32-x64": "0.27.7" + "@esbuild/aix-ppc64": "0.28.1", + "@esbuild/android-arm": "0.28.1", + "@esbuild/android-arm64": "0.28.1", + "@esbuild/android-x64": "0.28.1", + "@esbuild/darwin-arm64": "0.28.1", + "@esbuild/darwin-x64": "0.28.1", + "@esbuild/freebsd-arm64": "0.28.1", + "@esbuild/freebsd-x64": "0.28.1", + "@esbuild/linux-arm": "0.28.1", + "@esbuild/linux-arm64": "0.28.1", + "@esbuild/linux-ia32": "0.28.1", + "@esbuild/linux-loong64": "0.28.1", + "@esbuild/linux-mips64el": "0.28.1", + "@esbuild/linux-ppc64": "0.28.1", + "@esbuild/linux-riscv64": "0.28.1", + "@esbuild/linux-s390x": "0.28.1", + "@esbuild/linux-x64": "0.28.1", + "@esbuild/netbsd-arm64": "0.28.1", + "@esbuild/netbsd-x64": "0.28.1", + "@esbuild/openbsd-arm64": "0.28.1", + "@esbuild/openbsd-x64": "0.28.1", + "@esbuild/openharmony-arm64": "0.28.1", + "@esbuild/sunos-x64": "0.28.1", + "@esbuild/win32-arm64": "0.28.1", + "@esbuild/win32-ia32": "0.28.1", + "@esbuild/win32-x64": "0.28.1" } }, "node_modules/escape-string-regexp": { @@ -4104,10 +4110,20 @@ "license": "BSD-3-Clause" }, "node_modules/js-yaml": { - "version": "4.1.1", - "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz", - "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==", + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.2.0.tgz", + "integrity": "sha512-ePWsvanv0DWuDRsW8dnt+R4jQ31SCRCQ7hhNcPXZPsoBZiemuZNYGf7adZdqX2D86j6rvKp3RpCxVTSb8WQlOw==", "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/puzrin" + }, + { + "type": "github", + "url": "https://github.com/sponsors/nodeca" + } + ], "license": "MIT", "dependencies": { "argparse": "^2.0.1" @@ -4799,9 +4815,9 @@ } }, "node_modules/prettier": { - "version": "3.8.3", - "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.3.tgz", - "integrity": "sha512-7igPTM53cGHMW8xWuVTydi2KO233VFiTNyF5hLJqpilHfmn8C8gPf+PS7dUT64YcXFbiMGZxS9pCSxL/Dxm/Jw==", + "version": "3.8.4", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-3.8.4.tgz", + "integrity": "sha512-N2MylSdi48+5N/6S5j+maeHbUSIzzZ5uOcX5Hm4QpV8Dkb1HFjfAKTKX6yNPJQD9AhcT3ifHNB66tWTTJDi11Q==", "dev": true, "license": "MIT", "bin": { @@ -5100,9 +5116,9 @@ } }, "node_modules/semver": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz", - "integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==", + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.4.tgz", + "integrity": "sha512-rUCObTnP32Q08R2uuIrt7r9PlEonuTmtuXYcW6s5kjdlj3xbnwe+21yXptAUYcMAABLkYYTtnmzb3w3EDZfueA==", "dev": true, "license": "ISC", "bin": { @@ -5310,9 +5326,9 @@ } }, "node_modules/tar": { - "version": "7.5.15", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.15.tgz", - "integrity": "sha512-dzGK0boVlC4W5QFuQN1EFSl3bIDYsk7Tj40U6eIBnK2k/8ml7TZ5agbI5j5+qnoVcAA+rNtBml8SEiLxZpNqRQ==", + "version": "7.5.16", + "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.16.tgz", + "integrity": "sha512-56adEpPMouktRlBLXiaYFFzZ/3+JXa8P9n7WbR+ibIjtviN55mEaOkiysCnPnWm+7kkui1Dn8J9l+g6zV8731w==", "license": "BlueOak-1.0.0", "dependencies": { "@isaacs/fs-minipass": "^4.0.0", @@ -5416,9 +5432,9 @@ "license": "MIT" }, "node_modules/tinyglobby": { - "version": "0.2.16", - "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.16.tgz", - "integrity": "sha512-pn99VhoACYR8nFHhxqix+uvsbXineAasWm5ojXoN8xEwK5Kd3/TrhNn1wByuD52UxWRLy8pu+kRMniEi6Eq9Zg==", + "version": "0.2.17", + "resolved": "https://registry.npmjs.org/tinyglobby/-/tinyglobby-0.2.17.tgz", + "integrity": "sha512-wXR/dYpcqKmfWpEdZjiKJOwCNFndD0DMnrW/cYjVGttEkBfVgcLFHoNrlj47mjOVic9yyNu65alsgF4NQyTa2g==", "dev": true, "license": "MIT", "dependencies": { @@ -5522,9 +5538,9 @@ "license": "0BSD" }, "node_modules/tsx": { - "version": "4.22.3", - "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.22.3.tgz", - "integrity": "sha512-mdoNxBC/cSQObGGVQ5Bpn5i+yv7j68gk3Nfm3wFjcJg3Z0Mix9jzAFfP12prmm5eVGmDKtp0yyArrs0Q+8gZHg==", + "version": "4.22.4", + "resolved": "https://registry.npmjs.org/tsx/-/tsx-4.22.4.tgz", + "integrity": "sha512-X8EX+XV4QR5xCsrgxaED954zTDfY8KqlDtskKEL0cHhyS/P8b4IFOvGDQpsC9Q1XnLq915wEfwwY/zzskCtmhg==", "dev": true, "license": "MIT", "dependencies": { @@ -5540,490 +5556,6 @@ "fsevents": "~2.3.3" } }, - "node_modules/tsx/node_modules/@esbuild/aix-ppc64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.28.0.tgz", - "integrity": "sha512-lhRUCeuOyJQURhTxl4WkpFTjIsbDayJHih5kZC1giwE+MhIzAb7mEsQMqMf18rHLsrb5qI1tafG20mLxEWcWlA==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "aix" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/android-arm": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.28.0.tgz", - "integrity": "sha512-wqh0ByljabXLKHeWXYLqoJ5jKC4XBaw6Hk08OfMrCRd2nP2ZQ5eleDZC41XHyCNgktBGYMbqnrJKq/K/lzPMSQ==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/android-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.28.0.tgz", - "integrity": "sha512-+WzIXQOSaGs33tLEgYPYe/yQHf0WTU0X42Jca3y8NWMbUVhp7rUnw+vAsRC/QiDrdD31IszMrZy+qwPOPjd+rw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/android-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.28.0.tgz", - "integrity": "sha512-+VJggoaKhk2VNNqVL7f6S189UzShHC/mR9EE8rDdSkdpN0KflSwWY/gWjDrNxxisg8Fp1ZCD9jLMo4m0OUfeUA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "android" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/darwin-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.28.0.tgz", - "integrity": "sha512-0T+A9WZm+bZ84nZBtk1ckYsOvyA3x7e2Acj1KdVfV4/2tdG4fzUp91YHx+GArWLtwqp77pBXVCPn2We7Letr0Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/darwin-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.28.0.tgz", - "integrity": "sha512-fyzLm/DLDl/84OCfp2f/XQ4flmORsjU7VKt8HLjvIXChJoFFOIL6pLJPH4Yhd1n1gGFF9mPwtlN5Wf82DZs+LQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/freebsd-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.28.0.tgz", - "integrity": "sha512-l9GeW5UZBT9k9brBYI+0WDffcRxgHQD8ShN2Ur4xWq/NFzUKm3k5lsH4PdaRgb2w7mI9u61nr2gI2mLI27Nh3Q==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/freebsd-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.28.0.tgz", - "integrity": "sha512-BXoQai/A0wPO6Es3yFJ7APCiKGc1tdAEOgeTNy3SsB491S3aHn4S4r3e976eUnPdU+NbdtmBuLncYir2tMU9Nw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "freebsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/linux-arm": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.28.0.tgz", - "integrity": "sha512-CjaaREJagqJp7iTaNQjjidaNbCKYcd4IDkzbwwxtSvjI7NZm79qiHc8HqciMddQ6CKvJT6aBd8lO9kN/ZudLlw==", - "cpu": [ - "arm" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/linux-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.28.0.tgz", - "integrity": "sha512-RVyzfb3FWsGA55n6WY0MEIEPURL1FcbhFE6BffZEMEekfCzCIMtB5yyDcFnVbTnwk+CLAgTujmV/Lgvih56W+A==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/linux-ia32": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.28.0.tgz", - "integrity": "sha512-KBnSTt1kxl9x70q+ydterVdl+Cn0H18ngRMRCEQfrbqdUuntQQ0LoMZv47uB97NljZFzY6HcfqEZ2SAyIUTQBQ==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/linux-loong64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.28.0.tgz", - "integrity": "sha512-zpSlUce1mnxzgBADvxKXX5sl8aYQHo2ezvMNI8I0lbblJtp8V4odlm3Yzlj7gPyt3T8ReksE6bK+pT3WD+aJRg==", - "cpu": [ - "loong64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/linux-mips64el": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.28.0.tgz", - "integrity": "sha512-2jIfP6mmjkdmeTlsX/9vmdmhBmKADrWqN7zcdtHIeNSCH1SqIoNI63cYsjQR8J+wGa4Y5izRcSHSm8K3QWmk3w==", - "cpu": [ - "mips64el" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/linux-ppc64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.28.0.tgz", - "integrity": "sha512-bc0FE9wWeC0WBm49IQMPSPILRocGTQt3j5KPCA8os6VprfuJ7KD+5PzESSrJ6GmPIPJK965ZJHTUlSA6GNYEhg==", - "cpu": [ - "ppc64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/linux-riscv64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.28.0.tgz", - "integrity": "sha512-SQPZOwoTTT/HXFXQJG/vBX8sOFagGqvZyXcgLA3NhIqcBv1BJU1d46c0rGcrij2B56Z2rNiSLaZOYW5cUk7yLQ==", - "cpu": [ - "riscv64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/linux-s390x": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.28.0.tgz", - "integrity": "sha512-SCfR0HN8CEEjnYnySJTd2cw0k9OHB/YFzt5zgJEwa+wL/T/raGWYMBqwDNAC6dqFKmJYZoQBRfHjgwLHGSrn3Q==", - "cpu": [ - "s390x" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/linux-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.28.0.tgz", - "integrity": "sha512-us0dSb9iFxIi8srnpl931Nvs65it/Jd2a2K3qs7fz2WfGPHqzfzZTfec7oxZJRNPXPnNYZtanmRc4AL/JwVzHQ==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "linux" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/netbsd-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-arm64/-/netbsd-arm64-0.28.0.tgz", - "integrity": "sha512-CR/RYotgtCKwtftMwJlUU7xCVNg3lMYZ0RzTmAHSfLCXw3NtZtNpswLEj/Kkf6kEL3Gw+BpOekRX0BYCtklhUw==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/netbsd-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.28.0.tgz", - "integrity": "sha512-nU1yhmYutL+fQ71Kxnhg8uEOdC0pwEW9entHykTgEbna2pw2dkbFSMeqjjyHZoCmt8SBkOSvV+yNmm94aUrrqw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "netbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/openbsd-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-arm64/-/openbsd-arm64-0.28.0.tgz", - "integrity": "sha512-cXb5vApOsRsxsEl4mcZ1XY3D4DzcoMxR/nnc4IyqYs0rTI8ZKmW6kyyg+11Z8yvgMfAEldKzP7AdP64HnSC/6g==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/openbsd-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.28.0.tgz", - "integrity": "sha512-8wZM2qqtv9UP3mzy7HiGYNH/zjTA355mpeuA+859TyR+e+Tc08IHYpLJuMsfpDJwoLo1ikIJI8jC3GFjnRClzA==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openbsd" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/openharmony-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/openharmony-arm64/-/openharmony-arm64-0.28.0.tgz", - "integrity": "sha512-FLGfyizszcef5C3YtoyQDACyg95+dndv79i2EekILBofh5wpCa1KuBqOWKrEHZg3zrL3t5ouE5jgr94vA+Wb2w==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "openharmony" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/sunos-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.28.0.tgz", - "integrity": "sha512-1ZgjUoEdHZZl/YlV76TSCz9Hqj9h9YmMGAgAPYd+q4SicWNX3G5GCyx9uhQWSLcbvPW8Ni7lj4gDa1T40akdlw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "sunos" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/win32-arm64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.28.0.tgz", - "integrity": "sha512-Q9StnDmQ/enxnpxCCLSg0oo4+34B9TdXpuyPeTedN/6+iXBJ4J+zwfQI28u/Jl40nOYAxGoNi7mFP40RUtkmUA==", - "cpu": [ - "arm64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/win32-ia32": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.28.0.tgz", - "integrity": "sha512-zF3ag/gfiCe6U2iczcRzSYJKH1DCI+ByzSENHlM2FcDbEeo5Zd2C86Aq0tKUYAJJ1obRP84ymxIAksZUcdztHA==", - "cpu": [ - "ia32" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/@esbuild/win32-x64": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.28.0.tgz", - "integrity": "sha512-pEl1bO9mfAmIC+tW5btTmrKaujg3zGtUmWNdCw/xs70FBjwAL3o9OEKNHvNmnyylD6ubxUERiEhdsL0xBQ9efw==", - "cpu": [ - "x64" - ], - "dev": true, - "license": "MIT", - "optional": true, - "os": [ - "win32" - ], - "engines": { - "node": ">=18" - } - }, - "node_modules/tsx/node_modules/esbuild": { - "version": "0.28.0", - "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.28.0.tgz", - "integrity": "sha512-sNR9MHpXSUV/XB4zmsFKN+QgVG82Cc7+/aaxJ8Adi8hyOac+EXptIp45QBPaVyX3N70664wRbTcLTOemCAnyqw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "bin": { - "esbuild": "bin/esbuild" - }, - "engines": { - "node": ">=18" - }, - "optionalDependencies": { - "@esbuild/aix-ppc64": "0.28.0", - "@esbuild/android-arm": "0.28.0", - "@esbuild/android-arm64": "0.28.0", - "@esbuild/android-x64": "0.28.0", - "@esbuild/darwin-arm64": "0.28.0", - "@esbuild/darwin-x64": "0.28.0", - "@esbuild/freebsd-arm64": "0.28.0", - "@esbuild/freebsd-x64": "0.28.0", - "@esbuild/linux-arm": "0.28.0", - "@esbuild/linux-arm64": "0.28.0", - "@esbuild/linux-ia32": "0.28.0", - "@esbuild/linux-loong64": "0.28.0", - "@esbuild/linux-mips64el": "0.28.0", - "@esbuild/linux-ppc64": "0.28.0", - "@esbuild/linux-riscv64": "0.28.0", - "@esbuild/linux-s390x": "0.28.0", - "@esbuild/linux-x64": "0.28.0", - "@esbuild/netbsd-arm64": "0.28.0", - "@esbuild/netbsd-x64": "0.28.0", - "@esbuild/openbsd-arm64": "0.28.0", - "@esbuild/openbsd-x64": "0.28.0", - "@esbuild/openharmony-arm64": "0.28.0", - "@esbuild/sunos-x64": "0.28.0", - "@esbuild/win32-arm64": "0.28.0", - "@esbuild/win32-ia32": "0.28.0", - "@esbuild/win32-x64": "0.28.0" - } - }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -6064,16 +5596,16 @@ } }, "node_modules/typescript-eslint": { - "version": "8.60.0", - "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.60.0.tgz", - "integrity": "sha512-9f65qWLZdAW9m1JaxBDUHcqRUfL8bkxxXL7XxEfI+F09q56PkBvIfCjLF3yInsDM/BBmwkqmCQdCZe/RYlIWEw==", + "version": "8.61.1", + "resolved": "https://registry.npmjs.org/typescript-eslint/-/typescript-eslint-8.61.1.tgz", + "integrity": "sha512-V7PayAfJokV3pEHgN7/v03D1SpujhRfQtYLbLIiBfDDncdg4PAiRBfoS4cnCANK4jmAPncczi59QO3afiXUlNw==", "dev": true, "license": "MIT", "dependencies": { - "@typescript-eslint/eslint-plugin": "8.60.0", - "@typescript-eslint/parser": "8.60.0", - "@typescript-eslint/typescript-estree": "8.60.0", - "@typescript-eslint/utils": "8.60.0" + "@typescript-eslint/eslint-plugin": "8.61.1", + "@typescript-eslint/parser": "8.61.1", + "@typescript-eslint/typescript-estree": "8.61.1", + "@typescript-eslint/utils": "8.61.1" }, "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -6113,9 +5645,9 @@ } }, "node_modules/undici": { - "version": "6.26.0", - "resolved": "https://registry.npmjs.org/undici/-/undici-6.26.0.tgz", - "integrity": "sha512-4yqz8a3n5HmGTlsbADNtr/dJlhkh/55Rq798G6ibiULcXbDtaLpTl1pvdqcbFfeoj3iSi52lePFM7h9H21cw/A==", + "version": "6.27.0", + "resolved": "https://registry.npmjs.org/undici/-/undici-6.27.0.tgz", + "integrity": "sha512-YmfV3YnEDzXRC5lZ2jWtWWHKGUm1zIt8AhesR1tens+HTNv+YZlN/dp6G727LOvMJ8xjP9Be7Y2Sdr96LDm+pg==", "license": "MIT", "engines": { "node": ">=18.17" diff --git a/package.json b/package.json index 5eefd194..18caa09f 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "type": "module", "homepage": "https://appwrite.io/support", "description": "Appwrite is an open-source self-hosted backend server that abstracts and simplifies complex and repetitive development tasks behind a very simple REST API", - "version": "22.0.0", + "version": "22.1.0", "license": "BSD-3-Clause", "main": "dist/index.cjs", "module": "dist/index.js", @@ -51,7 +51,7 @@ "windows-arm64": "bun build cli.ts --compile --sourcemap=inline --target=bun-windows-arm64 --outfile build/appwrite-cli-win-arm64.exe" }, "dependencies": { - "@appwrite.io/console": "13.1.0", + "@appwrite.io/console": "^15.0.0", "chalk": "4.1.2", "chokidar": "^3.6.0", "cli-progress": "^3.12.0", @@ -74,7 +74,8 @@ "overrides": { "phin": "3.7.1", "@xmldom/xmldom": "^0.9.10", - "tmp": "^0.2.6" + "tmp": "^0.2.6", + "esbuild": "^0.28.1" }, "devDependencies": { "@typescript-eslint/eslint-plugin": "^8.0.0", @@ -88,7 +89,7 @@ "@types/json-bigint": "^1.0.4", "@types/node": "^18.19.0", "@types/tar": "^6.1.13", - "esbuild": "^0.27.2", + "esbuild": "^0.28.1", "prettier": "^3.7.4", "tsx": "^4.21.0", "typescript": "^5.3.3" diff --git a/scoop/appwrite.config.json b/scoop/appwrite.config.json index b33f5fc4..fba6cbe7 100644 --- a/scoop/appwrite.config.json +++ b/scoop/appwrite.config.json @@ -1,12 +1,12 @@ { "$schema": "https://raw.githubusercontent.com/ScoopInstaller/Scoop/master/schema.json", - "version": "22.0.0", + "version": "22.1.0", "description": "The Appwrite CLI is a command-line application that allows you to interact with Appwrite and perform server-side tasks using your terminal.", "homepage": "https://github.com/appwrite/sdk-for-cli", "license": "BSD-3-Clause", "architecture": { "64bit": { - "url": "https://github.com/appwrite/sdk-for-cli/releases/download/22.0.0/appwrite-cli-win-x64.exe", + "url": "https://github.com/appwrite/sdk-for-cli/releases/download/22.1.0/appwrite-cli-win-x64.exe", "bin": [ [ "appwrite-cli-win-x64.exe", @@ -15,7 +15,7 @@ ] }, "arm64": { - "url": "https://github.com/appwrite/sdk-for-cli/releases/download/22.0.0/appwrite-cli-win-arm64.exe", + "url": "https://github.com/appwrite/sdk-for-cli/releases/download/22.1.0/appwrite-cli-win-arm64.exe", "bin": [ [ "appwrite-cli-win-arm64.exe", From 71e6fdb1a338f70b16317de5ac96403ca27a97d9 Mon Sep 17 00:00:00 2001 From: Chirag Aggarwal Date: Fri, 19 Jun 2026 11:19:59 +0530 Subject: [PATCH 2/2] chore: update Command Line SDK to 22.1.0 --- CHANGELOG.md | 1 + lib/auth/login.ts | 5 ++--- lib/commands/services/functions.ts | 3 +++ lib/commands/services/organizations.ts | 6 ++++++ lib/commands/services/sites.ts | 3 +++ lib/commands/services/storage.ts | 9 +++++++++ lib/sdks.ts | 4 ++++ 7 files changed, 28 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 6556000f..6b5ab245 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,7 @@ ## 22.1.0 * Fixed: `organization get-invoice-download` and `get-invoice-view` now save the invoice to disk via `--destination` +* Fixed: File download commands now report a clear error when the download request fails * Added: `oauth2` command group (`authorize`, `create-token`, `create-grant`, `get-grant`, `approve`, `reject`, `revoke`, `logout`, `create-device-authorization`) * Added: `messaging create-ses-provider` and `update-ses-provider` commands for Amazon SES * Added: `project update-o-auth-2-server` command to configure the OIDC provider diff --git a/lib/auth/login.ts b/lib/auth/login.ts index 82be961f..603f1316 100644 --- a/lib/auth/login.ts +++ b/lib/auth/login.ts @@ -382,8 +382,6 @@ export const loginCommand = async ({ switch?: boolean; new?: boolean; }): Promise => { - let oldCurrent = globalConfig.getCurrentSession(); - if (switchAccount && newAccount) { throw new Error("Use either --switch or --new, not both."); } @@ -400,7 +398,7 @@ export const loginCommand = async ({ const shouldUseCloudLogin = isFlagEnabled("oauthLogin") && isCloudLoginEndpoint(configEndpoint); - oldCurrent = globalConfig.getCurrentSession(); + let oldCurrent = globalConfig.getCurrentSession(); if (oldCurrent !== "" && !newAccount) { let account: Models.User | null = null; @@ -409,6 +407,7 @@ export const loginCommand = async ({ } catch (_err) { account = null; } + // Refresh the current session after account lookup. oldCurrent = globalConfig.getCurrentSession(); if (account) { diff --git a/lib/commands/services/functions.ts b/lib/commands/services/functions.ts index 4a4f4a2b..5341c20d 100644 --- a/lib/commands/services/functions.ts +++ b/lib/commands/services/functions.ts @@ -407,6 +407,9 @@ const functionsGetDeploymentDownloadCommand = functions async ({ functionId, deploymentId, type, destination }) => { const url = await (await getFunctionsClient()).getDeploymentDownload(functionId, deploymentId, type); const response = await fetch(url); + if (!response.ok) { + throw new Error(`Failed to download file: ${response.status} ${response.statusText}`); + } const buffer = Buffer.from(await response.arrayBuffer()); fs.writeFileSync(destination, buffer); success(`File saved to ${destination}`); diff --git a/lib/commands/services/organizations.ts b/lib/commands/services/organizations.ts index 2c9719c1..3db62086 100644 --- a/lib/commands/services/organizations.ts +++ b/lib/commands/services/organizations.ts @@ -390,6 +390,9 @@ const organizationsGetInvoiceDownloadCommand = organizations async ({ organizationId, invoiceId, destination }) => { const url = await (await getOrganizationsClient()).getInvoiceDownload(organizationId, invoiceId); const response = await fetch(url); + if (!response.ok) { + throw new Error(`Failed to download file: ${response.status} ${response.statusText}`); + } const buffer = Buffer.from(await response.arrayBuffer()); fs.writeFileSync(destination, buffer); success(`File saved to ${destination}`); @@ -436,6 +439,9 @@ const organizationsGetInvoiceViewCommand = organizations async ({ organizationId, invoiceId, destination }) => { const url = await (await getOrganizationsClient()).getInvoiceView(organizationId, invoiceId); const response = await fetch(url); + if (!response.ok) { + throw new Error(`Failed to download file: ${response.status} ${response.statusText}`); + } const buffer = Buffer.from(await response.arrayBuffer()); fs.writeFileSync(destination, buffer); success(`File saved to ${destination}`); diff --git a/lib/commands/services/sites.ts b/lib/commands/services/sites.ts index 3ce2017f..df9dd423 100644 --- a/lib/commands/services/sites.ts +++ b/lib/commands/services/sites.ts @@ -403,6 +403,9 @@ const sitesGetDeploymentDownloadCommand = sites async ({ siteId, deploymentId, type, destination }) => { const url = await (await getSitesClient()).getDeploymentDownload(siteId, deploymentId, type); const response = await fetch(url); + if (!response.ok) { + throw new Error(`Failed to download file: ${response.status} ${response.statusText}`); + } const buffer = Buffer.from(await response.arrayBuffer()); fs.writeFileSync(destination, buffer); success(`File saved to ${destination}`); diff --git a/lib/commands/services/storage.ts b/lib/commands/services/storage.ts index 05bafc49..d88178b2 100644 --- a/lib/commands/services/storage.ts +++ b/lib/commands/services/storage.ts @@ -282,6 +282,9 @@ const storageGetFileDownloadCommand = storage async ({ bucketId, fileId, token, destination }) => { const url = await (await getStorageClient()).getFileDownload(bucketId, fileId, token); const response = await fetch(url); + if (!response.ok) { + throw new Error(`Failed to download file: ${response.status} ${response.statusText}`); + } const buffer = Buffer.from(await response.arrayBuffer()); fs.writeFileSync(destination, buffer); success(`File saved to ${destination}`); @@ -313,6 +316,9 @@ const storageGetFilePreviewCommand = storage async ({ bucketId, fileId, width, height, gravity, quality, borderWidth, borderColor, borderRadius, opacity, rotation, background, output, token, destination }) => { const url = await (await getStorageClient()).getFilePreview(bucketId, fileId, width, height, gravity, quality, borderWidth, borderColor, borderRadius, opacity, rotation, background, output, token); const response = await fetch(url); + if (!response.ok) { + throw new Error(`Failed to download file: ${response.status} ${response.statusText}`); + } const buffer = Buffer.from(await response.arrayBuffer()); fs.writeFileSync(destination, buffer); success(`File saved to ${destination}`); @@ -333,6 +339,9 @@ const storageGetFileViewCommand = storage async ({ bucketId, fileId, token, destination }) => { const url = await (await getStorageClient()).getFileView(bucketId, fileId, token); const response = await fetch(url); + if (!response.ok) { + throw new Error(`Failed to download file: ${response.status} ${response.statusText}`); + } const buffer = Buffer.from(await response.arrayBuffer()); fs.writeFileSync(destination, buffer); success(`File saved to ${destination}`); diff --git a/lib/sdks.ts b/lib/sdks.ts index 1eca90ee..795b4aca 100644 --- a/lib/sdks.ts +++ b/lib/sdks.ts @@ -28,6 +28,10 @@ export const getValidAccessToken = async ( return accessToken; } + if (accessToken && tokenExpiry === 0 && !refreshToken) { + return accessToken; + } + if (!refreshToken) { throw new Error( `Session expired. Please run \`${EXECUTABLE_NAME} login\` to create a new session.`,