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 +2 -0
- package/dist/functions/deployments.js +17 -9
- package/dist/interactiveCLI.js +90 -153
- package/package.json +1 -1
- package/src/functions/deployments.ts +20 -8
- package/src/interactiveCLI.ts +123 -206
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
|
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
@@ -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,126 +379,64 @@ export class InteractiveCLI {
|
|
361
379
|
console.log(chalk.red("Invalid function configuration"));
|
362
380
|
return;
|
363
381
|
}
|
364
|
-
|
365
|
-
|
366
|
-
|
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
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
|
424
|
-
|
425
|
-
|
426
|
-
|
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
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
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
|
425
|
+
console.log(chalk.red(`Function ${functionConfig.name} not found locally. Cannot deploy.`));
|
476
426
|
return;
|
477
427
|
}
|
478
428
|
}
|
479
|
-
|
480
|
-
|
481
|
-
|
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,
|
503
|
-
|
504
|
-
|
505
|
-
|
506
|
-
|
507
|
-
|
508
|
-
|
509
|
-
|
510
|
-
|
511
|
-
|
512
|
-
|
513
|
-
|
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:
|
472
|
+
type: multiple ? "checkbox" : "list",
|
536
473
|
name: "selectedFunctions",
|
537
|
-
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
|
-
|
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.
|
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
@@ -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
|
-
|
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
|
-
|
491
|
-
|
492
|
-
|
493
|
-
|
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
|
-
|
577
|
-
|
578
|
-
|
579
|
-
|
580
|
-
|
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
|
-
|
602
|
-
|
603
|
-
|
604
|
-
|
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
|
-
|
608
|
-
|
609
|
-
|
610
|
-
|
611
|
-
|
612
|
-
|
613
|
-
|
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
|
-
|
617
|
-
|
618
|
-
|
619
|
-
|
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
|
-
|
624
|
-
|
625
|
-
|
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
|
-
|
632
|
-
|
633
|
-
|
634
|
-
|
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
|
-
|
643
|
-
|
644
|
-
|
645
|
-
|
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
|
586
|
+
`Function ${functionConfig.name} not found locally. Cannot deploy.`
|
650
587
|
)
|
651
588
|
);
|
652
589
|
return;
|
653
590
|
}
|
654
591
|
}
|
592
|
+
}
|
655
593
|
|
656
|
-
|
657
|
-
|
658
|
-
|
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
|
-
|
699
|
-
|
640
|
+
multiple: boolean = true,
|
641
|
+
includeRemote: boolean = false
|
700
642
|
): Promise<AppwriteFunction[]> {
|
701
|
-
|
702
|
-
|
703
|
-
|
704
|
-
|
705
|
-
|
706
|
-
|
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
|
-
|
736
|
-
|
737
|
-
|
738
|
-
|
739
|
-
|
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:
|
660
|
+
type: multiple ? "checkbox" : "list",
|
745
661
|
name: "selectedFunctions",
|
746
|
-
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
|
-
|
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[] {
|