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,20 +4,27 @@ import { createEmptyCollection, setupDirsFiles } from "./utils/setupFiles.js";
4
4
  import { fetchAllDatabases } from "./databases/methods.js";
5
5
  import { fetchAllCollections } from "./collections/methods.js";
6
6
  import { listBuckets, createBucket } from "./storage/methods.js";
7
- import { Databases, Storage, Client, Compression, Query, } from "node-appwrite";
7
+ import { Databases, Storage, Client, Compression, Query, Functions, } from "node-appwrite";
8
8
  import { getClient } from "./utils/getClientFromConfig.js";
9
- import { parseAttribute, PermissionToAppwritePermission, } from "appwrite-utils";
9
+ import { AppwriteFunctionSchema, parseAttribute, PermissionToAppwritePermission, RuntimeSchema, permissionSchema, } from "appwrite-utils";
10
10
  import { ulid } from "ulidx";
11
11
  import chalk from "chalk";
12
12
  import { DateTime } from "luxon";
13
- import { listFunctions, listSpecifications } from "./functions/methods.js";
13
+ import { createFunctionTemplate, deleteFunction, downloadLatestFunctionDeployment, listFunctions, listSpecifications, } from "./functions/methods.js";
14
+ import { deployLocalFunction } from "./functions/deployments.js";
15
+ import { join } from "node:path";
16
+ import fs from "node:fs";
17
+ import { SchemaGenerator } from "./migrations/schemaStrings.js";
14
18
  var CHOICES;
