appwrite-utils-cli 0.9.992 → 0.9.994

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.
Files changed (46) hide show
  1. package/README.md +2 -2
  2. package/dist/collections/methods.js +6 -4
  3. package/dist/functions/deployments.d.ts +4 -0
  4. package/dist/functions/deployments.js +114 -0
  5. package/dist/functions/methods.d.ts +13 -8
  6. package/dist/functions/methods.js +75 -21
  7. package/dist/functions/templates/count-docs-in-collection/src/main.d.ts +21 -0
  8. package/dist/functions/templates/count-docs-in-collection/src/main.js +114 -0
  9. package/dist/functions/templates/count-docs-in-collection/src/request.d.ts +15 -0
  10. package/dist/functions/templates/count-docs-in-collection/src/request.js +6 -0
  11. package/dist/functions/templates/typescript-node/src/index.d.ts +11 -0
  12. package/dist/functions/templates/typescript-node/src/index.js +11 -0
  13. package/dist/interactiveCLI.d.ts +6 -0
  14. package/dist/interactiveCLI.js +437 -32
  15. package/dist/main.js +0 -0
  16. package/dist/migrations/appwriteToX.js +32 -1
  17. package/dist/migrations/schemaStrings.d.ts +1 -0
  18. package/dist/migrations/schemaStrings.js +74 -2
  19. package/dist/migrations/transfer.js +112 -123
  20. package/dist/utils/loadConfigs.d.ts +1 -0
  21. package/dist/utils/loadConfigs.js +19 -0
  22. package/dist/utils/schemaStrings.js +27 -0
  23. package/dist/utilsController.d.ts +5 -1
  24. package/dist/utilsController.js +54 -2
  25. package/package.json +57 -55
  26. package/src/collections/methods.ts +29 -10
  27. package/src/functions/deployments.ts +190 -0
  28. package/src/functions/methods.ts +295 -235
  29. package/src/functions/templates/count-docs-in-collection/README.md +54 -0
  30. package/src/functions/templates/count-docs-in-collection/src/main.ts +159 -0
  31. package/src/functions/templates/count-docs-in-collection/src/request.ts +9 -0
  32. package/src/functions/templates/poetry/README.md +30 -0
  33. package/src/functions/templates/poetry/pyproject.toml +16 -0
  34. package/src/functions/templates/poetry/src/__init__.py +0 -0
  35. package/src/functions/templates/poetry/src/index.py +16 -0
  36. package/src/functions/templates/typescript-node/README.md +32 -0
  37. package/src/functions/templates/typescript-node/src/index.ts +23 -0
  38. package/src/interactiveCLI.ts +606 -47
  39. package/src/migrations/appwriteToX.ts +44 -1
  40. package/src/migrations/schemaStrings.ts +83 -2
  41. package/src/migrations/transfer.ts +280 -207
  42. package/src/setupController.ts +41 -41
  43. package/src/utils/loadConfigs.ts +24 -0
  44. package/src/utils/schemaStrings.ts +27 -0
  45. package/src/utilsController.ts +63 -3
  46. package/tsconfig.json +8 -1
@@ -4,6 +4,7 @@ import fs from "fs";
4
4
  import path from "path";
5
5
  import { dump } from "js-yaml";
6
6
  import { getDatabaseFromConfig } from "./afterImportActions.js";
