appwrite-utils-cli 0.9.998 → 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,8 @@ 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
151
+ - 0.9.999: Fixed Functions, looks for `./functions` in addition to `appwriteConfigFolder/functions`
150
152
  - 0.9.998: Fixed transfer finally, added `--targetDbId` and `--sourceDbId` as aliases
151
153
  - 0.9.994: Added function deployment management, in BETA, and fixed document transfer between databases
152
154
  - 0.9.993: Fixed `updateFunctionSpecifications` resetting functions to default with undefined values (oops)
@@ -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";
@@ -292,27 +292,55 @@ export class InteractiveCLI {
292
292
  console.log(chalk.green("✨ Function created successfully!"));
293
293
  }
294
294
  async findFunctionInSubdirectories(basePath, functionName) {
295
- const queue = [basePath];
295
+ // Common locations to check first
296
+ const commonPaths = [
297
+ join(process.cwd(), "functions", functionName), // ./functions/functionName
298
+ join(process.cwd(), functionName), // ./functionName
299
+ join(basePath, "functions", functionName), // appwriteFolder/functions/functionName
300
+ join(basePath, functionName), // appwriteFolder/functionName
301
+ ];
302
+ // Check common locations first
303
+ for (const path of commonPaths) {
304
+ try {
305
+ const stats = await fs.promises.stat(path);
306
+ if (stats.isDirectory()) {
307
+ console.log(chalk.green(`Found function at common location: ${path}`));
308
+ return path;
309
+ }
310
+ }
311
+ catch (error) {
312
+ // Path doesn't exist, continue to next
313
+ }
314
+ }
315
+ // If not found in common locations, do recursive search
316
+ console.log(chalk.yellow("Function not found in common locations, searching subdirectories..."));
317
+ const queue = [process.cwd(), basePath];
318
+ const searched = new Set();
296
319
  while (queue.length > 0) {
297
320
  const currentPath = queue.shift();
321
+ if (searched.has(currentPath))
322
+ continue;
323
+ searched.add(currentPath);
298
324
  try {
299
325
  const entries = await fs.promises.readdir(currentPath, {
300
326
  withFileTypes: true,
301
327
  });
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
328
  for (const entry of entries) {
309
- if (entry.isDirectory()) {
310
- queue.push(join(currentPath, entry.name));
329
+ const fullPath = join(currentPath, entry.name);
330
+ // Skip node_modules and hidden directories
331
+ if (entry.isDirectory() &&
332
+ !entry.name.startsWith(".") &&
333
+ entry.name !== "node_modules") {
334
+ if (entry.name === functionName) {
335
+ console.log(chalk.green(`Found function at: ${fullPath}`));
336
+ return fullPath;
337
+ }
338
+ queue.push(fullPath);
311
339
  }
312
340
  }
313
341
  }
314
342
  catch (error) {
315
- console.log(chalk.yellow(`Skipping inaccessible directory: ${currentPath}`));
343
+ console.log(chalk.yellow(`Error reading directory ${currentPath}:`, error));
316
344
  }
317
345
  }
318
346
  return null;
@@ -333,83 +361,127 @@ export class InteractiveCLI {
333
361
  console.log(chalk.red("Invalid function configuration"));
334
362
  return;
335
363
  }
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;
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;
344
413
  }
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
- }
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;
401
449
  }
402
450
  else {
403
- console.log(chalk.yellow("Deployment cancelled"));
404
- return;
451
+ this.controller.config.functions.push(newFunction);
405
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;
406
461
  }
407
462
  }
408
- if (!this.controller.appwriteServer) {
409
- console.log(chalk.red("Appwrite server not initialized"));
410
- 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);
411
484
  }
412
- await deployLocalFunction(this.controller.appwriteServer, functionConfig.name, functionConfig);
413
485
  }
