UNPKG

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
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"; }