7
+ import { ulid } from "ulidx";
7
8
  export class SchemaGenerator {
8
9
  relationshipMap = new Map();
9
10
  config;
@@ -15,7 +16,9 @@ export class SchemaGenerator {
15
16
  }
16
17
  updateTsSchemas() {
17
18
  const collections = this.config.collections;
19
+ const functions = this.config.functions || [];
18
20
  delete this.config.collections;
21
+ delete this.config.functions;
19
22
  const configPath = path.join(this.appwriteFolderPath, "appwriteConfig.ts");
20
23
  const configContent = `import { type AppwriteConfig } from "appwrite-utils";
21
24
 
@@ -30,8 +33,33 @@ export class SchemaGenerator {
30
33
  enableMockData: ${this.config.enableMockData},
31
34
  documentBucketId: "${this.config.documentBucketId}",
32
35
  usersCollectionName: "${this.config.usersCollectionName}",
33
- databases: ${JSON.stringify(this.config.databases)},
34
- buckets: ${JSON.stringify(this.config.buckets)}
36
+ databases: ${JSON.stringify(this.config.databases, null, 4)},
37
+ buckets: ${JSON.stringify(this.config.buckets, null, 4)},
38
+ functions: ${JSON.stringify(functions.map((func) => ({
39
+ functionId: func.$id || ulid(),
40
+ name: func.name,
41
+ runtime: func.runtime,
42
+ path: func.dirPath || `functions/${func.name}`,
43
+ entrypoint: func.entrypoint || "src/index.ts",
44
+ execute: func.execute,
45
+ events: func.events || [],
46
+ schedule: func.schedule || "",
47
+ timeout: func.timeout || 15,
48
+ enabled: func.enabled !== false,
49
+ logging: func.logging !== false,
50
+ commands: func.commands || "npm install",
51
+ scopes: func.scopes || [],
52
+ installationId: func.installationId,
53
+ providerRepositoryId: func.providerRepositoryId,
54
+ providerBranch: func.providerBranch,
55
+ providerSilentMode: func.providerSilentMode,
56
+ providerRootDirectory: func.providerRootDirectory,
57
+ specification: func.specification,
58
+ ...(func.predeployCommands
59
+ ? { predeployCommands: func.predeployCommands }
60
+ : {}),
61
+ ...(func.deployDir ? { deployDir: func.deployDir } : {}),
62
+ })), null, 4)}
35
63
  };
36
64
 
37
65
  export default appwriteConfig;
@@ -106,6 +134,50 @@ export class SchemaGenerator {
106
134
  console.log(`Collection schema written to ${collectionFilePath}`);
107
135
  });
108
136
  }
137
+ updateConfig(config) {
138
+ const configPath = path.join(this.appwriteFolderPath, "appwriteConfig.ts");
139
+ const configContent = `import { type AppwriteConfig } from "appwrite-utils";
140
+
141
+ const appwriteConfig: AppwriteConfig = {
142
+ appwriteEndpoint: "${config.appwriteEndpoint}",
143
+ appwriteProject: "${config.appwriteProject}",
144
+ appwriteKey: "${config.appwriteKey}",
145
+ enableBackups: ${config.enableBackups},
146
+ backupInterval: ${config.backupInterval},
147
+ backupRetention: ${config.backupRetention},
148
+ enableBackupCleanup: ${config.enableBackupCleanup},
149
+ enableMockData: ${config.enableMockData},
150
+ documentBucketId: "${config.documentBucketId}",
151
+ usersCollectionName: "${config.usersCollectionName}",
152
+ databases: ${JSON.stringify(config.databases, null, 4)},
153
+ buckets: ${JSON.stringify(config.buckets, null, 4)},
154
+ functions: ${JSON.stringify(config.functions?.map((func) => ({
155
+ $id: func.$id || ulid(),
156
+ name: func.name,
157
+ runtime: func.runtime,
158
+ dirPath: func.dirPath || `functions/${func.name}`,
159
+ entrypoint: func.entrypoint || "src/index.ts",
160
+ execute: func.execute || [],
161
+ events: func.events || [],
162
+ schedule: func.schedule || "",
163
+ timeout: func.timeout || 15,
164
+ enabled: func.enabled !== false,
165
+ logging: func.logging !== false,
166
+ commands: func.commands || "npm install",
167
+ scopes: func.scopes || [],
168
+ installationId: func.installationId,
169
+ providerRepositoryId: func.providerRepositoryId,
170
+ providerBranch: func.providerBranch,
171
+ providerSilentMode: func.providerSilentMode,
172
+ providerRootDirectory: func.providerRootDirectory,
173
+ specification: func.specification,
174
+ })), null, 4)}
175
+ };
176
+
177
+ export default appwriteConfig;
178
+ `;
179
+ fs.writeFileSync(configPath, configContent, { encoding: "utf-8" });
180
+ }
109
181
  extractRelationships() {
110
182
  if (!this.config.collections) {
111
183
  return;
@@ -2,8 +2,11 @@ import { tryAwaitWithRetry } from "appwrite-utils";
2
2
  import { Client, Databases, IndexType, Query, Storage, } from "node-appwrite";
3
3
  import { InputFile } from "node-appwrite/file";
4
4
  import { getAppwriteClient } from "../utils/helperFunctions.js";
5
- import { createOrUpdateAttribute } from "../collections/attributes.js";
5
+ import { createOrUpdateAttribute, createUpdateCollectionAttributes, } from "../collections/attributes.js";
6
6
  import { parseAttribute } from "appwrite-utils";
7
+ import chalk from "chalk";
8
+ import { fetchAllCollections } from "src/collections/methods.js";
9
+ import { createOrUpdateIndex, createOrUpdateIndexes, } from "src/collections/indexes.js";
7
10
  export const transferStorageLocalToLocal = async (storage, fromBucketId, toBucketId) => {
8
11
  console.log(`Transferring files from ${fromBucketId} to ${toBucketId}`);
9
12
  let lastFileId;
@@ -70,6 +73,7 @@ export const transferStorageLocalToLocal = async (storage, fromBucketId, toBucke
70
73
  }
71
74
  catch (error) {
72
75
  // File already exists, so we can skip it
76
+ console.log(chalk.yellow(`File ${file.$id} already exists, skipping...`));
73
77
  continue;
74
78
  }
75
79
  numberOfFiles++;
@@ -107,6 +111,7 @@ export const transferStorageLocalToRemote = async (localStorage, endpoint, proje
107
111
  }
108
112
  catch (error) {
109
113
  // File already exists, so we can skip it
114
+ console.log(chalk.yellow(`File ${file.$id} already exists, skipping...`));
110
115
  continue;
111
116
  }
112
117
  numberOfFiles++;
@@ -244,146 +249,130 @@ export const transferDocumentsBetweenDbsLocalToRemote = async (localDb, endpoint
244
249
  * @return {Promise<void>} A promise that resolves when the transfer is complete.
245
250
  */
246
251
  export const transferDatabaseLocalToLocal = async (localDb, fromDbId, targetDbId) => {
247
- let lastCollectionId;
248
- let fromCollections = await tryAwaitWithRetry(async () => await localDb.listCollections(fromDbId, [Query.limit(50)]));
249
- const allFromCollections = fromCollections.collections;
250
- if (fromCollections.collections.length < 50) {
251
- lastCollectionId = undefined;
252
- }
253
- else {
254
- lastCollectionId =
255
- fromCollections.collections[fromCollections.collections.length - 1].$id;
256
- while (lastCollectionId) {
257
- const collections = await localDb.listCollections(fromDbId, [
258
- Query.limit(50),
259
- Query.cursorAfter(lastCollectionId),
260
- ]);
261
- allFromCollections.push(...collections.collections);
262
- if (collections.collections.length < 50) {
263
- break;
264
- }
265
- lastCollectionId =
266
- collections.collections[collections.collections.length - 1].$id;
267
- }
268
- }
269
- lastCollectionId = undefined;
270
- let toCollections = await tryAwaitWithRetry(async () => await localDb.listCollections(targetDbId, [Query.limit(50)]));
271
- const allToCollections = toCollections.collections;
272
- if (toCollections.collections.length < 50) {
273
- }
274
- else {
275
- lastCollectionId =
276
- toCollections.collections[toCollections.collections.length - 1].$id;
277
- while (lastCollectionId) {
278
- const collections = await localDb.listCollections(targetDbId, [
279
- Query.limit(50),
280
- Query.cursorAfter(lastCollectionId),
281
- ]);
282
- allToCollections.push(...collections.collections);
283
- if (collections.collections.length < 50) {
284
- lastCollectionId = undefined;
252
+ // Get all collections from source database
253
+ const sourceCollections = await fetchAllCollections(fromDbId, localDb);
254
+ console.log(chalk.blue(`Found ${sourceCollections.length} collections in source database`));
255
+ // Process each collection
256
+ for (const collection of sourceCollections) {
257
+ console.log(chalk.yellow(`Processing collection: ${collection.name} (${collection.$id})`));
258
+ try {
259
+ // Create or update collection in target
260
+ let targetCollection;
261
+ const existingCollection = await tryAwaitWithRetry(async () => localDb.listCollections(targetDbId, [
262
+ Query.equal("$id", collection.$id),
263
+ ]));
264
+ if (existingCollection.collections.length > 0) {
265
+ targetCollection = existingCollection.collections[0];
266
+ console.log(chalk.green(`Collection ${collection.name} exists in target database`));
267
+ // Update collection if needed
268
+ if (targetCollection.name !== collection.name ||
269
+ targetCollection.$permissions !== collection.$permissions ||
270
+ targetCollection.documentSecurity !== collection.documentSecurity ||
271
+ targetCollection.enabled !== collection.enabled) {
272
+ targetCollection = await tryAwaitWithRetry(async () => localDb.updateCollection(targetDbId, collection.$id, collection.name, collection.$permissions, collection.documentSecurity, collection.enabled));
273
+ console.log(chalk.green(`Collection ${collection.name} updated`));
274
+ }
285
275
  }
286
276
  else {
287
- lastCollectionId =
288
- collections.collections[collections.collections.length - 1].$id;
277
+ console.log(chalk.yellow(`Creating collection ${collection.name} in target database...`));
278
+ targetCollection = await tryAwaitWithRetry(async () => localDb.createCollection(targetDbId, collection.$id, collection.name, collection.$permissions, collection.documentSecurity, collection.enabled));
289
279
  }
290
- }
291
- }
292
- for (const collection of allFromCollections) {
293
- const toCollection = allToCollections.find((c) => c.$id === collection.$id);
294
- if (toCollection) {
295
- await transferDocumentsBetweenDbsLocalToLocal(localDb, fromDbId, targetDbId, collection.$id, toCollection.$id);
296
- }
297
- else {
298
- console.log(`Collection ${collection.name} not found in destination database, creating...`);
299
- const newCollection = await tryAwaitWithRetry(async () => await localDb.createCollection(targetDbId, collection.$id, collection.name, collection.$permissions, collection.documentSecurity, collection.enabled));
300
- console.log(`Collection ${newCollection.name} created`);
280
+ // Handle attributes
281
+ const existingAttributes = await tryAwaitWithRetry(async () => await localDb.listAttributes(targetDbId, targetCollection.$id));
301
282
  for (const attribute of collection.attributes) {
302
- await tryAwaitWithRetry(async () => await createOrUpdateAttribute(localDb, targetDbId, newCollection, parseAttribute(attribute)));
283
+ const parsedAttribute = parseAttribute(attribute);
284
+ const existingAttribute = existingAttributes.attributes.find((attr) => attr.key === parsedAttribute.key);
285
+ if (!existingAttribute) {
286
+ await tryAwaitWithRetry(async () => createOrUpdateAttribute(localDb, targetDbId, targetCollection, parsedAttribute));
287
+ console.log(chalk.green(`Attribute ${parsedAttribute.key} created`));
288
+ }
289
+ else {
290
+ console.log(chalk.blue(`Attribute ${parsedAttribute.key} exists, checking for updates...`));
291
+ await tryAwaitWithRetry(async () => createOrUpdateAttribute(localDb, targetDbId, targetCollection, parsedAttribute));
292
+ }
303
293
  }
294
+ // Handle indexes
295
+ const existingIndexes = await tryAwaitWithRetry(async () => await localDb.listIndexes(targetDbId, targetCollection.$id));
304
296
  for (const index of collection.indexes) {
305
- await tryAwaitWithRetry(async () => await localDb.createIndex(targetDbId, newCollection.$id, index.key, index.type, index.attributes, index.orders));
297
+ const existingIndex = existingIndexes.indexes.find((idx) => idx.key === index.key);
298
+ if (!existingIndex) {
299
+ await createOrUpdateIndex(targetDbId, localDb, targetCollection.$id, index);
300
+ console.log(chalk.green(`Index ${index.key} created`));
301
+ }
302
+ else {
303
+ console.log(chalk.blue(`Index ${index.key} exists, checking for updates...`));
304
+ await createOrUpdateIndex(targetDbId, localDb, targetCollection.$id, index);
305
+ }
306
306
  }
307
- await transferDocumentsBetweenDbsLocalToLocal(localDb, fromDbId, targetDbId, collection.$id, newCollection.$id);
307
+ // Transfer documents
308
+ await transferDocumentsBetweenDbsLocalToLocal(localDb, fromDbId, targetDbId, collection.$id, targetCollection.$id);
309
+ }
310
+ catch (error) {
311
+ console.error(chalk.red(`Error processing collection ${collection.name}:`), error);
308
312
  }
309
313
  }
310
314
  };
311
315
  export const transferDatabaseLocalToRemote = async (localDb, endpoint, projectId, apiKey, fromDbId, toDbId) => {
312
316
  const client = getAppwriteClient(endpoint, projectId, apiKey);
313
317
  const remoteDb = new Databases(client);
314
- let lastCollectionId;
315
- let fromCollections = await tryAwaitWithRetry(async () => await localDb.listCollections(fromDbId, [Query.limit(50)]));
316
- const allFromCollections = fromCollections.collections;
317
- if (fromCollections.collections.length >= 50) {
318
- lastCollectionId =
319
- fromCollections.collections[fromCollections.collections.length - 1].$id;
320
- while (lastCollectionId) {
321
- const collections = await tryAwaitWithRetry(async () => await localDb.listCollections(fromDbId, [
322
- Query.limit(50),
323
- Query.cursorAfter(lastCollectionId),
324
- ]));
325
- allFromCollections.push(...collections.collections);
326
- if (collections.collections.length < 50) {
327
- break;
328
- }
329
- lastCollectionId =
330
- collections.collections[collections.collections.length - 1].$id;
331
- }
332
- }
333
- for (const collection of allFromCollections) {
334
- let toCollection;
335
- const toCollectionExists = await tryAwaitWithRetry(async () => await remoteDb.listCollections(toDbId, [
336
- Query.equal("$id", collection.$id),
337
- ]));
338
- if (toCollectionExists.collections.length > 0) {
339
- console.log(`Collection ${collection.name} already exists. Updating...`);
340
- toCollection = toCollectionExists.collections[0];
341
- // Update collection if needed
342
- if (toCollection.name !== collection.name ||
343
- toCollection.$permissions !== collection.$permissions ||
344
- toCollection.documentSecurity !== collection.documentSecurity ||
345
- toCollection.enabled !== collection.enabled) {
346
- toCollection = await tryAwaitWithRetry(async () => await remoteDb.updateCollection(toDbId, collection.$id, collection.name, collection.$permissions, collection.documentSecurity, collection.enabled));
347
- console.log(`Collection ${toCollection.name} updated`);
348
- }
349
- }
350
- else {
351
- toCollection = await tryAwaitWithRetry(async () => await remoteDb.createCollection(toDbId, collection.$id, collection.name, collection.$permissions, collection.documentSecurity, collection.enabled));
352
- console.log(`Collection ${toCollection.name} created`);
353
- }
354
- // Check and update attributes
355
- const existingAttributes = await tryAwaitWithRetry(async () => await remoteDb.listAttributes(toDbId, toCollection.$id));
356
- for (const attribute of collection.attributes) {
357
- const parsedAttribute = parseAttribute(attribute);
358
- const existingAttribute = existingAttributes.attributes.find(
359
- // @ts-expect-error
360
- (attr) => attr.key === parsedAttribute.key);
361
- if (!existingAttribute) {
362
- await tryAwaitWithRetry(async () => await createOrUpdateAttribute(remoteDb, toDbId, toCollection, parsedAttribute));
363
- console.log(`Attribute ${parsedAttribute.key} created`);
318
+ // Get all collections from source database
319
+ const sourceCollections = await fetchAllCollections(fromDbId, localDb);
320
+ console.log(chalk.blue(`Found ${sourceCollections.length} collections in source database`));
321
+ // Process each collection
322
+ for (const collection of sourceCollections) {
323
+ console.log(chalk.yellow(`Processing collection: ${collection.name} (${collection.$id})`));
324
+ try {
325
+ // Create or update collection in target
326
+ let targetCollection;
327
+ const existingCollection = await tryAwaitWithRetry(async () => remoteDb.listCollections(toDbId, [Query.equal("$id", collection.$id)]));
328
+ if (existingCollection.collections.length > 0) {
329
+ targetCollection = existingCollection.collections[0];
330
+ console.log(chalk.green(`Collection ${collection.name} exists in remote database`));
331
+ // Update collection if needed
332
+ if (targetCollection.name !== collection.name ||
333
+ targetCollection.$permissions !== collection.$permissions ||
334
+ targetCollection.documentSecurity !== collection.documentSecurity ||
335
+ targetCollection.enabled !== collection.enabled) {
336
+ targetCollection = await tryAwaitWithRetry(async () => remoteDb.updateCollection(toDbId, collection.$id, collection.name, collection.$permissions, collection.documentSecurity, collection.enabled));
337
+ console.log(chalk.green(`Collection ${collection.name} updated`));
338
+ }
364
339
  }
365
340
  else {
366
- // Check if attribute needs updating
367
- // Note: Appwrite doesn't allow updating most attribute properties
368
- // You might need to delete and recreate the attribute if significant changes are needed
369
- console.log(`Attribute ${parsedAttribute.key} already exists`);
341
+ console.log(chalk.yellow(`Creating collection ${collection.name} in remote database...`));
342
+ targetCollection = await tryAwaitWithRetry(async () => remoteDb.createCollection(toDbId, collection.$id, collection.name, collection.$permissions, collection.documentSecurity, collection.enabled));
370
343
  }
371
- }
372
- // Check and update indexes
373
- const existingIndexes = await tryAwaitWithRetry(async () => await remoteDb.listIndexes(toDbId, toCollection.$id));
374
- for (const index of collection.indexes) {
375
- const existingIndex = existingIndexes.indexes.find((idx) => idx.key === index.key);
376
- if (!existingIndex) {
377
- await tryAwaitWithRetry(async () => await remoteDb.createIndex(toDbId, toCollection.$id, index.key, index.type, index.attributes, index.orders));
378
- console.log(`Index ${index.key} created`);
344
+ // Handle attributes
345
+ const existingAttributes = await tryAwaitWithRetry(async () => await remoteDb.listAttributes(toDbId, targetCollection.$id));
346
+ for (const attribute of collection.attributes) {
347
+ const parsedAttribute = parseAttribute(attribute);
348
+ const existingAttribute = existingAttributes.attributes.find((attr) => attr.key === parsedAttribute.key);
349
+ if (!existingAttribute) {
350
+ await tryAwaitWithRetry(async () => createOrUpdateAttribute(remoteDb, toDbId, targetCollection, parsedAttribute));
351
+ console.log(chalk.green(`Attribute ${parsedAttribute.key} created`));
352
+ }
353
+ else {
354
+ console.log(chalk.blue(`Attribute ${parsedAttribute.key} exists, checking for updates...`));
355
+ await tryAwaitWithRetry(async () => createOrUpdateAttribute(remoteDb, toDbId, targetCollection, parsedAttribute));
356
+ }
379
357
  }
380
- else {
381
- // Check if index needs updating
382
- // Note: Appwrite doesn't allow updating indexes
383
- // You might need to delete and recreate the index if changes are needed
384
- console.log(`Index ${index.key} already exists`);
358
+ // Handle indexes
359
+ const existingIndexes = await tryAwaitWithRetry(async () => await remoteDb.listIndexes(toDbId, targetCollection.$id));
360
+ for (const index of collection.indexes) {
361
+ const existingIndex = existingIndexes.indexes.find((idx) => idx.key === index.key);
362
+ if (!existingIndex) {
363
+ await createOrUpdateIndex(toDbId, remoteDb, targetCollection.$id, index);
364
+ console.log(chalk.green(`Index ${index.key} created`));
365
+ }
366
+ else {
367
+ console.log(chalk.blue(`Index ${index.key} exists, checking for updates...`));
368
+ await createOrUpdateIndex(toDbId, remoteDb, targetCollection.$id, index);
369
+ }
385
370
  }
371
+ // Transfer documents
372
+ await transferDocumentsBetweenDbsLocalToRemote(localDb, endpoint, projectId, apiKey, fromDbId, toDbId, collection.$id, targetCollection.$id);
373
+ }
374
+ catch (error) {
375
+ console.error(chalk.red(`Error processing collection ${collection.name}:`), error);
386
376
  }
387
- await transferDocumentsBetweenDbsLocalToRemote(localDb, endpoint, projectId, apiKey, fromDbId, toDbId, collection.$id, toCollection.$id);
388
377
  }
389
378
  };
@@ -11,3 +11,4 @@ export declare const findAppwriteConfig: (dir: string) => string | null;
11
11
  * @returns The loaded Appwrite configuration including collections.
12
12
  */
13
13
  export declare const loadConfig: (configDir: string) => Promise<AppwriteConfig>;
14
+ export declare const findFunctionsDir: (dir: string) => string | null;
@@ -3,6 +3,7 @@ import fs from "fs";
3
3
  import {} from "appwrite-utils";
4
4
  import { register } from "tsx/esm/api"; // Import the register function
5
5
  import { pathToFileURL } from "node:url";
6
+ import chalk from "chalk";
6
7
  /**
7
8
  * Recursively searches for a file named 'appwriteConfig.ts' starting from the given directory.
8
9
  * @param dir The directory to start the search from.
@@ -66,3 +67,21 @@ export const loadConfig = async (configDir) => {
66
67
  unregister(); // Unregister tsx when done
67
68
  }
68
69
  };
70
+ export const findFunctionsDir = (dir) => {
71
+ if (dir === "node_modules") {
72
+ return null;
73
+ }
74
+ const files = fs.readdirSync(dir, { withFileTypes: true });
75
+ for (const entry of files) {
76
+ if (!entry.isDirectory() || entry.name === "node_modules") {
77
+ continue;
78
+ }
79
+ if (entry.name === "functions") {
80
+ return path.join(dir, entry.name);
81
+ }
82
+ const result = findFunctionsDir(path.join(dir, entry.name));
83
+ if (result)
84
+ return result;
85
+ }
86
+ return null;
87
+ };
@@ -4,6 +4,8 @@ import { z } from "zod";
4
4
  import fs from "fs";
5
5
  import path from "path";
6
6
  import { dump } from "js-yaml";
7
+ import { findFunctionsDir } from "./loadConfigs.js";
8
+ import { ulid } from "ulidx";
7
9
  export class SchemaGenerator {
8
10
  relationshipMap = new Map();
9
11
  config;
@@ -15,7 +17,9 @@ export class SchemaGenerator {
15
17
  }
16
18
  updateTsSchemas() {
17
19
  const collections = this.config.collections;
20
+ const functions = this.config.functions || [];
18
21
  delete this.config.collections;
22
+ delete this.config.functions;
19
23
  const configPath = path.join(this.appwriteFolderPath, "appwriteConfig.ts");
20
24
  const configContent = `import { type AppwriteConfig } from "appwrite-utils";
21
25
 
@@ -32,6 +36,29 @@ export class SchemaGenerator {
32
36
  usersCollectionName: "${this.config.usersCollectionName}",
33
37
  databases: ${JSON.stringify(this.config.databases)},
34
38
  buckets: ${JSON.stringify(this.config.buckets)},
39
+ functions: ${JSON.stringify(functions.map(func => ({
40
+ functionId: func.$id || ulid(),
41
+ name: func.name,
42
+ runtime: func.runtime,
43
+ path: func.dirPath || `functions/${func.name}`,
44
+ entrypoint: func.entrypoint || 'src/index.ts',
45
+ execute: func.execute,
46
+ events: func.events || [],
47
+ schedule: func.schedule || '',
48
+ timeout: func.timeout || 15,
49
+ enabled: func.enabled !== false,
50
+ logging: func.logging !== false,
51
+ commands: func.commands || 'npm install',
52
+ scopes: func.scopes || [],
53
+ installationId: func.installationId,
54
+ providerRepositoryId: func.providerRepositoryId,
55
+ providerBranch: func.providerBranch,
56
+ providerSilentMode: func.providerSilentMode,
57
+ providerRootDirectory: func.providerRootDirectory,
58
+ specification: func.specification,
59
+ ...(func.predeployCommands ? { predeployCommands: func.predeployCommands } : {}),
60
+ ...(func.deployDir ? { deployDir: func.deployDir } : {})
61
+ })), null, 2)}
35
62
  };
36
63
 
37
64
  export default appwriteConfig;
@@ -1,5 +1,5 @@
1
1
  import { Client, Databases, Storage, type Models } from "node-appwrite";
2
- import { type AppwriteConfig, type Specification } from "appwrite-utils";
2
+ import { type AppwriteConfig, type AppwriteFunction, type Specification } from "appwrite-utils";
3
3
  import { type AfterImportActions, type ConverterFunctions, type ValidationRules } from "appwrite-utils";
4
4
  import { type TransferOptions } from "./migrations/transfer.js";
5
5
  export interface SetupOptions {
@@ -36,6 +36,10 @@ export declare class UtilsController {
36
36
  wipeOtherDatabases(databasesToKeep: Models.Database[]): Promise<void>;
37
37
  wipeUsers(): Promise<void>;
38
38
  backupDatabase(database: Models.Database): Promise<void>;
39
+ listAllFunctions(): Promise<Models.Function[]>;
40
+ findFunctionDirectories(): Promise<Map<any, any>>;
41
+ deployFunction(functionName: string, functionPath?: string, functionConfig?: AppwriteFunction): Promise<void>;
42
+ syncFunctions(): Promise<void>;
39
43
  wipeDatabase(database: Models.Database, wipeBucket?: boolean): Promise<void>;
40
44
  wipeBucketFromDatabase(database: Models.Database): Promise<void>;
41
45
  wipeCollection(database: Models.Database, collection: Models.Collection): Promise<void>;
@@ -1,6 +1,6 @@
1
1
  import { Client, Databases, Query, Storage } from "node-appwrite";
2
2
  import {} from "appwrite-utils";
3
- import { loadConfig, findAppwriteConfig } from "./utils/loadConfigs.js";
3
+ import { loadConfig, findAppwriteConfig, findFunctionsDir } from "./utils/loadConfigs.js";
4
4
  import { UsersController } from "./migrations/users.js";
5
5
  import { AppwriteToX } from "./migrations/appwriteToX.js";
6
6
  import { ImportController } from "./migrations/importController.js";
@@ -14,8 +14,10 @@ import { afterImportActions } from "./migrations/afterImportActions.js";
14
14
  import { transferDatabaseLocalToLocal, transferDatabaseLocalToRemote, transferStorageLocalToLocal, transferStorageLocalToRemote, } from "./migrations/transfer.js";
15
15
  import { getClient } from "./utils/getClientFromConfig.js";
16
16
  import { fetchAllDatabases } from "./migrations/databases.js";
17
- import { updateFunctionSpecifications } from "./functions/methods.js";
17
+ import { listFunctions, updateFunctionSpecifications } from "./functions/methods.js";
18
18
  import chalk from "chalk";
19
+ import { deployLocalFunction } from "./functions/deployments.js";
20
+ import fs from "node:fs";
19
21
  export class UtilsController {
20
22
  appwriteFolderPath;
21
23
  appwriteConfigPath;
@@ -125,6 +127,56 @@ export class UtilsController {
125
127
  throw new Error("Database, storage, or config not initialized");
126
128
  await backupDatabase(this.config, this.database, database.$id, this.storage);
127
129
  }
130
+ async listAllFunctions() {
131
+ await this.init();
132
+ if (!this.appwriteServer)
133
+ throw new Error("Appwrite server not initialized");
134
+ const { functions } = await listFunctions(this.appwriteServer, [Query.limit(1000)]);
135
+ return functions;
136
+ }
137
+ async findFunctionDirectories() {
138
+ const functionsDir = findFunctionsDir(this.appwriteFolderPath);
139
+ if (!functionsDir)
140
+ return new Map();
141
+ const functionDirMap = new Map();
142
+ const entries = fs.readdirSync(functionsDir, { withFileTypes: true });
143
+ for (const entry of entries) {
144
+ if (entry.isDirectory()) {
145
+ const functionPath = path.join(functionsDir, entry.name);
146
+ // Match with config functions by name
147
+ if (this.config?.functions) {
148
+ const matchingFunc = this.config.functions.find(f => f.name.toLowerCase() === entry.name.toLowerCase());
149
+ if (matchingFunc) {
150
+ functionDirMap.set(matchingFunc.name, functionPath);
151
+ }
152
+ }
153
+ }
154
+ }
155
+ return functionDirMap;
156
+ }
157
+ async deployFunction(functionName, functionPath, functionConfig) {
158
+ await this.init();
159
+ if (!this.appwriteServer)
160
+ throw new Error("Appwrite server not initialized");
161
+ if (!functionConfig) {
162
+ functionConfig = this.config?.functions?.find(f => f.name === functionName);
163
+ }
164
+ if (!functionConfig)
165
+ throw new Error(`Function ${functionName} not found in config`);
166
+ await deployLocalFunction(this.appwriteServer, functionName, functionConfig, functionPath);
167
+ }
168
+ async syncFunctions() {
169
+ await this.init();
170
+ if (!this.appwriteServer)
171
+ throw new Error("Appwrite server not initialized");
172
+ const localFunctions = this.config?.functions || [];
173
+ const remoteFunctions = await listFunctions(this.appwriteServer, [Query.limit(1000)]);
174
+ for (const localFunction of localFunctions) {
175
+ console.log(chalk.blue(`Syncing function ${localFunction.name}...`));
176
+ await this.deployFunction(localFunction.name);
177
+ }
178
+ console.log(chalk.green("✨ All functions synchronized successfully!"));
179
+ }
128
180
  async wipeDatabase(database, wipeBucket = false) {
129
181
  await this.init();
130
182
  if (!this.database)