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.
897 lines (889 loc) • 38.1 kB
JavaScript
import { mkdirSync, writeFileSync, existsSync } from "node:fs";
import path from "node:path";
import { findAppwriteConfig } from "./loadConfigs.js";
import { findYamlConfig } from "../config/yamlConfig.js";
import { ID } from "node-appwrite";
import { ulid } from "ulidx";
import { generateYamlConfigTemplate } from "../config/yamlConfig.js";
// Example base configuration using types from appwrite-utils
const baseConfig = {
appwriteEndpoint: "https://cloud.appwrite.io/v1",
appwriteProject: "YOUR_PROJECT_ID",
appwriteKey: "YOUR_API_KEY",
logging: {
enabled: false,
level: "info",
console: false,
},
enableBackups: true,
useMigrations: true,
backupInterval: 3600,
backupRetention: 30,
enableBackupCleanup: true,
enableMockData: false,
documentBucketId: "documents",
usersCollectionName: "Members",
databases: [
{
$id: "main",
name: "Main",
bucket: {
$id: "main_bucket",
name: "Main Bucket",
enabled: true,
maximumFileSize: 30000000,
allowedFileExtensions: [],
encryption: true,
antivirus: true,
},
},
{
$id: "staging",
name: "Staging",
bucket: {
$id: "staging_bucket",
name: "Staging Bucket",
enabled: true,
maximumFileSize: 30000000,
allowedFileExtensions: [],
encryption: true,
antivirus: true,
},
},
{
$id: "dev",
name: "Development",
bucket: {
$id: "dev_bucket",
name: "Development Bucket",
enabled: true,
maximumFileSize: 30000000,
allowedFileExtensions: [],
encryption: true,
antivirus: true,
},
},
],
buckets: [
{
$id: "global_bucket",
name: "Global Bucket",
enabled: true,
maximumFileSize: 30000000,
allowedFileExtensions: [],
encryption: true,
antivirus: true,
},
],
};
const collectionsConfig = [
{
name: "ExampleCollection",
content: `import type { CollectionCreate } from "appwrite-utils";
const ExampleCollection: Partial<CollectionCreate> = {
name: 'ExampleCollection',
$id: '${ulid()}',
documentSecurity: false,
enabled: true,
$permissions: [
{ permission: 'read', target: 'any' },
{ permission: 'create', target: 'users' },
{ permission: 'update', target: 'users' },
{ permission: 'delete', target: 'users' }
],
attributes: [
{ key: 'alterEgoName', type: 'string', size: 255, required: false },
// Add more attributes here
],
indexes: [
{ key: 'alterEgoName_search', type: 'fulltext', attributes: ['alterEgoName'] }
],
importDefs: [
// Define import definitions here
]
};
export default ExampleCollection;`,
},
// Add more collections here
];
// Define our YAML files
// Define our YAML files
const configFileExample = `d`;
export const customDefinitionsFile = `import type { ConverterFunctions, ValidationRules, AfterImportActions } from "appwrite-utils";
export const customConverterFunctions: ConverterFunctions = {
// Add your custom converter functions here
}
export const customValidationRules: ValidationRules = {
// Add your custom validation rules here
}
export const customAfterImportActions: AfterImportActions = {
// Add your custom after import actions here
}`;
export const createEmptyCollection = (collectionName) => {
const currentDir = process.cwd();
// Check for YAML config first (preferred)
const yamlConfigPath = findYamlConfig(currentDir);
const tsConfigPath = findAppwriteConfig(currentDir);
let configDir;
let isYamlProject = false;
if (yamlConfigPath) {
configDir = path.dirname(yamlConfigPath);
isYamlProject = true;
}
else if (tsConfigPath) {
configDir = path.dirname(tsConfigPath);
isYamlProject = false;
}
else {
// No config found - assume .appwrite directory and use YAML as default
configDir = path.join(currentDir, ".appwrite");
isYamlProject = true;
// Create .appwrite directory if it doesn't exist
if (!existsSync(configDir)) {
mkdirSync(configDir, { recursive: true });
}
}
const collectionsFolder = path.join(configDir, "collections");
if (!existsSync(collectionsFolder)) {
mkdirSync(collectionsFolder, { recursive: true });
}
if (isYamlProject) {
// Create YAML collection
const yamlCollection = `# yaml-language-server: $schema=../.yaml_schemas/collection.schema.json
# Collection Definition: ${collectionName}
name: ${collectionName}
id: ${ulid()}
documentSecurity: false
enabled: true
permissions:
- permission: read
target: any
- permission: create
target: users
- permission: update
target: users
- permission: delete
target: users
attributes:
# Add your attributes here
# Example:
# - key: title
# type: string
# size: 255
# required: true
# description: "The title of the item"
indexes:
# Add your indexes here
# Example:
# - key: title_search
# type: fulltext
# attributes:
# - title
importDefs: []
`;
const collectionFilePath = path.join(collectionsFolder, `${collectionName}.yaml`);
writeFileSync(collectionFilePath, yamlCollection);
console.log(`✨ Created YAML collection: ${collectionFilePath}`);
}
else {
// Create TypeScript collection
const emptyCollection = `import type { CollectionCreate } from "appwrite-utils";
const ${collectionName}: Partial<CollectionCreate> = {
$id: '${ulid()}',
documentSecurity: false,
enabled: true,
name: '${collectionName}',
$permissions: [
{ permission: 'read', target: 'any' },
{ permission: 'create', target: 'users' },
{ permission: 'update', target: 'users' },
{ permission: 'delete', target: 'users' }
],
attributes: [
// Add more attributes here
],
indexes: [
// Add more indexes here
],
};
export default ${collectionName};`;
const collectionFilePath = path.join(collectionsFolder, `${collectionName}.ts`);
writeFileSync(collectionFilePath, emptyCollection);
console.log(`✨ Created TypeScript collection: ${collectionFilePath}`);
}
};
export const generateYamlConfig = (currentDir, useAppwriteDir = true) => {
const basePath = currentDir || process.cwd();
const configDir = useAppwriteDir ? path.join(basePath, ".appwrite") : basePath;
if (!existsSync(configDir)) {
mkdirSync(configDir, { recursive: true });
}
const configPath = path.join(configDir, "config.yaml");
generateYamlConfigTemplate(configPath);
console.log(`✨ Generated YAML config template at: ${configPath}`);
console.log("📝 Please update the configuration with your Appwrite project details.");
return configPath;
};
export const setupDirsFiles = async (example = false, currentDir, useYaml = true) => {
const basePath = currentDir || process.cwd();
// Create .appwrite folder directly in project root
const appwriteFolder = path.join(basePath, ".appwrite");
const appwriteConfigFile = path.join(appwriteFolder, "appwriteConfig.ts");
const appwriteCustomDefsFile = path.join(appwriteFolder, "customDefinitions.ts");
const appwriteSchemaFolder = path.join(appwriteFolder, "schemas");
const appwriteYamlSchemaFolder = path.join(appwriteFolder, ".yaml_schemas");
const appwriteDataFolder = path.join(appwriteFolder, "importData");
const collectionsFolder = path.join(appwriteFolder, "collections");
// Create directory structure
if (!existsSync(appwriteFolder)) {
mkdirSync(appwriteFolder, { recursive: true });
}
if (!existsSync(collectionsFolder)) {
mkdirSync(collectionsFolder, { recursive: true });
}
// Handle configuration file creation - YAML is now default
if (useYaml) {
// Generate YAML config in .appwrite directory
const configPath = path.join(appwriteFolder, "config.yaml");
generateYamlConfigTemplate(configPath);
}
else if (!existsSync(appwriteConfigFile)) {
if (example) {
writeFileSync(appwriteConfigFile, configFileExample);
}
else {
const baseConfigContent = `import { type AppwriteConfig } from "appwrite-utils";
const appwriteConfig: AppwriteConfig = ${JSON.stringify(baseConfig, null, 2)};
export default appwriteConfig;
`;
writeFileSync(appwriteConfigFile, baseConfigContent);
}
}
// Create TypeScript files for each collection only if not using YAML
if (!useYaml) {
collectionsConfig.forEach((collection) => {
const collectionFilePath = path.join(collectionsFolder, `${collection.name}.ts`);
writeFileSync(collectionFilePath, collection.content);
});
}
// Create YAML collection example if using YAML config
if (useYaml) {
const yamlCollectionExample = `# yaml-language-server: $schema=../.yaml_schemas/collection.schema.json
# Example Collection Definition
name: ExampleCollection
id: example_collection_${Date.now()}
documentSecurity: false
enabled: true
permissions:
- permission: read
target: any
- permission: create
target: users
- permission: update
target: users
- permission: delete
target: users
attributes:
- key: title
type: string
size: 255
required: true
description: "The title of the item"
- key: description
type: string
size: 1000
required: false
description: "A longer description"
- key: isActive
type: boolean
required: false
default: true
indexes:
- key: title_search
type: fulltext
attributes:
- title
importDefs: []
`;
const yamlCollectionPath = path.join(collectionsFolder, "ExampleCollection.yaml");
writeFileSync(yamlCollectionPath, yamlCollectionExample);
// Create JSON schema for collection definitions
const collectionJsonSchema = {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://appwrite-utils.dev/schemas/collection.schema.json",
"title": "Appwrite Collection Definition",
"description": "Schema for defining Appwrite collections in YAML",
"type": "object",
"properties": {
"name": {
"type": "string",
"description": "The name of the collection"
},
"id": {
"type": "string",
"description": "The ID of the collection (optional, auto-generated if not provided)",
"pattern": "^[a-zA-Z0-9][a-zA-Z0-9._-]{0,35}$"
},
"documentSecurity": {
"type": "boolean",
"default": false,
"description": "Enable document-level permissions"
},
"enabled": {
"type": "boolean",
"default": true,
"description": "Whether the collection is enabled"
},
"permissions": {
"type": "array",
"description": "Collection-level permissions",
"items": {
"type": "object",
"properties": {
"permission": {
"type": "string",
"enum": ["read", "create", "update", "delete"],
"description": "The permission type"
},
"target": {
"type": "string",
"description": "Permission target (e.g., 'any', 'users', 'users/verified', 'label:admin')"
}
},
"required": ["permission", "target"],
"additionalProperties": false
}
},
"attributes": {
"type": "array",
"description": "Collection attributes (fields)",
"items": {
"type": "object",
"properties": {
"key": {
"type": "string",
"description": "Attribute name",
"pattern": "^[a-zA-Z][a-zA-Z0-9]*$"
},
"type": {
"type": "string",
"enum": ["string", "integer", "double", "boolean", "datetime", "email", "ip", "url", "enum", "relationship"],
"description": "Attribute data type"
},
"size": {
"type": "number",
"description": "Maximum size for string attributes",
"minimum": 1,
"maximum": 1073741824
},
"required": {
"type": "boolean",
"default": false,
"description": "Whether the attribute is required"
},
"array": {
"type": "boolean",
"default": false,
"description": "Whether the attribute is an array"
},
"default": {
"description": "Default value for the attribute"
},
"description": {
"type": "string",
"description": "Attribute description"
},
"min": {
"type": "number",
"description": "Minimum value for numeric attributes"
},
"max": {
"type": "number",
"description": "Maximum value for numeric attributes"
},
"elements": {
"type": "array",
"items": {
"type": "string"
},
"description": "Allowed values for enum attributes"
},
"relatedCollection": {
"type": "string",
"description": "Related collection name for relationship attributes"
},
"relationType": {
"type": "string",
"enum": ["oneToOne", "oneToMany", "manyToOne", "manyToMany"],
"description": "Type of relationship"
},
"twoWay": {
"type": "boolean",
"description": "Whether the relationship is bidirectional"
},
"twoWayKey": {
"type": "string",
"description": "Key name for the reverse relationship"
},
"onDelete": {
"type": "string",
"enum": ["cascade", "restrict", "setNull"],
"description": "Action to take when related document is deleted"
},
"side": {
"type": "string",
"enum": ["parent", "child"],
"description": "Side of the relationship"
}
},
"required": ["key", "type"],
"additionalProperties": false,
"allOf": [
{
"if": {
"properties": { "type": { "const": "enum" } }
},
"then": {
"required": ["elements"]
}
},
{
"if": {
"properties": { "type": { "const": "relationship" } }
},
"then": {
"required": ["relatedCollection", "relationType"]
}
}
]
}
},
"indexes": {
"type": "array",
"description": "Database indexes for the collection",
"items": {
"type": "object",
"properties": {
"key": {
"type": "string",
"description": "Index name"
},
"type": {
"type": "string",
"enum": ["key", "fulltext", "unique"],
"description": "Index type"
},
"attributes": {
"type": "array",
"items": {
"type": "string"
},
"description": "Attributes to index",
"minItems": 1
},
"orders": {
"type": "array",
"items": {
"type": "string",
"enum": ["ASC", "DESC"]
},
"description": "Sort order for each attribute"
}
},
"required": ["key", "type", "attributes"],
"additionalProperties": false
}
},
"importDefs": {
"type": "array",
"description": "Import definitions for data migration",
"default": []
}
},
"required": ["name"],
"additionalProperties": false
};
// Ensure YAML schemas directory exists before writing schema
if (!existsSync(appwriteYamlSchemaFolder)) {
mkdirSync(appwriteYamlSchemaFolder, { recursive: true });
}
const collectionSchemaPath = path.join(appwriteYamlSchemaFolder, "collection.schema.json");
writeFileSync(collectionSchemaPath, JSON.stringify(collectionJsonSchema, null, 2));
// Create JSON schema for appwriteConfig.yaml
const configJsonSchema = {
"$schema": "https://json-schema.org/draft/2020-12/schema",
"$id": "https://appwrite-utils.dev/schemas/appwrite-config.schema.json",
"title": "Appwrite Configuration",
"description": "Schema for Appwrite project configuration in YAML",
"type": "object",
"properties": {
"appwrite": {
"type": "object",
"description": "Appwrite connection settings",
"properties": {
"endpoint": {
"type": "string",
"default": "https://cloud.appwrite.io/v1",
"description": "Appwrite server endpoint URL"
},
"project": {
"type": "string",
"description": "Appwrite project ID"
},
"key": {
"type": "string",
"description": "Appwrite API key with appropriate permissions"
}
},
"required": ["project", "key"],
"additionalProperties": false
},
"logging": {
"type": "object",
"description": "Logging configuration",
"properties": {
"enabled": {
"type": "boolean",
"default": false,
"description": "Enable file logging"
},
"level": {
"type": "string",
"enum": ["error", "warn", "info", "debug"],
"default": "info",
"description": "Logging level"
},
"directory": {
"type": "string",
"description": "Custom log directory path (optional)"
},
"console": {
"type": "boolean",
"default": false,
"description": "Enable console logging"
}
},
"additionalProperties": false
},
"backups": {
"type": "object",
"description": "Backup configuration",
"properties": {
"enabled": {
"type": "boolean",
"default": true,
"description": "Enable automatic backups"
},
"interval": {
"type": "number",
"default": 3600,
"description": "Backup interval in seconds"
},
"retention": {
"type": "number",
"default": 30,
"description": "Backup retention in days"
},
"cleanup": {
"type": "boolean",
"default": true,
"description": "Enable automatic backup cleanup"
}
},
"additionalProperties": false
},
"data": {
"type": "object",
"description": "Data management settings",
"properties": {
"enableMockData": {
"type": "boolean",
"default": false,
"description": "Enable mock data generation"
},
"documentBucketId": {
"type": "string",
"default": "documents",
"description": "Default bucket ID for document attachments"
},
"usersCollectionName": {
"type": "string",
"default": "Members",
"description": "Name of the users/members collection"
},
"importDirectory": {
"type": "string",
"default": "importData",
"description": "Directory containing import data files"
}
},
"additionalProperties": false
},
"schemas": {
"type": "object",
"description": "Schema generation settings",
"properties": {
"outputDirectory": {
"type": "string",
"default": "schemas",
"description": "Directory where generated schemas are saved"
},
"yamlSchemaDirectory": {
"type": "string",
"default": ".yaml_schemas",
"description": "Directory containing YAML validation schemas"
}
},
"additionalProperties": false
},
"migrations": {
"type": "object",
"description": "Migration settings",
"properties": {
"enabled": {
"type": "boolean",
"default": true,
"description": "Enable migration tracking database"
}
},
"additionalProperties": false
},
"databases": {
"type": "array",
"description": "Database configurations",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "Database ID",
"pattern": "^[a-zA-Z0-9][a-zA-Z0-9._-]{0,35}$"
},
"name": {
"type": "string",
"description": "Database display name"
},
"bucket": {
"type": "object",
"description": "Associated storage bucket",
"properties": {
"id": {
"type": "string",
"description": "Bucket ID"
},
"name": {
"type": "string",
"description": "Bucket display name"
},
"permissions": {
"type": "array",
"items": {
"type": "string"
},
"description": "Bucket permissions"
},
"fileSecurity": {
"type": "boolean",
"description": "Enable file-level security"
},
"enabled": {
"type": "boolean",
"default": true,
"description": "Enable the bucket"
},
"maximumFileSize": {
"type": "number",
"default": 30000000,
"description": "Maximum file size in bytes"
},
"allowedFileExtensions": {
"type": "array",
"items": {
"type": "string"
},
"description": "Allowed file extensions (empty = all allowed)"
},
"compression": {
"type": "string",
"enum": ["none", "gzip", "zstd"],
"default": "none",
"description": "Compression algorithm"
},
"encryption": {
"type": "boolean",
"default": false,
"description": "Enable file encryption"
},
"antivirus": {
"type": "boolean",
"default": false,
"description": "Enable antivirus scanning"
}
},
"required": ["id", "name"],
"additionalProperties": false
}
},
"required": ["id", "name"],
"additionalProperties": false
}
},
"buckets": {
"type": "array",
"description": "Global storage buckets",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "Bucket ID"
},
"name": {
"type": "string",
"description": "Bucket display name"
},
"permissions": {
"type": "array",
"items": {
"type": "string"
},
"description": "Bucket permissions"
},
"fileSecurity": {
"type": "boolean",
"description": "Enable file-level security"
},
"enabled": {
"type": "boolean",
"default": true,
"description": "Enable the bucket"
},
"maximumFileSize": {
"type": "number",
"default": 30000000,
"description": "Maximum file size in bytes"
},
"allowedFileExtensions": {
"type": "array",
"items": {
"type": "string"
},
"description": "Allowed file extensions (empty = all allowed)"
},
"compression": {
"type": "string",
"enum": ["none", "gzip", "zstd"],
"default": "none",
"description": "Compression algorithm"
},
"encryption": {
"type": "boolean",
"default": false,
"description": "Enable file encryption"
},
"antivirus": {
"type": "boolean",
"default": false,
"description": "Enable antivirus scanning"
}
},
"required": ["id", "name"],
"additionalProperties": false
}
},
"functions": {
"type": "array",
"description": "Appwrite Functions",
"items": {
"type": "object",
"properties": {
"id": {
"type": "string",
"description": "Function ID"
},
"name": {
"type": "string",
"description": "Function name"
},
"runtime": {
"type": "string",
"description": "Runtime environment"
},
"execute": {
"type": "array",
"items": {
"type": "string"
},
"description": "Execution permissions"
},
"events": {
"type": "array",
"items": {
"type": "string"
},
"description": "Event triggers"
},
"schedule": {
"type": "string",
"description": "Cron schedule"
},
"timeout": {
"type": "number",
"default": 15,
"description": "Execution timeout in seconds"
},
"enabled": {
"type": "boolean",
"default": true,
"description": "Enable the function"
},
"logging": {
"type": "boolean",
"default": true,
"description": "Enable function logging"
},
"entrypoint": {
"type": "string",
"description": "Function entrypoint file"
},
"commands": {
"type": "string",
"description": "Build commands"
}
},
"required": ["id", "name", "runtime"],
"additionalProperties": false
}
}
},
"required": ["appwrite"],
"additionalProperties": false
};
const configSchemaPath = path.join(appwriteYamlSchemaFolder, "appwrite-config.schema.json");
writeFileSync(configSchemaPath, JSON.stringify(configJsonSchema, null, 2));
}
if (!existsSync(appwriteSchemaFolder)) {
mkdirSync(appwriteSchemaFolder, { recursive: true });
}
if (!existsSync(appwriteDataFolder)) {
mkdirSync(appwriteDataFolder, { recursive: true });
}
// Remove the nested .appwrite folder creation since we're using .appwrite as the main folder
const configType = useYaml ? "YAML" : "TypeScript";
console.log(`✨ Created ${configType} config and setup files/directories in .appwrite/ folder.`);
if (useYaml) {
console.log("🔧 You can now configure your project in .appwrite/config.yaml");
console.log("📁 Collections can be defined in .appwrite/collections/ as .ts or .yaml files");
console.log("📊 Schemas will be generated in .appwrite/schemas/");
console.log("📦 Import data can be placed in .appwrite/importData/");
}
else {
console.log("🔧 You can now configure logging in your .appwrite/appwriteConfig.ts file:");
console.log(" logging: {");
console.log(" enabled: true,");
console.log(" level: 'info',");
console.log(" console: true,");
console.log(" logDirectory: './logs' // optional custom directory");
console.log(" }");
}
};