appwrite-utils-cli 0.10.0 → 0.10.1

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.01: Fixed `predeployCommands` to work
151
+ - 0.10.001: Updated `deployFunction` to not updateConfig if it's already present
150
152
  - 0.10.0: Fixed `synchronize configurations` for functions, now you do not need to deploy the function first
151
153
  - 0.9.999: Fixed Functions, looks for `./functions` in addition to `appwriteConfigFolder/functions`
152
154
  - 0.9.998: Fixed transfer finally, added `--targetDbId` and `--sourceDbId` as aliases
@@ -3,6 +3,7 @@ import { InputFile } from "node-appwrite/file";
3
3
  import { create as createTarball } from "tar";
4
4
  import { join } from "node:path";
5
5
  import fs from "node:fs";
6
+ import { platform } from "node:os";
6
7
  import {} from "appwrite-utils";
7
8
  import chalk from "chalk";
8
9
  import cliProgress from "cli-progress";
@@ -46,20 +47,20 @@ export const deployFunction = async (client, functionId, codePath, activate = tr
46
47
  const fileObject = InputFile.fromBuffer(new Uint8Array(fileBuffer), `function-${functionId}.tar.gz`);
47
48
  try {
48
49
  console.log(chalk.blue("🚀 Creating deployment..."));
50
+ progressBar.start(1, 0);
49
51
  const functionResponse = await functions.createDeployment(functionId, fileObject, activate, entrypoint, commands, (progress) => {
50
52
  const chunks = progress.chunksUploaded;
51
53
  const total = progress.chunksTotal;
52
- if (chunks && total) {
53
- if (chunks === 0) {
54
+ if (chunks !== undefined && total) {
55
+ if (chunks === 0 && total !== 0) {
54
56
  progressBar.start(total, 0);
55
57
  }
56
- else if (chunks === total) {
57
- progressBar.update(total);
58
- progressBar.stop();
59
- console.log(chalk.green("✅ Upload complete!"));
60
- }
61
58
  else {
62
59
  progressBar.update(chunks);
60
+ if (chunks === total) {
61
+ progressBar.stop();
62
+ console.log(chalk.green("✅ Upload complete!"));
63
+ }
63
64
  }
64
65
  }
65
66
  });
@@ -85,22 +86,29 @@ export const deployLocalFunction = async (client, functionName, functionConfig,
85
86
  functionConfig.dirPath ||
86
87
  findFunctionDirectory(process.cwd(), functionName) ||
87
88
  join(process.cwd(), "functions", functionName.toLowerCase().replace(/\s+/g, "-"));
89
+ if (!fs.existsSync(resolvedPath)) {
90
+ throw new Error(`Function directory not found at ${resolvedPath}`);
91
+ }
88
92
  if (functionConfig.predeployCommands?.length) {
89
93
  console.log(chalk.blue("Executing predeploy commands..."));
94
+ const isWindows = platform() === "win32";
90
95
  for (const command of functionConfig.predeployCommands) {
91
96
  try {
92
97
  console.log(chalk.gray(`Executing: ${command}`));
93
98
  execSync(command, {
94
99
  cwd: resolvedPath,
95
100
  stdio: "inherit",
101
+ shell: isWindows ? "cmd.exe" : "/bin/sh",
102
+ windowsHide: true,
96
103
  });
97
104
  }
98
105
  catch (error) {
99
- console.error(chalk.red(`Failed to execute predeploy command: ${command}`));
100
- throw error;
106
+ console.error(chalk.red(`Failed to execute predeploy command: ${command}`), error);
107
+ throw new Error(``);
101
108
  }
102
109
  }
103
110
  }
111
+ // Only create function if it doesn't exist
104
112
  if (!functionExists) {
105
113
  await createFunction(client, functionConfig.$id, functionConfig.name, functionConfig.runtime, functionConfig.execute, functionConfig.events, functionConfig.schedule, functionConfig.timeout, functionConfig.enabled, functionConfig.logging, functionConfig.entrypoint, functionConfig.commands);
106
114
  }
@@ -298,7 +298,17 @@ export class InteractiveCLI {
298
298
  join(process.cwd(), functionName), // ./functionName
299
299
  join(basePath, "functions", functionName), // appwriteFolder/functions/functionName
300
300
  join(basePath, functionName), // appwriteFolder/functionName
301
+ join(basePath, functionName.toLowerCase()), // appwriteFolder/functionName.toLowerCase()
302
+ join(basePath, functionName.toLowerCase().replace(/\s+/g, "")), // appwriteFolder/functionName.toLowerCase().replace(/\s+/g, "")
303
+ join(process.cwd(), functionName.toLowerCase()), // ./functionName.toLowerCase()
301
304
  ];
305
+ // Create different variations of the function name for comparison
306
+ const functionNameVariations = new Set([
307
+ functionName.toLowerCase(),
308
+ functionName.toLowerCase().replace(/\s+/g, ""),
309
+ functionName.toLowerCase().replace(/[^a-z0-9]/g, ""),
310
+ functionName.toLowerCase().replace(/[-_\s]+/g, ""),
311
+ ]);
302
312
  // Check common locations first
303
313
  for (const path of commonPaths) {
304
314
  try {
@@ -331,7 +341,15 @@ export class InteractiveCLI {
331
341
  if (entry.isDirectory() &&
332
342
  !entry.name.startsWith(".") &&
333
343
  entry.name !== "node_modules") {
334
- if (entry.name === functionName) {
344
+ const entryNameVariations = new Set([
345
+ entry.name.toLowerCase(),
346
+ entry.name.toLowerCase().replace(/\s+/g, ""),
347
+ entry.name.toLowerCase().replace(/[^a-z0-9]/g, ""),
348
+ entry.name.toLowerCase().replace(/[-_\s]+/g, ""),
349
+ ]);
350
+ // Check if any variation of the entry name matches any variation of the function name
351
+ const hasMatch = [...functionNameVariations].some((fnVar) => [...entryNameVariations].includes(fnVar));
352
+ if (hasMatch) {
335
353
  console.log(chalk.green(`Found function at: ${fullPath}`));
336
354
  return fullPath;
337
355
  }
@@ -361,126 +379,64 @@ export class InteractiveCLI {
361
379
  console.log(chalk.red("Invalid function configuration"));
362
380
  return;
363
381
  }
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;
413
- }
414
- catch (error) {
415
- console.error(chalk.red("Failed to download function deployment:"), error);
416
- return;
417
- }
382
+ // Ensure functions array exists
383
+ if (!this.controller.config.functions) {
384
+ this.controller.config.functions = [];
418
385
  }
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;
449
- }
450
- else {
451
- this.controller.config.functions.push(newFunction);
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();
386
+ let functionPath = join(this.controller.getAppwriteFolderPath(), "functions", functionConfig.name);
387
+ if (!fs.existsSync(functionPath)) {
388
+ console.log(chalk.yellow(`Function not found in primary location, searching subdirectories...`));
389
+ const foundPath = await this.findFunctionInSubdirectories(this.controller.getAppwriteFolderPath(), functionConfig.name.toLowerCase());
390
+ if (foundPath) {
391
+ console.log(chalk.green(`Found function at: ${foundPath}`));
392
+ functionPath = foundPath;
393
+ functionConfig.dirPath = foundPath;
457
394
  }
458
- catch (error) {
459
- console.error(chalk.red("Failed to update function config:"), error);
460
- return;
461
- }
462
- }
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;
395
+ else {
396
+ const { shouldDownload } = await inquirer.prompt([
397
+ {
398
+ type: "confirm",
399
+ name: "shouldDownload",
400
+ message: "Function not found locally. Would you like to download the latest deployment?",
401
+ default: false,
402
+ },
403
+ ]);
404
+ if (shouldDownload) {
405
+ try {
406
+ console.log(chalk.blue("Downloading latest deployment..."));
407
+ const { path: downloadedPath, function: remoteFunction } = await downloadLatestFunctionDeployment(this.controller.appwriteServer, functionConfig.$id, join(this.controller.getAppwriteFolderPath(), "functions"));
408
+ console.log(chalk.green(`✨ Function downloaded to ${downloadedPath}`));
409
+ const existingIndex = this.controller.config.functions.findIndex((f) => f?.$id === remoteFunction.$id);
410
+ if (existingIndex >= 0) {
411
+ // Only update the dirPath if function exists
412
+ this.controller.config.functions[existingIndex].dirPath =
413
+ downloadedPath;
414
+ }
415
+ await this.controller.reloadConfig();
416
+ functionConfig.dirPath = downloadedPath;
417
+ functionPath = downloadedPath;
418
+ }
419
+ catch (error) {
420
+ console.error(chalk.red("Failed to download function deployment:"), error);
421
+ return;
422
+ }
473
423
  }
474
424
  else {
475
- console.log(chalk.red(`Function ${functionConfig.name} not found locally in any subdirectory. Cannot deploy.`));
425
+ console.log(chalk.red(`Function ${functionConfig.name} not found locally. Cannot deploy.`));
476
426
  return;
477
427
  }
478
428
  }
479
- if (!this.controller.appwriteServer) {
480
- console.log(chalk.red("Appwrite server not initialized"));
481
- return;
482
- }
429
+ }
430
+ if (!this.controller.appwriteServer) {
431
+ console.log(chalk.red("Appwrite server not initialized"));
432
+ return;
433
+ }
434
+ try {
483
435
  await deployLocalFunction(this.controller.appwriteServer, functionConfig.name, functionConfig);
436
+ console.log(chalk.green("✨ Function deployed successfully!"));
437
+ }
438
+ catch (error) {
439
+ console.error(chalk.red("Failed to deploy function:"), error);
484
440
  }
485
441
  }
486
442
  async deleteFunction() {
@@ -499,52 +455,33 @@ export class InteractiveCLI {
499
455
  }
500
456
  }
501
457
  }
502
- async selectFunctions(message, multiSelect = true, preferLocal = false) {
503
- await this.initControllerIfNeeded();
504
- const configFunctions = this.getLocalFunctions();
505
- let remoteFunctions = [];
506
- try {
507
- const functions = await this.controller.listAllFunctions();
508
- remoteFunctions = functions;
509
- }
510
- catch (error) {
511
- console.log(chalk.yellow(`Note: Remote functions not available, using only local functions`));
512
- }
513
- // Combine functions based on whether we're deploying or not
514
- const allFunctions = preferLocal
515
- ? [
516
- ...configFunctions,
517
- ...remoteFunctions.map((f) => AppwriteFunctionSchema.parse(f)),
518
- ]
519
- : [
520
- ...remoteFunctions.map((f) => AppwriteFunctionSchema.parse(f)),
521
- ...configFunctions.filter((f) => !remoteFunctions.some((rf) => rf.name === f.name)),
522
- ];
523
- if (allFunctions.length === 0) {
524
- console.log(chalk.red("No functions available"));
525
- return [];
526
- }
527
- const choices = allFunctions
528
- .sort((a, b) => a.name.localeCompare(b.name))
529
- .map((func) => ({
530
- name: func.name,
531
- value: func,
532
- }));
458
+ async selectFunctions(message, multiple = true, includeRemote = false) {
459
+ const remoteFunctions = includeRemote
460
+ ? await listFunctions(this.controller.appwriteServer, [
461
+ Query.limit(1000),
462
+ ])
463
+ : { functions: [] };
464
+ const localFunctions = this.getLocalFunctions();
465
+ // Combine functions, preferring local ones
466
+ const allFunctions = [
467
+ ...localFunctions,
468
+ ...remoteFunctions.functions.filter((rf) => !localFunctions.some((lf) => lf.name === rf.name)),
469
+ ];
533
470
  const { selectedFunctions } = await inquirer.prompt([
534
471
  {
535
- type: multiSelect ? "checkbox" : "list",
472
+ type: multiple ? "checkbox" : "list",
536
473
  name: "selectedFunctions",
537
- message: chalk.blue(message),
538
- choices,
474
+ message,
475
+ choices: allFunctions.map((f) => ({
476
+ name: `${f.name} (${f.$id})${localFunctions.some((lf) => lf.name === f.name)
477
+ ? " (Local)"
478
+ : " (Remote)"}`,
479
+ value: f,
480
+ })),
539
481
  loop: true,
540
- pageSize: 10,
541
482
  },
542
483
  ]);
543
- // For single selection, ensure we return an array
544
- if (!multiSelect) {
545
- return selectedFunctions ? [selectedFunctions] : [];
546
- }
547
- return selectedFunctions || [];
484
+ return multiple ? selectedFunctions : [selectedFunctions];
548
485
  }
549
486
  getLocalFunctions() {
550
487
  const configFunctions = this.controller.config?.functions || [];
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.10.0",
4
+ "version": "0.10.01",
5
5
  "main": "src/main.ts",
6
6
  "type": "module",
7
7
  "repository": {
@@ -3,6 +3,7 @@ import { InputFile } from "node-appwrite/file";
3
3
  import { create as createTarball } from "tar";
4
4
  import { join } from "node:path";
5
5
  import fs from "node:fs";
6
+ import { platform } from "node:os";
6
7
  import { type AppwriteFunction, type Specification } from "appwrite-utils";
7
8
  import chalk from "chalk";
8
9
  import cliProgress from "cli-progress";
@@ -76,6 +77,7 @@ export const deployFunction = async (
76
77
 
77
78
  try {
78
79
  console.log(chalk.blue("🚀 Creating deployment..."));
80
+ progressBar.start(1, 0);
79
81
  const functionResponse = await functions.createDeployment(
80
82
  functionId,
81
83
  fileObject,
@@ -85,15 +87,15 @@ export const deployFunction = async (
85
87
  (progress) => {
86
88
  const chunks = progress.chunksUploaded;
87
89
  const total = progress.chunksTotal;
88
- if (chunks && total) {
89
- if (chunks === 0) {
90
+ if (chunks !== undefined && total) {
91
+ if (chunks === 0 && total !== 0) {
90
92
  progressBar.start(total, 0);
91
- } else if (chunks === total) {
92
- progressBar.update(total);
93
- progressBar.stop();
94
- console.log(chalk.green("✅ Upload complete!"));
95
93
  } else {
96
94
  progressBar.update(chunks);
95
+ if (chunks === total) {
96
+ progressBar.stop();
97
+ console.log(chalk.green("✅ Upload complete!"));
98
+ }
97
99
  }
98
100
  }
99
101
  }
@@ -132,24 +134,34 @@ export const deployLocalFunction = async (
132
134
  functionName.toLowerCase().replace(/\s+/g, "-")
133
135
  );
134
136
 
137
+ if (!fs.existsSync(resolvedPath)) {
138
+ throw new Error(`Function directory not found at ${resolvedPath}`);
139
+ }
140
+
135
141
  if (functionConfig.predeployCommands?.length) {
136
142
  console.log(chalk.blue("Executing predeploy commands..."));
143
+ const isWindows = platform() === "win32";
144
+
137
145
  for (const command of functionConfig.predeployCommands) {
138
146
  try {
139
147
  console.log(chalk.gray(`Executing: ${command}`));
140
148
  execSync(command, {
141
149
  cwd: resolvedPath,
142
150
  stdio: "inherit",
151
+ shell: isWindows ? "cmd.exe" : "/bin/sh",
152
+ windowsHide: true,
143
153
  });
144
154
  } catch (error) {
145
155
  console.error(
146
- chalk.red(`Failed to execute predeploy command: ${command}`)
156
+ chalk.red(`Failed to execute predeploy command: ${command}`),
157
+ error
147
158
  );
148
- throw error;
159
+ throw new Error(``);
149
160
  }
150
161
  }
151
162
  }
152
163
 
164
+ // Only create function if it doesn't exist
153
165
  if (!functionExists) {
154
166
  await createFunction(
155
167
  client,
@@ -398,8 +398,19 @@ export class InteractiveCLI {
398
398
  join(process.cwd(), functionName), // ./functionName
399
399
  join(basePath, "functions", functionName), // appwriteFolder/functions/functionName
400
400
  join(basePath, functionName), // appwriteFolder/functionName
401
+ join(basePath, functionName.toLowerCase()), // appwriteFolder/functionName.toLowerCase()
402
+ join(basePath, functionName.toLowerCase().replace(/\s+/g, "")), // appwriteFolder/functionName.toLowerCase().replace(/\s+/g, "")
403
+ join(process.cwd(), functionName.toLowerCase()), // ./functionName.toLowerCase()
401
404
  ];
402
405
 
406
+ // Create different variations of the function name for comparison
407
+ const functionNameVariations = new Set([
408
+ functionName.toLowerCase(),
409
+ functionName.toLowerCase().replace(/\s+/g, ""),
410
+ functionName.toLowerCase().replace(/[^a-z0-9]/g, ""),
411
+ functionName.toLowerCase().replace(/[-_\s]+/g, ""),
412
+ ]);
413
+
403
414
  // Check common locations first
404
415
  for (const path of commonPaths) {
405
416
  try {
@@ -445,7 +456,19 @@ export class InteractiveCLI {
445
456
  !entry.name.startsWith(".") &&
446
457
  entry.name !== "node_modules"
447
458
  ) {
448
- if (entry.name === functionName) {
459
+ const entryNameVariations = new Set([
460
+ entry.name.toLowerCase(),
461
+ entry.name.toLowerCase().replace(/\s+/g, ""),
462
+ entry.name.toLowerCase().replace(/[^a-z0-9]/g, ""),
463
+ entry.name.toLowerCase().replace(/[-_\s]+/g, ""),
464
+ ]);
465
+
466
+ // Check if any variation of the entry name matches any variation of the function name
467
+ const hasMatch = [...functionNameVariations].some((fnVar) =>
468
+ [...entryNameVariations].includes(fnVar)
469
+ );
470
+
471
+ if (hasMatch) {
449
472
  console.log(chalk.green(`Found function at: ${fullPath}`));
450
473
  return fullPath;
451
474
  }
@@ -487,182 +510,101 @@ export class InteractiveCLI {
487
510
  return;
488
511
  }
489
512
 
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
- ]);
498
-
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
- );
539
-
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);
551
- console.log(
552
- chalk.green("✨ Updated appwriteConfig.ts with new function")
553
- );
554
-
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 || [];
570
-
571
- const remoteFunction = await getFunction(
572
- this.controller.appwriteServer!,
573
- functionConfig.$id
574
- );
513
+ // Ensure functions array exists
514
+ if (!this.controller.config.functions) {
515
+ this.controller.config.functions = [];
516
+ }
575
517
 
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
- );
518
+ let functionPath = join(
519
+ this.controller.getAppwriteFolderPath(),
520
+ "functions",
521
+ functionConfig.name
522
+ );
600
523
 
601
- if (existingIndex >= 0) {
602
- this.controller.config.functions[existingIndex] = newFunction;
603
- } else {
604
- this.controller.config.functions.push(newFunction);
605
- }
524
+ if (!fs.existsSync(functionPath)) {
525
+ console.log(
526
+ chalk.yellow(
527
+ `Function not found in primary location, searching subdirectories...`
528
+ )
529
+ );
530
+ const foundPath = await this.findFunctionInSubdirectories(
531
+ this.controller.getAppwriteFolderPath(),
532
+ functionConfig.name.toLowerCase()
533
+ );
606
534
 
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
- );
535
+ if (foundPath) {
536
+ console.log(chalk.green(`Found function at: ${foundPath}`));
537
+ functionPath = foundPath;
538
+ functionConfig.dirPath = foundPath;
539
+ } else {
540
+ const { shouldDownload } = await inquirer.prompt([
541
+ {
542
+ type: "confirm",
543
+ name: "shouldDownload",
544
+ message:
545
+ "Function not found locally. Would you like to download the latest deployment?",
546
+ default: false,
547
+ },
548
+ ]);
615
549
 
616
- await this.controller.reloadConfig();
617
- } catch (error) {
618
- console.error(chalk.red("Failed to update function config:"), error);
619
- return;
620
- }
621
- }
550
+ if (shouldDownload) {
551
+ try {
552
+ console.log(chalk.blue("Downloading latest deployment..."));
553
+ const { path: downloadedPath, function: remoteFunction } =
554
+ await downloadLatestFunctionDeployment(
555
+ this.controller.appwriteServer!,
556
+ functionConfig.$id,
557
+ join(this.controller.getAppwriteFolderPath(), "functions")
558
+ );
559
+ console.log(
560
+ chalk.green(`✨ Function downloaded to ${downloadedPath}`)
561
+ );
622
562
 
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
- );
563
+ const existingIndex = this.controller.config.functions.findIndex(
564
+ (f) => f?.$id === remoteFunction.$id
565
+ );
630
566
 
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
- );
567
+ if (existingIndex >= 0) {
568
+ // Only update the dirPath if function exists
569
+ this.controller.config.functions[existingIndex].dirPath =
570
+ downloadedPath;
571
+ }
641
572
 
642
- if (foundPath) {
643
- console.log(chalk.green(`Found function at: ${foundPath}`));
644
- functionPath = foundPath;
645
- functionConfig.dirPath = foundPath;
573
+ await this.controller.reloadConfig();
574
+ functionConfig.dirPath = downloadedPath;
575
+ functionPath = downloadedPath;
576
+ } catch (error) {
577
+ console.error(
578
+ chalk.red("Failed to download function deployment:"),
579
+ error
580
+ );
581
+ return;
582
+ }
646
583
  } else {
647
584
  console.log(
648
585
  chalk.red(
649
- `Function ${functionConfig.name} not found locally in any subdirectory. Cannot deploy.`
586
+ `Function ${functionConfig.name} not found locally. Cannot deploy.`
650
587
  )
651
588
  );
652
589
  return;
653
590
  }
654
591
  }
592
+ }
655
593
 
656
- if (!this.controller.appwriteServer) {
657
- console.log(chalk.red("Appwrite server not initialized"));
658
- return;
659
- }
594
+ if (!this.controller.appwriteServer) {
595
+ console.log(chalk.red("Appwrite server not initialized"));
596
+ return;
597
+ }
660
598
 
599
+ try {
661
600
  await deployLocalFunction(
662
601
  this.controller.appwriteServer,
663
602
  functionConfig.name,
664
603
  functionConfig
665
604
  );
605
+ console.log(chalk.green("✨ Function deployed successfully!"));
606
+ } catch (error) {
607
+ console.error(chalk.red("Failed to deploy function:"), error);
666
608
  }
667
609
  }
668
610
 
@@ -695,67 +637,42 @@ export class InteractiveCLI {
695
637
 
696
638
  private async selectFunctions(
697
639
  message: string,
698
- multiSelect = true,
699
- preferLocal = false
640
+ multiple: boolean = true,
641
+ includeRemote: boolean = false
700
642
  ): Promise<AppwriteFunction[]> {
701
- await this.initControllerIfNeeded();
702
-
703
- const configFunctions = this.getLocalFunctions();
704
- let remoteFunctions: Models.Function[] = [];
705
-
706
- try {
707
- const functions = await this.controller!.listAllFunctions();
708
- remoteFunctions = functions;
709
- } catch (error) {
710
- console.log(
711
- chalk.yellow(
712
- `Note: Remote functions not available, using only local functions`
713
- )
714
- );
715
- }
716
-
717
- // Combine functions based on whether we're deploying or not
718
- const allFunctions = preferLocal
719
- ? [
720
- ...configFunctions,
721
- ...remoteFunctions.map((f) => AppwriteFunctionSchema.parse(f)),
722
- ]
723
- : [
724
- ...remoteFunctions.map((f) => AppwriteFunctionSchema.parse(f)),
725
- ...configFunctions.filter(
726
- (f) => !remoteFunctions.some((rf) => rf.name === f.name)
727
- ),
728
- ];
729
-
730
- if (allFunctions.length === 0) {
731
- console.log(chalk.red("No functions available"));
732
- return [];
733
- }
643
+ const remoteFunctions = includeRemote
644
+ ? await listFunctions(this.controller!.appwriteServer!, [
645
+ Query.limit(1000),
646
+ ])
647
+ : { functions: [] };
648
+ const localFunctions = this.getLocalFunctions();
734
649
 
735
- const choices = allFunctions
736
- .sort((a, b) => a.name.localeCompare(b.name))
737
- .map((func) => ({
738
- name: func.name,
739
- value: func,
740
- }));
650
+ // Combine functions, preferring local ones
651
+ const allFunctions = [
652
+ ...localFunctions,
653
+ ...remoteFunctions.functions.filter(
654
+ (rf) => !localFunctions.some((lf) => lf.name === rf.name)
655
+ ),
656
+ ];
741
657
 
742
658
  const { selectedFunctions } = await inquirer.prompt([
743
659
  {
744
- type: multiSelect ? "checkbox" : "list",
660
+ type: multiple ? "checkbox" : "list",
745
661
  name: "selectedFunctions",
746
- message: chalk.blue(message),
747
- choices,
662
+ message,
663
+ choices: allFunctions.map((f) => ({
664
+ name: `${f.name} (${f.$id})${
665
+ localFunctions.some((lf) => lf.name === f.name)
666
+ ? " (Local)"
667
+ : " (Remote)"
668
+ }`,
669
+ value: f,
670
+ })),
748
671
  loop: true,
749
- pageSize: 10,
750
672
  },
751
673
  ]);
752
674
 
753
- // For single selection, ensure we return an array
754
- if (!multiSelect) {
755
- return selectedFunctions ? [selectedFunctions] : [];
756
- }
757
-
758
- return selectedFunctions || [];
675
+ return multiple ? selectedFunctions : [selectedFunctions];
759
676
  }
760
677
 
761
678
  private getLocalFunctions(): AppwriteFunction[] {