appwrite-utils-cli 0.9.992 → 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 +2 -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 +75 -21
- 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/dist/interactiveCLI.js
CHANGED
@@ -4,20 +4,27 @@ import { createEmptyCollection, setupDirsFiles } from "./utils/setupFiles.js";
|
|
4
4
|
import { fetchAllDatabases } from "./databases/methods.js";
|
5
5
|
import { fetchAllCollections } from "./collections/methods.js";
|
6
6
|
import { listBuckets, createBucket } from "./storage/methods.js";
|
7
|
-
import { Databases, Storage, Client, Compression, Query, } from "node-appwrite";
|
7
|
+
import { Databases, Storage, Client, Compression, Query, Functions, } from "node-appwrite";
|
8
8
|
import { getClient } from "./utils/getClientFromConfig.js";
|
9
|
-
import { parseAttribute, PermissionToAppwritePermission, } from "appwrite-utils";
|
9
|
+
import { AppwriteFunctionSchema, parseAttribute, PermissionToAppwritePermission, RuntimeSchema, permissionSchema, } from "appwrite-utils";
|
10
10
|
import { ulid } from "ulidx";
|
11
11
|
import chalk from "chalk";
|
12
12
|
import { DateTime } from "luxon";
|
13
|
-
import { listFunctions, listSpecifications } from "./functions/methods.js";
|
13
|
+
import { createFunctionTemplate, deleteFunction, downloadLatestFunctionDeployment, listFunctions, listSpecifications, } from "./functions/methods.js";
|
14
|
+
import { deployLocalFunction } from "./functions/deployments.js";
|
15
|
+
import { join } from "node:path";
|
16
|
+
import fs from "node:fs";
|
17
|
+
import { SchemaGenerator } from "./migrations/schemaStrings.js";
|
14
18
|
var CHOICES;
|
15
19
|
(function (CHOICES) {
|
16
20
|
CHOICES["CREATE_COLLECTION_CONFIG"] = "Create collection config file";
|
21
|
+
CHOICES["CREATE_FUNCTION"] = "Create a new function, from scratch or using a template";
|
22
|
+
CHOICES["DEPLOY_FUNCTION"] = "Deploy function";
|
23
|
+
CHOICES["DELETE_FUNCTION"] = "Delete function";
|
17
24
|
CHOICES["SETUP_DIRS_FILES"] = "Setup directories and files";
|
18
25
|
CHOICES["SETUP_DIRS_FILES_WITH_EXAMPLE_DATA"] = "Setup directories and files with example data";
|
19
26
|
CHOICES["SYNC_DB"] = "Push local config to Appwrite";
|
20
|
-
CHOICES["SYNCHRONIZE_CONFIGURATIONS"] = "Synchronize configurations";
|
27
|
+
CHOICES["SYNCHRONIZE_CONFIGURATIONS"] = "Synchronize configurations - Pull from Appwrite and write to local config";
|
21
28
|
CHOICES["TRANSFER_DATA"] = "Transfer data";
|
22
29
|
CHOICES["BACKUP_DATABASE"] = "Backup database";
|
23
30
|
CHOICES["WIPE_DATABASE"] = "Wipe database";
|
@@ -51,6 +58,18 @@ export class InteractiveCLI {
|
|
51
58
|
await this.initControllerIfNeeded();
|
52
59
|
await this.createCollectionConfig();
|
53
60
|
break;
|
61
|
+
case CHOICES.CREATE_FUNCTION:
|
62
|
+
await this.initControllerIfNeeded();
|
63
|
+
await this.createFunction();
|
64
|
+
break;
|
65
|
+
case CHOICES.DEPLOY_FUNCTION:
|
66
|
+
await this.initControllerIfNeeded();
|
67
|
+
await this.deployFunction();
|
68
|
+
break;
|
69
|
+
case CHOICES.DELETE_FUNCTION:
|
70
|
+
await this.initControllerIfNeeded();
|
71
|
+
await this.deleteFunction();
|
72
|
+
break;
|
54
73
|
case CHOICES.SETUP_DIRS_FILES:
|
55
74
|
await setupDirsFiles(false, this.currentDir);
|
56
75
|
break;
|
@@ -202,6 +221,283 @@ export class InteractiveCLI {
|
|
202
221
|
]);
|
203
222
|
return selectedCollections;
|
204
223
|
}
|
224
|
+
async createFunction() {
|
225
|
+
const { name } = await inquirer.prompt([
|
226
|
+
{
|
227
|
+
type: "input",
|
228
|
+
name: "name",
|
229
|
+
message: "Function name:",
|
230
|
+
validate: (input) => input.length > 0,
|
231
|
+
},
|
232
|
+
]);
|
233
|
+
const { template } = await inquirer.prompt([
|
234
|
+
{
|
235
|
+
type: "list",
|
236
|
+
name: "template",
|
237
|
+
message: "Select a template:",
|
238
|
+
choices: [
|
239
|
+
"typescript-node",
|
240
|
+
"poetry",
|
241
|
+
"count-docs-in-collection",
|
242
|
+
"none",
|
243
|
+
],
|
244
|
+
},
|
245
|
+
]);
|
246
|
+
const { runtime } = await inquirer.prompt([
|
247
|
+
{
|
248
|
+
type: "list",
|
249
|
+
name: "runtime",
|
250
|
+
message: "Select runtime:",
|
251
|
+
choices: Object.values(RuntimeSchema.Values),
|
252
|
+
},
|
253
|
+
]);
|
254
|
+
const specifications = await listSpecifications(this.controller.appwriteServer);
|
255
|
+
const { specification } = await inquirer.prompt([
|
256
|
+
{
|
257
|
+
type: "list",
|
258
|
+
name: "specification",
|
259
|
+
message: "Select specification:",
|
260
|
+
choices: [
|
261
|
+
{ name: "None", value: undefined },
|
262
|
+
...specifications.specifications.map((s) => ({
|
263
|
+
name: s.slug,
|
264
|
+
value: s.slug,
|
265
|
+
})),
|
266
|
+
],
|
267
|
+
},
|
268
|
+
]);
|
269
|
+
const functionConfig = {
|
270
|
+
$id: ulid(),
|
271
|
+
name,
|
272
|
+
runtime,
|
273
|
+
events: [],
|
274
|
+
execute: ["any"],
|
275
|
+
enabled: true,
|
276
|
+
logging: true,
|
277
|
+
entrypoint: template === "none" ? "src/index.ts" : undefined,
|
278
|
+
specification,
|
279
|
+
predeployCommands: template.includes("typescript")
|
280
|
+
? ["npm install", "npm run build"]
|
281
|
+
: undefined,
|
282
|
+
deployDir: template.includes("typescript") ? "dist" : undefined,
|
283
|
+
};
|
284
|
+
if (template !== "none") {
|
285
|
+
await createFunctionTemplate(template, name, "./functions");
|
286
|
+
}
|
287
|
+
// Add to config
|
288
|
+
if (!this.controller.config.functions) {
|
289
|
+
this.controller.config.functions = [];
|
290
|
+
}
|
291
|
+
this.controller.config.functions.push(functionConfig);
|
292
|
+
console.log(chalk.green("✨ Function created successfully!"));
|
293
|
+
}
|
294
|
+
async findFunctionInSubdirectories(basePath, functionName) {
|
295
|
+
const queue = [basePath];
|
296
|
+
while (queue.length > 0) {
|
297
|
+
const currentPath = queue.shift();
|
298
|
+
try {
|
299
|
+
const entries = await fs.promises.readdir(currentPath, {
|
300
|
+
withFileTypes: true,
|
301
|
+
});
|
302
|
+
// Check if function exists in current directory
|
303
|
+
const functionPath = join(currentPath, functionName);
|
304
|
+
if (fs.existsSync(functionPath)) {
|
305
|
+
return functionPath;
|
306
|
+
}
|
307
|
+
// Add subdirectories to queue
|
308
|
+
for (const entry of entries) {
|
309
|
+
if (entry.isDirectory()) {
|
310
|
+
queue.push(join(currentPath, entry.name));
|
311
|
+
}
|
312
|
+
}
|
313
|
+
}
|
314
|
+
catch (error) {
|
315
|
+
console.log(chalk.yellow(`Skipping inaccessible directory: ${currentPath}`));
|
316
|
+
}
|
317
|
+
}
|
318
|
+
return null;
|
319
|
+
}
|
320
|
+
async deployFunction() {
|
321
|
+
await this.initControllerIfNeeded();
|
322
|
+
if (!this.controller?.config) {
|
323
|
+
console.log(chalk.red("Failed to initialize controller or load config"));
|
324
|
+
return;
|
325
|
+
}
|
326
|
+
const functions = await this.selectFunctions("Select function to deploy:", false, true);
|
327
|
+
if (!functions?.length) {
|
328
|
+
console.log(chalk.red("No function selected"));
|
329
|
+
return;
|
330
|
+
}
|
331
|
+
const functionConfig = functions[0];
|
332
|
+
if (!functionConfig) {
|
333
|
+
console.log(chalk.red("Invalid function configuration"));
|
334
|
+
return;
|
335
|
+
}
|
336
|
+
let functionPath = join(this.controller.getAppwriteFolderPath(), "functions", functionConfig.name);
|
337
|
+
if (!fs.existsSync(functionPath)) {
|
338
|
+
console.log(chalk.yellow(`Function not found in primary location, searching subdirectories...`));
|
339
|
+
const foundPath = await this.findFunctionInSubdirectories(this.controller.getAppwriteFolderPath(), functionConfig.name);
|
340
|
+
if (foundPath) {
|
341
|
+
console.log(chalk.green(`Found function at: ${foundPath}`));
|
342
|
+
functionPath = foundPath;
|
343
|
+
functionConfig.dirPath = foundPath;
|
344
|
+
}
|
345
|
+
else {
|
346
|
+
console.log(chalk.yellow(`Function ${functionConfig.name} not found locally in any subdirectory`));
|
347
|
+
const { shouldDownload } = await inquirer.prompt([
|
348
|
+
{
|
349
|
+
type: "confirm",
|
350
|
+
name: "shouldDownload",
|
351
|
+
message: "Would you like to download the latest deployment?",
|
352
|
+
default: true,
|
353
|
+
},
|
354
|
+
]);
|
355
|
+
if (shouldDownload) {
|
356
|
+
try {
|
357
|
+
console.log(chalk.blue("Downloading latest deployment..."));
|
358
|
+
const { path: downloadedPath, function: remoteFunction } = await downloadLatestFunctionDeployment(this.controller.appwriteServer, functionConfig.$id, join(this.controller.getAppwriteFolderPath(), "functions"));
|
359
|
+
console.log(chalk.green(`✨ Function downloaded to ${downloadedPath}`));
|
360
|
+
// Update the config and functions array safely
|
361
|
+
this.controller.config.functions =
|
362
|
+
this.controller.config.functions || [];
|
363
|
+
const newFunction = {
|
364
|
+
$id: remoteFunction.$id,
|
365
|
+
name: remoteFunction.name,
|
366
|
+
runtime: remoteFunction.runtime,
|
367
|
+
execute: remoteFunction.execute || [],
|
368
|
+
events: remoteFunction.events || [],
|
369
|
+
schedule: remoteFunction.schedule || "",
|
370
|
+
timeout: remoteFunction.timeout || 15,
|
371
|
+
enabled: remoteFunction.enabled !== false,
|
372
|
+
logging: remoteFunction.logging !== false,
|
373
|
+
entrypoint: remoteFunction.entrypoint || "src/index.ts",
|
374
|
+
commands: remoteFunction.commands || "npm install",
|
375
|
+
dirPath: downloadedPath,
|
376
|
+
scopes: (remoteFunction.scopes || []),
|
377
|
+
installationId: remoteFunction.installationId,
|
378
|
+
providerRepositoryId: remoteFunction.providerRepositoryId,
|
379
|
+
providerBranch: remoteFunction.providerBranch,
|
380
|
+
providerSilentMode: remoteFunction.providerSilentMode,
|
381
|
+
providerRootDirectory: remoteFunction.providerRootDirectory,
|
382
|
+
specification: remoteFunction.specification,
|
383
|
+
};
|
384
|
+
const existingIndex = this.controller.config.functions.findIndex((f) => f?.$id === remoteFunction.$id);
|
385
|
+
if (existingIndex >= 0) {
|
386
|
+
this.controller.config.functions[existingIndex] = newFunction;
|
387
|
+
}
|
388
|
+
else {
|
389
|
+
this.controller.config.functions.push(newFunction);
|
390
|
+
}
|
391
|
+
const schemaGenerator = new SchemaGenerator(this.controller.config, this.controller.getAppwriteFolderPath());
|
392
|
+
schemaGenerator.updateConfig(this.controller.config);
|
393
|
+
console.log(chalk.green("✨ Updated appwriteConfig.ts with new function"));
|
394
|
+
await this.controller.reloadConfig();
|
395
|
+
functionConfig.dirPath = downloadedPath;
|
396
|
+
}
|
397
|
+
catch (error) {
|
398
|
+
console.error(chalk.red("Failed to download function deployment:"), error);
|
399
|
+
return;
|
400
|
+
}
|
401
|
+
}
|
402
|
+
else {
|
403
|
+
console.log(chalk.yellow("Deployment cancelled"));
|
404
|
+
return;
|
405
|
+
}
|
406
|
+
}
|
407
|
+
}
|
408
|
+
if (!this.controller.appwriteServer) {
|
409
|
+
console.log(chalk.red("Appwrite server not initialized"));
|
410
|
+
return;
|
411
|
+
}
|
412
|
+
await deployLocalFunction(this.controller.appwriteServer, functionConfig.name, functionConfig);
|
413
|
+
}
|
414
|
+
async deleteFunction() {
|
415
|
+
const functions = await this.selectFunctions("Select functions to delete:", true, false);
|
416
|
+
if (!functions.length) {
|
417
|
+
console.log(chalk.red("No functions selected"));
|
418
|
+
return;
|
419
|
+
}
|
420
|
+
for (const func of functions) {
|
421
|
+
try {
|
422
|
+
await deleteFunction(this.controller.appwriteServer, func.$id);
|
423
|
+
console.log(chalk.green(`✨ Function ${func.name} deleted successfully!`));
|
424
|
+
}
|
425
|
+
catch (error) {
|
426
|
+
console.error(chalk.red(`Failed to delete function ${func.name}:`), error);
|
427
|
+
}
|
428
|
+
}
|
429
|
+
}
|
430
|
+
async selectFunctions(message, multiSelect = true, preferLocal = false) {
|
431
|
+
await this.initControllerIfNeeded();
|
432
|
+
const configFunctions = this.getLocalFunctions();
|
433
|
+
let remoteFunctions = [];
|
434
|
+
try {
|
435
|
+
const functions = await this.controller.listAllFunctions();
|
436
|
+
remoteFunctions = functions;
|
437
|
+
}
|
438
|
+
catch (error) {
|
439
|
+
console.log(chalk.yellow(`Note: Remote functions not available, using only local functions`));
|
440
|
+
}
|
441
|
+
// Combine functions based on whether we're deploying or not
|
442
|
+
const allFunctions = preferLocal
|
443
|
+
? [
|
444
|
+
...configFunctions,
|
445
|
+
...remoteFunctions.map((f) => AppwriteFunctionSchema.parse(f)),
|
446
|
+
]
|
447
|
+
: [
|
448
|
+
...remoteFunctions.map((f) => AppwriteFunctionSchema.parse(f)),
|
449
|
+
...configFunctions.filter((f) => !remoteFunctions.some((rf) => rf.name === f.name)),
|
450
|
+
];
|
451
|
+
if (allFunctions.length === 0) {
|
452
|
+
console.log(chalk.red("No functions available"));
|
453
|
+
return [];
|
454
|
+
}
|
455
|
+
const choices = allFunctions
|
456
|
+
.sort((a, b) => a.name.localeCompare(b.name))
|
457
|
+
.map((func) => ({
|
458
|
+
name: func.name,
|
459
|
+
value: func,
|
460
|
+
}));
|
461
|
+
const { selectedFunctions } = await inquirer.prompt([
|
462
|
+
{
|
463
|
+
type: multiSelect ? "checkbox" : "list",
|
464
|
+
name: "selectedFunctions",
|
465
|
+
message: chalk.blue(message),
|
466
|
+
choices,
|
467
|
+
loop: true,
|
468
|
+
pageSize: 10,
|
469
|
+
},
|
470
|
+
]);
|
471
|
+
// For single selection, ensure we return an array
|
472
|
+
if (!multiSelect) {
|
473
|
+
return selectedFunctions ? [selectedFunctions] : [];
|
474
|
+
}
|
475
|
+
return selectedFunctions || [];
|
476
|
+
}
|
477
|
+
getLocalFunctions() {
|
478
|
+
const configFunctions = this.controller.config?.functions || [];
|
479
|
+
return configFunctions.map((f) => ({
|
480
|
+
$id: f.$id || ulid(),
|
481
|
+
$createdAt: DateTime.now().toISO(),
|
482
|
+
$updatedAt: DateTime.now().toISO(),
|
483
|
+
name: f.name,
|
484
|
+
runtime: f.runtime,
|
485
|
+
execute: f.execute || ["any"],
|
486
|
+
events: f.events || [],
|
487
|
+
schedule: f.schedule || "",
|
488
|
+
timeout: f.timeout || 15,
|
489
|
+
enabled: f.enabled !== false,
|
490
|
+
logging: f.logging !== false,
|
491
|
+
entrypoint: f.entrypoint || "src/index.ts",
|
492
|
+
commands: f.commands || "npm install",
|
493
|
+
path: f.dirPath || `functions/${f.name}`,
|
494
|
+
...(f.specification ? { specification: f.specification } : {}),
|
495
|
+
...(f.predeployCommands
|
496
|
+
? { predeployCommands: f.predeployCommands }
|
497
|
+
: {}),
|
498
|
+
...(f.deployDir ? { deployDir: f.deployDir } : {}),
|
499
|
+
}));
|
500
|
+
}
|
205
501
|
async selectBuckets(buckets, message, multiSelect = true) {
|
206
502
|
const choices = buckets.map((bucket) => ({
|
207
503
|
name: bucket.name,
|
@@ -306,8 +602,10 @@ export class InteractiveCLI {
|
|
306
602
|
},
|
307
603
|
]);
|
308
604
|
if (action === "assign") {
|
309
|
-
const
|
310
|
-
|
605
|
+
const selectedBuckets = await this.selectBuckets(allBuckets.buckets.filter((b) => !globalBuckets.some((gb) => gb.$id === b.$id)), `Select a bucket for the database "${database.name}":`, false // multiSelect = false
|
606
|
+
);
|
607
|
+
if (selectedBuckets.length > 0) {
|
608
|
+
const selectedBucket = selectedBuckets[0];
|
311
609
|
database.bucket = {
|
312
610
|
$id: selectedBucket.$id,
|
313
611
|
name: selectedBucket.name,
|
@@ -317,6 +615,7 @@ export class InteractiveCLI {
|
|
317
615
|
compression: selectedBucket.compression,
|
318
616
|
encryption: selectedBucket.encryption,
|
319
617
|
antivirus: selectedBucket.antivirus,
|
618
|
+
permissions: selectedBucket.$permissions.map((p) => permissionSchema.parse(p)),
|
320
619
|
};
|
321
620
|
}
|
322
621
|
}
|
@@ -411,23 +710,124 @@ export class InteractiveCLI {
|
|
411
710
|
}
|
412
711
|
async syncDb() {
|
413
712
|
console.log(chalk.yellow("Syncing database..."));
|
713
|
+
const functionsClient = new Functions(this.controller.appwriteServer);
|
414
714
|
const databases = await this.selectDatabases(await fetchAllDatabases(this.controller.database), chalk.blue("Select databases to synchronize:"), true);
|
415
715
|
const collections = await this.selectCollections(databases[0], this.controller.database, chalk.blue("Select collections to synchronize:"), true, true // prefer local
|
416
716
|
);
|
417
|
-
await
|
717
|
+
const answer = await inquirer.prompt([
|
718
|
+
{
|
719
|
+
type: "confirm",
|
720
|
+
name: "syncFunctions",
|
721
|
+
message: "Do you want to synchronize functions?",
|
722
|
+
default: false,
|
723
|
+
},
|
724
|
+
]);
|
725
|
+
if (answer.syncFunctions) {
|
726
|
+
const functions = await this.selectFunctions(chalk.blue("Select functions to synchronize:"), true, true // prefer local
|
727
|
+
);
|
728
|
+
await this.controller.syncDb(databases, collections);
|
729
|
+
for (const func of functions) {
|
730
|
+
await deployLocalFunction(this.controller.appwriteServer, func.dirPath || `functions/${func.name}`, func);
|
731
|
+
}
|
732
|
+
}
|
418
733
|
console.log(chalk.green("Database sync completed."));
|
419
734
|
}
|
420
735
|
async synchronizeConfigurations() {
|
421
|
-
|
422
|
-
|
736
|
+
console.log(chalk.blue("Synchronizing configurations..."));
|
737
|
+
await this.controller.init();
|
738
|
+
// Sync databases and buckets first
|
739
|
+
const { syncDatabases } = await inquirer.prompt([
|
740
|
+
{
|
741
|
+
type: "confirm",
|
742
|
+
name: "syncDatabases",
|
743
|
+
message: "Do you want to synchronize databases and their buckets?",
|
744
|
+
default: true,
|
745
|
+
},
|
746
|
+
]);
|
747
|
+
if (syncDatabases) {
|
748
|
+
const remoteDatabases = await fetchAllDatabases(this.controller.database);
|
749
|
+
const localDatabases = this.controller.config?.databases || [];
|
750
|
+
// Update config with remote databases that don't exist locally
|
751
|
+
const updatedConfig = await this.configureBuckets({
|
752
|
+
...this.controller.config,
|
753
|
+
databases: [
|
754
|
+
...localDatabases,
|
755
|
+
...remoteDatabases.filter((rd) => !localDatabases.some((ld) => ld.name === rd.name)),
|
756
|
+
],
|
757
|
+
});
|
758
|
+
this.controller.config = updatedConfig;
|
423
759
|
}
|
424
|
-
|
425
|
-
const
|
426
|
-
|
427
|
-
|
428
|
-
|
429
|
-
|
430
|
-
|
760
|
+
// Then sync functions
|
761
|
+
const { syncFunctions } = await inquirer.prompt([
|
762
|
+
{
|
763
|
+
type: "confirm",
|
764
|
+
name: "syncFunctions",
|
765
|
+
message: "Do you want to synchronize functions?",
|
766
|
+
default: true,
|
767
|
+
},
|
768
|
+
]);
|
769
|
+
if (syncFunctions) {
|
770
|
+
const remoteFunctions = await this.controller.listAllFunctions();
|
771
|
+
const localFunctions = this.controller.config?.functions || [];
|
772
|
+
const allFunctions = [
|
773
|
+
...remoteFunctions,
|
774
|
+
...localFunctions.filter((f) => !remoteFunctions.some((rf) => rf.$id === f.$id)),
|
775
|
+
];
|
776
|
+
for (const func of allFunctions) {
|
777
|
+
const hasLocal = localFunctions.some((lf) => lf.$id === func.$id);
|
778
|
+
const hasRemote = remoteFunctions.some((rf) => rf.$id === func.$id);
|
779
|
+
if (hasLocal && hasRemote) {
|
780
|
+
const { preference } = await inquirer.prompt([
|
781
|
+
{
|
782
|
+
type: "list",
|
783
|
+
name: "preference",
|
784
|
+
message: `Function "${func.name}" exists both locally and remotely. What would you like to do?`,
|
785
|
+
choices: [
|
786
|
+
{
|
787
|
+
name: "Keep local version (deploy to remote)",
|
788
|
+
value: "local",
|
789
|
+
},
|
790
|
+
{ name: "Use remote version (download)", value: "remote" },
|
791
|
+
{ name: "Skip this function", value: "skip" },
|
792
|
+
],
|
793
|
+
},
|
794
|
+
]);
|
795
|
+
if (preference === "local") {
|
796
|
+
await this.controller.deployFunction(func.name);
|
797
|
+
}
|
798
|
+
else if (preference === "remote") {
|
799
|
+
await downloadLatestFunctionDeployment(this.controller.appwriteServer, func.$id, join(this.controller.getAppwriteFolderPath(), "functions"));
|
800
|
+
}
|
801
|
+
}
|
802
|
+
else if (hasLocal) {
|
803
|
+
const { deploy } = await inquirer.prompt([
|
804
|
+
{
|
805
|
+
type: "confirm",
|
806
|
+
name: "deploy",
|
807
|
+
message: `Function "${func.name}" exists only locally. Deploy to remote?`,
|
808
|
+
default: true,
|
809
|
+
},
|
810
|
+
]);
|
811
|
+
if (deploy) {
|
812
|
+
await this.controller.deployFunction(func.name);
|
813
|
+
}
|
814
|
+
}
|
815
|
+
else if (hasRemote) {
|
816
|
+
const { download } = await inquirer.prompt([
|
817
|
+
{
|
818
|
+
type: "confirm",
|
819
|
+
name: "download",
|
820
|
+
message: `Function "${func.name}" exists only remotely. Download locally?`,
|
821
|
+
default: true,
|
822
|
+
},
|
823
|
+
]);
|
824
|
+
if (download) {
|
825
|
+
await downloadLatestFunctionDeployment(this.controller.appwriteServer, func.$id, join(this.controller.getAppwriteFolderPath(), "functions"));
|
826
|
+
}
|
827
|
+
}
|
828
|
+
}
|
829
|
+
}
|
830
|
+
console.log(chalk.green("✨ Configurations synchronized successfully!"));
|
431
831
|
}
|
432
832
|
async backupDatabase() {
|
433
833
|
if (!this.controller.database) {
|
@@ -696,32 +1096,37 @@ export class InteractiveCLI {
|
|
696
1096
|
}
|
697
1097
|
}
|
698
1098
|
async updateFunctionSpec() {
|
699
|
-
const
|
700
|
-
|
701
|
-
|
1099
|
+
const remoteFunctions = await listFunctions(this.controller.appwriteServer, [Query.limit(1000)]);
|
1100
|
+
const localFunctions = this.getLocalFunctions();
|
1101
|
+
const allFunctions = [
|
1102
|
+
...remoteFunctions.functions,
|
1103
|
+
...localFunctions.filter((f) => !remoteFunctions.functions.some((rf) => rf.name === f.name)),
|
1104
|
+
];
|
702
1105
|
const functionsToUpdate = await inquirer.prompt([
|
703
1106
|
{
|
704
|
-
type:
|
705
|
-
name:
|
706
|
-
message:
|
707
|
-
choices:
|
708
|
-
name: `${f.name} (${f.$id})
|
709
|
-
|
1107
|
+
type: "checkbox",
|
1108
|
+
name: "functionId",
|
1109
|
+
message: "Select functions to update:",
|
1110
|
+
choices: allFunctions.map((f) => ({
|
1111
|
+
name: `${f.name} (${f.$id})${localFunctions.some((lf) => lf.name === f.name)
|
1112
|
+
? " (Local)"
|
1113
|
+
: " (Remote)"}`,
|
1114
|
+
value: f.$id,
|
710
1115
|
})),
|
711
1116
|
loop: true,
|
712
|
-
}
|
1117
|
+
},
|
713
1118
|
]);
|
714
1119
|
const specifications = await listSpecifications(this.controller.appwriteServer);
|
715
1120
|
const { specification } = await inquirer.prompt([
|
716
1121
|
{
|
717
|
-
type:
|
718
|
-
name:
|
719
|
-
message:
|
1122
|
+
type: "list",
|
1123
|
+
name: "specification",
|
1124
|
+
message: "Select new specification:",
|
720
1125
|
choices: specifications.specifications.map((s) => ({
|
721
1126
|
name: `${s.slug}`,
|
722
|
-
value: s.slug
|
1127
|
+
value: s.slug,
|
723
1128
|
})),
|
724
|
-
}
|
1129
|
+
},
|
725
1130
|
]);
|
726
1131
|
try {
|
727
1132
|
for (const functionId of functionsToUpdate.functionId) {
|
@@ -730,7 +1135,7 @@ export class InteractiveCLI {
|
|
730
1135
|
}
|
731
1136
|
}
|
732
1137
|
catch (error) {
|
733
|
-
console.error(chalk.red(
|
1138
|
+
console.error(chalk.red("Error updating function specification:"), error);
|
734
1139
|
}
|
735
1140
|
}
|
736
1141
|
}
|
package/dist/main.js
CHANGED
File without changes
|
@@ -5,6 +5,7 @@ import { fetchAllDatabases } from "./databases.js";
|
|
5
5
|
import { CollectionSchema, attributeSchema, AppwriteConfigSchema, permissionsSchema, attributesSchema, indexesSchema, parseAttribute, } from "appwrite-utils";
|
6
6
|
import { getDatabaseFromConfig } from "./afterImportActions.js";
|
7
7
|
import { listBuckets } from "../storage/methods.js";
|
8
|
+
import { listFunctions } from "../functions/methods.js";
|
8
9
|
export class AppwriteToX {
|
9
10
|
config;
|
10
11
|
storage;
|
@@ -153,7 +154,37 @@ export class AppwriteToX {
|
|
153
154
|
encryption: bucket.encryption,
|
154
155
|
antivirus: bucket.antivirus,
|
155
156
|
}));
|
156
|
-
this.
|
157
|
+
const remoteFunctions = await listFunctions(this.config.appwriteClient, [
|
158
|
+
Query.limit(1000),
|
159
|
+
]);
|
160
|
+
const functionDeployments = await Promise.all(remoteFunctions.functions.map(async (func) => {
|
161
|
+
const deployments = await this.config.appwriteClient.functions.listDeployments(func.$id, [Query.orderDesc("$createdAt"), Query.limit(1)]);
|
162
|
+
return {
|
163
|
+
function: func,
|
164
|
+
schemaStrings: deployments.deployments[0]?.schemaStrings || [],
|
165
|
+
};
|
166
|
+
}));
|
167
|
+
this.updatedConfig.functions = functionDeployments.map(({ function: func, schemaStrings }) => ({
|
168
|
+
$id: func.$id,
|
169
|
+
name: func.name,
|
170
|
+
runtime: func.runtime,
|
171
|
+
execute: func.execute,
|
172
|
+
events: func.events || [],
|
173
|
+
schedule: func.schedule || "",
|
174
|
+
timeout: func.timeout || 15,
|
175
|
+
enabled: func.enabled !== false,
|
176
|
+
logging: func.logging !== false,
|
177
|
+
entrypoint: func.entrypoint || "src/index.ts",
|
178
|
+
commands: func.commands || "npm install",
|
179
|
+
dirPath: `functions/${func.name}`,
|
180
|
+
specification: func.specification,
|
181
|
+
schemaStrings,
|
182
|
+
}));
|
183
|
+
// Make sure to update the config with all changes
|
184
|
+
this.updatedConfig = {
|
185
|
+
...updatedConfig,
|
186
|
+
functions: this.updatedConfig.functions,
|
187
|
+
};
|
157
188
|
}
|
158
189
|
async toSchemas(databases) {
|
159
190
|
await this.appwriteSync(this.config, databases);
|
@@ -5,6 +5,7 @@ export declare class SchemaGenerator {
|
|
5
5
|
private appwriteFolderPath;
|
6
6
|
constructor(config: AppwriteConfig, appwriteFolderPath: string);
|
7
7
|
updateTsSchemas(): void;
|
8
|
+
updateConfig(config: AppwriteConfig): void;
|
8
9
|
private extractRelationships;
|
9
10
|
private addRelationship;
|
10
11
|
generateSchemas(): void;
|