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
779 changes: 664 additions & 115 deletions bun.lock

Large diffs are not rendered by default.

5 changes: 4 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,8 @@
"build": "cd packages/appwrite-utils && bun run build && cd ../appwrite-utils-helpers && bun run build && cd ../appwrite-utils-cli && bun run build && cd ../appwrite-utils-mcp && bun run build",
"deploy": "cd packages/appwrite-utils && bun run deploy && cd ../appwrite-utils-cli && bun run deploy"
},
"packageManager": "pnpm@10.11.1+sha512.e519b9f7639869dc8d5c3c5dfef73b3f091094b0a006d7317353c72b124e80e1afd429732e28705ad6bfa1ee879c1fce46c128ccebd3192101f43dd67c667912"
"packageManager": "pnpm@10.11.1+sha512.e519b9f7639869dc8d5c3c5dfef73b3f091094b0a006d7317353c72b124e80e1afd429732e28705ad6bfa1ee879c1fce46c128ccebd3192101f43dd67c667912",
"overrides": {
"zod": "4.4.3"
}
}
1 change: 1 addition & 0 deletions packages/appwrite-utils-cli/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@
"@types/lodash": "^4.17.18",
"@types/luxon": "^3.6.2",
"@types/papaparse": "^5.5.2",
"appwrite-cli": "^20.1.0",
"jest": "^29.7.0",
"ts-jest": "^29.1.2",
"typescript": "^5.8.3"
Expand Down
145 changes: 145 additions & 0 deletions packages/appwrite-utils-cli/src/cli/commands/initFlow.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
import path from "node:path";
import { access } from "node:fs/promises";
import {
runAppwriteCli,
appwriteWhoami,
findExtensionConfig,
findProjectRoot,
saveExtensionConfig,
MessageFormatter,
type AppwriteCliCredentials,
} from "appwrite-utils-helpers";
import type { AppwriteUtilsExtensionInput } from "appwrite-utils";

export interface InitFlowOptions {
cwd: string;
credentials?: AppwriteCliCredentials;
/**
* Explicit sidecar path from --config <path>. When set,
* dirname(resolve(configPath)) is the project root and findProjectRoot is skipped.
*/
configPath?: string;
}

async function fileExists(p: string): Promise<boolean> {
try {
await access(p);
return true;
} catch {
return false;
}
}

