Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 17 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,22 @@
# Change Log

## 24.1.0

* Added: `previousKey` on an attribute or column renames it in place on `push`
* Fixed: `client --endpoint` reuses or switches an existing session instead of minting an unauthenticated stub
* Fixed: `client --reset` asks for confirmation before signing out signed-in accounts
* Fixed: Commands that need a session now list signed-in accounts and suggest `login --switch`
* Fixed: Regional cloud endpoints match their canonical host when resolving a session
* Fixed: `push` updates attributes in place instead of recreating them where the API allows it
* Fixed: `push` resizes a `string` attribute in place instead of recreating it
* Fixed: `push` no longer recreates indexes whose attributes were renamed
* Fixed: `push` aborts on a failed attribute update instead of continuing past it
* Fixed: `push` waits for index deletion to finish before recreating indexes
* Fixed: `push` leaves `encrypt` untouched when the config omits it
* Fixed: `logout` signs out the selected account instead of the current session id
* Fixed: Table output wraps long reasons instead of truncating them mid-word
* Updated: Project policy limits for password history, session duration, session count, and users

## 24.0.0

* Added: `login` against Cloud now signs in through your browser
Expand Down
4 changes: 2 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ Once the installation is complete, you can verify the install using

```sh
$ appwrite -v
24.0.0
24.1.0
```

### Install using prebuilt binaries
Expand Down Expand Up @@ -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
24.0.0
24.1.0
```

## Getting Started
Expand Down
6 changes: 3 additions & 3 deletions bun.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion docs/examples/project/update-session-duration-policy.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
```bash
appwrite project update-session-duration-policy \
--duration 5
--duration 60
```
2 changes: 1 addition & 1 deletion docs/examples/project/update-user-limit-policy.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
```bash
appwrite project update-user-limit-policy \
--total 1
--total 0
```
4 changes: 2 additions & 2 deletions install.ps1
Original file line number Diff line number Diff line change
Expand Up @@ -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/24.0.0/appwrite-cli-win-x64.exe"
$GITHUB_arm64_URL = "https://github.com/appwrite/sdk-for-cli/releases/download/24.0.0/appwrite-cli-win-arm64.exe"
$GITHUB_x64_URL = "https://github.com/appwrite/sdk-for-cli/releases/download/24.1.0/appwrite-cli-win-x64.exe"
$GITHUB_arm64_URL = "https://github.com/appwrite/sdk-for-cli/releases/download/24.1.0/appwrite-cli-win-arm64.exe"

$APPWRITE_BINARY_NAME = "appwrite.exe"

Expand Down
2 changes: 1 addition & 1 deletion install.sh
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ verifyMacOSCodeSignature() {
downloadBinary() {
echo "[2/5] Downloading executable for $OS ($ARCH) ..."

GITHUB_LATEST_VERSION="24.0.0"
GITHUB_LATEST_VERSION="24.1.0"
GITHUB_FILE="appwrite-cli-${OS}-${ARCH}"
GITHUB_URL="https://github.com/$GITHUB_REPOSITORY_NAME/releases/download/$GITHUB_LATEST_VERSION/$GITHUB_FILE"

Expand Down
81 changes: 80 additions & 1 deletion lib/auth/session.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,8 @@
import { globalConfig, normalizeCloudConsoleEndpoint } from "../config.js";
import {
endpointsMatch,
globalConfig,
normalizeCloudConsoleEndpoint,
} from "../config.js";
import type { SessionData } from "../types.js";
import ClientLegacy from "../client.js";
import { OAUTH2_CLIENT_ID } from "../constants.js";
Expand Down Expand Up @@ -32,6 +36,81 @@ export const createLegacyConsoleClient = (
export const hasAuthSession = (): boolean =>
globalConfig.getAccessToken() !== "" || globalConfig.getCookie() !== "";

/**
* Whether a stored session record has console auth material (token or cookie),
* independent of which session is currently active.
*/
export const isAuthenticatedSession = (sessionId: string): boolean => {
const session = getSession(sessionId);
return Boolean(session?.accessToken || session?.cookie);
};

/**
* Find existing sessions that match an endpoint. Prefers authenticated
* sessions over endpoint-only stubs so `client --endpoint` can reuse login
* state instead of minting a new unauthenticated record.
*/
export const findSessionForEndpoint = (
endpoint: string,
): { authenticated?: string; endpointOnly?: string } => {
let authenticated: string | undefined;
let endpointOnly: string | undefined;

for (const sessionId of globalConfig.getSessionIds()) {
const session = getSession(sessionId);
if (!session?.endpoint || !endpointsMatch(session.endpoint, endpoint)) {
continue;
}

if (isAuthenticatedSession(sessionId)) {
if (!authenticated) {
authenticated = sessionId;
}
} else if (!endpointOnly) {
endpointOnly = sessionId;
}
}

return { authenticated, endpointOnly };
};

/**
* Deduped signed-in accounts (email + auth material) for recoverable
* messaging when the current session pointer is unauthenticated.
*/
export const getSignedInAccounts = (): Array<{
id: string;
email: string;
endpoint: string;
}> => {
const accounts = new Map<
string,
{ id: string; email: string; endpoint: string }
>();
const current = globalConfig.getCurrentSession();

for (const sessionId of globalConfig.getSessionIds()) {
const session = getSession(sessionId);
if (!session?.email || !isAuthenticatedSession(sessionId)) {
continue;
}

const endpoint = normalizeCloudConsoleEndpoint(session.endpoint ?? "");
const key = `${session.email}|${endpoint}`;
const existing = accounts.get(key);

if (!existing || sessionId === current || existing.id !== current) {
accounts.set(key, {
id: sessionId,
email: session.email,
endpoint: session.endpoint ?? endpoint,
});
}
}

return Array.from(accounts.values());
};

/**
* 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`.
Expand Down
38 changes: 37 additions & 1 deletion lib/commands/config-validations.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ export const validateStringSize = (data: {

interface AttributeOrColumn {
key: string;
previousKey?: string;
}

interface Index {
Expand All @@ -63,7 +64,8 @@ interface CollectionOrTableData {
}

/**
* Validates duplicate keys in attributes/columns and indexes
* Validates duplicate keys in attributes/columns and indexes,
* plus previousKey rename-hint consistency.
*/
export const validateContainerDuplicates = (
data: CollectionOrTableData,
Expand All @@ -88,6 +90,40 @@ export const validateContainerDuplicates = (
seenKeys.add(item.key);
}
});

// Validate previousKey rename hints
const seenPreviousKeys = new Set<string>();
items.forEach((item, index) => {
if (!item.previousKey) {
return;
}

if (item.previousKey === item.key) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: `${itemType} '${item.key}' has previousKey equal to key. previousKey must be the old name, not the new one.`,
path: [itemPath, index, "previousKey"],
});
}

if (seenKeys.has(item.previousKey) && item.previousKey !== item.key) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: `${itemType} '${item.key}' has previousKey '${item.previousKey}', but another ${itemType.toLowerCase()} already uses that key. Rename cannot proceed while both names exist locally.`,
path: [itemPath, index, "previousKey"],
});
}

