Skip to content

Commit 5708849

Browse files
authored
feat: SDK update for version 25.1.0 (#350)
* chore: update Command Line SDK to 24.0.0 * chore: update Command Line SDK to 24.1.0 * chore: update Command Line SDK to 25.0.0 * chore: update Command Line SDK to 25.1.0
1 parent 32930de commit 5708849

23 files changed

Lines changed: 646 additions & 123 deletions

‎CHANGELOG.md‎

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,15 @@
11
# Change Log
22

3+
## 25.1.0
4+
5+
* Added: `list-organizations` and `list-projects` run at the root, alongside `login`
6+
* Added: Hint pointing at `/v1` when an endpoint misses the API path
7+
* Fixed: `list-organizations`, `list-projects`, and `organization get` work on self-hosted installs
8+
* Fixed: `login --endpoint` verifies the endpoint before prompting for credentials
9+
* Fixed: Trailing slashes in an endpoint no longer produce double-slashed request paths
10+
* Fixed: HTML and oversized error pages are summarized instead of printed verbatim
11+
* Fixed: `--report` issue links stay within the length GitHub accepts
12+
313
## 25.0.0
414

515
* Breaking: Removed the `organizations` command group covering billing, plans, invoices, and add-ons

‎README.md‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ Once the installation is complete, you can verify the install using
2929

3030
```sh
3131
$ appwrite -v
32-
25.0.0
32+
25.1.0
3333
```
3434

3535
### Install using prebuilt binaries
@@ -83,7 +83,7 @@ $ scoop install https://raw.githubusercontent.com/appwrite/sdk-for-cli/master/sc
8383
Once the installation completes, you can verify your install using
8484
```
8585
$ appwrite -v
86-
25.0.0
86+
25.1.0
8787
```
8888

8989
## Getting Started

‎cli.ts‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@ import { locale } from './lib/commands/services/locale.js';
4747
import { messaging } from './lib/commands/services/messaging.js';
4848
import { migrations } from './lib/commands/services/migrations.js';
4949
import { notifications } from './lib/commands/services/notifications.js';
50-
import { oauth2 } from './lib/commands/services/oauth2.js';
50+
import { oauth2, oauth2ListOrganizationsRootCommand, oauth2ListProjectsRootCommand } from './lib/commands/services/oauth2.js';
5151
import { organization } from './lib/commands/services/organization.js';
5252
import { presences } from './lib/commands/services/presences.js';
5353
import { project } from './lib/commands/services/project.js';
@@ -227,6 +227,8 @@ if (process.argv.includes('-v') || process.argv.includes('--version')) {
227227
.addCommand(migrations)
228228
.addCommand(notifications)
229229
.addCommand(oauth2)
230+
.addCommand(oauth2ListOrganizationsRootCommand)
231+
.addCommand(oauth2ListProjectsRootCommand)
230232
.addCommand(organization)
231233
.addCommand(presences)
232234
.addCommand(project)
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
```bash
2-
appwrite oauth2 list-organizations
2+
appwrite list-organizations
33
```
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
```bash
2-
appwrite oauth2 list-projects
2+
appwrite list-projects
33
```

‎install.ps1‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@
1313
# You can use "View source" of this page to see the full script.
1414

1515
# REPO
16-
$GITHUB_x64_URL = "https://github.com/appwrite/sdk-for-cli/releases/download/25.0.0/appwrite-cli-win-x64.exe"
17-
$GITHUB_arm64_URL = "https://github.com/appwrite/sdk-for-cli/releases/download/25.0.0/appwrite-cli-win-arm64.exe"
16+
$GITHUB_x64_URL = "https://github.com/appwrite/sdk-for-cli/releases/download/25.1.0/appwrite-cli-win-x64.exe"
17+
$GITHUB_arm64_URL = "https://github.com/appwrite/sdk-for-cli/releases/download/25.1.0/appwrite-cli-win-arm64.exe"
1818

1919
$APPWRITE_BINARY_NAME = "appwrite.exe"
2020

‎install.sh‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,7 @@ verifyMacOSCodeSignature() {
120120
downloadBinary() {
121121
echo "[2/5] Downloading executable for $OS ($ARCH) ..."
122122

123-
GITHUB_LATEST_VERSION="25.0.0"
123+
GITHUB_LATEST_VERSION="25.1.0"
124124
GITHUB_FILE="appwrite-cli-${OS}-${ARCH}"
125125
GITHUB_URL="https://github.com/$GITHUB_REPOSITORY_NAME/releases/download/$GITHUB_LATEST_VERSION/$GITHUB_FILE"
126126

‎lib/auth/login.ts‎

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@ import {
3030
removeLegacySessionsExcept,
3131
restoreCurrentSession,
3232
deleteServerSession,
33+
verifyEndpoint,
3334
} from "./session.js";
3435
import { setStoredRefreshToken } from "./refresh-token.js";
3536

@@ -340,7 +341,10 @@ const loginWithOAuthDevice = async ({
340341
}): Promise<void> => {
341342
const clientId = OAUTH2_CLIENT_ID;
342343
const oauth2 = await getOauth2Service(
343-
await sdkForConsole({ requiresAuth: false, endpointOverride: configEndpoint }),
344+
await sdkForConsole({
345+
requiresAuth: false,
346+
endpointOverride: configEndpoint,
347+
}),
344348
);
345349

346350
globalConfig.addSession(id, { endpoint: configEndpoint, clientId });
@@ -478,6 +482,12 @@ export const loginCommand = async ({
478482

479483
const shouldUseCloudLogin = isCloudLoginEndpoint(configEndpoint);
480484

485+
// Check the endpoint before anything is prompted for, so a wrong endpoint
486+
// fails immediately instead of after the email and password are typed.
487+
if (endpoint && !shouldUseCloudLogin) {
488+
await verifyEndpoint(configEndpoint);
489+
}
490+
481491
if (shouldUseCloudLogin && (email || password || mfa || code)) {
482492
throw new Error(
483493
`Cloud sign-in happens in your browser. Run '${EXECUTABLE_NAME} login' without --email, --password, --mfa or --code — those options are for self-hosted instances.`,

‎lib/auth/session.ts‎

Lines changed: 52 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,55 @@ export const createLegacyConsoleClient = (
3333
return legacyClient;
3434
};
3535

36+
/**
37+
* Confirms an endpoint really is an Appwrite API root before anything is stored
38+
* or prompted for. Failures carry the underlying response's code and type so
39+
* error hints (e.g. a missing `/v1`) can still fire.
40+
*/
41+
export const verifyEndpoint = async (
42+
endpoint: string,
43+
selfSigned: boolean = globalConfig.getSelfSigned(),
44+
): Promise<void> => {
45+
let protocol = "";
46+
try {
47+
protocol = new URL(endpoint).protocol;
48+
} catch {
49+
throw new Error(`Invalid endpoint URL: ${endpoint}`);
50+
}
51+
52+
if (protocol !== "http:" && protocol !== "https:") {
53+
throw new Error(`Invalid endpoint URL: ${endpoint}`);
54+
}
55+
56+
let caught: { code?: number; type?: string; response?: unknown } = {};
57+
58+
try {
59+
const response = (await createLegacyConsoleClient(
60+
endpoint,
61+
selfSigned,
62+
).call("GET", "/health/version")) as { version?: string };
63+
64+
if (response.version) {
65+
return;
66+
}
67+
} catch (e) {
68+
caught = e as { code?: number; type?: string; response?: unknown };
69+
}
70+
71+
const failure = new Error(
72+
"Invalid endpoint or your Appwrite server is not running as expected.",
73+
);
74+
Object.assign(
75+
failure,
76+
{ endpoint },
77+
caught.code === undefined ? {} : { code: caught.code },
78+
caught.type === undefined ? {} : { type: caught.type },
79+
caught.response === undefined ? {} : { response: caught.response },
80+
);
81+
82+
throw failure;
83+
};
84+
3685
export const hasAuthSession = (): boolean =>
3786
globalConfig.getAccessToken() !== "" || globalConfig.getCookie() !== "";
3887

@@ -117,7 +166,9 @@ export const getSignedInAccounts = (): Array<{
117166
*/
118167
export const isLocalOnlySession = (sessionId: string): boolean => {
119168
const session = getSession(sessionId);
120-
return Boolean(session && !hasStoredRefreshToken(sessionId) && !session.cookie);
169+
return Boolean(
170+
session && !hasStoredRefreshToken(sessionId) && !session.cookie,
171+
);
121172
};
122173

123174
/**

‎lib/client.ts‎

Lines changed: 55 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ import {
2020
EXECUTABLE_NAME,
2121
} from "./constants.js";
2222
import { deleteStoredRefreshToken } from "./auth/refresh-token.js";
23+
import { describeHttpFailure } from "./errors.js";
2324

2425
class Client {
2526
private endpoint: string;
@@ -144,7 +145,8 @@ class Client {
144145
throw new AppwriteException("Invalid endpoint URL: " + endpoint);
145146
}
146147

147-
this.endpoint = endpoint;
148+
// Paths are appended verbatim, so a trailing slash would produce `//account`.
149+
this.endpoint = endpoint.replace(/\/+$/, "");
148150
return this;
149151
}
150152

@@ -169,6 +171,15 @@ class Client {
169171
return { ...this.headers };
170172
}
171173

174+
/**
175+
* Records the endpoint the failed request used, so error hints can talk about
176+
* the endpoint actually called rather than the one in config.
177+
*/
178+
private tagEndpoint<T extends Error>(exception: T): T {
179+
(exception as T & { endpoint?: string }).endpoint = this.endpoint;
180+
return exception;
181+
}
182+
172183
async call<T = unknown>(
173184
method: string,
174185
path: string = "",
@@ -227,17 +238,41 @@ class Client {
227238
}),
228239
});
229240
} catch (error) {
230-
throw new AppwriteException((error as Error).message);
241+
throw this.tagEndpoint(new AppwriteException((error as Error).message));
231242
}
232243

233244
if (response.status >= 400) {
234245
const text = await response.text();
235246
let json: { message?: string; code?: number; type?: string } | undefined =
236247
undefined;
237248
try {
238-
json = JSON.parse(text);
249+
const parsed: unknown = JSON.parse(text);
250+
if (parsed && typeof parsed === "object") {
251+
json = parsed as { message?: string; code?: number; type?: string };
252+
}
239253
} catch (_error) {
240-
throw new AppwriteException(text, response.status, "", text);
254+
json = undefined;
255+
}
256+
257+
if (!json) {
258+
// Proxies, load balancers and the console answer with HTML or plain
259+
// text. Summarize it — the raw body stays on the exception for
260+
// `--verbose` and `--report`.
261+
const failure = describeHttpFailure(
262+
response.status,
263+
text,
264+
response.statusText,
265+
response.headers.get("content-type") ?? undefined,
266+
);
267+
268+
throw this.tagEndpoint(
269+
new AppwriteException(
270+
failure.message,
271+
response.status,
272+
failure.type,
273+
text,
274+
),
275+
);
241276
}
242277

243278
if (
@@ -262,19 +297,25 @@ class Client {
262297
/role:\s*guests/i.test(json.message);
263298

264299
if (isUnauthorized) {
265-
throw new AppwriteException(
266-
`You are not authenticated. Run '${EXECUTABLE_NAME} login' to authenticate and try again.`,
267-
json.code,
268-
json.type,
269-
text,
300+
throw this.tagEndpoint(
301+
new AppwriteException(
302+
`You are not authenticated. Run '${EXECUTABLE_NAME} login' to authenticate and try again.`,
303+
json.code,
304+
json.type,
305+
text,
306+
),
270307
);
271308
}
272309

273-
throw new AppwriteException(
274-
json.message || text,
275-
json.code,
276-
json.type,
277-
text,
310+
throw this.tagEndpoint(
311+
new AppwriteException(
312+
json.message ||
313+
describeHttpFailure(response.status, text, response.statusText)
314+
.message,
315+
json.code ?? response.status,
316+
json.type,
317+
text,
318+
),
278319
);
279320
}
280321

0 commit comments

Comments
 (0)