appwrite-utils-cli 0.9.999 → 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 +3 -0
- package/dist/functions/deployments.js +17 -9
- package/dist/interactiveCLI.js +176 -97
- package/package.json +1 -1
- package/src/functions/deployments.ts +20 -8
- package/src/interactiveCLI.ts +241 -123
package/README.md
CHANGED
@@ -147,6 +147,9 @@ 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
|
152
|
+
- 0.10.0: Fixed `synchronize configurations` for functions, now you do not need to deploy the function first
|
150
153
|
- 0.9.999: Fixed Functions, looks for `./functions` in addition to `appwriteConfigFolder/functions`
|
151
154
|
- 0.9.998: Fixed transfer finally, added `--targetDbId` and `--sourceDbId` as aliases
|
152
155
|
- 0.9.994: Added function deployment management, in BETA, and fixed document transfer between databases
|
@@ -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
|
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
|
}
|
package/dist/interactiveCLI.js
CHANGED
@@ -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";
|
@@ -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
|
-
|
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,23 +379,26 @@ export class InteractiveCLI {
|
|
361
379
|
console.log(chalk.red("Invalid function configuration"));
|
362
380
|
return;
|
363
381
|
}
|
382
|
+
// Ensure functions array exists
|
383
|
+
if (!this.controller.config.functions) {
|
384
|
+
this.controller.config.functions = [];
|
385
|
+
}
|
364
386
|
let functionPath = join(this.controller.getAppwriteFolderPath(), "functions", functionConfig.name);
|
365
387
|
if (!fs.existsSync(functionPath)) {
|
366
388
|
console.log(chalk.yellow(`Function not found in primary location, searching subdirectories...`));
|
367
|
-
const foundPath = await this.findFunctionInSubdirectories(this.controller.getAppwriteFolderPath(), functionConfig.name);
|
389
|
+
const foundPath = await this.findFunctionInSubdirectories(this.controller.getAppwriteFolderPath(), functionConfig.name.toLowerCase());
|
368
390
|
if (foundPath) {
|
369
391
|
console.log(chalk.green(`Found function at: ${foundPath}`));
|
370
392
|
functionPath = foundPath;
|
371
393
|
functionConfig.dirPath = foundPath;
|
372
394
|
}
|
373
395
|
else {
|
374
|
-
console.log(chalk.yellow(`Function ${functionConfig.name} not found locally in any subdirectory`));
|
375
396
|
const { shouldDownload } = await inquirer.prompt([
|
376
397
|
{
|
377
398
|
type: "confirm",
|
378
399
|
name: "shouldDownload",
|
379
|
-
message: "Would you like to download the latest deployment?",
|
380
|
-
default:
|
400
|
+
message: "Function not found locally. Would you like to download the latest deployment?",
|
401
|
+
default: false,
|
381
402
|
},
|
382
403
|
]);
|
383
404
|
if (shouldDownload) {
|
@@ -385,42 +406,15 @@ export class InteractiveCLI {
|
|
385
406
|
console.log(chalk.blue("Downloading latest deployment..."));
|
386
407
|
const { path: downloadedPath, function: remoteFunction } = await downloadLatestFunctionDeployment(this.controller.appwriteServer, functionConfig.$id, join(this.controller.getAppwriteFolderPath(), "functions"));
|
387
408
|
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
409
|
const existingIndex = this.controller.config.functions.findIndex((f) => f?.$id === remoteFunction.$id);
|
413
410
|
if (existingIndex >= 0) {
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
this.controller.config.functions.push(newFunction);
|
411
|
+
// Only update the dirPath if function exists
|
412
|
+
this.controller.config.functions[existingIndex].dirPath =
|
413
|
+
downloadedPath;
|
418
414
|
}
|
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
415
|
await this.controller.reloadConfig();
|
423
416
|
functionConfig.dirPath = downloadedPath;
|
417
|
+
functionPath = downloadedPath;
|
424
418
|
}
|
425
419
|
catch (error) {
|
426
420
|
console.error(chalk.red("Failed to download function deployment:"), error);
|
@@ -428,7 +422,7 @@ export class InteractiveCLI {
|
|
428
422
|
}
|
429
423
|
}
|
430
424
|
else {
|
431
|
-
console.log(chalk.
|
425
|
+
console.log(chalk.red(`Function ${functionConfig.name} not found locally. Cannot deploy.`));
|
432
426
|
return;
|
433
427
|
}
|
434
428
|
}
|
@@ -437,7 +431,13 @@ export class InteractiveCLI {
|
|
437
431
|
console.log(chalk.red("Appwrite server not initialized"));
|
438
432
|
return;
|
439
433
|
}
|
440
|
-
|
434
|
+
try {
|
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);
|
440
|
+
}
|
441
441
|
}
|
442
442
|
async deleteFunction() {
|
443
443
|
const functions = await this.selectFunctions("Select functions to delete:", true, false);
|
@@ -455,52 +455,33 @@ export class InteractiveCLI {
|
|
455
455
|
}
|
456
456
|
}
|
457
457
|
}
|
458
|
-
async selectFunctions(message,
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
const allFunctions = preferLocal
|
471
|
-
? [
|
472
|
-
...configFunctions,
|
473
|
-
...remoteFunctions.map((f) => AppwriteFunctionSchema.parse(f)),
|
474
|
-
]
|
475
|
-
: [
|
476
|
-
...remoteFunctions.map((f) => AppwriteFunctionSchema.parse(f)),
|
477
|
-
...configFunctions.filter((f) => !remoteFunctions.some((rf) => rf.name === f.name)),
|
478
|
-
];
|
479
|
-
if (allFunctions.length === 0) {
|
480
|
-
console.log(chalk.red("No functions available"));
|
481
|
-
return [];
|
482
|
-
}
|
483
|
-
const choices = allFunctions
|
484
|
-
.sort((a, b) => a.name.localeCompare(b.name))
|
485
|
-
.map((func) => ({
|
486
|
-
name: func.name,
|
487
|
-
value: func,
|
488
|
-
}));
|
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
|
+
];
|
489
470
|
const { selectedFunctions } = await inquirer.prompt([
|
490
471
|
{
|
491
|
-
type:
|
472
|
+
type: multiple ? "checkbox" : "list",
|
492
473
|
name: "selectedFunctions",
|
493
|
-
message
|
494
|
-
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
|
+
})),
|
495
481
|
loop: true,
|
496
|
-
pageSize: 10,
|
497
482
|
},
|
498
483
|
]);
|
499
|
-
|
500
|
-
if (!multiSelect) {
|
501
|
-
return selectedFunctions ? [selectedFunctions] : [];
|
502
|
-
}
|
503
|
-
return selectedFunctions || [];
|
484
|
+
return multiple ? selectedFunctions : [selectedFunctions];
|
504
485
|
}
|
505
486
|
getLocalFunctions() {
|
506
487
|
const configFunctions = this.controller.config?.functions || [];
|
@@ -805,55 +786,153 @@ export class InteractiveCLI {
|
|
805
786
|
const hasLocal = localFunctions.some((lf) => lf.$id === func.$id);
|
806
787
|
const hasRemote = remoteFunctions.some((rf) => rf.$id === func.$id);
|
807
788
|
if (hasLocal && hasRemote) {
|
789
|
+
// First try to find the function locally
|
790
|
+
let functionPath = join(this.controller.getAppwriteFolderPath(), "functions", func.name);
|
791
|
+
if (!fs.existsSync(functionPath)) {
|
792
|
+
console.log(chalk.yellow(`Function not found in primary location, searching subdirectories...`));
|
793
|
+
const foundPath = await this.findFunctionInSubdirectories(this.controller.getAppwriteFolderPath(), func.name);
|
794
|
+
if (foundPath) {
|
795
|
+
console.log(chalk.green(`Found function at: ${foundPath}`));
|
796
|
+
functionPath = foundPath;
|
797
|
+
}
|
798
|
+
}
|
808
799
|
const { preference } = await inquirer.prompt([
|
809
800
|
{
|
810
801
|
type: "list",
|
811
802
|
name: "preference",
|
812
|
-
message: `Function "${func.name}"
|
803
|
+
message: `Function "${func.name}" ${functionPath ? "found at " + functionPath : "not found locally"}. What would you like to do?`,
|
813
804
|
choices: [
|
814
|
-
|
815
|
-
|
816
|
-
|
817
|
-
|
805
|
+
...(functionPath
|
806
|
+
? [
|
807
|
+
{
|
808
|
+
name: "Keep local version (deploy to remote)",
|
809
|
+
value: "local",
|
810
|
+
},
|
811
|
+
]
|
812
|
+
: []),
|
818
813
|
{ name: "Use remote version (download)", value: "remote" },
|
814
|
+
{ name: "Update config only", value: "config" },
|
819
815
|
{ name: "Skip this function", value: "skip" },
|
820
816
|
],
|
821
817
|
},
|
822
818
|
]);
|
823
|
-
if (preference === "local") {
|
819
|
+
if (preference === "local" && functionPath) {
|
824
820
|
await this.controller.deployFunction(func.name);
|
825
821
|
}
|
826
822
|
else if (preference === "remote") {
|
827
823
|
await downloadLatestFunctionDeployment(this.controller.appwriteServer, func.$id, join(this.controller.getAppwriteFolderPath(), "functions"));
|
828
824
|
}
|
825
|
+
else if (preference === "config") {
|
826
|
+
const remoteFunction = await getFunction(this.controller.appwriteServer, func.$id);
|
827
|
+
const newFunction = {
|
828
|
+
$id: remoteFunction.$id,
|
829
|
+
name: remoteFunction.name,
|
830
|
+
runtime: remoteFunction.runtime,
|
831
|
+
execute: remoteFunction.execute || [],
|
832
|
+
events: remoteFunction.events || [],
|
833
|
+
schedule: remoteFunction.schedule || "",
|
834
|
+
timeout: remoteFunction.timeout || 15,
|
835
|
+
enabled: remoteFunction.enabled !== false,
|
836
|
+
logging: remoteFunction.logging !== false,
|
837
|
+
entrypoint: remoteFunction.entrypoint || "src/index.ts",
|
838
|
+
commands: remoteFunction.commands || "npm install",
|
839
|
+
scopes: (remoteFunction.scopes || []),
|
840
|
+
installationId: remoteFunction.installationId,
|
841
|
+
providerRepositoryId: remoteFunction.providerRepositoryId,
|
842
|
+
providerBranch: remoteFunction.providerBranch,
|
843
|
+
providerSilentMode: remoteFunction.providerSilentMode,
|
844
|
+
providerRootDirectory: remoteFunction.providerRootDirectory,
|
845
|
+
specification: remoteFunction.specification,
|
846
|
+
};
|
847
|
+
const existingIndex = this.controller.config.functions.findIndex((f) => f.$id === remoteFunction.$id);
|
848
|
+
if (existingIndex >= 0) {
|
849
|
+
this.controller.config.functions[existingIndex] = newFunction;
|
850
|
+
}
|
851
|
+
else {
|
852
|
+
this.controller.config.functions.push(newFunction);
|
853
|
+
}
|
854
|
+
console.log(chalk.green(`Updated config for function: ${func.name}`));
|
855
|
+
}
|
829
856
|
}
|
830
857
|
else if (hasLocal) {
|
831
|
-
|
858
|
+
// Similar check for local-only functions
|
859
|
+
let functionPath = join(this.controller.getAppwriteFolderPath(), "functions", func.name);
|
860
|
+
if (!fs.existsSync(functionPath)) {
|
861
|
+
const foundPath = await this.findFunctionInSubdirectories(this.controller.getAppwriteFolderPath(), func.name);
|
862
|
+
if (foundPath) {
|
863
|
+
functionPath = foundPath;
|
864
|
+
}
|
865
|
+
}
|
866
|
+
const { action } = await inquirer.prompt([
|
832
867
|
{
|
833
|
-
type: "
|
834
|
-
name: "
|
835
|
-
message: `Function "${func.name}"
|
836
|
-
|
868
|
+
type: "list",
|
869
|
+
name: "action",
|
870
|
+
message: `Function "${func.name}" ${functionPath ? "found at " + functionPath : "not found locally"}. What would you like to do?`,
|
871
|
+
choices: [
|
872
|
+
...(functionPath
|
873
|
+
? [
|
874
|
+
{
|
875
|
+
name: "Deploy to remote",
|
876
|
+
value: "deploy",
|
877
|
+
},
|
878
|
+
]
|
879
|
+
: []),
|
880
|
+
{ name: "Skip this function", value: "skip" },
|
881
|
+
],
|
837
882
|
},
|
838
883
|
]);
|
839
|
-
if (deploy) {
|
884
|
+
if (action === "deploy" && functionPath) {
|
840
885
|
await this.controller.deployFunction(func.name);
|
841
886
|
}
|
842
887
|
}
|
843
888
|
else if (hasRemote) {
|
844
|
-
const {
|
889
|
+
const { action } = await inquirer.prompt([
|
845
890
|
{
|
846
|
-
type: "
|
847
|
-
name: "
|
848
|
-
message: `Function "${func.name}" exists only remotely.
|
849
|
-
|
891
|
+
type: "list",
|
892
|
+
name: "action",
|
893
|
+
message: `Function "${func.name}" exists only remotely. What would you like to do?`,
|
894
|
+
choices: [
|
895
|
+
{ name: "Update config only", value: "config" },
|
896
|
+
{ name: "Download locally", value: "download" },
|
897
|
+
{ name: "Skip this function", value: "skip" },
|
898
|
+
],
|
850
899
|
},
|
851
900
|
]);
|
852
|
-
if (download) {
|
901
|
+
if (action === "download") {
|
853
902
|
await downloadLatestFunctionDeployment(this.controller.appwriteServer, func.$id, join(this.controller.getAppwriteFolderPath(), "functions"));
|
854
903
|
}
|
904
|
+
else if (action === "config") {
|
905
|
+
const remoteFunction = await getFunction(this.controller.appwriteServer, func.$id);
|
906
|
+
const newFunction = {
|
907
|
+
$id: remoteFunction.$id,
|
908
|
+
name: remoteFunction.name,
|
909
|
+
runtime: remoteFunction.runtime,
|
910
|
+
execute: remoteFunction.execute || [],
|
911
|
+
events: remoteFunction.events || [],
|
912
|
+
schedule: remoteFunction.schedule || "",
|
913
|
+
timeout: remoteFunction.timeout || 15,
|
914
|
+
enabled: remoteFunction.enabled !== false,
|
915
|
+
logging: remoteFunction.logging !== false,
|
916
|
+
entrypoint: remoteFunction.entrypoint || "src/index.ts",
|
917
|
+
commands: remoteFunction.commands || "npm install",
|
918
|
+
scopes: (remoteFunction.scopes || []),
|
919
|
+
installationId: remoteFunction.installationId,
|
920
|
+
providerRepositoryId: remoteFunction.providerRepositoryId,
|
921
|
+
providerBranch: remoteFunction.providerBranch,
|
922
|
+
providerSilentMode: remoteFunction.providerSilentMode,
|
923
|
+
providerRootDirectory: remoteFunction.providerRootDirectory,
|
924
|
+
specification: remoteFunction.specification,
|
925
|
+
};
|
926
|
+
this.controller.config.functions =
|
927
|
+
this.controller.config.functions || [];
|
928
|
+
this.controller.config.functions.push(newFunction);
|
929
|
+
console.log(chalk.green(`Added config for remote function: ${func.name}`));
|
930
|
+
}
|
855
931
|
}
|
856
932
|
}
|
933
|
+
// Update schemas after all changes
|
934
|
+
const schemaGenerator = new SchemaGenerator(this.controller.config, this.controller.getAppwriteFolderPath());
|
935
|
+
schemaGenerator.updateConfig(this.controller.config);
|
857
936
|
}
|
858
937
|
console.log(chalk.green("✨ Configurations synchronized successfully!"));
|
859
938
|
}
|
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.
|
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
|
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,
|
package/src/interactiveCLI.ts
CHANGED
@@ -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";
|
@@ -397,8 +398,19 @@ export class InteractiveCLI {
|
|
397
398
|
join(process.cwd(), functionName), // ./functionName
|
398
399
|
join(basePath, "functions", functionName), // appwriteFolder/functions/functionName
|
399
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()
|
400
404
|
];
|
401
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
|
+
|
402
414
|
// Check common locations first
|
403
415
|
for (const path of commonPaths) {
|
404
416
|
try {
|
@@ -444,7 +456,19 @@ export class InteractiveCLI {
|
|
444
456
|
!entry.name.startsWith(".") &&
|
445
457
|
entry.name !== "node_modules"
|
446
458
|
) {
|
447
|
-
|
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) {
|
448
472
|
console.log(chalk.green(`Found function at: ${fullPath}`));
|
449
473
|
return fullPath;
|
450
474
|
}
|
@@ -486,6 +510,11 @@ export class InteractiveCLI {
|
|
486
510
|
return;
|
487
511
|
}
|
488
512
|
|
513
|
+
// Ensure functions array exists
|
514
|
+
if (!this.controller.config.functions) {
|
515
|
+
this.controller.config.functions = [];
|
516
|
+
}
|
517
|
+
|
489
518
|
let functionPath = join(
|
490
519
|
this.controller.getAppwriteFolderPath(),
|
491
520
|
"functions",
|
@@ -500,7 +529,7 @@ export class InteractiveCLI {
|
|
500
529
|
);
|
501
530
|
const foundPath = await this.findFunctionInSubdirectories(
|
502
531
|
this.controller.getAppwriteFolderPath(),
|
503
|
-
functionConfig.name
|
532
|
+
functionConfig.name.toLowerCase()
|
504
533
|
);
|
505
534
|
|
506
535
|
if (foundPath) {
|
@@ -508,18 +537,13 @@ export class InteractiveCLI {
|
|
508
537
|
functionPath = foundPath;
|
509
538
|
functionConfig.dirPath = foundPath;
|
510
539
|
} else {
|
511
|
-
console.log(
|
512
|
-
chalk.yellow(
|
513
|
-
`Function ${functionConfig.name} not found locally in any subdirectory`
|
514
|
-
)
|
515
|
-
);
|
516
|
-
|
517
540
|
const { shouldDownload } = await inquirer.prompt([
|
518
541
|
{
|
519
542
|
type: "confirm",
|
520
543
|
name: "shouldDownload",
|
521
|
-
message:
|
522
|
-
|
544
|
+
message:
|
545
|
+
"Function not found locally. Would you like to download the latest deployment?",
|
546
|
+
default: false,
|
523
547
|
},
|
524
548
|
]);
|
525
549
|
|
@@ -536,53 +560,19 @@ export class InteractiveCLI {
|
|
536
560
|
chalk.green(`✨ Function downloaded to ${downloadedPath}`)
|
537
561
|
);
|
538
562
|
|
539
|
-
// Update the config and functions array safely
|
540
|
-
this.controller.config.functions =
|
541
|
-
this.controller.config.functions || [];
|
542
|
-
|
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
|
-
};
|
564
|
-
|
565
563
|
const existingIndex = this.controller.config.functions.findIndex(
|
566
564
|
(f) => f?.$id === remoteFunction.$id
|
567
565
|
);
|
568
566
|
|
569
567
|
if (existingIndex >= 0) {
|
570
|
-
|
571
|
-
|
572
|
-
|
568
|
+
// Only update the dirPath if function exists
|
569
|
+
this.controller.config.functions[existingIndex].dirPath =
|
570
|
+
downloadedPath;
|
573
571
|
}
|
574
572
|
|
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
|
-
);
|
583
|
-
|
584
573
|
await this.controller.reloadConfig();
|
585
574
|
functionConfig.dirPath = downloadedPath;
|
575
|
+
functionPath = downloadedPath;
|
586
576
|
} catch (error) {
|
587
577
|
console.error(
|
588
578
|
chalk.red("Failed to download function deployment:"),
|
@@ -591,7 +581,11 @@ export class InteractiveCLI {
|
|
591
581
|
return;
|
592
582
|
}
|
593
583
|
} else {
|
594
|
-
console.log(
|
584
|
+
console.log(
|
585
|
+
chalk.red(
|
586
|
+
`Function ${functionConfig.name} not found locally. Cannot deploy.`
|
587
|
+
)
|
588
|
+
);
|
595
589
|
return;
|
596
590
|
}
|
597
591
|
}
|
@@ -602,11 +596,16 @@ export class InteractiveCLI {
|
|
602
596
|
return;
|
603
597
|
}
|
604
598
|
|
605
|
-
|
606
|
-
|
607
|
-
|
608
|
-
|
609
|
-
|
599
|
+
try {
|
600
|
+
await deployLocalFunction(
|
601
|
+
this.controller.appwriteServer,
|
602
|
+
functionConfig.name,
|
603
|
+
functionConfig
|
604
|
+
);
|
605
|
+
console.log(chalk.green("✨ Function deployed successfully!"));
|
606
|
+
} catch (error) {
|
607
|
+
console.error(chalk.red("Failed to deploy function:"), error);
|
608
|
+
}
|
610
609
|
}
|
611
610
|
|
612
611
|
private async deleteFunction(): Promise<void> {
|
@@ -638,67 +637,42 @@ export class InteractiveCLI {
|
|
638
637
|
|
639
638
|
private async selectFunctions(
|
640
639
|
message: string,
|
641
|
-
|
642
|
-
|
640
|
+
multiple: boolean = true,
|
641
|
+
includeRemote: boolean = false
|
643
642
|
): Promise<AppwriteFunction[]> {
|
644
|
-
|
645
|
-
|
646
|
-
|
647
|
-
|
648
|
-
|
649
|
-
|
650
|
-
const functions = await this.controller!.listAllFunctions();
|
651
|
-
remoteFunctions = functions;
|
652
|
-
} catch (error) {
|
653
|
-
console.log(
|
654
|
-
chalk.yellow(
|
655
|
-
`Note: Remote functions not available, using only local functions`
|
656
|
-
)
|
657
|
-
);
|
658
|
-
}
|
659
|
-
|
660
|
-
// Combine functions based on whether we're deploying or not
|
661
|
-
const allFunctions = preferLocal
|
662
|
-
? [
|
663
|
-
...configFunctions,
|
664
|
-
...remoteFunctions.map((f) => AppwriteFunctionSchema.parse(f)),
|
665
|
-
]
|
666
|
-
: [
|
667
|
-
...remoteFunctions.map((f) => AppwriteFunctionSchema.parse(f)),
|
668
|
-
...configFunctions.filter(
|
669
|
-
(f) => !remoteFunctions.some((rf) => rf.name === f.name)
|
670
|
-
),
|
671
|
-
];
|
672
|
-
|
673
|
-
if (allFunctions.length === 0) {
|
674
|
-
console.log(chalk.red("No functions available"));
|
675
|
-
return [];
|
676
|
-
}
|
643
|
+
const remoteFunctions = includeRemote
|
644
|
+
? await listFunctions(this.controller!.appwriteServer!, [
|
645
|
+
Query.limit(1000),
|
646
|
+
])
|
647
|
+
: { functions: [] };
|
648
|
+
const localFunctions = this.getLocalFunctions();
|
677
649
|
|
678
|
-
|
679
|
-
|
680
|
-
|
681
|
-
|
682
|
-
|
683
|
-
|
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
|
+
];
|
684
657
|
|
685
658
|
const { selectedFunctions } = await inquirer.prompt([
|
686
659
|
{
|
687
|
-
type:
|
660
|
+
type: multiple ? "checkbox" : "list",
|
688
661
|
name: "selectedFunctions",
|
689
|
-
message
|
690
|
-
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
|
+
})),
|
691
671
|
loop: true,
|
692
|
-
pageSize: 10,
|
693
672
|
},
|
694
673
|
]);
|
695
674
|
|
696
|
-
|
697
|
-
if (!multiSelect) {
|
698
|
-
return selectedFunctions ? [selectedFunctions] : [];
|
699
|
-
}
|
700
|
-
|
701
|
-
return selectedFunctions || [];
|
675
|
+
return multiple ? selectedFunctions : [selectedFunctions];
|
702
676
|
}
|
703
677
|
|
704
678
|
private getLocalFunctions(): AppwriteFunction[] {
|
@@ -1102,23 +1076,54 @@ export class InteractiveCLI {
|
|
1102
1076
|
const hasRemote = remoteFunctions.some((rf) => rf.$id === func.$id);
|
1103
1077
|
|
1104
1078
|
if (hasLocal && hasRemote) {
|
1079
|
+
// First try to find the function locally
|
1080
|
+
let functionPath = join(
|
1081
|
+
this.controller!.getAppwriteFolderPath(),
|
1082
|
+
"functions",
|
1083
|
+
func.name
|
1084
|
+
);
|
1085
|
+
|
1086
|
+
if (!fs.existsSync(functionPath)) {
|
1087
|
+
console.log(
|
1088
|
+
chalk.yellow(
|
1089
|
+
`Function not found in primary location, searching subdirectories...`
|
1090
|
+
)
|
1091
|
+
);
|
1092
|
+
const foundPath = await this.findFunctionInSubdirectories(
|
1093
|
+
this.controller!.getAppwriteFolderPath(),
|
1094
|
+
func.name
|
1095
|
+
);
|
1096
|
+
|
1097
|
+
if (foundPath) {
|
1098
|
+
console.log(chalk.green(`Found function at: ${foundPath}`));
|
1099
|
+
functionPath = foundPath;
|
1100
|
+
}
|
1101
|
+
}
|
1102
|
+
|
1105
1103
|
const { preference } = await inquirer.prompt([
|
1106
1104
|
{
|
1107
1105
|
type: "list",
|
1108
1106
|
name: "preference",
|
1109
|
-
message: `Function "${func.name}"
|
1107
|
+
message: `Function "${func.name}" ${
|
1108
|
+
functionPath ? "found at " + functionPath : "not found locally"
|
1109
|
+
}. What would you like to do?`,
|
1110
1110
|
choices: [
|
1111
|
-
|
1112
|
-
|
1113
|
-
|
1114
|
-
|
1111
|
+
...(functionPath
|
1112
|
+
? [
|
1113
|
+
{
|
1114
|
+
name: "Keep local version (deploy to remote)",
|
1115
|
+
value: "local",
|
1116
|
+
},
|
1117
|
+
]
|
1118
|
+
: []),
|
1115
1119
|
{ name: "Use remote version (download)", value: "remote" },
|
1120
|
+
{ name: "Update config only", value: "config" },
|
1116
1121
|
{ name: "Skip this function", value: "skip" },
|
1117
1122
|
],
|
1118
1123
|
},
|
1119
1124
|
]);
|
1120
1125
|
|
1121
|
-
if (preference === "local") {
|
1126
|
+
if (preference === "local" && functionPath) {
|
1122
1127
|
await this.controller!.deployFunction(func.name);
|
1123
1128
|
} else if (preference === "remote") {
|
1124
1129
|
await downloadLatestFunctionDeployment(
|
@@ -1126,39 +1131,152 @@ export class InteractiveCLI {
|
|
1126
1131
|
func.$id,
|
1127
1132
|
join(this.controller!.getAppwriteFolderPath(), "functions")
|
1128
1133
|
);
|
1134
|
+
} else if (preference === "config") {
|
1135
|
+
const remoteFunction = await getFunction(
|
1136
|
+
this.controller!.appwriteServer!,
|
1137
|
+
func.$id
|
1138
|
+
);
|
1139
|
+
|
1140
|
+
const newFunction = {
|
1141
|
+
$id: remoteFunction.$id,
|
1142
|
+
name: remoteFunction.name,
|
1143
|
+
runtime: remoteFunction.runtime as Runtime,
|
1144
|
+
execute: remoteFunction.execute || [],
|
1145
|
+
events: remoteFunction.events || [],
|
1146
|
+
schedule: remoteFunction.schedule || "",
|
1147
|
+
timeout: remoteFunction.timeout || 15,
|
1148
|
+
enabled: remoteFunction.enabled !== false,
|
1149
|
+
logging: remoteFunction.logging !== false,
|
1150
|
+
entrypoint: remoteFunction.entrypoint || "src/index.ts",
|
1151
|
+
commands: remoteFunction.commands || "npm install",
|
1152
|
+
scopes: (remoteFunction.scopes || []) as FunctionScope[],
|
1153
|
+
installationId: remoteFunction.installationId,
|
1154
|
+
providerRepositoryId: remoteFunction.providerRepositoryId,
|
1155
|
+
providerBranch: remoteFunction.providerBranch,
|
1156
|
+
providerSilentMode: remoteFunction.providerSilentMode,
|
1157
|
+
providerRootDirectory: remoteFunction.providerRootDirectory,
|
1158
|
+
specification: remoteFunction.specification as Specification,
|
1159
|
+
};
|
1160
|
+
|
1161
|
+
const existingIndex = this.controller!.config!.functions!.findIndex(
|
1162
|
+
(f) => f.$id === remoteFunction.$id
|
1163
|
+
);
|
1164
|
+
|
1165
|
+
if (existingIndex >= 0) {
|
1166
|
+
this.controller!.config!.functions![existingIndex] = newFunction;
|
1167
|
+
} else {
|
1168
|
+
this.controller!.config!.functions!.push(newFunction);
|
1169
|
+
}
|
1170
|
+
console.log(
|
1171
|
+
chalk.green(`Updated config for function: ${func.name}`)
|
1172
|
+
);
|
1129
1173
|
}
|
1130
1174
|
} else if (hasLocal) {
|
1131
|
-
|
1175
|
+
// Similar check for local-only functions
|
1176
|
+
let functionPath = join(
|
1177
|
+
this.controller!.getAppwriteFolderPath(),
|
1178
|
+
"functions",
|
1179
|
+
func.name
|
1180
|
+
);
|
1181
|
+
|
1182
|
+
if (!fs.existsSync(functionPath)) {
|
1183
|
+
const foundPath = await this.findFunctionInSubdirectories(
|
1184
|
+
this.controller!.getAppwriteFolderPath(),
|
1185
|
+
func.name
|
1186
|
+
);
|
1187
|
+
|
1188
|
+
if (foundPath) {
|
1189
|
+
functionPath = foundPath;
|
1190
|
+
}
|
1191
|
+
}
|
1192
|
+
|
1193
|
+
const { action } = await inquirer.prompt([
|
1132
1194
|
{
|
1133
|
-
type: "
|
1134
|
-
name: "
|
1135
|
-
message: `Function "${func.name}"
|
1136
|
-
|
1195
|
+
type: "list",
|
1196
|
+
name: "action",
|
1197
|
+
message: `Function "${func.name}" ${
|
1198
|
+
functionPath ? "found at " + functionPath : "not found locally"
|
1199
|
+
}. What would you like to do?`,
|
1200
|
+
choices: [
|
1201
|
+
...(functionPath
|
1202
|
+
? [
|
1203
|
+
{
|
1204
|
+
name: "Deploy to remote",
|
1205
|
+
value: "deploy",
|
1206
|
+
},
|
1207
|
+
]
|
1208
|
+
: []),
|
1209
|
+
{ name: "Skip this function", value: "skip" },
|
1210
|
+
],
|
1137
1211
|
},
|
1138
1212
|
]);
|
1139
1213
|
|
1140
|
-
if (deploy) {
|
1214
|
+
if (action === "deploy" && functionPath) {
|
1141
1215
|
await this.controller!.deployFunction(func.name);
|
1142
1216
|
}
|
1143
1217
|
} else if (hasRemote) {
|
1144
|
-
const {
|
1218
|
+
const { action } = await inquirer.prompt([
|
1145
1219
|
{
|
1146
|
-
type: "
|
1147
|
-
name: "
|
1148
|
-
message: `Function "${func.name}" exists only remotely.
|
1149
|
-
|
1220
|
+
type: "list",
|
1221
|
+
name: "action",
|
1222
|
+
message: `Function "${func.name}" exists only remotely. What would you like to do?`,
|
1223
|
+
choices: [
|
1224
|
+
{ name: "Update config only", value: "config" },
|
1225
|
+
{ name: "Download locally", value: "download" },
|
1226
|
+
{ name: "Skip this function", value: "skip" },
|
1227
|
+
],
|
1150
1228
|
},
|
1151
1229
|
]);
|
1152
1230
|
|
1153
|
-
if (download) {
|
1231
|
+
if (action === "download") {
|
1154
1232
|
await downloadLatestFunctionDeployment(
|
1155
1233
|
this.controller!.appwriteServer!,
|
1156
1234
|
func.$id,
|
1157
1235
|
join(this.controller!.getAppwriteFolderPath(), "functions")
|
1158
1236
|
);
|
1237
|
+
} else if (action === "config") {
|
1238
|
+
const remoteFunction = await getFunction(
|
1239
|
+
this.controller!.appwriteServer!,
|
1240
|
+
func.$id
|
1241
|
+
);
|
1242
|
+
|
1243
|
+
const newFunction = {
|
1244
|
+
$id: remoteFunction.$id,
|
1245
|
+
name: remoteFunction.name,
|
1246
|
+
runtime: remoteFunction.runtime as Runtime,
|
1247
|
+
execute: remoteFunction.execute || [],
|
1248
|
+
events: remoteFunction.events || [],
|
1249
|
+
schedule: remoteFunction.schedule || "",
|
1250
|
+
timeout: remoteFunction.timeout || 15,
|
1251
|
+
enabled: remoteFunction.enabled !== false,
|
1252
|
+
logging: remoteFunction.logging !== false,
|
1253
|
+
entrypoint: remoteFunction.entrypoint || "src/index.ts",
|
1254
|
+
commands: remoteFunction.commands || "npm install",
|
1255
|
+
scopes: (remoteFunction.scopes || []) as FunctionScope[],
|
1256
|
+
installationId: remoteFunction.installationId,
|
1257
|
+
providerRepositoryId: remoteFunction.providerRepositoryId,
|
1258
|
+
providerBranch: remoteFunction.providerBranch,
|
1259
|
+
providerSilentMode: remoteFunction.providerSilentMode,
|
1260
|
+
providerRootDirectory: remoteFunction.providerRootDirectory,
|
1261
|
+
specification: remoteFunction.specification as Specification,
|
1262
|
+
};
|
1263
|
+
|
1264
|
+
this.controller!.config!.functions =
|
1265
|
+
this.controller!.config!.functions || [];
|
1266
|
+
this.controller!.config!.functions.push(newFunction);
|
1267
|
+
console.log(
|
1268
|
+
chalk.green(`Added config for remote function: ${func.name}`)
|
1269
|
+
);
|
1159
1270
|
}
|
1160
1271
|
}
|
1161
1272
|
}
|
1273
|
+
|
1274
|
+
// Update schemas after all changes
|
1275
|
+
const schemaGenerator = new SchemaGenerator(
|
1276
|
+
this.controller!.config!,
|
1277
|
+
this.controller!.getAppwriteFolderPath()
|
1278
|
+
);
|
1279
|
+
schemaGenerator.updateConfig(this.controller!.config!);
|
1162
1280
|
}
|
1163
1281
|
|
1164
1282
|
console.log(chalk.green("✨ Configurations synchronized successfully!"));
|