414
486
  async deleteFunction() {
415
487
  const functions = await this.selectFunctions("Select functions to delete:", true, false);
@@ -777,55 +849,153 @@ export class InteractiveCLI {
777
849
  const hasLocal = localFunctions.some((lf) => lf.$id === func.$id);
778
850
  const hasRemote = remoteFunctions.some((rf) => rf.$id === func.$id);
779
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
+ }
780
862
  const { preference } = await inquirer.prompt([
781
863
  {
782
864
  type: "list",
783
865
  name: "preference",
784
- 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?`,
785
867
  choices: [
786
- {
787
- name: "Keep local version (deploy to remote)",
788
- value: "local",
789
- },
868
+ ...(functionPath
869
+ ? [
870
+ {
871
+ name: "Keep local version (deploy to remote)",
872
+ value: "local",
873
+ },
874
+ ]
875
+ : []),
790
876
  { name: "Use remote version (download)", value: "remote" },
877
+ { name: "Update config only", value: "config" },
791
878
  { name: "Skip this function", value: "skip" },
792
879
  ],
793
880
  },
794
881
  ]);
795
- if (preference === "local") {
882
+ if (preference === "local" && functionPath) {
796
883
  await this.controller.deployFunction(func.name);
797
884
  }
798
885
  else if (preference === "remote") {
799
886
  await downloadLatestFunctionDeployment(this.controller.appwriteServer, func.$id, join(this.controller.getAppwriteFolderPath(), "functions"));
800
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
+ }
801
919
  }
802
920
  else if (hasLocal) {
803
- 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([
804
930
  {
805
- type: "confirm",
806
- name: "deploy",
807
- message: `Function "${func.name}" exists only locally. Deploy to remote?`,
808
- 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
+ ],
809
945
  },
810
946
  ]);
811
- if (deploy) {
947
+ if (action === "deploy" && functionPath) {
812
948
  await this.controller.deployFunction(func.name);
813
949
  }
814
950
  }
815
951
  else if (hasRemote) {
816
- const { download } = await inquirer.prompt([
952
+ const { action } = await inquirer.prompt([
817
953
  {
818
- type: "confirm",
819
- name: "download",
820
- message: `Function "${func.name}" exists only remotely. Download locally?`,
821
- 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
+ ],
822
962
  },
823
963
  ]);
824
- if (download) {
964
+ if (action === "download") {
825
965
  await downloadLatestFunctionDeployment(this.controller.appwriteServer, func.$id, join(this.controller.getAppwriteFolderPath(), "functions"));
826
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
+ }
827
994
  }
828
995
  }
996
+ // Update schemas after all changes
997
+ const schemaGenerator = new SchemaGenerator(this.controller.config, this.controller.getAppwriteFolderPath());
998
+ schemaGenerator.updateConfig(this.controller.config);
829
999
  }
830
1000
  console.log(chalk.green("✨ Configurations synchronized successfully!"));
831
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.998",
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";
@@ -391,31 +392,70 @@ export class InteractiveCLI {
391
392
  basePath: string,
392
393
  functionName: string
393
394
  ): Promise<string | null> {
394
- const queue = [basePath];
395
+ // Common locations to check first
396
+ const commonPaths = [
397
+ join(process.cwd(), "functions", functionName), // ./functions/functionName
398
+ join(process.cwd(), functionName), // ./functionName
399
+ join(basePath, "functions", functionName), // appwriteFolder/functions/functionName
400
+ join(basePath, functionName), // appwriteFolder/functionName
401
+ ];
402
+
403
+ // Check common locations first
404
+ for (const path of commonPaths) {
405
+ try {
406
+ const stats = await fs.promises.stat(path);
407
+ if (stats.isDirectory()) {
408
+ console.log(
409
+ chalk.green(`Found function at common location: ${path}`)
410
+ );
411
+ return path;
412
+ }
413
+ } catch (error) {
414
+ // Path doesn't exist, continue to next
415
+ }
416
+ }
417
+
418
+ // If not found in common locations, do recursive search
419
+ console.log(
420
+ chalk.yellow(
421
+ "Function not found in common locations, searching subdirectories..."
422
+ )
423
+ );
424
+
425
+ const queue = [process.cwd(), basePath];
426
+ const searched = new Set<string>();
395
427
 
396
428
  while (queue.length > 0) {
397
429
  const currentPath = queue.shift()!;
398
430
 
431
+ if (searched.has(currentPath)) continue;
432
+ searched.add(currentPath);
433
+
399
434
  try {
400
435
  const entries = await fs.promises.readdir(currentPath, {
401
436
  withFileTypes: true,
402
437
  });
403
438
 
404
- // Check if function exists in current directory
405
- const functionPath = join(currentPath, functionName);
406
- if (fs.existsSync(functionPath)) {
407
- return functionPath;
408
- }
409
-
410
- // Add subdirectories to queue
411
439
  for (const entry of entries) {
412
- if (entry.isDirectory()) {
413
- queue.push(join(currentPath, entry.name));
440
+ const fullPath = join(currentPath, entry.name);
441
+
442
+ // Skip node_modules and hidden directories
443
+ if (
444
+ entry.isDirectory() &&
445
+ !entry.name.startsWith(".") &&
446
+ entry.name !== "node_modules"
447
+ ) {
448
+ if (entry.name === functionName) {
449
+ console.log(chalk.green(`Found function at: ${fullPath}`));
450
+ return fullPath;
451
+ }
452
+
453
+ queue.push(fullPath);
414
454
  }
415
455
  }
416
456
  } catch (error) {
417
457
  console.log(
418
- chalk.yellow(`Skipping inaccessible directory: ${currentPath}`)
458
+ chalk.yellow(`Error reading directory ${currentPath}:`, error)
419
459
  );
420
460
  }
421
461
  }
@@ -447,127 +487,183 @@ export class InteractiveCLI {
447
487
  return;
448
488
  }
449
489
 
450
- let functionPath = join(
451
- this.controller.getAppwriteFolderPath(),
452
- "functions",
453
- functionConfig.name
454
- );
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
+ ]);
455
498
 
456
- if (!fs.existsSync(functionPath)) {
457
- console.log(
458
- chalk.yellow(
459
- `Function not found in primary location, searching subdirectories...`
460
- )
461
- );
462
- const foundPath = await this.findFunctionInSubdirectories(
463
- this.controller.getAppwriteFolderPath(),
464
- functionConfig.name
465
- );
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
+ );
466
539
 
467
- if (foundPath) {
468
- console.log(chalk.green(`Found function at: ${foundPath}`));
469
- functionPath = foundPath;
470
- functionConfig.dirPath = foundPath;
471
- } 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);
472
551
  console.log(
473
- chalk.yellow(
474
- `Function ${functionConfig.name} not found locally in any subdirectory`
475
- )
552
+ chalk.green("✨ Updated appwriteConfig.ts with new function")
476
553
  );
477
554
 
478
- const { shouldDownload } = await inquirer.prompt([
479
- {
480
- type: "confirm",
481
- name: "shouldDownload",
482
- message: "Would you like to download the latest deployment?",
483
- default: true,
484
- },
485
- ]);
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 || [];
486
570
 
487
- if (shouldDownload) {
488
- try {
489
- console.log(chalk.blue("Downloading latest deployment..."));
490
- const { path: downloadedPath, function: remoteFunction } =
491
- await downloadLatestFunctionDeployment(
492
- this.controller.appwriteServer!,
493
- functionConfig.$id,
494
- join(this.controller.getAppwriteFolderPath(), "functions")
495
- );
496
- console.log(
497
- chalk.green(`✨ Function downloaded to ${downloadedPath}`)
498
- );
571
+ const remoteFunction = await getFunction(
572
+ this.controller.appwriteServer!,
573
+ functionConfig.$id
574
+ );
499
575
 
500
- // Update the config and functions array safely
501
- this.controller.config.functions =
502
- 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
+ );
503
600
 
504
- const newFunction = {
505
- $id: remoteFunction.$id,
506
- name: remoteFunction.name,
507
- runtime: remoteFunction.runtime as Runtime,
508
- execute: remoteFunction.execute || [],
509
- events: remoteFunction.events || [],
510
- schedule: remoteFunction.schedule || "",
511
- timeout: remoteFunction.timeout || 15,
512
- enabled: remoteFunction.enabled !== false,
513
- logging: remoteFunction.logging !== false,
514
- entrypoint: remoteFunction.entrypoint || "src/index.ts",
515
- commands: remoteFunction.commands || "npm install",
516
- dirPath: downloadedPath,
517
- scopes: (remoteFunction.scopes || []) as FunctionScope[],
518
- installationId: remoteFunction.installationId,
519
- providerRepositoryId: remoteFunction.providerRepositoryId,
520
- providerBranch: remoteFunction.providerBranch,
521
- providerSilentMode: remoteFunction.providerSilentMode,
522
- providerRootDirectory: remoteFunction.providerRootDirectory,
523
- specification: remoteFunction.specification as Specification,
524
- };
601
+ if (existingIndex >= 0) {
602
+ this.controller.config.functions[existingIndex] = newFunction;
603
+ } else {
604
+ this.controller.config.functions.push(newFunction);
605
+ }
525
606
 
526
- const existingIndex = this.controller.config.functions.findIndex(
527
- (f) => f?.$id === remoteFunction.$id
528
- );
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
+ );
529
615
 
530
- if (existingIndex >= 0) {
531
- this.controller.config.functions[existingIndex] = newFunction;
532
- } else {
533
- this.controller.config.functions.push(newFunction);
534
- }
616
+ await this.controller.reloadConfig();
617
+ } catch (error) {
618
+ console.error(chalk.red("Failed to update function config:"), error);
619
+ return;
620
+ }
621
+ }
535
622
 
536
- const schemaGenerator = new SchemaGenerator(
537
- this.controller.config,
538
- this.controller.getAppwriteFolderPath()
539
- );
540
- schemaGenerator.updateConfig(this.controller.config);
541
- console.log(
542
- chalk.green("✨ Updated appwriteConfig.ts with new function")
543
- );
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
+ );
544
630
 
545
- await this.controller.reloadConfig();
546
- functionConfig.dirPath = downloadedPath;
547
- } catch (error) {
548
- console.error(
549
- chalk.red("Failed to download function deployment:"),
550
- error
551
- );
552
- return;
553
- }
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;
554
646
  } else {
555
- 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
+ );
556
652
  return;
557
653
  }
558
654
  }
559
- }
560
655
 
561
- if (!this.controller.appwriteServer) {
562
- console.log(chalk.red("Appwrite server not initialized"));
563
- return;
564
- }
656
+ if (!this.controller.appwriteServer) {
657
+ console.log(chalk.red("Appwrite server not initialized"));
658
+ return;
659
+ }
565
660
 
566
- await deployLocalFunction(
567
- this.controller.appwriteServer,
568
- functionConfig.name,
569
- functionConfig
570
- );
661
+ await deployLocalFunction(
662
+ this.controller.appwriteServer,
663
+ functionConfig.name,
664
+ functionConfig
665
+ );
666
+ }
571
667
  }
572
668
 
573
669
  private async deleteFunction(): Promise<void> {
@@ -1063,23 +1159,54 @@ export class InteractiveCLI {
1063
1159
  const hasRemote = remoteFunctions.some((rf) => rf.$id === func.$id);
1064
1160
 
1065
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
+
1066
1186
  const { preference } = await inquirer.prompt([
1067
1187
  {
1068
1188
  type: "list",
1069
1189
  name: "preference",
1070
- 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?`,
1071
1193
  choices: [
1072
- {
1073
- name: "Keep local version (deploy to remote)",
1074
- value: "local",
1075
- },
1194
+ ...(functionPath
1195
+ ? [
1196
+ {
1197
+ name: "Keep local version (deploy to remote)",
1198
+ value: "local",
1199
+ },
1200
+ ]
1201
+ : []),
1076
1202
  { name: "Use remote version (download)", value: "remote" },
1203
+ { name: "Update config only", value: "config" },
1077
1204
  { name: "Skip this function", value: "skip" },
1078
1205
  ],
1079
1206
  },
1080
1207
  ]);
1081
1208
 
1082
- if (preference === "local") {
1209
+ if (preference === "local" && functionPath) {
1083
1210
  await this.controller!.deployFunction(func.name);
1084
1211
  } else if (preference === "remote") {
1085
1212
  await downloadLatestFunctionDeployment(
@@ -1087,39 +1214,152 @@ export class InteractiveCLI {
1087
1214
  func.$id,
1088
1215
  join(this.controller!.getAppwriteFolderPath(), "functions")
1089
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
+ );
1090
1256
  }
1091
1257
  } else if (hasLocal) {
1092
- 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([
1093
1277
  {
1094
- type: "confirm",
1095
- name: "deploy",
1096
- message: `Function "${func.name}" exists only locally. Deploy to remote?`,
1097
- 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
+ ],
1098
1294
  },
1099
1295
  ]);
1100
1296
 
1101
- if (deploy) {
1297
+ if (action === "deploy" && functionPath) {
1102
1298
  await this.controller!.deployFunction(func.name);
1103
1299
  }
1104
1300
  } else if (hasRemote) {
1105
- const { download } = await inquirer.prompt([
1301
+ const { action } = await inquirer.prompt([
1106
1302
  {
1107
- type: "confirm",
1108
- name: "download",
1109
- message: `Function "${func.name}" exists only remotely. Download locally?`,
1110
- 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
+ ],
1111
1311
  },
1112
1312
  ]);
1113
1313
 
1114
- if (download) {
1314
+ if (action === "download") {
1115
1315
  await downloadLatestFunctionDeployment(
1116
1316
  this.controller!.appwriteServer!,
1117
1317
  func.$id,
1118
1318
  join(this.controller!.getAppwriteFolderPath(), "functions")
1119
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
+ );
1120
1353
  }
1121
1354
  }
1122
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!);
1123
1363
  }
1124
1364
 
1125
1365
  console.log(chalk.green("✨ Configurations synchronized successfully!"));