-
Notifications
You must be signed in to change notification settings - Fork 45
feat: SDK update for version 20.1.0 #306
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,7 +2,11 @@ import inquirer from "inquirer"; | |
| import { Command } from "commander"; | ||
| import { Client } from "@appwrite.io/console"; | ||
| import { sdkForConsole } from "../sdks.js"; | ||
| import { globalConfig, localConfig } from "../config.js"; | ||
| import { | ||
| globalConfig, | ||
| localConfig, | ||
| normalizeCloudConsoleEndpoint, | ||
| } from "../config.js"; | ||
| import { EXECUTABLE_NAME } from "../constants.js"; | ||
| import { | ||
| actionRunner, | ||
|
|
@@ -16,14 +20,20 @@ import { | |
| drawTable, | ||
| cliConfig, | ||
| } from "../parser.js"; | ||
| import { isCloudHostname } from "../utils.js"; | ||
| import ID from "../id.js"; | ||
| import { | ||
| questionsLogin, | ||
| questionsLogout, | ||
| questionsListFactors, | ||
| questionsMFAChallenge, | ||
| questionsSwitchAccount, | ||
| } from "../questions.js"; | ||
| import { Account, Client as ConsoleClient } from "@appwrite.io/console"; | ||
| import { | ||
| Account, | ||
| Client as ConsoleClient, | ||
| type Models, | ||
| } from "@appwrite.io/console"; | ||
| import ClientLegacy from "../client.js"; | ||
|
|
||
| const DEFAULT_ENDPOINT = "https://cloud.appwrite.io/v1"; | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
|
|
@@ -37,6 +47,56 @@ const isMfaRequiredError = (err: unknown): err is AppwriteError => | |
| (err as AppwriteError)?.type === "user_more_factors_required" || | ||
| (err as AppwriteError)?.response === "user_more_factors_required"; | ||
|
|
||
| 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<Models.User | null> => { | ||
| if (globalConfig.getEndpoint() === "" || globalConfig.getCookie() === "") { | ||
| return null; | ||
| } | ||
|
|
||
| const endpoint = normalizeCloudConsoleEndpoint(globalConfig.getEndpoint()); | ||
| if (endpoint !== globalConfig.getEndpoint()) { | ||
| globalConfig.setEndpoint(endpoint); | ||
| } | ||
|
|
||
| const client = await sdkForConsole(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; | ||
| } | ||
| }; | ||
|
Comment on lines
+75
to
+98
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Beyond returning the current user, this function: (1) mutates |
||
|
|
||
| const createLegacyConsoleClient = (endpoint: string): ClientLegacy => { | ||
| const legacyClient = new ClientLegacy(); | ||
| legacyClient.setEndpoint(endpoint); | ||
|
|
@@ -115,7 +175,9 @@ const getSessionAccountKey = (sessionId: string): string | undefined => { | |
| | { email?: string; endpoint?: string } | ||
| | undefined; | ||
| if (!session) return undefined; | ||
| return `${session.email ?? ""}|${session.endpoint ?? ""}`; | ||
| return `${session.email ?? ""}|${normalizeCloudConsoleEndpoint( | ||
| session.endpoint ?? "", | ||
| )}`; | ||
| }; | ||
|
|
||
| /** | ||
|
|
@@ -165,33 +227,62 @@ export const loginCommand = async ({ | |
| endpoint, | ||
| mfa, | ||
| code, | ||
| switch: switchAccount, | ||
| new: newAccount, | ||
| }: { | ||
| email?: string; | ||
| password?: string; | ||
| endpoint?: string; | ||
| mfa?: string; | ||
| code?: string; | ||
| switch?: boolean; | ||
| new?: boolean; | ||
| }): Promise<void> => { | ||
| const oldCurrent = globalConfig.getCurrentSession(); | ||
| let oldCurrent = globalConfig.getCurrentSession(); | ||
|
|
||
| if (switchAccount && newAccount) { | ||
| throw new Error("Use either --switch or --new, not both."); | ||
| } | ||
|
|
||
| const configEndpoint = | ||
| (endpoint ?? globalConfig.getEndpoint()) || DEFAULT_ENDPOINT; | ||
| 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() !== "") { | ||
| log("You are currently signed in as " + globalConfig.getEmail()); | ||
| const account = await getCurrentAccount(); | ||
| oldCurrent = globalConfig.getCurrentSession(); | ||
|
|
||
| if (globalConfig.getSessions().length === 1) { | ||
| hint("You can sign in and manage multiple accounts with Appwrite CLI"); | ||
| 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; | ||
| } | ||
| } | ||
| } | ||
|
|
||
| const answers = | ||
| email && password | ||
| ? { email, password } | ||
| : await inquirer.prompt(questionsLogin); | ||
| 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 = "login"; | ||
| answers.method = switchAccount ? "select" : "login"; | ||
| } | ||
|
|
||
| if (answers.method === "select") { | ||
|
|
@@ -201,7 +292,21 @@ export const loginCommand = async ({ | |
| 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(false); | ||
| const accountClient = new Account(client); | ||
|
|
@@ -213,6 +318,10 @@ export const loginCommand = async ({ | |
| await accountClient.get(); | ||
| } catch (err) { | ||
| if (!isMfaRequiredError(err)) { | ||
| if (isGuestUnauthorizedError(err)) { | ||
| globalConfig.removeSession(accountId); | ||
| } | ||
| restoreCurrentSession(oldCurrent); | ||
| throw err; | ||
| } | ||
|
|
||
|
|
@@ -306,14 +415,8 @@ export const whoami = new Command("whoami") | |
| return; | ||
| } | ||
|
|
||
| const client = await sdkForConsole(false); | ||
| const accountClient = new Account(client); | ||
|
|
||
| let account; | ||
|
|
||
| try { | ||
| account = await accountClient.get(); | ||
| } catch (_) { | ||
| const account = await getCurrentAccount(); | ||
| if (!account) { | ||
| error("No user is signed in. To sign in, run 'appwrite login'"); | ||
| return; | ||
| } | ||
|
|
@@ -358,6 +461,8 @@ export const login = new Command("login") | |
| `Multi-factor authentication login factor: totp, email, phone or recoveryCode`, | ||
| ) | ||
| .option(`--code [code]`, `Multi-factor code`) | ||
| .option(`--switch`, `Switch to another signed-in account`) | ||
| .option(`--new`, `Sign in to another account`) | ||
| .configureHelp({ | ||
| helpWidth: process.stdout.columns || 80, | ||
| }) | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
DEFAULT_ENDPOINTis already exported fromlib/constants.ts(whichquestions.tsand other modules already import). Defining a private duplicate here creates a maintenance risk — if the canonical value ever changes, this copy would silently diverge.