appwrite-utils-cli 1.0.1 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -375,6 +375,10 @@ This updated CLI ensures that developers have robust tools at their fingertips t
375
375
  - **Better error messages**: Clear validation and error reporting
376
376
  - **Type safety**: Full TypeScript support for all new features
377
377
 
378
+ ### Changelog
379
+
380
+ - 1.0.2: Fixed migrations, sorry about that!
381
+
378
382
  **Migration Note**: While fully backward compatible, we recommend migrating to YAML configuration for the best experience. Use `--setup` to generate new YAML configurations.
379
383
 
380
384
  - 0.10.86: Fixed `selectCollections` not always filtering by `databaseId`
@@ -47,7 +47,7 @@ async function findAppwriteConfigFiles(dir) {
47
47
  }
48
48
  async function migrateConfigFile(configFilePath, workingDir) {
49
49
  const configDir = path.dirname(configFilePath);
50
- const appwriteDir = path.join(configDir, '.appwrite');
50
+ const appwriteDir = path.join(path.dirname(configDir), '.appwrite');
51
51
  MessageFormatter.info(`Migrating ${path.relative(workingDir, configFilePath)}`, { prefix: "Migration" });
52
52
  // Check if .appwrite directory already exists
53
53
  if (existsSync(appwriteDir)) {
@@ -57,9 +57,8 @@ async function migrateConfigFile(configFilePath, workingDir) {
57
57
  return;
58
58
  }
59
59
  }
60
- // Read and parse the TypeScript config
61
- const configContent = await fs.readFile(configFilePath, 'utf8');
62
- const config = await parseTypeScriptConfig(configContent);
60
+ // Load and parse the TypeScript config
61
+ const config = await parseTypeScriptConfig(configFilePath);
63
62
  // Create .appwrite directory
64
63
  await fs.mkdir(appwriteDir, { recursive: true });
65
64
  // Convert config to YAML and save
@@ -70,87 +69,194 @@ async function migrateConfigFile(configFilePath, workingDir) {
70
69
  noRefs: true
71
70
  });
72
71
  await fs.writeFile(path.join(appwriteDir, 'appwriteConfig.yaml'), yamlContent);
