appwrite-utils-cli
Version:
Appwrite Utility Functions to help with database management, data conversion, data import, migrations, and much more. Meant to be used as a CLI tool, I do not recommend installing this in frontend environments.
207 lines (206 loc) • 7.55 kB
JavaScript
import { existsSync, readFileSync } from "node:fs";
import { join, dirname, resolve, isAbsolute } from "node:path";
import { MessageFormatter } from "../shared/messageFormatter.js";
/**
* Find appwrite.json or appwrite.config.json in current directory or parents
*/
export function findAppwriteProjectConfig(startDir = process.cwd()) {
const configNames = ["appwrite.json", "appwrite.config.json"];
let currentDir = startDir;
// Search up to 5 levels up the directory tree
for (let i = 0; i < 5; i++) {
for (const configName of configNames) {
const configPath = join(currentDir, configName);
if (existsSync(configPath)) {
return configPath;
}
}
const parentDir = dirname(currentDir);
if (parentDir === currentDir) {
break; // Reached filesystem root
}
currentDir = parentDir;
}
return null;
}
/**
* Load and parse appwrite project configuration
*/
export function loadAppwriteProjectConfig(configPath) {
let actualConfigPath = configPath;
if (!actualConfigPath) {
actualConfigPath = findAppwriteProjectConfig() || undefined;
}
if (!actualConfigPath || !existsSync(actualConfigPath)) {
return null;
}
try {
const configContent = readFileSync(actualConfigPath, "utf-8");
const config = JSON.parse(configContent);
// Validate required fields
if (!config.projectId) {
MessageFormatter.warning(`Appwrite project config missing required 'projectId' field: ${actualConfigPath}`, { prefix: "ProjectConfig" });
return null;
}
return config;
}
catch (error) {
MessageFormatter.error(`Failed to parse Appwrite project config: ${actualConfigPath}`, error instanceof Error ? error : new Error(String(error)), { prefix: "ProjectConfig" });
return null;
}
}
/**
* Detect API mode from project configuration
*/
export function detectApiModeFromProject(projectConfig) {
// If both are present, prefer TablesDB (newer)
if (projectConfig.tablesDB && projectConfig.tables) {
return "tablesdb";
}
// If only legacy structure is present
if (projectConfig.databases && projectConfig.collections) {
return "legacy";
}
// If only TablesDB structure is present
if (projectConfig.tablesDB && projectConfig.tables) {
return "tablesdb";
}
// Default to auto-detection
return "auto";
}
/**
* Convert project config to AppwriteConfig format
* @param projectConfig The project config to convert
* @param configPath Optional path to the config file (for resolving relative paths)
* @param existingConfig Optional existing config to merge with
*/
export function projectConfigToAppwriteConfig(projectConfig, configPath, existingConfig) {
const apiMode = detectApiModeFromProject(projectConfig);
const configDir = configPath ? dirname(configPath) : process.cwd();
const baseConfig = {
...existingConfig,
appwriteProject: projectConfig.projectId,
apiMode,
};
// Set endpoint if provided in project config
if (projectConfig.endpoint) {
baseConfig.appwriteEndpoint = projectConfig.endpoint;
}
// Merge databases and tablesDB arrays (modern Appwrite may have both)
const allDatabases = [
...(projectConfig.databases || []),
...(projectConfig.tablesDB || [])
];
// Remove duplicates by $id
const uniqueDatabasesMap = new Map();
for (const db of allDatabases) {
if (!uniqueDatabasesMap.has(db.$id)) {
uniqueDatabasesMap.set(db.$id, db);
}
}
if (uniqueDatabasesMap.size > 0) {
baseConfig.databases = Array.from(uniqueDatabasesMap.values()).map(db => ({
$id: db.$id,
name: db.name,
// Add basic bucket configuration if not exists
bucket: {
$id: `${db.$id}_bucket`,
name: `${db.name} Bucket`,
enabled: true,
maximumFileSize: 30000000,
allowedFileExtensions: [],
encryption: true,
antivirus: true,
},
}));
}
// Convert buckets if present
if (projectConfig.buckets) {
baseConfig.buckets = projectConfig.buckets.map(bucket => ({
$id: (bucket.bucketId || bucket.$id || `bucket_${Date.now()}`),
name: bucket.name,
enabled: true,
maximumFileSize: bucket.maximumFileSize || 30000000,
allowedFileExtensions: bucket.allowedFileExtensions || [],
encryption: bucket.encryption ?? true,
antivirus: bucket.antiVirus ?? false,
}));
}
// Convert functions with path normalization
if (projectConfig.functions) {
baseConfig.functions = projectConfig.functions.map(func => {
const normalizedFunc = {
$id: func.$id,
name: func.name,
runtime: func.runtime,
entrypoint: func.entrypoint,
commands: func.commands,
events: func.events,
};
// Convert path to dirPath and make absolute
if (func.path) {
const expandedPath = func.path;
normalizedFunc.dirPath = isAbsolute(expandedPath)
? expandedPath
: resolve(configDir, expandedPath);
}
return normalizedFunc;
});
}
return baseConfig;
}
/**
* Get collection/table definitions from project config
*/
export function getCollectionsFromProject(projectConfig) {
if (projectConfig.tables) {
// Convert TablesDB tables to collection format
return projectConfig.tables.map(table => ({
name: table.name,
$id: table.$id,
$permissions: table.$permissions || [],
documentSecurity: table.rowSecurity || false,
enabled: table.enabled !== false,
// Convert columns to attributes for compatibility
attributes: table.columns.map(col => ({
key: col.key,
type: col.type,
required: col.required,
array: col.array,
size: col.size,
default: col.default,
encrypt: col.encrypt,
unique: col.unique,
})),
indexes: table.indexes || [],
// Mark as coming from TablesDB for processing
_isFromTablesDir: true,
}));
}
if (projectConfig.collections) {
// Legacy collections format
return projectConfig.collections.map(collection => ({
name: collection.name,
$id: collection.$id,
$permissions: collection.$permissions || [],
documentSecurity: collection.documentSecurity || false,
enabled: collection.enabled !== false,
attributes: collection.attributes,
indexes: collection.indexes || [],
_isFromTablesDir: false,
}));
}
return [];
}
/**
* Check if project config indicates TablesDB usage
*/
export function isTablesDBProject(projectConfig) {
return !!(projectConfig.tablesDB && projectConfig.tables);
}
/**
* Get the appropriate directory name based on project config
*/
export function getProjectDirectoryName(projectConfig) {
return isTablesDBProject(projectConfig) ? "tables" : "collections";
}