appwrite-utils-cli 0.9.999 → 0.10.0

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
@@ -147,6 +147,7 @@ This updated CLI ensures that developers have robust tools at their fingertips t
147
147
 
148
148
  ## Changelog
149
149
 
150
+ - 0.10.0: Fixed `synchronize configurations` for functions, now you do not need to deploy the function first
150
151
  - 0.9.999: Fixed Functions, looks for `./functions` in addition to `appwriteConfigFolder/functions`
151
152
  - 0.9.998: Fixed transfer finally, added `--targetDbId` and `--sourceDbId` as aliases
152
153
  - 0.9.994: Added function deployment management, in BETA, and fixed document transfer between databases
@@ -10,7 +10,7 @@ import { AppwriteFunctionSchema, parseAttribute, PermissionToAppwritePermission,
10
10
  import { ulid } from "ulidx";
11
11
  import chalk from "chalk";
12
12
  import { DateTime } from "luxon";
13
- import { createFunctionTemplate, deleteFunction, downloadLatestFunctionDeployment, listFunctions, listSpecifications, } from "./functions/methods.js";
13
+ import { createFunctionTemplate, deleteFunction, downloadLatestFunctionDeployment, getFunction, listFunctions, listSpecifications, } from "./functions/methods.js";
14
14
  import { deployLocalFunction } from "./functions/deployments.js";
15
15
  import { join } from "node:path";
16
16
  import fs from "node:fs";
@@ -361,83 +361,127 @@ export class InteractiveCLI {
361
361
  console.log(chalk.red("Invalid function configuration"));
362
362
  return;
363
363
  }
364
- let functionPath = join(this.controller.getAppwriteFolderPath(), "functions", functionConfig.name);
365
- if (!fs.existsSync(functionPath)) {
366
- console.log(chalk.yellow(`Function not found in primary location, searching subdirectories...`));
367
- const foundPath = await this.findFunctionInSubdirectories(this.controller.getAppwriteFolderPath(), functionConfig.name);
368
- if (foundPath) {
369
- console.log(chalk.green(`Found function at: ${foundPath}`));
370
- functionPath = foundPath;
371
- functionConfig.dirPath = foundPath;
364
+ const { shouldDownload } = await inquirer.prompt([
365
+ {
366
+ type: "confirm",
367
+ name: "shouldDownload",
368
+ message: "Would you like to download the latest deployment?",
369
+ default: false,
370
+ },
371
+ ]);
372
+ if (shouldDownload) {
373
+ try {
374
+ console.log(chalk.blue("Downloading latest deployment..."));
375
+ const { path: downloadedPath, function: remoteFunction } = await downloadLatestFunctionDeployment(this.controller.appwriteServer, functionConfig.$id, join(this.controller.getAppwriteFolderPath(), "functions"));
376
+ console.log(chalk.green(`✨ Function downloaded to ${downloadedPath}`));
377
+ // Update the config and functions array safely
378
+ this.controller.config.functions =
379
+ this.controller.config.functions || [];
380
+ const newFunction = {
381
+ $id: remoteFunction.$id,
382
+ name: remoteFunction.name,
383
+ runtime: remoteFunction.runtime,
384
+ execute: remoteFunction.execute || [],
385
+ events: remoteFunction.events || [],
386
+ schedule: remoteFunction.schedule || "",
387
+ timeout: remoteFunction.timeout || 15,
388
+ enabled: remoteFunction.enabled !== false,
389
+ logging: remoteFunction.logging !== false,
390
+ entrypoint: remoteFunction.entrypoint || "src/index.ts",
391
+ commands: remoteFunction.commands || "npm install",
392
+ dirPath: downloadedPath,
393
+ scopes: (remoteFunction.scopes || []),
394
+ installationId: remoteFunction.installationId,
395
+ providerRepositoryId: remoteFunction.providerRepositoryId,
396
+ providerBranch: remoteFunction.providerBranch,
397
+ providerSilentMode: remoteFunction.providerSilentMode,
398
+ providerRootDirectory: remoteFunction.providerRootDirectory,
399
+ specification: remoteFunction.specification,
400
+ };
401
+ const existingIndex = this.controller.config.functions.findIndex((f) => f?.$id === remoteFunction.$id);
402
+ if (existingIndex >= 0) {
403
+ this.controller.config.functions[existingIndex] = newFunction;
404
+ }
405
+ else {
406
+ this.controller.config.functions.push(newFunction);
407
+ }
408
+ const schemaGenerator = new SchemaGenerator(this.controller.config, this.controller.getAppwriteFolderPath());
409
+ schemaGenerator.updateConfig(this.controller.config);
410
+ console.log(chalk.green("✨ Updated appwriteConfig.ts with new function"));
411
+ await this.controller.reloadConfig();
412
+ functionConfig.dirPath = downloadedPath;
372
413
  }
373
- else {
374
- console.log(chalk.yellow(`Function ${functionConfig.name} not found locally in any subdirectory`));
375
- const { shouldDownload } = await inquirer.prompt([
376
- {
377
- type: "confirm",
378
- name: "shouldDownload",
379
- message: "Would you like to download the latest deployment?",
380
- default: true,
381
- },
382
- ]);
383
- if (shouldDownload) {
384
- try {
385
- console.log(chalk.blue("Downloading latest deployment..."));
386
- const { path: downloadedPath, function: remoteFunction } = await downloadLatestFunctionDeployment(this.controller.appwriteServer, functionConfig.$id, join(this.controller.getAppwriteFolderPath(), "functions"));
387
- console.log(chalk.green(`✨ Function downloaded to ${downloadedPath}`));
388
- // Update the config and functions array safely
389
- this.controller.config.functions =
390
- this.controller.config.functions || [];
391
- const newFunction = {
392
- $id: remoteFunction.$id,
393
- name: remoteFunction.name,
394
- runtime: remoteFunction.runtime,
395
- execute: remoteFunction.execute || [],
396
- events: remoteFunction.events || [],
397
- schedule: remoteFunction.schedule || "",
398
- timeout: remoteFunction.timeout || 15,
399
- enabled: remoteFunction.enabled !== false,
400
- logging: remoteFunction.logging !== false,
401
- entrypoint: remoteFunction.entrypoint || "src/index.ts",
402
- commands: remoteFunction.commands || "npm install",
403
- dirPath: downloadedPath,
404
- scopes: (remoteFunction.scopes || []),
405
- installationId: remoteFunction.installationId,
406
- providerRepositoryId: remoteFunction.providerRepositoryId,
407
- providerBranch: remoteFunction.providerBranch,
408
- providerSilentMode: remoteFunction.providerSilentMode,
409
- providerRootDirectory: remoteFunction.providerRootDirectory,
410
- specification: remoteFunction.specification,
411
- };
412
- const existingIndex = this.controller.config.functions.findIndex((f) => f?.$id === remoteFunction.$id);
413
- if (existingIndex >= 0) {
414
- this.controller.config.functions[existingIndex] = newFunction;
415
- }
416
- else {
417
- this.controller.config.functions.push(newFunction);
418
- }
419
- const schemaGenerator = new SchemaGenerator(this.controller.config, this.controller.getAppwriteFolderPath());
420
- schemaGenerator.updateConfig(this.controller.config);
421
- console.log(chalk.green("✨ Updated appwriteConfig.ts with new function"));
422
- await this.controller.reloadConfig();
423
- functionConfig.dirPath = downloadedPath;
424
- }
425
- catch (error) {
426
- console.error(chalk.red("Failed to download function deployment:"), error);
427
- return;
428
- }
414
+ catch (error) {
415
+ console.error(chalk.red("Failed to download function deployment:"), error);
416
+ return;
417
+ }
418
+ }
419
+ else {
420
+ console.log(chalk.yellow("Skipping download, updating config only..."));
421
+ try {
422
+ // Update the config with remote function info
423
+ this.controller.config.functions =
424
+ this.controller.config.functions || [];
425
+ const remoteFunction = await getFunction(this.controller.appwriteServer, functionConfig.$id);
426
+ const newFunction = {
427
+ $id: remoteFunction.$id,
428
+ name: remoteFunction.name,
429
+ runtime: remoteFunction.runtime,
430
+ execute: remoteFunction.execute || [],
431
+ events: remoteFunction.events || [],
432
+ schedule: remoteFunction.schedule || "",
433
+ timeout: remoteFunction.timeout || 15,
434
+ enabled: remoteFunction.enabled !== false,
435
+ logging: remoteFunction.logging !== false,
436
+ entrypoint: remoteFunction.entrypoint || "src/index.ts",
437
+ commands: remoteFunction.commands || "npm install",
438
+ scopes: (remoteFunction.scopes || []),
439
+ installationId: remoteFunction.installationId,
440
+ providerRepositoryId: remoteFunction.providerRepositoryId,
441
+ providerBranch: remoteFunction.providerBranch,
442
+ providerSilentMode: remoteFunction.providerSilentMode,
443
+ providerRootDirectory: remoteFunction.providerRootDirectory,
444
+ specification: remoteFunction.specification,
445
+ };
446
+ const existingIndex = this.controller.config.functions.findIndex((f) => f?.$id === remoteFunction.$id);
447
+ if (existingIndex >= 0) {
448
+ this.controller.config.functions[existingIndex] = newFunction;
429
449
  }
430
450
  else {
431
- console.log(chalk.yellow("Deployment cancelled"));
432
- return;
451
+ this.controller.config.functions.push(newFunction);
433
452
  }
453
+ const schemaGenerator = new SchemaGenerator(this.controller.config, this.controller.getAppwriteFolderPath());
454
+ schemaGenerator.updateConfig(this.controller.config);
455
+ console.log(chalk.green("✨ Updated appwriteConfig.ts with function info"));
456
+ await this.controller.reloadConfig();
457
+ }
458
+ catch (error) {
459
+ console.error(chalk.red("Failed to update function config:"), error);
460
+ return;
434
461
  }
435
462
  }
436
- if (!this.controller.appwriteServer) {
437
- console.log(chalk.red("Appwrite server not initialized"));
438
- return;
463
+ // Only try to deploy if we didn't just download
464
+ if (!shouldDownload) {
465
+ let functionPath = join(this.controller.getAppwriteFolderPath(), "functions", functionConfig.name);
466
+ if (!fs.existsSync(functionPath)) {
467
+ console.log(chalk.yellow(`Function not found in primary location, searching subdirectories...`));
468
+ const foundPath = await this.findFunctionInSubdirectories(this.controller.getAppwriteFolderPath(), functionConfig.name);
469
+ if (foundPath) {
470
+ console.log(chalk.green(`Found function at: ${foundPath}`));
471
+ functionPath = foundPath;
472
+ functionConfig.dirPath = foundPath;
473
+ }
474
+ else {
475
+ console.log(chalk.red(`Function ${functionConfig.name} not found locally in any subdirectory. Cannot deploy.`));
476
+ return;
477
+ }
478
+ }
479
+ if (!this.controller.appwriteServer) {
480
+ console.log(chalk.red("Appwrite server not initialized"));
481
+ return;
482
+ }
483
+ await deployLocalFunction(this.controller.appwriteServer, functionConfig.name, functionConfig);
439
484
  }
440
- await deployLocalFunction(this.controller.appwriteServer, functionConfig.name, functionConfig);
441
485
  }
442
486
  async deleteFunction() {
443
487
  const functions = await this.selectFunctions("Select functions to delete:", true, false);
@@ -805,55 +849,153 @@ export class InteractiveCLI {
805
849
  const hasLocal = localFunctions.some((lf) => lf.$id === func.$id);
806
850
  const hasRemote = remoteFunctions.some((rf) => rf.$id === func.$id);
807
851
  if (hasLocal && hasRemote) {
852
+ // First try to find the function locally
853
+ let functionPath = join(this.controller.getAppwriteFolderPath(), "functions", func.name);
854
+ if (!fs.existsSync(functionPath)) {
855
+ console.log(chalk.yellow(`Function not found in primary location, searching subdirectories...`));
856
+ const foundPath = await this.findFunctionInSubdirectories(this.controller.getAppwriteFolderPath(), func.name);
857
+ if (foundPath) {
858
+ console.log(chalk.green(`Found function at: ${foundPath}`));
859
+ functionPath = foundPath;
860
+ }
861
+ }
808
862
  const { preference } = await inquirer.prompt([
809
863
  {
810
864
  type: "list",
811
865
  name: "preference",
812
- message: `Function "${func.name}" exists both locally and remotely. What would you like to do?`,
866
+ message: `Function "${func.name}" ${functionPath ? "found at " + functionPath : "not found locally"}. What would you like to do?`,
813
867
  choices: [
814
- {
815
- name: "Keep local version (deploy to remote)",
816
- value: "local",
817
- },
868
+ ...(functionPath
869
+ ? [
870
+ {
871
+ name: "Keep local version (deploy to remote)",
872
+ value: "local",
873
+ },
874
+ ]
875
+ : []),
818
876
  { name: "Use remote version (download)", value: "remote" },
877
+ { name: "Update config only", value: "config" },
819
878
  { name: "Skip this function", value: "skip" },
820
879
  ],
821
880
  },
822
881
  ]);
823
- if (preference === "local") {
882
+ if (preference === "local" && functionPath) {
824
883
  await this.controller.deployFunction(func.name);
825
884
  }
826
885
  else if (preference === "remote") {
827
886
  await downloadLatestFunctionDeployment(this.controller.appwriteServer, func.$id, join(this.controller.getAppwriteFolderPath(), "functions"));
828
887
  }
888
+ else if (preference === "config") {
889
+ const remoteFunction = await getFunction(this.controller.appwriteServer, func.$id);
890
+ const newFunction = {
891
+ $id: remoteFunction.$id,
892
+ name: remoteFunction.name,
893
+ runtime: remoteFunction.runtime,
894
+ execute: remoteFunction.execute || [],
895
+ events: remoteFunction.events || [],
896
+ schedule: remoteFunction.schedule || "",
897
+ timeout: remoteFunction.timeout || 15,
898
+ enabled: remoteFunction.enabled !== false,
899
+ logging: remoteFunction.logging !== false,
900
+ entrypoint: remoteFunction.entrypoint || "src/index.ts",
901
+ commands: remoteFunction.commands || "npm install",
902
+ scopes: (remoteFunction.scopes || []),
903
+ installationId: remoteFunction.installationId,
904
+ providerRepositoryId: remoteFunction.providerRepositoryId,
905
+ providerBranch: remoteFunction.providerBranch,
906
+ providerSilentMode: remoteFunction.providerSilentMode,
907
+ providerRootDirectory: remoteFunction.providerRootDirectory,
908
+ specification: remoteFunction.specification,
909
+ };
910
+ const existingIndex = this.controller.config.functions.findIndex((f) => f.$id === remoteFunction.$id);
911
+ if (existingIndex >= 0) {
912
+ this.controller.config.functions[existingIndex] = newFunction;
913
+ }
914
+ else {
915
+ this.controller.config.functions.push(newFunction);
916
+ }
917
+ console.log(chalk.green(`Updated config for function: ${func.name}`));
918
+ }
829
919
  }
830
920
  else if (hasLocal) {
831
- const { deploy } = await inquirer.prompt([
921
+ // Similar check for local-only functions
922
+ let functionPath = join(this.controller.getAppwriteFolderPath(), "functions", func.name);
923
+ if (!fs.existsSync(functionPath)) {
924
+ const foundPath = await this.findFunctionInSubdirectories(this.controller.getAppwriteFolderPath(), func.name);
925
+ if (foundPath) {
926
+ functionPath = foundPath;
927
+ }
928
+ }
929
+ const { action } = await inquirer.prompt([
832
930
  {
833
- type: "confirm",
834
- name: "deploy",
835
- message: `Function "${func.name}" exists only locally. Deploy to remote?`,
836
- default: true,
931
+ type: "list",
932
+ name: "action",
933
+ message: `Function "${func.name}" ${functionPath ? "found at " + functionPath : "not found locally"}. What would you like to do?`,
934
+ choices: [
935
+ ...(functionPath
936
+ ? [
937
+ {
938
+ name: "Deploy to remote",
939
+ value: "deploy",
940
+ },
941
+ ]
942
+ : []),
943
+ { name: "Skip this function", value: "skip" },
944
+ ],
837
945
  },
838
946
  ]);
839
- if (deploy) {
947
+ if (action === "deploy" && functionPath) {
840
948
  await this.controller.deployFunction(func.name);
841
949
  }
842
950
  }
843
951
  else if (hasRemote) {
844
- const { download } = await inquirer.prompt([
952
+ const { action } = await inquirer.prompt([
845
953
  {
846
- type: "confirm",
847
- name: "download",
848
- message: `Function "${func.name}" exists only remotely. Download locally?`,
849
- default: true,
954
+ type: "list",
955
+ name: "action",
956
+ message: `Function "${func.name}" exists only remotely. What would you like to do?`,
957
+ choices: [
958
+ { name: "Update config only", value: "config" },
959
+ { name: "Download locally", value: "download" },
960
+ { name: "Skip this function", value: "skip" },
961
+ ],
850
962
  },
851
963
  ]);
852
- if (download) {
964
+ if (action === "download") {
853
965
  await downloadLatestFunctionDeployment(this.controller.appwriteServer, func.$id, join(this.controller.getAppwriteFolderPath(), "functions"));
854
966
  }
967
+ else if (action === "config") {
968
+ const remoteFunction = await getFunction(this.controller.appwriteServer, func.$id);
969
+ const newFunction = {
970
+ $id: remoteFunction.$id,
971
+ name: remoteFunction.name,
972
+ runtime: remoteFunction.runtime,
973
+ execute: remoteFunction.execute || [],
974
+ events: remoteFunction.events || [],
975
+ schedule: remoteFunction.schedule || "",
976
+ timeout: remoteFunction.timeout || 15,
977
+ enabled: remoteFunction.enabled !== false,
978
+ logging: remoteFunction.logging !== false,
979
+ entrypoint: remoteFunction.entrypoint || "src/index.ts",
980
+ commands: remoteFunction.commands || "npm install",
981
+ scopes: (remoteFunction.scopes || []),
982
+ installationId: remoteFunction.installationId,
983
+ providerRepositoryId: remoteFunction.providerRepositoryId,
984
+ providerBranch: remoteFunction.providerBranch,
985
+ providerSilentMode: remoteFunction.providerSilentMode,
986
+ providerRootDirectory: remoteFunction.providerRootDirectory,
987
+ specification: remoteFunction.specification,
988
+ };
989
+ this.controller.config.functions =
990
+ this.controller.config.functions || [];
991
+ this.controller.config.functions.push(newFunction);
992
+ console.log(chalk.green(`Added config for remote function: ${func.name}`));
993
+ }
855
994
  }
856
995
  }
996
+ // Update schemas after all changes
997
+ const schemaGenerator = new SchemaGenerator(this.controller.config, this.controller.getAppwriteFolderPath());
998
+ schemaGenerator.updateConfig(this.controller.config);
857
999
  }
858
1000
  console.log(chalk.green("✨ Configurations synchronized successfully!"));
859
1001
  }
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": "0.9.999",
4
+ "version": "0.10.0",
5
5
  "main": "src/main.ts",
6
6
  "type": "module",
7
7
  "repository": {
@@ -35,6 +35,7 @@ import {
35
35
  createFunctionTemplate,
36
36
  deleteFunction,
37
37
  downloadLatestFunctionDeployment,
38
+ getFunction,
38
39
  listFunctions,
39
40
  listSpecifications,
40
41
  } from "./functions/methods.js";
@@ -486,127 +487,183 @@ export class InteractiveCLI {
486
487
  return;
487
488
  }
488
489
 
489
- let functionPath = join(
490
- this.controller.getAppwriteFolderPath(),
491
- "functions",
492
- functionConfig.name
493
- );
490
+ const { shouldDownload } = await inquirer.prompt([
491
+ {
492
+ type: "confirm",
493
+ name: "shouldDownload",
494
+ message: "Would you like to download the latest deployment?",
495
+ default: false,
496
+ },
497
+ ]);
494
498
 
495
- if (!fs.existsSync(functionPath)) {
496
- console.log(
497
- chalk.yellow(
498
- `Function not found in primary location, searching subdirectories...`
499
- )
500
- );
501
- const foundPath = await this.findFunctionInSubdirectories(
502
- this.controller.getAppwriteFolderPath(),
503
- functionConfig.name
504
- );
499
+ if (shouldDownload) {
500
+ try {
501
+ console.log(chalk.blue("Downloading latest deployment..."));
502
+ const { path: downloadedPath, function: remoteFunction } =
503
+ await downloadLatestFunctionDeployment(
504
+ this.controller.appwriteServer!,
505
+ functionConfig.$id,
506
+ join(this.controller.getAppwriteFolderPath(), "functions")
507
+ );
508
+ console.log(chalk.green(`✨ Function downloaded to ${downloadedPath}`));
509
+
510
+ // Update the config and functions array safely
511
+ this.controller.config.functions =
512
+ this.controller.config.functions || [];
513
+
514
+ const newFunction = {
515
+ $id: remoteFunction.$id,
516
+ name: remoteFunction.name,
517
+ runtime: remoteFunction.runtime as Runtime,
518
+ execute: remoteFunction.execute || [],
519
+ events: remoteFunction.events || [],
520
+ schedule: remoteFunction.schedule || "",
521
+ timeout: remoteFunction.timeout || 15,
522
+ enabled: remoteFunction.enabled !== false,
523
+ logging: remoteFunction.logging !== false,
524
+ entrypoint: remoteFunction.entrypoint || "src/index.ts",
525
+ commands: remoteFunction.commands || "npm install",
526
+ dirPath: downloadedPath,
527
+ scopes: (remoteFunction.scopes || []) as FunctionScope[],
528
+ installationId: remoteFunction.installationId,
529
+ providerRepositoryId: remoteFunction.providerRepositoryId,
530
+ providerBranch: remoteFunction.providerBranch,
531
+ providerSilentMode: remoteFunction.providerSilentMode,
532
+ providerRootDirectory: remoteFunction.providerRootDirectory,
533
+ specification: remoteFunction.specification as Specification,
534
+ };
535
+
536
+ const existingIndex = this.controller.config.functions.findIndex(
537
+ (f) => f?.$id === remoteFunction.$id
538
+ );
505
539
 
506
- if (foundPath) {
507
- console.log(chalk.green(`Found function at: ${foundPath}`));
508
- functionPath = foundPath;
509
- functionConfig.dirPath = foundPath;
510
- } else {
540
+ if (existingIndex >= 0) {
541
+ this.controller.config.functions[existingIndex] = newFunction;
542
+ } else {
543
+ this.controller.config.functions.push(newFunction);
544
+ }
545
+
546
+ const schemaGenerator = new SchemaGenerator(
547
+ this.controller.config,
548
+ this.controller.getAppwriteFolderPath()
549
+ );
550
+ schemaGenerator.updateConfig(this.controller.config);
511
551
  console.log(
512
- chalk.yellow(
513
- `Function ${functionConfig.name} not found locally in any subdirectory`
514
- )
552
+ chalk.green("✨ Updated appwriteConfig.ts with new function")
515
553
  );
516
554
 
517
- const { shouldDownload } = await inquirer.prompt([
518
- {
519
- type: "confirm",
520
- name: "shouldDownload",
521
- message: "Would you like to download the latest deployment?",
522
- default: true,
523
- },
524
- ]);
555
+ await this.controller.reloadConfig();
556
+ functionConfig.dirPath = downloadedPath;
557
+ } catch (error) {
558
+ console.error(
559
+ chalk.red("Failed to download function deployment:"),
560
+ error
561
+ );
562
+ return;
563
+ }
564
+ } else {
565
+ console.log(chalk.yellow("Skipping download, updating config only..."));
566
+ try {
567
+ // Update the config with remote function info
568
+ this.controller.config.functions =
569
+ this.controller.config.functions || [];
525
570
 
526
- if (shouldDownload) {
527
- try {
528
- console.log(chalk.blue("Downloading latest deployment..."));
529
- const { path: downloadedPath, function: remoteFunction } =
530
- await downloadLatestFunctionDeployment(
531
- this.controller.appwriteServer!,
532
- functionConfig.$id,
533
- join(this.controller.getAppwriteFolderPath(), "functions")
534
- );
535
- console.log(
536
- chalk.green(`✨ Function downloaded to ${downloadedPath}`)
537
- );
571
+ const remoteFunction = await getFunction(
572
+ this.controller.appwriteServer!,
573
+ functionConfig.$id
574
+ );
538
575
 
539
- // Update the config and functions array safely
540
- this.controller.config.functions =
541
- this.controller.config.functions || [];
576
+ const newFunction = {
577
+ $id: remoteFunction.$id,
578
+ name: remoteFunction.name,
579
+ runtime: remoteFunction.runtime as Runtime,
580
+ execute: remoteFunction.execute || [],
581
+ events: remoteFunction.events || [],
582
+ schedule: remoteFunction.schedule || "",
583
+ timeout: remoteFunction.timeout || 15,
584
+ enabled: remoteFunction.enabled !== false,
585
+ logging: remoteFunction.logging !== false,
586
+ entrypoint: remoteFunction.entrypoint || "src/index.ts",
587
+ commands: remoteFunction.commands || "npm install",
588
+ scopes: (remoteFunction.scopes || []) as FunctionScope[],
589
+ installationId: remoteFunction.installationId,
590
+ providerRepositoryId: remoteFunction.providerRepositoryId,
591
+ providerBranch: remoteFunction.providerBranch,
592
+ providerSilentMode: remoteFunction.providerSilentMode,
593
+ providerRootDirectory: remoteFunction.providerRootDirectory,
594
+ specification: remoteFunction.specification as Specification,
595
+ };
596
+
597
+ const existingIndex = this.controller.config.functions.findIndex(
598
+ (f) => f?.$id === remoteFunction.$id
599
+ );
542
600
 
543
- const newFunction = {
544
- $id: remoteFunction.$id,
545
- name: remoteFunction.name,
546
- runtime: remoteFunction.runtime as Runtime,
547
- execute: remoteFunction.execute || [],
548
- events: remoteFunction.events || [],
549
- schedule: remoteFunction.schedule || "",
550
- timeout: remoteFunction.timeout || 15,
551
- enabled: remoteFunction.enabled !== false,
552
- logging: remoteFunction.logging !== false,
553
- entrypoint: remoteFunction.entrypoint || "src/index.ts",
554
- commands: remoteFunction.commands || "npm install",
555
- dirPath: downloadedPath,
556
- scopes: (remoteFunction.scopes || []) as FunctionScope[],
557
- installationId: remoteFunction.installationId,
558
- providerRepositoryId: remoteFunction.providerRepositoryId,
559
- providerBranch: remoteFunction.providerBranch,
560
- providerSilentMode: remoteFunction.providerSilentMode,
561
- providerRootDirectory: remoteFunction.providerRootDirectory,
562
- specification: remoteFunction.specification as Specification,
563
- };
601
+ if (existingIndex >= 0) {
602
+ this.controller.config.functions[existingIndex] = newFunction;
603
+ } else {
604
+ this.controller.config.functions.push(newFunction);
605
+ }
564
606
 
565
- const existingIndex = this.controller.config.functions.findIndex(
566
- (f) => f?.$id === remoteFunction.$id
567
- );
607
+ const schemaGenerator = new SchemaGenerator(
608
+ this.controller.config,
609
+ this.controller.getAppwriteFolderPath()
610
+ );
611
+ schemaGenerator.updateConfig(this.controller.config);
612
+ console.log(
613
+ chalk.green("✨ Updated appwriteConfig.ts with function info")
614
+ );
568
615
 
569
- if (existingIndex >= 0) {
570
- this.controller.config.functions[existingIndex] = newFunction;
571
- } else {
572
- this.controller.config.functions.push(newFunction);
573
- }
616
+ await this.controller.reloadConfig();
617
+ } catch (error) {
618
+ console.error(chalk.red("Failed to update function config:"), error);
619
+ return;
620
+ }
621
+ }
574
622
 
575
- const schemaGenerator = new SchemaGenerator(
576
- this.controller.config,
577
- this.controller.getAppwriteFolderPath()
578
- );
579
- schemaGenerator.updateConfig(this.controller.config);
580
- console.log(
581
- chalk.green("✨ Updated appwriteConfig.ts with new function")
582
- );
623
+ // Only try to deploy if we didn't just download
624
+ if (!shouldDownload) {
625
+ let functionPath = join(
626
+ this.controller.getAppwriteFolderPath(),
627
+ "functions",
628
+ functionConfig.name
629
+ );
583
630
 
584
- await this.controller.reloadConfig();
585
- functionConfig.dirPath = downloadedPath;
586
- } catch (error) {
587
- console.error(
588
- chalk.red("Failed to download function deployment:"),
589
- error
590
- );
591
- return;
592
- }
631
+ if (!fs.existsSync(functionPath)) {
632
+ console.log(
633
+ chalk.yellow(
634
+ `Function not found in primary location, searching subdirectories...`
635
+ )
636
+ );
637
+ const foundPath = await this.findFunctionInSubdirectories(
638
+ this.controller.getAppwriteFolderPath(),
639
+ functionConfig.name
640
+ );
641
+
642
+ if (foundPath) {
643
+ console.log(chalk.green(`Found function at: ${foundPath}`));
644
+ functionPath = foundPath;
645
+ functionConfig.dirPath = foundPath;
593
646
  } else {
594
- console.log(chalk.yellow("Deployment cancelled"));
647
+ console.log(
648
+ chalk.red(
649
+ `Function ${functionConfig.name} not found locally in any subdirectory. Cannot deploy.`
650
+ )
651
+ );
595
652
  return;
596
653
  }
597
654
  }
598
- }
599
655
 
600
- if (!this.controller.appwriteServer) {
601
- console.log(chalk.red("Appwrite server not initialized"));
602
- return;
603
- }
656
+ if (!this.controller.appwriteServer) {
657
+ console.log(chalk.red("Appwrite server not initialized"));
658
+ return;
659
+ }
604
660
 
605
- await deployLocalFunction(
606
- this.controller.appwriteServer,
607
- functionConfig.name,
608
- functionConfig
609
- );
661
+ await deployLocalFunction(
662
+ this.controller.appwriteServer,
663
+ functionConfig.name,
664
+ functionConfig
665
+ );
666
+ }
610
667
  }
611
668
 
612
669
  private async deleteFunction(): Promise<void> {
@@ -1102,23 +1159,54 @@ export class InteractiveCLI {
1102
1159
  const hasRemote = remoteFunctions.some((rf) => rf.$id === func.$id);
1103
1160
 
1104
1161
  if (hasLocal && hasRemote) {
1162
+ // First try to find the function locally
1163
+ let functionPath = join(
1164
+ this.controller!.getAppwriteFolderPath(),
1165
+ "functions",
1166
+ func.name
1167
+ );
1168
+
1169
+ if (!fs.existsSync(functionPath)) {
1170
+ console.log(
1171
+ chalk.yellow(
1172
+ `Function not found in primary location, searching subdirectories...`
1173
+ )
1174
+ );
1175
+ const foundPath = await this.findFunctionInSubdirectories(
1176
+ this.controller!.getAppwriteFolderPath(),
1177
+ func.name
1178
+ );
1179
+
1180
+ if (foundPath) {
1181
+ console.log(chalk.green(`Found function at: ${foundPath}`));
1182
+ functionPath = foundPath;
1183
+ }
1184
+ }
1185
+
1105
1186
  const { preference } = await inquirer.prompt([
1106
1187
  {
1107
1188
  type: "list",
1108
1189
  name: "preference",
1109
- message: `Function "${func.name}" exists both locally and remotely. What would you like to do?`,
1190
+ message: `Function "${func.name}" ${
1191
+ functionPath ? "found at " + functionPath : "not found locally"
1192
+ }. What would you like to do?`,
1110
1193
  choices: [
1111
- {
1112
- name: "Keep local version (deploy to remote)",
1113
- value: "local",
1114
- },
1194
+ ...(functionPath
1195
+ ? [
1196
+ {
1197
+ name: "Keep local version (deploy to remote)",
1198
+ value: "local",
1199
+ },
1200
+ ]
1201
+ : []),
1115
1202
  { name: "Use remote version (download)", value: "remote" },
1203
+ { name: "Update config only", value: "config" },
1116
1204
  { name: "Skip this function", value: "skip" },
1117
1205
  ],
1118
1206
  },
1119
1207
  ]);
1120
1208
 
1121
- if (preference === "local") {
1209
+ if (preference === "local" && functionPath) {
1122
1210
  await this.controller!.deployFunction(func.name);
1123
1211
  } else if (preference === "remote") {
1124
1212
  await downloadLatestFunctionDeployment(
@@ -1126,39 +1214,152 @@ export class InteractiveCLI {
1126
1214
  func.$id,
1127
1215
  join(this.controller!.getAppwriteFolderPath(), "functions")
1128
1216
  );
1217
+ } else if (preference === "config") {
1218
+ const remoteFunction = await getFunction(
1219
+ this.controller!.appwriteServer!,
1220
+ func.$id
1221
+ );
1222
+
1223
+ const newFunction = {
1224
+ $id: remoteFunction.$id,
1225
+ name: remoteFunction.name,
1226
+ runtime: remoteFunction.runtime as Runtime,
1227
+ execute: remoteFunction.execute || [],
1228
+ events: remoteFunction.events || [],
1229
+ schedule: remoteFunction.schedule || "",
1230
+ timeout: remoteFunction.timeout || 15,
1231
+ enabled: remoteFunction.enabled !== false,
1232
+ logging: remoteFunction.logging !== false,
1233
+ entrypoint: remoteFunction.entrypoint || "src/index.ts",
1234
+ commands: remoteFunction.commands || "npm install",
1235
+ scopes: (remoteFunction.scopes || []) as FunctionScope[],
1236
+ installationId: remoteFunction.installationId,
1237
+ providerRepositoryId: remoteFunction.providerRepositoryId,
1238
+ providerBranch: remoteFunction.providerBranch,
1239
+ providerSilentMode: remoteFunction.providerSilentMode,
1240
+ providerRootDirectory: remoteFunction.providerRootDirectory,
1241
+ specification: remoteFunction.specification as Specification,
1242
+ };
1243
+
1244
+ const existingIndex = this.controller!.config!.functions!.findIndex(
1245
+ (f) => f.$id === remoteFunction.$id
1246
+ );
1247
+
1248
+ if (existingIndex >= 0) {
1249
+ this.controller!.config!.functions![existingIndex] = newFunction;
1250
+ } else {
1251
+ this.controller!.config!.functions!.push(newFunction);
1252
+ }
1253
+ console.log(
1254
+ chalk.green(`Updated config for function: ${func.name}`)
1255
+ );
1129
1256
  }
1130
1257
  } else if (hasLocal) {
1131
- const { deploy } = await inquirer.prompt([
1258
+ // Similar check for local-only functions
1259
+ let functionPath = join(
1260
+ this.controller!.getAppwriteFolderPath(),
1261
+ "functions",
1262
+ func.name
1263
+ );
1264
+
1265
+ if (!fs.existsSync(functionPath)) {
1266
+ const foundPath = await this.findFunctionInSubdirectories(
1267
+ this.controller!.getAppwriteFolderPath(),
1268
+ func.name
1269
+ );
1270
+
1271
+ if (foundPath) {
1272
+ functionPath = foundPath;
1273
+ }
1274
+ }
1275
+
1276
+ const { action } = await inquirer.prompt([
1132
1277
  {
1133
- type: "confirm",
1134
- name: "deploy",
1135
- message: `Function "${func.name}" exists only locally. Deploy to remote?`,
1136
- default: true,
1278
+ type: "list",
1279
+ name: "action",
1280
+ message: `Function "${func.name}" ${
1281
+ functionPath ? "found at " + functionPath : "not found locally"
1282
+ }. What would you like to do?`,
1283
+ choices: [
1284
+ ...(functionPath
1285
+ ? [
1286
+ {
1287
+ name: "Deploy to remote",
1288
+ value: "deploy",
1289
+ },
1290
+ ]
1291
+ : []),
1292
+ { name: "Skip this function", value: "skip" },
1293
+ ],
1137
1294
  },
1138
1295
  ]);
1139
1296
 
1140
- if (deploy) {
1297
+ if (action === "deploy" && functionPath) {
1141
1298
  await this.controller!.deployFunction(func.name);
1142
1299
  }
1143
1300
  } else if (hasRemote) {
1144
- const { download } = await inquirer.prompt([
1301
+ const { action } = await inquirer.prompt([
1145
1302
  {
1146
- type: "confirm",
1147
- name: "download",
1148
- message: `Function "${func.name}" exists only remotely. Download locally?`,
1149
- default: true,
1303
+ type: "list",
1304
+ name: "action",
1305
+ message: `Function "${func.name}" exists only remotely. What would you like to do?`,
1306
+ choices: [
1307
+ { name: "Update config only", value: "config" },
1308
+ { name: "Download locally", value: "download" },
1309
+ { name: "Skip this function", value: "skip" },
1310
+ ],
1150
1311
  },
1151
1312
  ]);
1152
1313
 
1153
- if (download) {
1314
+ if (action === "download") {
1154
1315
  await downloadLatestFunctionDeployment(
1155
1316
  this.controller!.appwriteServer!,
1156
1317
  func.$id,
1157
1318
  join(this.controller!.getAppwriteFolderPath(), "functions")
1158
1319
  );
1320
+ } else if (action === "config") {
1321
+ const remoteFunction = await getFunction(
1322
+ this.controller!.appwriteServer!,
1323
+ func.$id
1324
+ );
1325
+
1326
+ const newFunction = {
1327
+ $id: remoteFunction.$id,
1328
+ name: remoteFunction.name,
1329
+ runtime: remoteFunction.runtime as Runtime,
1330
+ execute: remoteFunction.execute || [],
1331
+ events: remoteFunction.events || [],
1332
+ schedule: remoteFunction.schedule || "",
1333
+ timeout: remoteFunction.timeout || 15,
1334
+ enabled: remoteFunction.enabled !== false,
1335
+ logging: remoteFunction.logging !== false,
1336
+ entrypoint: remoteFunction.entrypoint || "src/index.ts",
1337
+ commands: remoteFunction.commands || "npm install",
1338
+ scopes: (remoteFunction.scopes || []) as FunctionScope[],
1339
+ installationId: remoteFunction.installationId,
1340
+ providerRepositoryId: remoteFunction.providerRepositoryId,
1341
+ providerBranch: remoteFunction.providerBranch,
1342
+ providerSilentMode: remoteFunction.providerSilentMode,
1343
+ providerRootDirectory: remoteFunction.providerRootDirectory,
1344
+ specification: remoteFunction.specification as Specification,
1345
+ };
1346
+
1347
+ this.controller!.config!.functions =
1348
+ this.controller!.config!.functions || [];
1349
+ this.controller!.config!.functions.push(newFunction);
1350
+ console.log(
1351
+ chalk.green(`Added config for remote function: ${func.name}`)
1352
+ );
1159
1353
  }
1160
1354
  }
1161
1355
  }
1356
+
1357
+ // Update schemas after all changes
1358
+ const schemaGenerator = new SchemaGenerator(
1359
+ this.controller!.config!,
1360
+ this.controller!.getAppwriteFolderPath()
1361
+ );
1362
+ schemaGenerator.updateConfig(this.controller!.config!);
1162
1363
  }
1163
1364
 
1164
1365
  console.log(chalk.green("✨ Configurations synchronized successfully!"));