73
- // Move related directories
74
- const foldersToMove = ['collections', 'schemas', 'importData', 'functions'];
75
- for (const folder of foldersToMove) {
76
- const sourcePath = path.join(configDir, folder);
77
- const targetPath = path.join(appwriteDir, folder);
78
- if (existsSync(sourcePath)) {
79
- await fs.rename(sourcePath, targetPath);
80
- MessageFormatter.info(`Moved ${folder}/ to .appwrite/${folder}/`, { prefix: "Migration" });
72
+ // Copy all directories except collections and schemas (we handle collections separately, skip schemas entirely)
73
+ const entries = await fs.readdir(configDir, { withFileTypes: true });
74
+ for (const entry of entries) {
75
+ if (entry.isDirectory() && entry.name !== 'collections' && entry.name !== 'schemas') {
76
+ const sourcePath = path.join(configDir, entry.name);
77
+ const targetPath = path.join(appwriteDir, entry.name);
78
+ await fs.cp(sourcePath, targetPath, { recursive: true });
79
+ MessageFormatter.info(`Copied ${entry.name}/ to .appwrite/${entry.name}/`, { prefix: "Migration" });
81
80
  }
82
81
  }
83
- // Backup original config file
84
- const backupPath = configFilePath + '.backup';
85
- await fs.copyFile(configFilePath, backupPath);
86
- MessageFormatter.info(`Created backup at ${path.relative(workingDir, backupPath)}`, { prefix: "Migration" });
87
- // Optionally remove original config file
88
- const shouldRemoveOriginal = await ConfirmationDialogs.confirmRemoval(`Remove original ${path.relative(workingDir, configFilePath)}?`);
89
- if (shouldRemoveOriginal) {
90
- await fs.unlink(configFilePath);
91
- MessageFormatter.info(`Removed original ${path.relative(workingDir, configFilePath)}`, { prefix: "Migration" });
82
+ // Convert TypeScript collections to YAML collections
83
+ const collectionsPath = path.join(configDir, 'collections');
84
+ if (existsSync(collectionsPath)) {
85
+ const targetCollectionsPath = path.join(appwriteDir, 'collections');
86
+ await fs.mkdir(targetCollectionsPath, { recursive: true });
87
+ const collectionFiles = await fs.readdir(collectionsPath);
88
+ for (const file of collectionFiles) {
89
+ if (file.endsWith('.ts')) {
90
+ await convertCollectionToYaml(path.join(collectionsPath, file), targetCollectionsPath);
91
+ }
92
+ }
93
+ MessageFormatter.info(`Converted TypeScript collections to YAML in .appwrite/collections/`, { prefix: "Migration" });
92
94
  }
95
+ // Keep original config file in place (no backup needed since we're not deleting it)
93
96
  MessageFormatter.success(`Migration completed for ${path.relative(workingDir, configFilePath)}`, { prefix: "Migration" });
94
97
  }
95
- async function parseTypeScriptConfig(content) {
96
- // This is a simplified parser - in a real implementation, you might want to use a proper TypeScript parser
97
- // For now, we'll use a regex-based approach to extract the config object
98
+ async function parseTypeScriptConfig(configFilePath) {
98
99
  try {
99
- // Remove comments and imports
100
- const cleanContent = content
101
- .replace(/\/\*[\s\S]*?\*\//g, '') // Remove block comments
102
- .replace(/\/\/.*$/gm, '') // Remove line comments
103
- .replace(/^import.*$/gm, '') // Remove imports
104
- .replace(/^export.*$/gm, ''); // Remove exports
105
- // Find the config object
106
- const configMatch = cleanContent.match(/const\s+\w+\s*:\s*\w+\s*=\s*({[\s\S]*?});/);
107
- if (!configMatch) {
108
- throw new Error('Could not find config object in TypeScript file');
100
+ // Use tsx to import the TypeScript config file directly
101
+ const { register } = await import("tsx/esm/api");
102
+ const { pathToFileURL } = await import("node:url");
103
+ const unregister = register();
104
+ try {
105
+ const configUrl = pathToFileURL(configFilePath).href;
106
+ const configModule = await import(configUrl);
107
+ const config = configModule.default?.default || configModule.default || configModule;
108
+ if (!config) {
109
+ throw new Error("Failed to load config from TypeScript file");
110
+ }
111
+ return config;
112
+ }
113
+ finally {
114
+ unregister();
109
115
  }
110
- // Convert to JSON-like format and parse
111
- let configStr = configMatch[1];
112
- // Replace TypeScript-specific syntax
113
- configStr = configStr
114
- .replace(/(\w+):/g, '"$1":') // Quote property names
115
- .replace(/'/g, '"') // Convert single quotes to double quotes
116
- .replace(/,(\s*[}\]])/g, '$1'); // Remove trailing commas
117
- const config = JSON.parse(configStr);
118
- return config;
119
116
  }
120
117
  catch (error) {
121
- MessageFormatter.error("Could not parse TypeScript config", error instanceof Error ? error : new Error(String(error)), { prefix: "Migration" });
122
- throw new Error('Failed to parse TypeScript configuration file. Please ensure it follows standard format.');
118
+ MessageFormatter.error("Could not load TypeScript config", error instanceof Error ? error : new Error(String(error)), { prefix: "Migration" });
119
+ throw new Error('Failed to load TypeScript configuration file. Please ensure it exports a valid config object.');
123
120
  }
124
121
  }
125
122
  function convertToYAMLConfig(config) {
126
- // Convert the config to YAML-friendly format
123
+ // Convert the config to the nested YAML structure
127
124
  const yamlConfig = {
128
- appwriteEndpoint: config.appwriteEndpoint,
129
- appwriteProject: config.appwriteProject,
130
- appwriteKey: config.appwriteKey,
131
- databases: config.databases || [],
132
- buckets: config.buckets || [],
125
+ appwrite: {
126
+ endpoint: config.appwriteEndpoint,
127
+ project: config.appwriteProject,
128
+ key: config.appwriteKey
129
+ },
130
+ logging: {
131
+ enabled: config.logging?.enabled ?? false,
132
+ level: config.logging?.level ?? "info",
133
+ console: config.logging?.console ?? false,
134
+ logDirectory: "./logs"
135
+ },
136
+ backups: {
137
+ enabled: config.enableBackups ?? false,
138
+ interval: config.backupInterval ?? 3600,
139
+ retention: config.backupRetention ?? 30,
140
+ cleanup: config.enableBackupCleanup ?? false
141
+ },
142
+ data: {
143
+ enableMockData: config.enableMockData ?? false,
144
+ documentBucketId: config.documentBucketId ?? "documents",
145
+ usersCollectionName: config.usersCollectionName ?? "Users",
146
+ importDirectory: "importData"
147
+ },
148
+ schemas: {
149
+ outputDirectory: "schemas",
150
+ yamlSchemaDirectory: ".yaml_schemas"
151
+ },
152
+ migrations: {
153
+ enabled: true
154
+ },
155
+ databases: (config.databases || []).map(db => ({
156
+ id: db.$id,
157
+ name: db.name,
158
+ collections: [] // Collections will be handled separately
159
+ })),
160
+ buckets: (config.buckets || []).map(bucket => ({
161
+ id: bucket.$id,
162
+ name: bucket.name,
163
+ permissions: bucket.$permissions?.map((p) => ({
164
+ permission: p.permission,
165
+ target: p.target
166
+ })) || [],
167
+ fileSecurity: bucket.fileSecurity ?? false,
168
+ enabled: bucket.enabled ?? true,
169
+ maximumFileSize: bucket.maximumFileSize ?? 30000000,
170
+ allowedFileExtensions: bucket.allowedFileExtensions || [],
171
+ compression: bucket.compression || "gzip",
172
+ encryption: bucket.encryption ?? false,
173
+ antivirus: bucket.antivirus ?? false
174
+ })),
175
+ functions: (config.functions || []).map((func) => ({
176
+ id: func.$id,
177
+ name: func.name,
178
+ runtime: func.runtime,
179
+ execute: func.execute || [],
180
+ events: func.events || [],
181
+ schedule: func.schedule || "",
182
+ timeout: func.timeout ?? 15,
183
+ enabled: func.enabled ?? true,
184
+ logging: func.logging ?? false,
185
+ entrypoint: func.entrypoint || "src/main.js",
186
+ commands: func.commands || "",
187
+ scopes: func.scopes || [],
188
+ specification: func.specification || "s-1vcpu-512mb"
189
+ }))
133
190
  };
134
- // Add optional properties if they exist
135
- if (config.enableBackups !== undefined)
136
- yamlConfig.enableBackups = config.enableBackups;
137
- if (config.backupInterval !== undefined)
138
- yamlConfig.backupInterval = config.backupInterval;
139
- if (config.backupRetention !== undefined)
140
- yamlConfig.backupRetention = config.backupRetention;
141
- if (config.enableBackupCleanup !== undefined)
142
- yamlConfig.enableBackupCleanup = config.enableBackupCleanup;
143
- if (config.enableMockData !== undefined)
144
- yamlConfig.enableMockData = config.enableMockData;
145
- if (config.documentBucketId !== undefined)
146
- yamlConfig.documentBucketId = config.documentBucketId;
147
- if (config.usersCollectionName !== undefined)
148
- yamlConfig.usersCollectionName = config.usersCollectionName;
149
- // Copy any additional properties
150
- for (const [key, value] of Object.entries(config)) {
151
- if (!(key in yamlConfig)) {
152
- yamlConfig[key] = value;
191
+ return yamlConfig;
192
+ }
193
+ async function convertCollectionToYaml(tsFilePath, targetDir) {
194
+ try {
195
+ // Load the TypeScript collection using tsx
196
+ const { register } = await import("tsx/esm/api");
197
+ const { pathToFileURL } = await import("node:url");
198
+ const unregister = register();
199
+ try {
200
+ const configUrl = pathToFileURL(tsFilePath).href;
201
+ const collectionModule = await import(configUrl);
202
+ const collection = collectionModule.default?.default || collectionModule.default || collectionModule;
203
+ if (!collection) {
204
+ throw new Error("Failed to load collection from TypeScript file");
205
+ }
206
+ // Convert collection to YAML format
207
+ const yamlCollection = {
208
+ name: collection.name,
209
+ id: collection.$id,
210
+ documentSecurity: collection.documentSecurity ?? false,
211
+ enabled: collection.enabled ?? true,
212
+ permissions: (collection.permissions || collection.$permissions || []).map((p) => ({
213
+ permission: p.permission,
214
+ target: p.target
215
+ })),
216
+ attributes: (collection.attributes || []).map((attr) => ({
217
+ key: attr.key,
218
+ type: attr.type,
219
+ size: attr.size,
220
+ required: attr.required ?? false,
221
+ array: attr.array,
222
+ default: attr.xdefault || attr.default,
223
+ description: attr.description,
224
+ min: attr.min,
225
+ max: attr.max,
226
+ elements: attr.elements,
227
+ relatedCollection: attr.relatedCollection,
228
+ relationType: attr.relationType,
229
+ twoWay: attr.twoWay,
230
+ twoWayKey: attr.twoWayKey,
231
+ onDelete: attr.onDelete,
232
+ side: attr.side
233
+ })),
234
+ indexes: (collection.indexes || []).map((idx) => ({
235
+ key: idx.key,
236
+ type: idx.type,
237
+ attributes: idx.attributes,
238
+ orders: idx.orders
239
+ })),
240
+ importDefs: collection.importDefs || []
241
+ };
242
+ // Remove undefined values
243
+ const cleanYamlCollection = JSON.parse(JSON.stringify(yamlCollection, (key, value) => value === undefined ? undefined : value));
244
+ // Write YAML file
245
+ const fileName = path.basename(tsFilePath, '.ts') + '.yaml';
246
+ const targetPath = path.join(targetDir, fileName);
247
+ const yamlContent = yaml.dump(cleanYamlCollection, {
248
+ indent: 2,
249
+ lineWidth: 120,
250
+ noRefs: true
251
+ });
252
+ await fs.writeFile(targetPath, yamlContent);
253
+ MessageFormatter.info(`Converted ${path.basename(tsFilePath)} to ${fileName}`, { prefix: "Migration" });
254
+ }
255
+ finally {
256
+ unregister();
153
257
  }
154
258
  }
155
- return yamlConfig;
259
+ catch (error) {
260
+ MessageFormatter.error(`Failed to convert collection ${path.basename(tsFilePath)}`, error instanceof Error ? error : new Error(String(error)), { prefix: "Migration" });
261
+ }
156
262
  }
@@ -68,7 +68,7 @@ const shouldIgnoreDirectory = (dirName) => {
68
68
  };
69
69
  const findAppwriteConfigTS = (dir, depth = 0) => {
70
70
  // Limit search depth to prevent infinite recursion
71
- if (depth > 5) {
71
+ if (depth > 10) {
72
72
  return null;
73
73
  }
74
74
  if (shouldIgnoreDirectory(path.basename(dir))) {
@@ -79,6 +79,7 @@ const findAppwriteConfigTS = (dir, depth = 0) => {
79
79
  // First check current directory for appwriteConfig.ts
80
80
  for (const entry of entries) {
81
81
  if (entry.isFile() && entry.name === "appwriteConfig.ts") {
82
+ console.log(`Found appwriteConfig.ts at: ${path.join(dir, entry.name)}`);
82
83
  return path.join(dir, entry.name);
83
84
  }
84
85
  }
@@ -93,6 +94,7 @@ const findAppwriteConfigTS = (dir, depth = 0) => {
93
94
  }
94
95
  catch (error) {
95
96
  // Ignore directory access errors
97
+ console.log(`Error accessing directory ${dir}:`, error);
96
98
  }
97
99
  return null;
98
100
  };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "appwrite-utils-cli",
3
3
  "description": "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.",
4
- "version": "1.0.1",
4
+ "version": "1.0.2",
5
5
  "main": "src/main.ts",
6
6
  "type": "module",
7
7
  "repository": {
@@ -22,19 +22,39 @@ interface AppwriteConfigTS {
22
22
  }
23
23
 
24
24
  interface AppwriteConfigYAML {
25
- appwriteEndpoint: string;
26
- appwriteProject: string;
27
- appwriteKey: string;
28
- enableBackups?: boolean;
29
- backupInterval?: number;
30
- backupRetention?: number;
31
- enableBackupCleanup?: boolean;
32
- enableMockData?: boolean;
33
- documentBucketId?: string;
34
- usersCollectionName?: string;
35
- databases: Array<{ $id: string; name: string }>;
25
+ appwrite: {
26
+ endpoint: string;
27
+ project: string;
28
+ key: string;
29
+ };
30
+ logging: {
31
+ enabled: boolean;
32
+ level: string;
33
+ console: boolean;
34
+ logDirectory: string;
35
+ };
36
+ backups: {
37
+ enabled: boolean;
38
+ interval: number;
39
+ retention: number;
40
+ cleanup: boolean;
41
+ };
42
+ data: {
43
+ enableMockData: boolean;
44
+ documentBucketId: string;
45
+ usersCollectionName: string;
46
+ importDirectory: string;
47
+ };
48
+ schemas: {
49
+ outputDirectory: string;
50
+ yamlSchemaDirectory: string;
51
+ };
52
+ migrations: {
53
+ enabled: boolean;
54
+ };
55
+ databases: Array<{ id: string; name: string; collections?: string[] }>;
36
56
  buckets: Array<any>;
37
- [key: string]: any;
57
+ functions: Array<any>;
38
58
  }
39
59
 
40
60
  export async function migrateConfig(workingDir: string): Promise<void> {
@@ -87,7 +107,7 @@ async function findAppwriteConfigFiles(dir: string): Promise<string[]> {
87
107
 
88
108
  async function migrateConfigFile(configFilePath: string, workingDir: string): Promise<void> {
89
109
  const configDir = path.dirname(configFilePath);
90
- const appwriteDir = path.join(configDir, '.appwrite');
110
+ const appwriteDir = path.join(path.dirname(configDir), '.appwrite');
91
111
 
92
112
  MessageFormatter.info(`Migrating ${path.relative(workingDir, configFilePath)}`, { prefix: "Migration" });
93
113
 
@@ -102,9 +122,8 @@ async function migrateConfigFile(configFilePath: string, workingDir: string): Pr
102
122
  }
103
123
  }
104
124
 
105
- // Read and parse the TypeScript config
106
- const configContent = await fs.readFile(configFilePath, 'utf8');
107
- const config = await parseTypeScriptConfig(configContent);
125
+ // Load and parse the TypeScript config
126
+ const config = await parseTypeScriptConfig(configFilePath);
108
127
 
109
128
  // Create .appwrite directory
110
129
  await fs.mkdir(appwriteDir, { recursive: true });
@@ -118,95 +137,213 @@ async function migrateConfigFile(configFilePath: string, workingDir: string): Pr
118
137
  });
119
138
  await fs.writeFile(path.join(appwriteDir, 'appwriteConfig.yaml'), yamlContent);
120
139
 
121
- // Move related directories
122
- const foldersToMove = ['collections', 'schemas', 'importData', 'functions'];
123
- for (const folder of foldersToMove) {
124
- const sourcePath = path.join(configDir, folder);
125
- const targetPath = path.join(appwriteDir, folder);
126
-
127
- if (existsSync(sourcePath)) {
128
- await fs.rename(sourcePath, targetPath);
129
- MessageFormatter.info(`Moved ${folder}/ to .appwrite/${folder}/`, { prefix: "Migration" });
140
+ // Copy all directories except collections and schemas (we handle collections separately, skip schemas entirely)
141
+ const entries = await fs.readdir(configDir, { withFileTypes: true });
142
+ for (const entry of entries) {
143
+ if (entry.isDirectory() && entry.name !== 'collections' && entry.name !== 'schemas') {
144
+ const sourcePath = path.join(configDir, entry.name);
145
+ const targetPath = path.join(appwriteDir, entry.name);
146
+
147
+ await fs.cp(sourcePath, targetPath, { recursive: true });
148
+ MessageFormatter.info(`Copied ${entry.name}/ to .appwrite/${entry.name}/`, { prefix: "Migration" });
130
149
  }
131
150
  }
132
151
 
133
- // Backup original config file
134
- const backupPath = configFilePath + '.backup';
135
- await fs.copyFile(configFilePath, backupPath);
136
- MessageFormatter.info(`Created backup at ${path.relative(workingDir, backupPath)}`, { prefix: "Migration" });
137
-
138
- // Optionally remove original config file
139
- const shouldRemoveOriginal = await ConfirmationDialogs.confirmRemoval(
140
- `Remove original ${path.relative(workingDir, configFilePath)}?`
141
- );
142
- if (shouldRemoveOriginal) {
143
- await fs.unlink(configFilePath);
144
- MessageFormatter.info(`Removed original ${path.relative(workingDir, configFilePath)}`, { prefix: "Migration" });
152
+ // Convert TypeScript collections to YAML collections
153
+ const collectionsPath = path.join(configDir, 'collections');
154
+ if (existsSync(collectionsPath)) {
155
+ const targetCollectionsPath = path.join(appwriteDir, 'collections');
156
+ await fs.mkdir(targetCollectionsPath, { recursive: true });
157
+
158
+ const collectionFiles = await fs.readdir(collectionsPath);
159
+ for (const file of collectionFiles) {
160
+ if (file.endsWith('.ts')) {
161
+ await convertCollectionToYaml(path.join(collectionsPath, file), targetCollectionsPath);
162
+ }
163
+ }
164
+ MessageFormatter.info(`Converted TypeScript collections to YAML in .appwrite/collections/`, { prefix: "Migration" });
145
165
  }
146
166
 
167
+ // Keep original config file in place (no backup needed since we're not deleting it)
168
+
147
169
  MessageFormatter.success(`Migration completed for ${path.relative(workingDir, configFilePath)}`, { prefix: "Migration" });
148
170
  }
149
171
 
150
- async function parseTypeScriptConfig(content: string): Promise<AppwriteConfigTS> {
151
- // This is a simplified parser - in a real implementation, you might want to use a proper TypeScript parser
152
- // For now, we'll use a regex-based approach to extract the config object
153
-
172
+ async function parseTypeScriptConfig(configFilePath: string): Promise<AppwriteConfigTS> {
154
173
  try {
155
- // Remove comments and imports
156
- const cleanContent = content
157
- .replace(/\/\*[\s\S]*?\*\//g, '') // Remove block comments
158
- .replace(/\/\/.*$/gm, '') // Remove line comments
159
- .replace(/^import.*$/gm, '') // Remove imports
160
- .replace(/^export.*$/gm, ''); // Remove exports
174
+ // Use tsx to import the TypeScript config file directly
175
+ const { register } = await import("tsx/esm/api");
176
+ const { pathToFileURL } = await import("node:url");
161
177
 
162
- // Find the config object
163
- const configMatch = cleanContent.match(/const\s+\w+\s*:\s*\w+\s*=\s*({[\s\S]*?});/);
164
- if (!configMatch) {
165
- throw new Error('Could not find config object in TypeScript file');
166
- }
167
-
168
- // Convert to JSON-like format and parse
169
- let configStr = configMatch[1];
178
+ const unregister = register();
170
179
 
171
- // Replace TypeScript-specific syntax
172
- configStr = configStr
173
- .replace(/(\w+):/g, '"$1":') // Quote property names
174
- .replace(/'/g, '"') // Convert single quotes to double quotes
175
- .replace(/,(\s*[}\]])/g, '$1'); // Remove trailing commas
176
-
177
- const config = JSON.parse(configStr);
178
- return config as AppwriteConfigTS;
180
+ try {
181
+ const configUrl = pathToFileURL(configFilePath).href;
182
+ const configModule = await import(configUrl);
183
+ const config = configModule.default?.default || configModule.default || configModule;
184
+
185
+ if (!config) {
186
+ throw new Error("Failed to load config from TypeScript file");
187
+ }
188
+
189
+ return config as AppwriteConfigTS;
190
+ } finally {
191
+ unregister();
192
+ }
179
193
  } catch (error) {
180
- MessageFormatter.error("Could not parse TypeScript config", error instanceof Error ? error : new Error(String(error)), { prefix: "Migration" });
181
- throw new Error('Failed to parse TypeScript configuration file. Please ensure it follows standard format.');
194
+ MessageFormatter.error("Could not load TypeScript config", error instanceof Error ? error : new Error(String(error)), { prefix: "Migration" });
195
+ throw new Error('Failed to load TypeScript configuration file. Please ensure it exports a valid config object.');
182
196
  }
183
197
  }
184
198
 
185
199
  function convertToYAMLConfig(config: AppwriteConfigTS): AppwriteConfigYAML {
186
- // Convert the config to YAML-friendly format
200
+ // Convert the config to the nested YAML structure
187
201
  const yamlConfig: AppwriteConfigYAML = {
188
- appwriteEndpoint: config.appwriteEndpoint,
189
- appwriteProject: config.appwriteProject,
190
- appwriteKey: config.appwriteKey,
191
- databases: config.databases || [],
192
- buckets: config.buckets || [],
202
+ appwrite: {
203
+ endpoint: config.appwriteEndpoint,
204
+ project: config.appwriteProject,
205
+ key: config.appwriteKey
206
+ },
207
+ logging: {
208
+ enabled: config.logging?.enabled ?? false,
209
+ level: config.logging?.level ?? "info",
210
+ console: config.logging?.console ?? false,
211
+ logDirectory: "./logs"
212
+ },
213
+ backups: {
214
+ enabled: config.enableBackups ?? false,
215
+ interval: config.backupInterval ?? 3600,
216
+ retention: config.backupRetention ?? 30,
217
+ cleanup: config.enableBackupCleanup ?? false
218
+ },
219
+ data: {
220
+ enableMockData: config.enableMockData ?? false,
221
+ documentBucketId: config.documentBucketId ?? "documents",
222
+ usersCollectionName: config.usersCollectionName ?? "Users",
223
+ importDirectory: "importData"
224
+ },
225
+ schemas: {
226
+ outputDirectory: "schemas",
227
+ yamlSchemaDirectory: ".yaml_schemas"
228
+ },
229
+ migrations: {
230
+ enabled: true
231
+ },
232
+ databases: (config.databases || []).map(db => ({
233
+ id: db.$id,
234
+ name: db.name,
235
+ collections: [] // Collections will be handled separately
236
+ })),
237
+ buckets: (config.buckets || []).map(bucket => ({
238
+ id: bucket.$id,
239
+ name: bucket.name,
240
+ permissions: bucket.$permissions?.map((p: any) => ({
241
+ permission: p.permission,
242
+ target: p.target
243
+ })) || [],
244
+ fileSecurity: bucket.fileSecurity ?? false,
245
+ enabled: bucket.enabled ?? true,
246
+ maximumFileSize: bucket.maximumFileSize ?? 30000000,
247
+ allowedFileExtensions: bucket.allowedFileExtensions || [],
248
+ compression: bucket.compression || "gzip",
249
+ encryption: bucket.encryption ?? false,
250
+ antivirus: bucket.antivirus ?? false
251
+ })),
252
+ functions: (config.functions || []).map((func: any) => ({
253
+ id: func.$id,
254
+ name: func.name,
255
+ runtime: func.runtime,
256
+ execute: func.execute || [],
257
+ events: func.events || [],
258
+ schedule: func.schedule || "",
259
+ timeout: func.timeout ?? 15,
260
+ enabled: func.enabled ?? true,
261
+ logging: func.logging ?? false,
262
+ entrypoint: func.entrypoint || "src/main.js",
263
+ commands: func.commands || "",
264
+ scopes: func.scopes || [],
265
+ specification: func.specification || "s-1vcpu-512mb"
266
+ }))
193
267
  };
194
268
 
195
- // Add optional properties if they exist
196
- if (config.enableBackups !== undefined) yamlConfig.enableBackups = config.enableBackups;
197
- if (config.backupInterval !== undefined) yamlConfig.backupInterval = config.backupInterval;
198
- if (config.backupRetention !== undefined) yamlConfig.backupRetention = config.backupRetention;
199
- if (config.enableBackupCleanup !== undefined) yamlConfig.enableBackupCleanup = config.enableBackupCleanup;
200
- if (config.enableMockData !== undefined) yamlConfig.enableMockData = config.enableMockData;
201
- if (config.documentBucketId !== undefined) yamlConfig.documentBucketId = config.documentBucketId;
202
- if (config.usersCollectionName !== undefined) yamlConfig.usersCollectionName = config.usersCollectionName;
203
-
204
- // Copy any additional properties
205
- for (const [key, value] of Object.entries(config)) {
206
- if (!(key in yamlConfig)) {
207
- yamlConfig[key] = value;
269
+ return yamlConfig;
270
+ }
271
+
272
+ async function convertCollectionToYaml(tsFilePath: string, targetDir: string): Promise<void> {
273
+ try {
274
+ // Load the TypeScript collection using tsx
275
+ const { register } = await import("tsx/esm/api");
276
+ const { pathToFileURL } = await import("node:url");
277
+
278
+ const unregister = register();
279
+
280
+ try {
281
+ const configUrl = pathToFileURL(tsFilePath).href;
282
+ const collectionModule = await import(configUrl);
283
+ const collection = collectionModule.default?.default || collectionModule.default || collectionModule;
284
+
285
+ if (!collection) {
286
+ throw new Error("Failed to load collection from TypeScript file");
287
+ }
288
+
289
+ // Convert collection to YAML format
290
+ const yamlCollection = {
291
+ name: collection.name,
292
+ id: collection.$id,
293
+ documentSecurity: collection.documentSecurity ?? false,
294
+ enabled: collection.enabled ?? true,
295
+ permissions: (collection.permissions || collection.$permissions || []).map((p: any) => ({
296
+ permission: p.permission,
297
+ target: p.target
298
+ })),
299
+ attributes: (collection.attributes || []).map((attr: any) => ({
300
+ key: attr.key,
301
+ type: attr.type,
302
+ size: attr.size,
303
+ required: attr.required ?? false,
304
+ array: attr.array,
305
+ default: attr.xdefault || attr.default,
306
+ description: attr.description,
307
+ min: attr.min,
308
+ max: attr.max,
309
+ elements: attr.elements,
310
+ relatedCollection: attr.relatedCollection,
311
+ relationType: attr.relationType,
312
+ twoWay: attr.twoWay,
313
+ twoWayKey: attr.twoWayKey,
314
+ onDelete: attr.onDelete,
315
+ side: attr.side
316
+ })),
317
+ indexes: (collection.indexes || []).map((idx: any) => ({
318
+ key: idx.key,
319
+ type: idx.type,
320
+ attributes: idx.attributes,
321
+ orders: idx.orders
322
+ })),
323
+ importDefs: collection.importDefs || []
324
+ };
325
+
326
+ // Remove undefined values
327
+ const cleanYamlCollection = JSON.parse(JSON.stringify(yamlCollection, (key, value) =>
328
+ value === undefined ? undefined : value
329
+ ));
330
+
331
+ // Write YAML file
332
+ const fileName = path.basename(tsFilePath, '.ts') + '.yaml';
333
+ const targetPath = path.join(targetDir, fileName);
334
+ const yamlContent = yaml.dump(cleanYamlCollection, {
335
+ indent: 2,
336
+ lineWidth: 120,
337
+ noRefs: true
338
+ });
339
+
340
+ await fs.writeFile(targetPath, yamlContent);
341
+ MessageFormatter.info(`Converted ${path.basename(tsFilePath)} to ${fileName}`, { prefix: "Migration" });
342
+
343
+ } finally {
344
+ unregister();
208
345
  }
346
+ } catch (error) {
347
+ MessageFormatter.error(`Failed to convert collection ${path.basename(tsFilePath)}`, error instanceof Error ? error : new Error(String(error)), { prefix: "Migration" });
209
348
  }
210
-
211
- return yamlConfig;
212
349
  }
@@ -74,7 +74,7 @@ const shouldIgnoreDirectory = (dirName: string): boolean => {
74
74
 
75
75
  const findAppwriteConfigTS = (dir: string, depth: number = 0): string | null => {
76
76
  // Limit search depth to prevent infinite recursion
77
- if (depth > 5) {
77
+ if (depth > 10) {
78
78
  return null;
79
79
  }
80
80
 
@@ -88,6 +88,7 @@ const findAppwriteConfigTS = (dir: string, depth: number = 0): string | null =>
88
88
  // First check current directory for appwriteConfig.ts
89
89
  for (const entry of entries) {
90
90
  if (entry.isFile() && entry.name === "appwriteConfig.ts") {
91
+ console.log(`Found appwriteConfig.ts at: ${path.join(dir, entry.name)}`);
91
92
  return path.join(dir, entry.name);
92
93
  }
93
94
  }
@@ -101,6 +102,7 @@ const findAppwriteConfigTS = (dir: string, depth: number = 0): string | null =>
101
102
  }
102
103
  } catch (error) {
103
104
  // Ignore directory access errors
105
+ console.log(`Error accessing directory ${dir}:`, error);
104
106
  }
105
107
 
106
108
  return null;