15
19
  (function (CHOICES) {
16
20
  CHOICES["CREATE_COLLECTION_CONFIG"] = "Create collection config file";
21
+ CHOICES["CREATE_FUNCTION"] = "Create a new function, from scratch or using a template";
22
+ CHOICES["DEPLOY_FUNCTION"] = "Deploy function";
23
+ CHOICES["DELETE_FUNCTION"] = "Delete function";
17
24
  CHOICES["SETUP_DIRS_FILES"] = "Setup directories and files";
18
25
  CHOICES["SETUP_DIRS_FILES_WITH_EXAMPLE_DATA"] = "Setup directories and files with example data";
19
26
  CHOICES["SYNC_DB"] = "Push local config to Appwrite";
20
- CHOICES["SYNCHRONIZE_CONFIGURATIONS"] = "Synchronize configurations";
27
+ CHOICES["SYNCHRONIZE_CONFIGURATIONS"] = "Synchronize configurations - Pull from Appwrite and write to local config";
21
28
  CHOICES["TRANSFER_DATA"] = "Transfer data";
22
29
  CHOICES["BACKUP_DATABASE"] = "Backup database";
23
30
  CHOICES["WIPE_DATABASE"] = "Wipe database";
@@ -51,6 +58,18 @@ export class InteractiveCLI {
51
58
  await this.initControllerIfNeeded();
52
59
  await this.createCollectionConfig();
53
60
  break;
61
+ case CHOICES.CREATE_FUNCTION:
62
+ await this.initControllerIfNeeded();
63
+ await this.createFunction();
64
+ break;
65
+ case CHOICES.DEPLOY_FUNCTION:
66
+ await this.initControllerIfNeeded();
67
+ await this.deployFunction();
68
+ break;
69
+ case CHOICES.DELETE_FUNCTION:
70
+ await this.initControllerIfNeeded();
71
+ await this.deleteFunction();
72
+ break;
54
73
  case CHOICES.SETUP_DIRS_FILES:
55
74
  await setupDirsFiles(false, this.currentDir);
56
75
  break;
@@ -202,6 +221,283 @@ export class InteractiveCLI {
202
221
  ]);
203
222
  return selectedCollections;
204
223
  }
224
+ async createFunction() {
225
+ const { name } = await inquirer.prompt([
226
+ {
227
+ type: "input",
228
+ name: "name",
229
+ message: "Function name:",
230
+ validate: (input) => input.length > 0,
231
+ },
232
+ ]);
233
+ const { template } = await inquirer.prompt([
234
+ {
235
+ type: "list",
236
+ name: "template",
237
+ message: "Select a template:",
238
+ choices: [
239
+ "typescript-node",
240
+ "poetry",
241
+ "count-docs-in-collection",
242
+ "none",
243
+ ],
244
+ },
245
+ ]);
246
+ const { runtime } = await inquirer.prompt([
247
+ {
248
+ type: "list",
249
+ name: "runtime",
250
+ message: "Select runtime:",
251
+ choices: Object.values(RuntimeSchema.Values),
252
+ },
253
+ ]);
254
+ const specifications = await listSpecifications(this.controller.appwriteServer);
255
+ const { specification } = await inquirer.prompt([
256
+ {
257
+ type: "list",
258
+ name: "specification",
259
+ message: "Select specification:",
260
+ choices: [
261
+ { name: "None", value: undefined },
262
+ ...specifications.specifications.map((s) => ({
263
+ name: s.slug,
264
+ value: s.slug,
265
+ })),
266
+ ],
267
+ },
268
+ ]);
269
+ const functionConfig = {
270
+ $id: ulid(),
271
+ name,
272
+ runtime,
273
+ events: [],
274
+ execute: ["any"],
275
+ enabled: true,
276
+ logging: true,
277
+ entrypoint: template === "none" ? "src/index.ts" : undefined,
278
+ specification,
279
+ predeployCommands: template.includes("typescript")
280
+ ? ["npm install", "npm run build"]
281
+ : undefined,
282
+ deployDir: template.includes("typescript") ? "dist" : undefined,
283
+ };
284
+ if (template !== "none") {
285
+ await createFunctionTemplate(template, name, "./functions");
286
+ }
287
+ // Add to config
288
+ if (!this.controller.config.functions) {
289
+ this.controller.config.functions = [];
290
+ }
291
+ this.controller.config.functions.push(functionConfig);
292
+ console.log(chalk.green("✨ Function created successfully!"));
293
+ }
294
+ async findFunctionInSubdirectories(basePath, functionName) {
295
+ const queue = [basePath];
296
+ while (queue.length > 0) {
297
+ const currentPath = queue.shift();
298
+ try {
299
+ const entries = await fs.promises.readdir(currentPath, {
300
+ withFileTypes: true,
301
+ });
302
+ // Check if function exists in current directory
303
+ const functionPath = join(currentPath, functionName);
304
+ if (fs.existsSync(functionPath)) {
305
+ return functionPath;
306
+ }
307
+ // Add subdirectories to queue
308
+ for (const entry of entries) {
309
+ if (entry.isDirectory()) {
310
+ queue.push(join(currentPath, entry.name));
311
+ }
312
+ }
313
+ }
314
+ catch (error) {
315
+ console.log(chalk.yellow(`Skipping inaccessible directory: ${currentPath}`));
316
+ }
317
+ }
318
+ return null;
319
+ }
320
+ async deployFunction() {
321
+ await this.initControllerIfNeeded();
322
+ if (!this.controller?.config) {
323
+ console.log(chalk.red("Failed to initialize controller or load config"));
324
+ return;
325
+ }
326
+ const functions = await this.selectFunctions("Select function to deploy:", false, true);
327
+ if (!functions?.length) {
328
+ console.log(chalk.red("No function selected"));
329
+ return;
330
+ }
331
+ const functionConfig = functions[0];
332
+ if (!functionConfig) {
333
+ console.log(chalk.red("Invalid function configuration"));
334
+ return;
335
+ }
336
+ let functionPath = join(this.controller.getAppwriteFolderPath(), "functions", functionConfig.name);
337
+ if (!fs.existsSync(functionPath)) {
338
+ console.log(chalk.yellow(`Function not found in primary location, searching subdirectories...`));
339
+ const foundPath = await this.findFunctionInSubdirectories(this.controller.getAppwriteFolderPath(), functionConfig.name);
340
+ if (foundPath) {
341
+ console.log(chalk.green(`Found function at: ${foundPath}`));
342
+ functionPath = foundPath;
343
+ functionConfig.dirPath = foundPath;
344
+ }
345
+ else {
346
+ console.log(chalk.yellow(`Function ${functionConfig.name} not found locally in any subdirectory`));
347
+ const { shouldDownload } = await inquirer.prompt([
348
+ {
349
+ type: "confirm",
350
+ name: "shouldDownload",
351
+ message: "Would you like to download the latest deployment?",
352
+ default: true,
353
+ },
354
+ ]);
355
+ if (shouldDownload) {
356
+ try {
357
+ console.log(chalk.blue("Downloading latest deployment..."));
358
+ const { path: downloadedPath, function: remoteFunction } = await downloadLatestFunctionDeployment(this.controller.appwriteServer, functionConfig.$id, join(this.controller.getAppwriteFolderPath(), "functions"));
359
+ console.log(chalk.green(`✨ Function downloaded to ${downloadedPath}`));
360
+ // Update the config and functions array safely
361
+ this.controller.config.functions =
362
+ this.controller.config.functions || [];
363
+ const newFunction = {
364
+ $id: remoteFunction.$id,
365
+ name: remoteFunction.name,
366
+ runtime: remoteFunction.runtime,
367
+ execute: remoteFunction.execute || [],
368
+ events: remoteFunction.events || [],
369
+ schedule: remoteFunction.schedule || "",
370
+ timeout: remoteFunction.timeout || 15,
371
+ enabled: remoteFunction.enabled !== false,
372
+ logging: remoteFunction.logging !== false,
373
+ entrypoint: remoteFunction.entrypoint || "src/index.ts",
374
+ commands: remoteFunction.commands || "npm install",
375
+ dirPath: downloadedPath,
376
+ scopes: (remoteFunction.scopes || []),
377
+ installationId: remoteFunction.installationId,
378
+ providerRepositoryId: remoteFunction.providerRepositoryId,
379
+ providerBranch: remoteFunction.providerBranch,
380
+ providerSilentMode: remoteFunction.providerSilentMode,
381
+ providerRootDirectory: remoteFunction.providerRootDirectory,
382
+ specification: remoteFunction.specification,
383
+ };
384
+ const existingIndex = this.controller.config.functions.findIndex((f) => f?.$id === remoteFunction.$id);
385
+ if (existingIndex >= 0) {
386
+ this.controller.config.functions[existingIndex] = newFunction;
387
+ }
388
+ else {
389
+ this.controller.config.functions.push(newFunction);
390
+ }
391
+ const schemaGenerator = new SchemaGenerator(this.controller.config, this.controller.getAppwriteFolderPath());
392
+ schemaGenerator.updateConfig(this.controller.config);
393
+ console.log(chalk.green("✨ Updated appwriteConfig.ts with new function"));
394
+ await this.controller.reloadConfig();
395
+ functionConfig.dirPath = downloadedPath;
396
+ }
397
+ catch (error) {
398
+ console.error(chalk.red("Failed to download function deployment:"), error);
399
+ return;
400
+ }
401
+ }
402
+ else {
403
+ console.log(chalk.yellow("Deployment cancelled"));
404
+ return;
405
+ }
406
+ }
407
+ }
408
+ if (!this.controller.appwriteServer) {
409
+ console.log(chalk.red("Appwrite server not initialized"));
410
+ return;
411
+ }
412
+ await deployLocalFunction(this.controller.appwriteServer, functionConfig.name, functionConfig);
413
+ }
414
+ async deleteFunction() {
415
+ const functions = await this.selectFunctions("Select functions to delete:", true, false);
416
+ if (!functions.length) {
417
+ console.log(chalk.red("No functions selected"));
418
+ return;
419
+ }
420
+ for (const func of functions) {
421
+ try {
422
+ await deleteFunction(this.controller.appwriteServer, func.$id);
423
+ console.log(chalk.green(`✨ Function ${func.name} deleted successfully!`));
424
+ }
425
+ catch (error) {
426
+ console.error(chalk.red(`Failed to delete function ${func.name}:`), error);
427
+ }
428
+ }
429
+ }
430
+ async selectFunctions(message, multiSelect = true, preferLocal = false) {
431
+ await this.initControllerIfNeeded();
432
+ const configFunctions = this.getLocalFunctions();
433
+ let remoteFunctions = [];
434
+ try {
435
+ const functions = await this.controller.listAllFunctions();
436
+ remoteFunctions = functions;
437
+ }
438
+ catch (error) {
439
+ console.log(chalk.yellow(`Note: Remote functions not available, using only local functions`));
440
+ }
441
+ // Combine functions based on whether we're deploying or not
442
+ const allFunctions = preferLocal
443
+ ? [
444
+ ...configFunctions,
445
+ ...remoteFunctions.map((f) => AppwriteFunctionSchema.parse(f)),
446
+ ]
447
+ : [
448
+ ...remoteFunctions.map((f) => AppwriteFunctionSchema.parse(f)),
449
+ ...configFunctions.filter((f) => !remoteFunctions.some((rf) => rf.name === f.name)),
450
+ ];
451
+ if (allFunctions.length === 0) {
452
+ console.log(chalk.red("No functions available"));
453
+ return [];
454
+ }
455
+ const choices = allFunctions
456
+ .sort((a, b) => a.name.localeCompare(b.name))
457
+ .map((func) => ({
458
+ name: func.name,
459
+ value: func,
460
+ }));
461
+ const { selectedFunctions } = await inquirer.prompt([
462
+ {
463
+ type: multiSelect ? "checkbox" : "list",
464
+ name: "selectedFunctions",
465
+ message: chalk.blue(message),
466
+ choices,
467
+ loop: true,
468
+ pageSize: 10,
469
+ },
470
+ ]);
471
+ // For single selection, ensure we return an array
472
+ if (!multiSelect) {
473
+ return selectedFunctions ? [selectedFunctions] : [];
474
+ }
475
+ return selectedFunctions || [];
476
+ }
477
+ getLocalFunctions() {
478
+ const configFunctions = this.controller.config?.functions || [];
479
+ return configFunctions.map((f) => ({
480
+ $id: f.$id || ulid(),
481
+ $createdAt: DateTime.now().toISO(),
482
+ $updatedAt: DateTime.now().toISO(),
483
+ name: f.name,
484
+ runtime: f.runtime,
485
+ execute: f.execute || ["any"],
486
+ events: f.events || [],
487
+ schedule: f.schedule || "",
488
+ timeout: f.timeout || 15,
489
+ enabled: f.enabled !== false,
490
+ logging: f.logging !== false,
491
+ entrypoint: f.entrypoint || "src/index.ts",
492
+ commands: f.commands || "npm install",
493
+ path: f.dirPath || `functions/${f.name}`,
494
+ ...(f.specification ? { specification: f.specification } : {}),
495
+ ...(f.predeployCommands
496
+ ? { predeployCommands: f.predeployCommands }
497
+ : {}),
498
+ ...(f.deployDir ? { deployDir: f.deployDir } : {}),
499
+ }));
500
+ }
205
501
  async selectBuckets(buckets, message, multiSelect = true) {
206
502
  const choices = buckets.map((bucket) => ({
207
503
  name: bucket.name,
@@ -306,8 +602,10 @@ export class InteractiveCLI {
306
602
  },
307
603
  ]);
308
604
  if (action === "assign") {
309
- const [selectedBucket] = await this.selectBuckets(allBuckets.buckets.filter((b) => !globalBuckets.some((gb) => gb.$id === b.$id)), `Select a bucket for the database "${database.name}":`, false);
310
- if (selectedBucket) {
605
+ const selectedBuckets = await this.selectBuckets(allBuckets.buckets.filter((b) => !globalBuckets.some((gb) => gb.$id === b.$id)), `Select a bucket for the database "${database.name}":`, false // multiSelect = false
606
+ );
607
+ if (selectedBuckets.length > 0) {
608
+ const selectedBucket = selectedBuckets[0];
311
609
  database.bucket = {
312
610
  $id: selectedBucket.$id,
313
611
  name: selectedBucket.name,
@@ -317,6 +615,7 @@ export class InteractiveCLI {
317
615
  compression: selectedBucket.compression,
318
616
  encryption: selectedBucket.encryption,
319
617
  antivirus: selectedBucket.antivirus,
618
+ permissions: selectedBucket.$permissions.map((p) => permissionSchema.parse(p)),
320
619
  };
321
620
  }
322
621
  }
@@ -411,23 +710,124 @@ export class InteractiveCLI {
411
710
  }
412
711
  async syncDb() {
413
712
  console.log(chalk.yellow("Syncing database..."));
713
+ const functionsClient = new Functions(this.controller.appwriteServer);
414
714
  const databases = await this.selectDatabases(await fetchAllDatabases(this.controller.database), chalk.blue("Select databases to synchronize:"), true);
415
715
  const collections = await this.selectCollections(databases[0], this.controller.database, chalk.blue("Select collections to synchronize:"), true, true // prefer local
416
716
  );
417
- await this.controller.syncDb(databases, collections);
717
+ const answer = await inquirer.prompt([
718
+ {
719
+ type: "confirm",
720
+ name: "syncFunctions",
721
+ message: "Do you want to synchronize functions?",
722
+ default: false,
723
+ },
724
+ ]);
725
+ if (answer.syncFunctions) {
726
+ const functions = await this.selectFunctions(chalk.blue("Select functions to synchronize:"), true, true // prefer local
727
+ );
728
+ await this.controller.syncDb(databases, collections);
729
+ for (const func of functions) {
730
+ await deployLocalFunction(this.controller.appwriteServer, func.dirPath || `functions/${func.name}`, func);
731
+ }
732
+ }
418
733
  console.log(chalk.green("Database sync completed."));
419
734
  }
420
735
  async synchronizeConfigurations() {
421
- if (!this.controller.database) {
422
- throw new Error("Database is not initialized. Is the config file correct and created?");
736
+ console.log(chalk.blue("Synchronizing configurations..."));
737
+ await this.controller.init();
738
+ // Sync databases and buckets first
739
+ const { syncDatabases } = await inquirer.prompt([
740
+ {
741
+ type: "confirm",
742
+ name: "syncDatabases",
743
+ message: "Do you want to synchronize databases and their buckets?",
744
+ default: true,
745
+ },
746
+ ]);
747
+ if (syncDatabases) {
748
+ const remoteDatabases = await fetchAllDatabases(this.controller.database);
749
+ const localDatabases = this.controller.config?.databases || [];
750
+ // Update config with remote databases that don't exist locally
751
+ const updatedConfig = await this.configureBuckets({
752
+ ...this.controller.config,
753
+ databases: [
754
+ ...localDatabases,
755
+ ...remoteDatabases.filter((rd) => !localDatabases.some((ld) => ld.name === rd.name)),
756
+ ],
757
+ });
758
+ this.controller.config = updatedConfig;
423
759
  }
424
- const databases = await fetchAllDatabases(this.controller.database);
425
- const selectedDatabases = await this.selectDatabases(databases, "Select databases to synchronize:");
426
- console.log(chalk.yellow("Configuring storage buckets..."));
427
- const updatedConfig = await this.configureBuckets(this.controller.config, selectedDatabases);
428
- console.log(chalk.yellow("Synchronizing configurations..."));
429
- await this.controller.synchronizeConfigurations(selectedDatabases, updatedConfig);
430
- console.log(chalk.green("Configuration synchronization completed."));
760
+ // Then sync functions
761
+ const { syncFunctions } = await inquirer.prompt([
762
+ {
763
+ type: "confirm",
764
+ name: "syncFunctions",
765
+ message: "Do you want to synchronize functions?",
766
+ default: true,
767
+ },
768
+ ]);
769
+ if (syncFunctions) {
770
+ const remoteFunctions = await this.controller.listAllFunctions();
771
+ const localFunctions = this.controller.config?.functions || [];
772
+ const allFunctions = [
773
+ ...remoteFunctions,
774
+ ...localFunctions.filter((f) => !remoteFunctions.some((rf) => rf.$id === f.$id)),
775
+ ];
776
+ for (const func of allFunctions) {
777
+ const hasLocal = localFunctions.some((lf) => lf.$id === func.$id);
778
+ const hasRemote = remoteFunctions.some((rf) => rf.$id === func.$id);
779
+ if (hasLocal && hasRemote) {
780
+ const { preference } = await inquirer.prompt([
781
+ {
782
+ type: "list",
783
+ name: "preference",
784
+ message: `Function "${func.name}" exists both locally and remotely. What would you like to do?`,
785
+ choices: [
786
+ {
787
+ name: "Keep local version (deploy to remote)",
788
+ value: "local",
789
+ },
790
+ { name: "Use remote version (download)", value: "remote" },
791
+ { name: "Skip this function", value: "skip" },
792
+ ],
793
+ },
794
+ ]);
795
+ if (preference === "local") {
796
+ await this.controller.deployFunction(func.name);
797
+ }
798
+ else if (preference === "remote") {
799
+ await downloadLatestFunctionDeployment(this.controller.appwriteServer, func.$id, join(this.controller.getAppwriteFolderPath(), "functions"));
800
+ }
801
+ }
802
+ else if (hasLocal) {
803
+ const { deploy } = await inquirer.prompt([
804
+ {
805
+ type: "confirm",
806
+ name: "deploy",
807
+ message: `Function "${func.name}" exists only locally. Deploy to remote?`,
808
+ default: true,
809
+ },
810
+ ]);
811
+ if (deploy) {
812
+ await this.controller.deployFunction(func.name);
813
+ }
814
+ }
815
+ else if (hasRemote) {
816
+ const { download } = await inquirer.prompt([
817
+ {
818
+ type: "confirm",
819
+ name: "download",
820
+ message: `Function "${func.name}" exists only remotely. Download locally?`,
821
+ default: true,
822
+ },
823
+ ]);
824
+ if (download) {
825
+ await downloadLatestFunctionDeployment(this.controller.appwriteServer, func.$id, join(this.controller.getAppwriteFolderPath(), "functions"));
826
+ }
827
+ }
828
+ }
829
+ }
830
+ console.log(chalk.green("✨ Configurations synchronized successfully!"));
431
831
  }
432
832
  async backupDatabase() {
433
833
  if (!this.controller.database) {
@@ -696,32 +1096,37 @@ export class InteractiveCLI {
696
1096
  }
697
1097
  }
698
1098
  async updateFunctionSpec() {
699
- const functions = await listFunctions(this.controller.appwriteServer, [
700
- Query.limit(1000),
701
- ]);
1099
+ const remoteFunctions = await listFunctions(this.controller.appwriteServer, [Query.limit(1000)]);
1100
+ const localFunctions = this.getLocalFunctions();
1101
+ const allFunctions = [
1102
+ ...remoteFunctions.functions,
1103
+ ...localFunctions.filter((f) => !remoteFunctions.functions.some((rf) => rf.name === f.name)),
1104
+ ];
702
1105
  const functionsToUpdate = await inquirer.prompt([
703
1106
  {
704
- type: 'checkbox',
705
- name: 'functionId',
706
- message: 'Select functions to update:',
707
- choices: functions.functions.map(f => ({
708
- name: `${f.name} (${f.$id})`,
709
- value: f.$id
1107
+ type: "checkbox",
1108
+ name: "functionId",
1109
+ message: "Select functions to update:",
1110
+ choices: allFunctions.map((f) => ({
1111
+ name: `${f.name} (${f.$id})${localFunctions.some((lf) => lf.name === f.name)
1112
+ ? " (Local)"
1113
+ : " (Remote)"}`,
1114
+ value: f.$id,
710
1115
  })),
711
1116
  loop: true,
712
- }
1117
+ },
713
1118
  ]);
714
1119
  const specifications = await listSpecifications(this.controller.appwriteServer);
715
1120
  const { specification } = await inquirer.prompt([
716
1121
  {
717
- type: 'list',
718
- name: 'specification',
719
- message: 'Select new specification:',
1122
+ type: "list",
1123
+ name: "specification",
1124
+ message: "Select new specification:",
720
1125
  choices: specifications.specifications.map((s) => ({
721
1126
  name: `${s.slug}`,
722
- value: s.slug
1127
+ value: s.slug,
723
1128
  })),
724
- }
1129
+ },
725
1130
  ]);
726
1131
  try {
727
1132
  for (const functionId of functionsToUpdate.functionId) {
@@ -730,7 +1135,7 @@ export class InteractiveCLI {
730
1135
  }
731
1136
  }
732
1137
  catch (error) {
733
- console.error(chalk.red('Error updating function specification:'), error);
1138
+ console.error(chalk.red("Error updating function specification:"), error);
734
1139
  }
735
1140
  }
736
1141
  }
package/dist/main.js CHANGED
File without changes
@@ -5,6 +5,7 @@ import { fetchAllDatabases } from "./databases.js";
5
5
  import { CollectionSchema, attributeSchema, AppwriteConfigSchema, permissionsSchema, attributesSchema, indexesSchema, parseAttribute, } from "appwrite-utils";
6
6
  import { getDatabaseFromConfig } from "./afterImportActions.js";
7
7
  import { listBuckets } from "../storage/methods.js";
8
+ import { listFunctions } from "../functions/methods.js";
8
9
  export class AppwriteToX {
9
10
  config;
10
11
  storage;
@@ -153,7 +154,37 @@ export class AppwriteToX {
153
154
  encryption: bucket.encryption,
154
155
  antivirus: bucket.antivirus,
155
156
  }));
156
- this.updatedConfig = updatedConfig;
157
+ const remoteFunctions = await listFunctions(this.config.appwriteClient, [
158
+ Query.limit(1000),
159
+ ]);
160
+ const functionDeployments = await Promise.all(remoteFunctions.functions.map(async (func) => {
161
+ const deployments = await this.config.appwriteClient.functions.listDeployments(func.$id, [Query.orderDesc("$createdAt"), Query.limit(1)]);
162
+ return {
163
+ function: func,
164
+ schemaStrings: deployments.deployments[0]?.schemaStrings || [],
165
+ };
166
+ }));
167
+ this.updatedConfig.functions = functionDeployments.map(({ function: func, schemaStrings }) => ({
168
+ $id: func.$id,
169
+ name: func.name,
170
+ runtime: func.runtime,
171
+ execute: func.execute,
172
+ events: func.events || [],
173
+ schedule: func.schedule || "",
174
+ timeout: func.timeout || 15,
175
+ enabled: func.enabled !== false,
176
+ logging: func.logging !== false,
177
+ entrypoint: func.entrypoint || "src/index.ts",
178
+ commands: func.commands || "npm install",
179
+ dirPath: `functions/${func.name}`,
180
+ specification: func.specification,
181
+ schemaStrings,
182
+ }));
183
+ // Make sure to update the config with all changes
184
+ this.updatedConfig = {
185
+ ...updatedConfig,
186
+ functions: this.updatedConfig.functions,
187
+ };
157
188
  }
158
189
  async toSchemas(databases) {
159
190
  await this.appwriteSync(this.config, databases);
@@ -5,6 +5,7 @@ export declare class SchemaGenerator {
5
5
  private appwriteFolderPath;
6
6
  constructor(config: AppwriteConfig, appwriteFolderPath: string);
7
7
  updateTsSchemas(): void;
8
+ updateConfig(config: AppwriteConfig): void;
8
9
  private extractRelationships;
9
10
  private addRelationship;
10
11
  generateSchemas(): void;