export async function runInitFlow(opts: InitFlowOptions): Promise<void> {
// 1. Auth check
const who = await appwriteWhoami();
if (!who.authenticated) {
MessageFormatter.warning(
"Not authenticated. Run `appwrite login` (or pass --endpoint --projectId --apiKey) first.",
{ prefix: "Init" }
);
return;
}

// 2. appwrite.config.json — resolve project root, then run `appwrite init project` if missing.
let projectRoot: string;
let absoluteConfigPath: string | undefined;
if (opts.configPath) {
// Explicit --config: skip walk-up; root is the sidecar's directory.
absoluteConfigPath = path.isAbsolute(opts.configPath)
? opts.configPath
: path.resolve(process.cwd(), opts.configPath);
projectRoot = path.dirname(absoluteConfigPath);
const appwriteConfigJsonPath = path.join(projectRoot, "appwrite.config.json");
if (await fileExists(appwriteConfigJsonPath)) {
MessageFormatter.info(
`appwrite.config.json already exists at ${appwriteConfigJsonPath} — skipping init`,
{ prefix: "Init" }
);
} else {
MessageFormatter.info("Running `appwrite init project` to bootstrap Appwrite config...", { prefix: "Init" });
await runAppwriteCli(["init", "project"], {
cwd: projectRoot,
stream: true,
force: false,
credentials: opts.credentials,
});
}
} else {
try {
const result = await findProjectRoot(opts.cwd);
projectRoot = result.root;
if (result.anchor === "official-config") {
MessageFormatter.info(
`appwrite.config.json already exists at ${result.anchorPath} — skipping init`,
{ prefix: "Init" }
);
} else {
// sidecar or git anchor: bootstrap appwrite.config.json at the resolved root
MessageFormatter.info("Running `appwrite init project` to bootstrap Appwrite config...", { prefix: "Init" });
await runAppwriteCli(["init", "project"], {
cwd: projectRoot,
stream: true,
force: false,
credentials: opts.credentials,
});
}
} catch {
// No anchor anywhere — fall back to opts.cwd as the project root
MessageFormatter.warning(
`Could not locate project root from ${opts.cwd} (no sidecar, no appwrite.config.json, no .git). Initializing in place.`,
{ prefix: "Init" }
);
projectRoot = opts.cwd;
MessageFormatter.info("Running `appwrite init project` to bootstrap Appwrite config...", { prefix: "Init" });
await runAppwriteCli(["init", "project"], {
cwd: projectRoot,
stream: true,
force: false,
credentials: opts.credentials,
});
}
}

// 3. Sidecar — write if missing
if (absoluteConfigPath) {
// Explicit --config: sidecar location is fixed, no walk-up.
if (await fileExists(absoluteConfigPath)) {
MessageFormatter.info(`Sidecar already exists at ${absoluteConfigPath} — skipping`, { prefix: "Init" });
} else {
const sidecar: AppwriteUtilsExtensionInput = {
appwriteConfig: "./appwrite.config.json",
apiMode: "auto",
enableBackups: true,
backupInterval: 3600,
backupRetention: 30,
enableBackupCleanup: true,
enableMockData: false,
documentBucketId: "documents",
usersCollectionName: "Members",
};
await saveExtensionConfig(sidecar as any, absoluteConfigPath);
MessageFormatter.success(`Created sidecar config at ${absoluteConfigPath}`, { prefix: "Init" });
}
} else {
const sidecarPath = await findExtensionConfig({ cwd: projectRoot });
if (!sidecarPath) {
const newSidecarPath = path.join(projectRoot, "appwrite-utils.config.yaml");
const sidecar: AppwriteUtilsExtensionInput = {
appwriteConfig: "./appwrite.config.json",
apiMode: "auto",
enableBackups: true,
backupInterval: 3600,
backupRetention: 30,
enableBackupCleanup: true,
enableMockData: false,
documentBucketId: "documents",
usersCollectionName: "Members",
};
await saveExtensionConfig(sidecar as any, newSidecarPath);
MessageFormatter.success(`Created sidecar config at ${newSidecarPath}`, { prefix: "Init" });
} else {
MessageFormatter.info(`Sidecar already exists at ${sidecarPath} — skipping`, { prefix: "Init" });
}
}
}
39 changes: 39 additions & 0 deletions packages/appwrite-utils-cli/src/cli/commands/passthroughCommand.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import { dirname, isAbsolute, resolve } from "node:path";
import {
findProjectRoot,
runAppwriteCli,
type AppwriteCliCredentials,
} from "appwrite-utils-helpers";

export interface PassthroughOptions {
credentials?: AppwriteCliCredentials;
/**
* Explicit sidecar path from --config <path>. When set,
* dirname(resolve(configPath)) is the project root and findProjectRoot is skipped.
*/
configPath?: string;
}

/**
* Forward an arbitrary appwrite CLI invocation through our auth bridge.
* Example: `appwrite-migrate --passthrough -- functions list` → `appwrite functions list`
*/
export async function runPassthrough(args: string[], opts: PassthroughOptions): Promise<void> {
if (args.length === 0) {
throw new Error("--passthrough requires at least one argument after `--` (e.g. `--passthrough -- functions list`)");
}
let root: string;
if (opts.configPath) {
// Explicit --config overrides root discovery
const absolute = isAbsolute(opts.configPath) ? opts.configPath : resolve(process.cwd(), opts.configPath);
root = dirname(absolute);
} else {
({ root } = await findProjectRoot());
}
await runAppwriteCli(args, {
cwd: root,
stream: true,
force: false,
credentials: opts.credentials,
});
}
Loading