if (seenPreviousKeys.has(item.previousKey)) {
ctx.addIssue({
code: z.ZodIssueCode.custom,
message: `Multiple ${itemType.toLowerCase()}s share previousKey '${item.previousKey}'. Each previousKey must be unique within a ${itemType === "Attribute" ? "collection" : "table"}.`,
path: [itemPath, index, "previousKey"],
});
} else {
seenPreviousKeys.add(item.previousKey);
}
});
}

// Validate duplicate index keys
Expand Down
6 changes: 6 additions & 0 deletions lib/commands/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -248,6 +248,9 @@ const AttributeSchema = z
attributes: z.array(z.string()).optional(),
orders: z.array(z.string()).optional(),
encrypt: z.boolean().optional(),
// Local-only hint for in-place renames via the API's newKey parameter.
// Cleared automatically on pull; ignored once the rename has been applied.
previousKey: z.string().optional(),
})
.strict()
.refine(validateRequiredDefault, {
Expand Down Expand Up @@ -330,6 +333,9 @@ const ColumnSchema = z
columns: z.array(z.string()).optional(),
orders: z.array(z.string()).optional(),
encrypt: z.boolean().optional(),
// Local-only hint for in-place renames via the API's newKey parameter.
// Cleared automatically on pull; ignored once the rename has been applied.
previousKey: z.string().optional(),
})
.strict()
.refine(validateRequiredDefault, {
Expand Down
Loading