appwrite-utils-cli 0.9.993 → 0.9.994
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 +1 -2
- package/dist/collections/methods.js +6 -4
- package/dist/functions/deployments.d.ts +4 -0
- package/dist/functions/deployments.js +114 -0
- package/dist/functions/methods.d.ts +13 -8
- package/dist/functions/methods.js +74 -20
- package/dist/functions/templates/count-docs-in-collection/src/main.d.ts +21 -0
- package/dist/functions/templates/count-docs-in-collection/src/main.js +114 -0
- package/dist/functions/templates/count-docs-in-collection/src/request.d.ts +15 -0
- package/dist/functions/templates/count-docs-in-collection/src/request.js +6 -0
- package/dist/functions/templates/typescript-node/src/index.d.ts +11 -0
- package/dist/functions/templates/typescript-node/src/index.js +11 -0
- package/dist/interactiveCLI.d.ts +6 -0
- package/dist/interactiveCLI.js +437 -32
- package/dist/main.js +0 -0
- package/dist/migrations/appwriteToX.js +32 -1
- package/dist/migrations/schemaStrings.d.ts +1 -0
- package/dist/migrations/schemaStrings.js +74 -2
- package/dist/migrations/transfer.js +112 -123
- package/dist/utils/loadConfigs.d.ts +1 -0
- package/dist/utils/loadConfigs.js +19 -0
- package/dist/utils/schemaStrings.js +27 -0
- package/dist/utilsController.d.ts +5 -1
- package/dist/utilsController.js +54 -2
- package/package.json +57 -55
- package/src/collections/methods.ts +29 -10
- package/src/functions/deployments.ts +190 -0
- package/src/functions/methods.ts +295 -235
- package/src/functions/templates/count-docs-in-collection/README.md +54 -0
- package/src/functions/templates/count-docs-in-collection/src/main.ts +159 -0
- package/src/functions/templates/count-docs-in-collection/src/request.ts +9 -0
- package/src/functions/templates/poetry/README.md +30 -0
- package/src/functions/templates/poetry/pyproject.toml +16 -0
- package/src/functions/templates/poetry/src/__init__.py +0 -0
- package/src/functions/templates/poetry/src/index.py +16 -0
- package/src/functions/templates/typescript-node/README.md +32 -0
- package/src/functions/templates/typescript-node/src/index.ts +23 -0
- package/src/interactiveCLI.ts +606 -47
- package/src/migrations/appwriteToX.ts +44 -1
- package/src/migrations/schemaStrings.ts +83 -2
- package/src/migrations/transfer.ts +280 -207
- package/src/setupController.ts +41 -41
- package/src/utils/loadConfigs.ts +24 -0
- package/src/utils/schemaStrings.ts +27 -0
- package/src/utilsController.ts +63 -3
- package/tsconfig.json +8 -1
package/src/interactiveCLI.ts
CHANGED
@@ -11,26 +11,47 @@ import {
|
|
11
11
|
type Models,
|
12
12
|
Compression,
|
13
13
|
Query,
|
14
|
+
Functions,
|
14
15
|
} from "node-appwrite";
|
15
16
|
import { getClient } from "./utils/getClientFromConfig.js";
|
16
17
|
import type { TransferOptions } from "./migrations/transfer.js";
|
17
18
|
import {
|
19
|
+
AppwriteFunctionSchema,
|
18
20
|
parseAttribute,
|
19
21
|
PermissionToAppwritePermission,
|
22
|
+
RuntimeSchema,
|
23
|
+
permissionSchema,
|
20
24
|
type AppwriteConfig,
|
25
|
+
type AppwriteFunction,
|
21
26
|
type ConfigDatabases,
|
27
|
+
type Runtime,
|
28
|
+
type Specification,
|
29
|
+
type FunctionScope,
|
22
30
|
} from "appwrite-utils";
|
23
31
|
import { ulid } from "ulidx";
|
24
32
|
import chalk from "chalk";
|
25
33
|
import { DateTime } from "luxon";
|
26
|
-
import {
|
34
|
+
import {
|
35
|
+
createFunctionTemplate,
|
36
|
+
deleteFunction,
|
37
|
+
downloadLatestFunctionDeployment,
|
38
|
+
listFunctions,
|
39
|
+
listSpecifications,
|
40
|
+
} from "./functions/methods.js";
|
41
|
+
import { deployLocalFunction } from "./functions/deployments.js";
|
42
|
+
import { join } from "node:path";
|
43
|
+
import fs from "node:fs";
|
44
|
+
import { SchemaGenerator } from "./migrations/schemaStrings.js";
|
27
45
|
|
28
46
|
enum CHOICES {
|
29
47
|
CREATE_COLLECTION_CONFIG = "Create collection config file",
|
48
|
+
CREATE_FUNCTION = "Create a new function, from scratch or using a template",
|
49
|
+
DEPLOY_FUNCTION = "Deploy function",
|
50
|
+
DELETE_FUNCTION = "Delete function",
|
30
51
|
SETUP_DIRS_FILES = "Setup directories and files",
|
31
52
|
SETUP_DIRS_FILES_WITH_EXAMPLE_DATA = "Setup directories and files with example data",
|
32
53
|
SYNC_DB = "Push local config to Appwrite",
|
33
|
-
SYNCHRONIZE_CONFIGURATIONS = "Synchronize configurations",
|
54
|
+
SYNCHRONIZE_CONFIGURATIONS = "Synchronize configurations - Pull from Appwrite and write to local config",
|
34
55
|
TRANSFER_DATA = "Transfer data",
|
35
56
|
BACKUP_DATABASE = "Backup database",
|
36
57
|
WIPE_DATABASE = "Wipe database",
|
@@ -72,6 +93,18 @@ export class InteractiveCLI {
|
|
72
93
|
await this.initControllerIfNeeded();
|
73
94
|
await this.createCollectionConfig();
|
74
95
|
break;
|
96
|
+
case CHOICES.CREATE_FUNCTION:
|
97
|
+
await this.initControllerIfNeeded();
|
98
|
+
await this.createFunction();
|
99
|
+
break;
|
100
|
+
case CHOICES.DEPLOY_FUNCTION:
|
101
|
+
await this.initControllerIfNeeded();
|
102
|
+
await this.deployFunction();
|
103
|
+
break;
|
104
|
+
case CHOICES.DELETE_FUNCTION:
|
105
|
+
await this.initControllerIfNeeded();
|
106
|
+
await this.deleteFunction();
|
107
|
+
break;
|
75
108
|
case CHOICES.SETUP_DIRS_FILES:
|
76
109
|
await setupDirsFiles(false, this.currentDir);
|
77
110
|
break;
|
@@ -270,6 +303,389 @@ export class InteractiveCLI {
|
|
270
303
|
return selectedCollections;
|
271
304
|
}
|
272
305
|
|
306
|
+
private async createFunction(): Promise<void> {
|
307
|
+
const { name } = await inquirer.prompt([
|
308
|
+
{
|
309
|
+
type: "input",
|
310
|
+
name: "name",
|
311
|
+
message: "Function name:",
|
312
|
+
validate: (input) => input.length > 0,
|
313
|
+
},
|
314
|
+
]);
|
315
|
+
|
316
|
+
const { template } = await inquirer.prompt([
|
317
|
+
{
|
318
|
+
type: "list",
|
319
|
+
name: "template",
|
320
|
+
message: "Select a template:",
|
321
|
+
choices: [
|
322
|
+
"typescript-node",
|
323
|
+
"poetry",
|
324
|
+
"count-docs-in-collection",
|
325
|
+
"none",
|
326
|
+
],
|
327
|
+
},
|
328
|
+
]);
|
329
|
+
|
330
|
+
const { runtime } = await inquirer.prompt([
|
331
|
+
{
|
332
|
+
type: "list",
|
333
|
+
name: "runtime",
|
334
|
+
message: "Select runtime:",
|
335
|
+
choices: Object.values(RuntimeSchema.Values),
|
336
|
+
},
|
337
|
+
]);
|
338
|
+
|
339
|
+
const specifications = await listSpecifications(
|
340
|
+
this.controller!.appwriteServer!
|
341
|
+
);
|
342
|
+
const { specification } = await inquirer.prompt([
|
343
|
+
{
|
344
|
+
type: "list",
|
345
|
+
name: "specification",
|
346
|
+
message: "Select specification:",
|
347
|
+
choices: [
|
348
|
+
{ name: "None", value: undefined },
|
349
|
+
...specifications.specifications.map((s) => ({
|
350
|
+
name: s.slug,
|
351
|
+
value: s.slug,
|
352
|
+
})),
|
353
|
+
],
|
354
|
+
},
|
355
|
+
]);
|
356
|
+
|
357
|
+
const functionConfig: AppwriteFunction = {
|
358
|
+
$id: ulid(),
|
359
|
+
name,
|
360
|
+
runtime,
|
361
|
+
events: [],
|
362
|
+
execute: ["any"],
|
363
|
+
enabled: true,
|
364
|
+
logging: true,
|
365
|
+
entrypoint: template === "none" ? "src/index.ts" : undefined,
|
366
|
+
specification,
|
367
|
+
predeployCommands: template.includes("typescript")
|
368
|
+
? ["npm install", "npm run build"]
|
369
|
+
: undefined,
|
370
|
+
deployDir: template.includes("typescript") ? "dist" : undefined,
|
371
|
+
};
|
372
|
+
|
373
|
+
if (template !== "none") {
|
374
|
+
await createFunctionTemplate(
|
375
|
+
template as "typescript-node" | "poetry" | "count-docs-in-collection",
|
376
|
+
name,
|
377
|
+
"./functions"
|
378
|
+
);
|
379
|
+
}
|
380
|
+
|
381
|
+
// Add to config
|
382
|
+
if (!this.controller!.config!.functions) {
|
383
|
+
this.controller!.config!.functions = [];
|
384
|
+
}
|
385
|
+
this.controller!.config!.functions.push(functionConfig);
|
386
|
+
|
387
|
+
console.log(chalk.green("✨ Function created successfully!"));
|
388
|
+
}
|
389
|
+
|
390
|
+
private async findFunctionInSubdirectories(
|
391
|
+
basePath: string,
|
392
|
+
functionName: string
|
393
|
+
): Promise<string | null> {
|
394
|
+
const queue = [basePath];
|
395
|
+
|
396
|
+
while (queue.length > 0) {
|
397
|
+
const currentPath = queue.shift()!;
|
398
|
+
|
399
|
+
try {
|
400
|
+
const entries = await fs.promises.readdir(currentPath, {
|
401
|
+
withFileTypes: true,
|
402
|
+
});
|
403
|
+
|
404
|
+
// Check if function exists in current directory
|
405
|
+
const functionPath = join(currentPath, functionName);
|
406
|
+
if (fs.existsSync(functionPath)) {
|
407
|
+
return functionPath;
|
408
|
+
}
|
409
|
+
|
410
|
+
// Add subdirectories to queue
|
411
|
+
for (const entry of entries) {
|
412
|
+
if (entry.isDirectory()) {
|
413
|
+
queue.push(join(currentPath, entry.name));
|
414
|
+
}
|
415
|
+
}
|
416
|
+
} catch (error) {
|
417
|
+
console.log(
|
418
|
+
chalk.yellow(`Skipping inaccessible directory: ${currentPath}`)
|
419
|
+
);
|
420
|
+
}
|
421
|
+
}
|
422
|
+
|
423
|
+
return null;
|
424
|
+
}
|
425
|
+
|
426
|
+
private async deployFunction(): Promise<void> {
|
427
|
+
await this.initControllerIfNeeded();
|
428
|
+
if (!this.controller?.config) {
|
429
|
+
console.log(chalk.red("Failed to initialize controller or load config"));
|
430
|
+
return;
|
431
|
+
}
|
432
|
+
|
433
|
+
const functions = await this.selectFunctions(
|
434
|
+
"Select function to deploy:",
|
435
|
+
false,
|
436
|
+
true
|
437
|
+
);
|
438
|
+
|
439
|
+
if (!functions?.length) {
|
440
|
+
console.log(chalk.red("No function selected"));
|
441
|
+
return;
|
442
|
+
}
|
443
|
+
|
444
|
+
const functionConfig = functions[0];
|
445
|
+
if (!functionConfig) {
|
446
|
+
console.log(chalk.red("Invalid function configuration"));
|
447
|
+
return;
|
448
|
+
}
|
449
|
+
|
450
|
+
let functionPath = join(
|
451
|
+
this.controller.getAppwriteFolderPath(),
|
452
|
+
"functions",
|
453
|
+
functionConfig.name
|
454
|
+
);
|
455
|
+
|
456
|
+
if (!fs.existsSync(functionPath)) {
|
457
|
+
console.log(
|
458
|
+
chalk.yellow(
|
459
|
+
`Function not found in primary location, searching subdirectories...`
|
460
|
+
)
|
461
|
+
);
|
462
|
+
const foundPath = await this.findFunctionInSubdirectories(
|
463
|
+
this.controller.getAppwriteFolderPath(),
|
464
|
+
functionConfig.name
|
465
|
+
);
|
466
|
+
|
467
|
+
if (foundPath) {
|
468
|
+
console.log(chalk.green(`Found function at: ${foundPath}`));
|
469
|
+
functionPath = foundPath;
|
470
|
+
functionConfig.dirPath = foundPath;
|
471
|
+
} else {
|
472
|
+
console.log(
|
473
|
+
chalk.yellow(
|
474
|
+
`Function ${functionConfig.name} not found locally in any subdirectory`
|
475
|
+
)
|
476
|
+
);
|
477
|
+
|
478
|
+
const { shouldDownload } = await inquirer.prompt([
|
479
|
+
{
|
480
|
+
type: "confirm",
|
481
|
+
name: "shouldDownload",
|
482
|
+
message: "Would you like to download the latest deployment?",
|
483
|
+
default: true,
|
484
|
+
},
|
485
|
+
]);
|
486
|
+
|
487
|
+
if (shouldDownload) {
|
488
|
+
try {
|
489
|
+
console.log(chalk.blue("Downloading latest deployment..."));
|
490
|
+
const { path: downloadedPath, function: remoteFunction } =
|
491
|
+
await downloadLatestFunctionDeployment(
|
492
|
+
this.controller.appwriteServer!,
|
493
|
+
functionConfig.$id,
|
494
|
+
join(this.controller.getAppwriteFolderPath(), "functions")
|
495
|
+
);
|
496
|
+
console.log(
|
497
|
+
chalk.green(`✨ Function downloaded to ${downloadedPath}`)
|
498
|
+
);
|
499
|
+
|
500
|
+
// Update the config and functions array safely
|
501
|
+
this.controller.config.functions =
|
502
|
+
this.controller.config.functions || [];
|
503
|
+
|
504
|
+
const newFunction = {
|
505
|
+
$id: remoteFunction.$id,
|
506
|
+
name: remoteFunction.name,
|
507
|
+
runtime: remoteFunction.runtime as Runtime,
|
508
|
+
execute: remoteFunction.execute || [],
|
509
|
+
events: remoteFunction.events || [],
|
510
|
+
schedule: remoteFunction.schedule || "",
|
511
|
+
timeout: remoteFunction.timeout || 15,
|
512
|
+
enabled: remoteFunction.enabled !== false,
|
513
|
+
logging: remoteFunction.logging !== false,
|
514
|
+
entrypoint: remoteFunction.entrypoint || "src/index.ts",
|
515
|
+
commands: remoteFunction.commands || "npm install",
|
516
|
+
dirPath: downloadedPath,
|
517
|
+
scopes: (remoteFunction.scopes || []) as FunctionScope[],
|
518
|
+
installationId: remoteFunction.installationId,
|
519
|
+
providerRepositoryId: remoteFunction.providerRepositoryId,
|
520
|
+
providerBranch: remoteFunction.providerBranch,
|
521
|
+
providerSilentMode: remoteFunction.providerSilentMode,
|
522
|
+
providerRootDirectory: remoteFunction.providerRootDirectory,
|
523
|
+
specification: remoteFunction.specification as Specification,
|
524
|
+
};
|
525
|
+
|
526
|
+
const existingIndex = this.controller.config.functions.findIndex(
|
527
|
+
(f) => f?.$id === remoteFunction.$id
|
528
|
+
);
|
529
|
+
|
530
|
+
if (existingIndex >= 0) {
|
531
|
+
this.controller.config.functions[existingIndex] = newFunction;
|
532
|
+
} else {
|
533
|
+
this.controller.config.functions.push(newFunction);
|
534
|
+
}
|
535
|
+
|
536
|
+
const schemaGenerator = new SchemaGenerator(
|
537
|
+
this.controller.config,
|
538
|
+
this.controller.getAppwriteFolderPath()
|
539
|
+
);
|
540
|
+
schemaGenerator.updateConfig(this.controller.config);
|
541
|
+
console.log(
|
542
|
+
chalk.green("✨ Updated appwriteConfig.ts with new function")
|
543
|
+
);
|
544
|
+
|
545
|
+
await this.controller.reloadConfig();
|
546
|
+
functionConfig.dirPath = downloadedPath;
|
547
|
+
} catch (error) {
|
548
|
+
console.error(
|
549
|
+
chalk.red("Failed to download function deployment:"),
|
550
|
+
error
|
551
|
+
);
|
552
|
+
return;
|
553
|
+
}
|
554
|
+
} else {
|
555
|
+
console.log(chalk.yellow("Deployment cancelled"));
|
556
|
+
return;
|
557
|
+
}
|
558
|
+
}
|
559
|
+
}
|
560
|
+
|
561
|
+
if (!this.controller.appwriteServer) {
|
562
|
+
console.log(chalk.red("Appwrite server not initialized"));
|
563
|
+
return;
|
564
|
+
}
|
565
|
+
|
566
|
+
await deployLocalFunction(
|
567
|
+
this.controller.appwriteServer,
|
568
|
+
functionConfig.name,
|
569
|
+
functionConfig
|
570
|
+
);
|
571
|
+
}
|
572
|
+
|
573
|
+
private async deleteFunction(): Promise<void> {
|
574
|
+
const functions = await this.selectFunctions(
|
575
|
+
"Select functions to delete:",
|
576
|
+
true,
|
577
|
+
false
|
578
|
+
);
|
579
|
+
|
580
|
+
if (!functions.length) {
|
581
|
+
console.log(chalk.red("No functions selected"));
|
582
|
+
return;
|
583
|
+
}
|
584
|
+
|
585
|
+
for (const func of functions) {
|
586
|
+
try {
|
587
|
+
await deleteFunction(this.controller!.appwriteServer!, func.$id);
|
588
|
+
console.log(
|
589
|
+
chalk.green(`✨ Function ${func.name} deleted successfully!`)
|
590
|
+
);
|
591
|
+
} catch (error) {
|
592
|
+
console.error(
|
593
|
+
chalk.red(`Failed to delete function ${func.name}:`),
|
594
|
+
error
|
595
|
+
);
|
596
|
+
}
|
597
|
+
}
|
598
|
+
}
|
599
|
+
|
600
|
+
private async selectFunctions(
|
601
|
+
message: string,
|
602
|
+
multiSelect = true,
|
603
|
+
preferLocal = false
|
604
|
+
): Promise<AppwriteFunction[]> {
|
605
|
+
await this.initControllerIfNeeded();
|
606
|
+
|
607
|
+
const configFunctions = this.getLocalFunctions();
|
608
|
+
let remoteFunctions: Models.Function[] = [];
|
609
|
+
|
610
|
+
try {
|
611
|
+
const functions = await this.controller!.listAllFunctions();
|
612
|
+
remoteFunctions = functions;
|
613
|
+
} catch (error) {
|
614
|
+
console.log(
|
615
|
+
chalk.yellow(
|
616
|
+
`Note: Remote functions not available, using only local functions`
|
617
|
+
)
|
618
|
+
);
|
619
|
+
}
|
620
|
+
|
621
|
+
// Combine functions based on whether we're deploying or not
|
622
|
+
const allFunctions = preferLocal
|
623
|
+
? [
|
624
|
+
...configFunctions,
|
625
|
+
...remoteFunctions.map((f) => AppwriteFunctionSchema.parse(f)),
|
626
|
+
]
|
627
|
+
: [
|
628
|
+
...remoteFunctions.map((f) => AppwriteFunctionSchema.parse(f)),
|
629
|
+
...configFunctions.filter(
|
630
|
+
(f) => !remoteFunctions.some((rf) => rf.name === f.name)
|
631
|
+
),
|
632
|
+
];
|
633
|
+
|
634
|
+
if (allFunctions.length === 0) {
|
635
|
+
console.log(chalk.red("No functions available"));
|
636
|
+
return [];
|
637
|
+
}
|
638
|
+
|
639
|
+
const choices = allFunctions
|
640
|
+
.sort((a, b) => a.name.localeCompare(b.name))
|
641
|
+
.map((func) => ({
|
642
|
+
name: func.name,
|
643
|
+
value: func,
|
644
|
+
}));
|
645
|
+
|
646
|
+
const { selectedFunctions } = await inquirer.prompt([
|
647
|
+
{
|
648
|
+
type: multiSelect ? "checkbox" : "list",
|
649
|
+
name: "selectedFunctions",
|
650
|
+
message: chalk.blue(message),
|
651
|
+
choices,
|
652
|
+
loop: true,
|
653
|
+
pageSize: 10,
|
654
|
+
},
|
655
|
+
]);
|
656
|
+
|
657
|
+
// For single selection, ensure we return an array
|
658
|
+
if (!multiSelect) {
|
659
|
+
return selectedFunctions ? [selectedFunctions] : [];
|
660
|
+
}
|
661
|
+
|
662
|
+
return selectedFunctions || [];
|
663
|
+
}
|
664
|
+
|
665
|
+
private getLocalFunctions(): AppwriteFunction[] {
|
666
|
+
const configFunctions = this.controller!.config?.functions || [];
|
667
|
+
return configFunctions.map((f) => ({
|
668
|
+
$id: f.$id || ulid(),
|
669
|
+
$createdAt: DateTime.now().toISO(),
|
670
|
+
$updatedAt: DateTime.now().toISO(),
|
671
|
+
name: f.name,
|
672
|
+
runtime: f.runtime,
|
673
|
+
execute: f.execute || ["any"],
|
674
|
+
events: f.events || [],
|
675
|
+
schedule: f.schedule || "",
|
676
|
+
timeout: f.timeout || 15,
|
677
|
+
enabled: f.enabled !== false,
|
678
|
+
logging: f.logging !== false,
|
679
|
+
entrypoint: f.entrypoint || "src/index.ts",
|
680
|
+
commands: f.commands || "npm install",
|
681
|
+
path: f.dirPath || `functions/${f.name}`,
|
682
|
+
...(f.specification ? { specification: f.specification } : {}),
|
683
|
+
...(f.predeployCommands
|
684
|
+
? { predeployCommands: f.predeployCommands }
|
685
|
+
: {}),
|
686
|
+
...(f.deployDir ? { deployDir: f.deployDir } : {}),
|
687
|
+
}));
|
688
|
+
}
|
273
689
|
private async selectBuckets(
|
274
690
|
buckets: Models.Bucket[],
|
275
691
|
message: string,
|
@@ -407,15 +823,16 @@ export class InteractiveCLI {
|
|
407
823
|
]);
|
408
824
|
|
409
825
|
if (action === "assign") {
|
410
|
-
const
|
826
|
+
const selectedBuckets = await this.selectBuckets(
|
411
827
|
allBuckets.buckets.filter(
|
412
828
|
(b) => !globalBuckets.some((gb) => gb.$id === b.$id)
|
413
829
|
),
|
414
830
|
`Select a bucket for the database "${database.name}":`,
|
415
|
-
false
|
831
|
+
false // multiSelect = false
|
416
832
|
);
|
417
833
|
|
418
|
-
if (
|
834
|
+
if (selectedBuckets.length > 0) {
|
835
|
+
const selectedBucket = selectedBuckets[0];
|
419
836
|
database.bucket = {
|
420
837
|
$id: selectedBucket.$id,
|
421
838
|
name: selectedBucket.name,
|
@@ -425,6 +842,9 @@ export class InteractiveCLI {
|
|
425
842
|
compression: selectedBucket.compression as Compression,
|
426
843
|
encryption: selectedBucket.encryption,
|
427
844
|
antivirus: selectedBucket.antivirus,
|
845
|
+
permissions: selectedBucket.$permissions.map((p) =>
|
846
|
+
permissionSchema.parse(p)
|
847
|
+
),
|
428
848
|
};
|
429
849
|
}
|
430
850
|
} else if (action === "create") {
|
@@ -545,6 +965,7 @@ export class InteractiveCLI {
|
|
545
965
|
|
546
966
|
private async syncDb(): Promise<void> {
|
547
967
|
console.log(chalk.yellow("Syncing database..."));
|
968
|
+
const functionsClient = new Functions(this.controller!.appwriteServer!);
|
548
969
|
const databases = await this.selectDatabases(
|
549
970
|
await fetchAllDatabases(this.controller!.database!),
|
550
971
|
chalk.blue("Select databases to synchronize:"),
|
@@ -557,35 +978,151 @@ export class InteractiveCLI {
|
|
557
978
|
true,
|
558
979
|
true // prefer local
|
559
980
|
);
|
560
|
-
await
|
981
|
+
const answer = await inquirer.prompt([
|
982
|
+
{
|
983
|
+
type: "confirm",
|
984
|
+
name: "syncFunctions",
|
985
|
+
message: "Do you want to synchronize functions?",
|
986
|
+
default: false,
|
987
|
+
},
|
988
|
+
]);
|
989
|
+
if (answer.syncFunctions) {
|
990
|
+
const functions = await this.selectFunctions(
|
991
|
+
chalk.blue("Select functions to synchronize:"),
|
992
|
+
true,
|
993
|
+
true // prefer local
|
994
|
+
);
|
995
|
+
await this.controller!.syncDb(databases, collections);
|
996
|
+
for (const func of functions) {
|
997
|
+
await deployLocalFunction(
|
998
|
+
this.controller!.appwriteServer!,
|
999
|
+
func.dirPath || `functions/${func.name}`,
|
1000
|
+
func
|
1001
|
+
);
|
1002
|
+
}
|
1003
|
+
}
|
561
1004
|
console.log(chalk.green("Database sync completed."));
|
562
1005
|
}
|
563
1006
|
|
564
1007
|
private async synchronizeConfigurations(): Promise<void> {
|
565
|
-
|
566
|
-
|
567
|
-
|
1008
|
+
console.log(chalk.blue("Synchronizing configurations..."));
|
1009
|
+
await this.controller!.init();
|
1010
|
+
// Sync databases and buckets first
|
1011
|
+
const { syncDatabases } = await inquirer.prompt([
|
1012
|
+
{
|
1013
|
+
type: "confirm",
|
1014
|
+
name: "syncDatabases",
|
1015
|
+
message: "Do you want to synchronize databases and their buckets?",
|
1016
|
+
default: true,
|
1017
|
+
},
|
1018
|
+
]);
|
1019
|
+
|
1020
|
+
if (syncDatabases) {
|
1021
|
+
const remoteDatabases = await fetchAllDatabases(
|
1022
|
+
this.controller!.database!
|
568
1023
|
);
|
1024
|
+
const localDatabases = this.controller!.config?.databases || [];
|
1025
|
+
|
1026
|
+
// Update config with remote databases that don't exist locally
|
1027
|
+
const updatedConfig = await this.configureBuckets({
|
1028
|
+
...this.controller!.config!,
|
1029
|
+
databases: [
|
1030
|
+
...localDatabases,
|
1031
|
+
...remoteDatabases.filter(
|
1032
|
+
(rd) => !localDatabases.some((ld) => ld.name === rd.name)
|
1033
|
+
),
|
1034
|
+
],
|
1035
|
+
});
|
1036
|
+
|
1037
|
+
this.controller!.config = updatedConfig;
|
569
1038
|
}
|
570
|
-
const databases = await fetchAllDatabases(this.controller!.database);
|
571
1039
|
|
572
|
-
|
573
|
-
|
574
|
-
|
575
|
-
|
1040
|
+
// Then sync functions
|
1041
|
+
const { syncFunctions } = await inquirer.prompt([
|
1042
|
+
{
|
1043
|
+
type: "confirm",
|
1044
|
+
name: "syncFunctions",
|
1045
|
+
message: "Do you want to synchronize functions?",
|
1046
|
+
default: true,
|
1047
|
+
},
|
1048
|
+
]);
|
576
1049
|
|
577
|
-
|
578
|
-
|
579
|
-
this.controller!.config
|
580
|
-
selectedDatabases
|
581
|
-
);
|
1050
|
+
if (syncFunctions) {
|
1051
|
+
const remoteFunctions = await this.controller!.listAllFunctions();
|
1052
|
+
const localFunctions = this.controller!.config?.functions || [];
|
582
1053
|
|
583
|
-
|
584
|
-
|
585
|
-
|
586
|
-
|
587
|
-
|
588
|
-
|
1054
|
+
const allFunctions = [
|
1055
|
+
...remoteFunctions,
|
1056
|
+
...localFunctions.filter(
|
1057
|
+
(f) => !remoteFunctions.some((rf) => rf.$id === f.$id)
|
1058
|
+
),
|
1059
|
+
];
|
1060
|
+
|
1061
|
+
for (const func of allFunctions) {
|
1062
|
+
const hasLocal = localFunctions.some((lf) => lf.$id === func.$id);
|
1063
|
+
const hasRemote = remoteFunctions.some((rf) => rf.$id === func.$id);
|
1064
|
+
|
1065
|
+
if (hasLocal && hasRemote) {
|
1066
|
+
const { preference } = await inquirer.prompt([
|
1067
|
+
{
|
1068
|
+
type: "list",
|
1069
|
+
name: "preference",
|
1070
|
+
message: `Function "${func.name}" exists both locally and remotely. What would you like to do?`,
|
1071
|
+
choices: [
|
1072
|
+
{
|
1073
|
+
name: "Keep local version (deploy to remote)",
|
1074
|
+
value: "local",
|
1075
|
+
},
|
1076
|
+
{ name: "Use remote version (download)", value: "remote" },
|
1077
|
+
{ name: "Skip this function", value: "skip" },
|
1078
|
+
],
|
1079
|
+
},
|
1080
|
+
]);
|
1081
|
+
|
1082
|
+
if (preference === "local") {
|
1083
|
+
await this.controller!.deployFunction(func.name);
|
1084
|
+
} else if (preference === "remote") {
|
1085
|
+
await downloadLatestFunctionDeployment(
|
1086
|
+
this.controller!.appwriteServer!,
|
1087
|
+
func.$id,
|
1088
|
+
join(this.controller!.getAppwriteFolderPath(), "functions")
|
1089
|
+
);
|
1090
|
+
}
|
1091
|
+
} else if (hasLocal) {
|
1092
|
+
const { deploy } = await inquirer.prompt([
|
1093
|
+
{
|
1094
|
+
type: "confirm",
|
1095
|
+
name: "deploy",
|
1096
|
+
message: `Function "${func.name}" exists only locally. Deploy to remote?`,
|
1097
|
+
default: true,
|
1098
|
+
},
|
1099
|
+
]);
|
1100
|
+
|
1101
|
+
if (deploy) {
|
1102
|
+
await this.controller!.deployFunction(func.name);
|
1103
|
+
}
|
1104
|
+
} else if (hasRemote) {
|
1105
|
+
const { download } = await inquirer.prompt([
|
1106
|
+
{
|
1107
|
+
type: "confirm",
|
1108
|
+
name: "download",
|
1109
|
+
message: `Function "${func.name}" exists only remotely. Download locally?`,
|
1110
|
+
default: true,
|
1111
|
+
},
|
1112
|
+
]);
|
1113
|
+
|
1114
|
+
if (download) {
|
1115
|
+
await downloadLatestFunctionDeployment(
|
1116
|
+
this.controller!.appwriteServer!,
|
1117
|
+
func.$id,
|
1118
|
+
join(this.controller!.getAppwriteFolderPath(), "functions")
|
1119
|
+
);
|
1120
|
+
}
|
1121
|
+
}
|
1122
|
+
}
|
1123
|
+
}
|
1124
|
+
|
1125
|
+
console.log(chalk.green("✨ Configurations synchronized successfully!"));
|
589
1126
|
}
|
590
1127
|
|
591
1128
|
private async backupDatabase(): Promise<void> {
|
@@ -970,43 +1507,65 @@ export class InteractiveCLI {
|
|
970
1507
|
}
|
971
1508
|
|
972
1509
|
private async updateFunctionSpec(): Promise<void> {
|
973
|
-
const
|
974
|
-
|
975
|
-
|
976
|
-
|
1510
|
+
const remoteFunctions = await listFunctions(
|
1511
|
+
this.controller!.appwriteServer!,
|
1512
|
+
[Query.limit(1000)]
|
1513
|
+
);
|
1514
|
+
const localFunctions = this.getLocalFunctions();
|
1515
|
+
|
1516
|
+
const allFunctions = [
|
1517
|
+
...remoteFunctions.functions,
|
1518
|
+
...localFunctions.filter(
|
1519
|
+
(f) => !remoteFunctions.functions.some((rf) => rf.name === f.name)
|
1520
|
+
),
|
1521
|
+
];
|
1522
|
+
|
977
1523
|
const functionsToUpdate = await inquirer.prompt([
|
978
1524
|
{
|
979
|
-
type:
|
980
|
-
name:
|
981
|
-
message:
|
982
|
-
choices:
|
983
|
-
name: `${f.name} (${f.$id})
|
984
|
-
|
1525
|
+
type: "checkbox",
|
1526
|
+
name: "functionId",
|
1527
|
+
message: "Select functions to update:",
|
1528
|
+
choices: allFunctions.map((f) => ({
|
1529
|
+
name: `${f.name} (${f.$id})${
|
1530
|
+
localFunctions.some((lf) => lf.name === f.name)
|
1531
|
+
? " (Local)"
|
1532
|
+
: " (Remote)"
|
1533
|
+
}`,
|
1534
|
+
value: f.$id,
|
985
1535
|
})),
|
986
1536
|
loop: true,
|
987
|
-
}
|
1537
|
+
},
|
988
1538
|
]);
|
989
1539
|
|
990
|
-
const specifications = await listSpecifications(
|
1540
|
+
const specifications = await listSpecifications(
|
1541
|
+
this.controller!.appwriteServer!
|
1542
|
+
);
|
991
1543
|
const { specification } = await inquirer.prompt([
|
992
1544
|
{
|
993
|
-
type:
|
994
|
-
name:
|
995
|
-
message:
|
1545
|
+
type: "list",
|
1546
|
+
name: "specification",
|
1547
|
+
message: "Select new specification:",
|
996
1548
|
choices: specifications.specifications.map((s) => ({
|
997
1549
|
name: `${s.slug}`,
|
998
|
-
value: s.slug
|
1550
|
+
value: s.slug,
|
999
1551
|
})),
|
1000
|
-
}
|
1552
|
+
},
|
1001
1553
|
]);
|
1002
|
-
|
1003
|
-
try {
|
1554
|
+
|
1555
|
+
try {
|
1004
1556
|
for (const functionId of functionsToUpdate.functionId) {
|
1005
|
-
await this.controller!.updateFunctionSpecifications(
|
1006
|
-
|
1557
|
+
await this.controller!.updateFunctionSpecifications(
|
1558
|
+
functionId,
|
1559
|
+
specification
|
1560
|
+
);
|
1561
|
+
console.log(
|
1562
|
+
chalk.green(
|
1563
|
+
`Successfully updated function specification to ${specification}`
|
1564
|
+
)
|
1565
|
+
);
|
1007
1566
|
}
|
1008
1567
|
} catch (error) {
|
1009
|
-
console.error(chalk.red(
|
1568
|
+
console.error(chalk.red("Error updating function specification:"), error);
|
1010
1569
|
}
|
1011
1570
|
}
|
1012
1571
|
}
|