appwrite-utils-cli 1.7.9 → 1.8.2
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/CHANGELOG.md +14 -199
- package/README.md +87 -30
- package/dist/adapters/AdapterFactory.js +5 -25
- package/dist/adapters/DatabaseAdapter.d.ts +17 -2
- package/dist/adapters/LegacyAdapter.d.ts +2 -1
- package/dist/adapters/LegacyAdapter.js +212 -16
- package/dist/adapters/TablesDBAdapter.d.ts +2 -12
- package/dist/adapters/TablesDBAdapter.js +261 -57
- package/dist/cli/commands/databaseCommands.js +4 -3
- package/dist/cli/commands/functionCommands.js +17 -8
- package/dist/collections/attributes.js +447 -125
- package/dist/collections/methods.js +197 -186
- package/dist/collections/tableOperations.d.ts +86 -0
- package/dist/collections/tableOperations.js +434 -0
- package/dist/collections/transferOperations.d.ts +3 -2
- package/dist/collections/transferOperations.js +93 -12
- package/dist/config/yamlConfig.d.ts +221 -88
- package/dist/examples/yamlTerminologyExample.d.ts +1 -1
- package/dist/examples/yamlTerminologyExample.js +6 -3
- package/dist/functions/fnConfigDiscovery.d.ts +3 -0
- package/dist/functions/fnConfigDiscovery.js +108 -0
- package/dist/interactiveCLI.js +18 -15
- package/dist/main.js +211 -73
- package/dist/migrations/appwriteToX.d.ts +88 -23
- package/dist/migrations/comprehensiveTransfer.d.ts +2 -0
- package/dist/migrations/comprehensiveTransfer.js +83 -6
- package/dist/migrations/dataLoader.d.ts +227 -69
- package/dist/migrations/dataLoader.js +3 -3
- package/dist/migrations/importController.js +3 -3
- package/dist/migrations/relationships.d.ts +8 -2
- package/dist/migrations/services/ImportOrchestrator.js +3 -3
- package/dist/migrations/transfer.js +159 -37
- package/dist/shared/attributeMapper.d.ts +20 -0
- package/dist/shared/attributeMapper.js +203 -0
- package/dist/shared/selectionDialogs.js +8 -4
- package/dist/storage/schemas.d.ts +354 -92
- package/dist/utils/configDiscovery.js +4 -3
- package/dist/utils/versionDetection.d.ts +0 -4
- package/dist/utils/versionDetection.js +41 -173
- package/dist/utils/yamlConverter.js +89 -16
- package/dist/utils/yamlLoader.d.ts +1 -1
- package/dist/utils/yamlLoader.js +6 -2
- package/dist/utilsController.js +56 -19
- package/package.json +4 -4
- package/src/adapters/AdapterFactory.ts +119 -143
- package/src/adapters/DatabaseAdapter.ts +18 -3
- package/src/adapters/LegacyAdapter.ts +236 -105
- package/src/adapters/TablesDBAdapter.ts +773 -643
- package/src/cli/commands/databaseCommands.ts +13 -12
- package/src/cli/commands/functionCommands.ts +23 -14
- package/src/collections/attributes.ts +2054 -1611
- package/src/collections/methods.ts +208 -293
- package/src/collections/tableOperations.ts +506 -0
- package/src/collections/transferOperations.ts +218 -144
- package/src/examples/yamlTerminologyExample.ts +10 -5
- package/src/functions/fnConfigDiscovery.ts +103 -0
- package/src/interactiveCLI.ts +25 -20
- package/src/main.ts +549 -194
- package/src/migrations/comprehensiveTransfer.ts +126 -50
- package/src/migrations/dataLoader.ts +3 -3
- package/src/migrations/importController.ts +3 -3
- package/src/migrations/services/ImportOrchestrator.ts +3 -3
- package/src/migrations/transfer.ts +148 -131
- package/src/shared/attributeMapper.ts +229 -0
- package/src/shared/selectionDialogs.ts +29 -25
- package/src/utils/configDiscovery.ts +9 -3
- package/src/utils/versionDetection.ts +74 -228
- package/src/utils/yamlConverter.ts +94 -17
- package/src/utils/yamlLoader.ts +11 -4
- package/src/utilsController.ts +80 -30
package/src/main.ts
CHANGED
|
@@ -21,16 +21,26 @@ import type { SyncSelectionSummary, DatabaseSelection, BucketSelection } from ".
|
|
|
21
21
|
import path from "path";
|
|
22
22
|
import fs from "fs";
|
|
23
23
|
import { createRequire } from "node:module";
|
|
24
|
-
import {
|
|
25
|
-
|
|
26
|
-
|
|
24
|
+
import {
|
|
25
|
+
loadAppwriteProjectConfig,
|
|
26
|
+
findAppwriteProjectConfig,
|
|
27
|
+
projectConfigToAppwriteConfig,
|
|
28
|
+
} from "./utils/projectConfig.js";
|
|
29
|
+
import {
|
|
30
|
+
hasSessionAuth,
|
|
31
|
+
getAvailableSessions,
|
|
32
|
+
getAuthenticationStatus,
|
|
33
|
+
} from "./utils/sessionAuth.js";
|
|
34
|
+
import {
|
|
35
|
+
findYamlConfig,
|
|
36
|
+
loadYamlConfigWithSession,
|
|
37
|
+
} from "./config/yamlConfig.js";
|
|
27
38
|
|
|
28
39
|
const require = createRequire(import.meta.url);
|
|
29
40
|
if (!(globalThis as any).require) {
|
|
30
41
|
(globalThis as any).require = require;
|
|
31
42
|
}
|
|
32
43
|
|
|
33
|
-
|
|
34
44
|
interface CliOptions {
|
|
35
45
|
config?: string;
|
|
36
46
|
it?: boolean;
|
|
@@ -42,7 +52,7 @@ interface CliOptions {
|
|
|
42
52
|
generate?: boolean;
|
|
43
53
|
import?: boolean;
|
|
44
54
|
backup?: boolean;
|
|
45
|
-
backupFormat?:
|
|
55
|
+
backupFormat?: "json" | "zip";
|
|
46
56
|
comprehensiveBackup?: boolean;
|
|
47
57
|
trackingDatabaseId?: string;
|
|
48
58
|
parallelDownloads?: number;
|
|
@@ -170,15 +180,15 @@ async function performEnhancedSync(
|
|
|
170
180
|
const allowNewOnly = !syncExisting;
|
|
171
181
|
|
|
172
182
|
// Select databases
|
|
173
|
-
const selectedDatabaseIds = await SelectionDialogs.selectDatabases(
|
|
174
|
-
availableDatabases,
|
|
175
|
-
configuredDatabases,
|
|
176
|
-
{
|
|
177
|
-
showSelectAll:
|
|
178
|
-
allowNewOnly,
|
|
179
|
-
defaultSelected:
|
|
180
|
-
}
|
|
181
|
-
);
|
|
183
|
+
const selectedDatabaseIds = await SelectionDialogs.selectDatabases(
|
|
184
|
+
availableDatabases,
|
|
185
|
+
configuredDatabases,
|
|
186
|
+
{
|
|
187
|
+
showSelectAll: false,
|
|
188
|
+
allowNewOnly,
|
|
189
|
+
defaultSelected: []
|
|
190
|
+
}
|
|
191
|
+
);
|
|
182
192
|
|
|
183
193
|
if (selectedDatabaseIds.length === 0) {
|
|
184
194
|
MessageFormatter.warning("No databases selected for sync", { prefix: "Sync" });
|
|
@@ -203,17 +213,17 @@ async function performEnhancedSync(
|
|
|
203
213
|
const configuredTables = controller.config.collections || [];
|
|
204
214
|
|
|
205
215
|
// Select tables for this database
|
|
206
|
-
const selectedTableIds = await SelectionDialogs.selectTablesForDatabase(
|
|
207
|
-
databaseId,
|
|
208
|
-
database.name,
|
|
209
|
-
availableTables,
|
|
210
|
-
configuredTables,
|
|
211
|
-
{
|
|
212
|
-
showSelectAll:
|
|
213
|
-
allowNewOnly,
|
|
214
|
-
defaultSelected:
|
|
215
|
-
}
|
|
216
|
-
);
|
|
216
|
+
const selectedTableIds = await SelectionDialogs.selectTablesForDatabase(
|
|
217
|
+
databaseId,
|
|
218
|
+
database.name,
|
|
219
|
+
availableTables,
|
|
220
|
+
configuredTables,
|
|
221
|
+
{
|
|
222
|
+
showSelectAll: false,
|
|
223
|
+
allowNewOnly,
|
|
224
|
+
defaultSelected: []
|
|
225
|
+
}
|
|
226
|
+
);
|
|
217
227
|
|
|
218
228
|
tableSelectionsMap.set(databaseId, selectedTableIds);
|
|
219
229
|
|
|
@@ -236,17 +246,17 @@ async function performEnhancedSync(
|
|
|
236
246
|
// you'd fetch this from the Appwrite API
|
|
237
247
|
const availableBuckets = configuredBuckets; // Placeholder
|
|
238
248
|
|
|
239
|
-
selectedBucketIds = await SelectionDialogs.selectBucketsForDatabases(
|
|
240
|
-
selectedDatabaseIds,
|
|
241
|
-
availableBuckets,
|
|
242
|
-
configuredBuckets,
|
|
243
|
-
{
|
|
244
|
-
showSelectAll:
|
|
245
|
-
allowNewOnly: parsedArgv.selectBuckets ? false : allowNewOnly,
|
|
246
|
-
groupByDatabase: true,
|
|
247
|
-
defaultSelected:
|
|
248
|
-
}
|
|
249
|
-
);
|
|
249
|
+
selectedBucketIds = await SelectionDialogs.selectBucketsForDatabases(
|
|
250
|
+
selectedDatabaseIds,
|
|
251
|
+
availableBuckets,
|
|
252
|
+
configuredBuckets,
|
|
253
|
+
{
|
|
254
|
+
showSelectAll: false,
|
|
255
|
+
allowNewOnly: parsedArgv.selectBuckets ? false : allowNewOnly,
|
|
256
|
+
groupByDatabase: true,
|
|
257
|
+
defaultSelected: []
|
|
258
|
+
}
|
|
259
|
+
);
|
|
250
260
|
} catch (error) {
|
|
251
261
|
MessageFormatter.warning("Could not fetch storage buckets", { prefix: "Sync" });
|
|
252
262
|
logger.warn("Failed to fetch buckets during sync", { error });
|
|
@@ -303,7 +313,10 @@ async function performEnhancedSync(
|
|
|
303
313
|
* - allowed: boolean indicating if migration should proceed
|
|
304
314
|
* - reason: string explaining why migration was blocked (if not allowed)
|
|
305
315
|
*/
|
|
306
|
-
function checkMigrationConditions(configPath: string): {
|
|
316
|
+
function checkMigrationConditions(configPath: string): {
|
|
317
|
+
allowed: boolean;
|
|
318
|
+
reason?: string;
|
|
319
|
+
} {
|
|
307
320
|
const collectionsPath = path.join(configPath, "collections");
|
|
308
321
|
const tablesPath = path.join(configPath, "tables");
|
|
309
322
|
|
|
@@ -311,32 +324,34 @@ function checkMigrationConditions(configPath: string): { allowed: boolean; reaso
|
|
|
311
324
|
if (!fs.existsSync(collectionsPath)) {
|
|
312
325
|
return {
|
|
313
326
|
allowed: false,
|
|
314
|
-
reason:
|
|
327
|
+
reason:
|
|
328
|
+
"No collections/ folder found. Migration requires existing collections to migrate.",
|
|
315
329
|
};
|
|
316
330
|
}
|
|
317
331
|
|
|
318
332
|
// Check if collections/ folder has YAML files
|
|
319
|
-
const collectionFiles = fs
|
|
320
|
-
|
|
321
|
-
|
|
333
|
+
const collectionFiles = fs
|
|
334
|
+
.readdirSync(collectionsPath)
|
|
335
|
+
.filter((file) => file.endsWith(".yaml") || file.endsWith(".yml"));
|
|
322
336
|
|
|
323
337
|
if (collectionFiles.length === 0) {
|
|
324
338
|
return {
|
|
325
339
|
allowed: false,
|
|
326
|
-
reason:
|
|
340
|
+
reason:
|
|
341
|
+
"No YAML files found in collections/ folder. Migration requires existing collection YAML files.",
|
|
327
342
|
};
|
|
328
343
|
}
|
|
329
344
|
|
|
330
345
|
// Check if tables/ folder exists and has YAML files
|
|
331
346
|
if (fs.existsSync(tablesPath)) {
|
|
332
|
-
const tableFiles = fs
|
|
333
|
-
|
|
334
|
-
|
|
347
|
+
const tableFiles = fs
|
|
348
|
+
.readdirSync(tablesPath)
|
|
349
|
+
.filter((file) => file.endsWith(".yaml") || file.endsWith(".yml"));
|
|
335
350
|
|
|
336
351
|
if (tableFiles.length > 0) {
|
|
337
352
|
return {
|
|
338
353
|
allowed: false,
|
|
339
|
-
reason: `Tables folder already exists with ${tableFiles.length} YAML file(s). Migration appears to have already been completed
|
|
354
|
+
reason: `Tables folder already exists with ${tableFiles.length} YAML file(s). Migration appears to have already been completed.`,
|
|
340
355
|
};
|
|
341
356
|
}
|
|
342
357
|
}
|
|
@@ -357,12 +372,14 @@ const argv = yargs(hideBin(process.argv))
|
|
|
357
372
|
})
|
|
358
373
|
.option("dbIds", {
|
|
359
374
|
type: "string",
|
|
360
|
-
description:
|
|
375
|
+
description:
|
|
376
|
+
"Comma-separated list of database IDs to target (e.g., 'db1,db2,db3')",
|
|
361
377
|
})
|
|
362
378
|
.option("collectionIds", {
|
|
363
379
|
alias: ["collIds", "tableIds", "tables"],
|
|
364
380
|
type: "string",
|
|
365
|
-
description:
|
|
381
|
+
description:
|
|
382
|
+
"Comma-separated list of collection/table IDs to target (e.g., 'users,posts')",
|
|
366
383
|
})
|
|
367
384
|
.option("bucketIds", {
|
|
368
385
|
type: "string",
|
|
@@ -384,11 +401,13 @@ const argv = yargs(hideBin(process.argv))
|
|
|
384
401
|
})
|
|
385
402
|
.option("generate", {
|
|
386
403
|
type: "boolean",
|
|
387
|
-
description:
|
|
404
|
+
description:
|
|
405
|
+
"Generate TypeScript schemas and types from your Appwrite database schemas",
|
|
388
406
|
})
|
|
389
407
|
.option("import", {
|
|
390
408
|
type: "boolean",
|
|
391
|
-
description:
|
|
409
|
+
description:
|
|
410
|
+
"Import data from importData/ directory into your Appwrite databases",
|
|
392
411
|
})
|
|
393
412
|
.option("backup", {
|
|
394
413
|
type: "boolean",
|
|
@@ -407,21 +426,25 @@ const argv = yargs(hideBin(process.argv))
|
|
|
407
426
|
.option("comprehensiveBackup", {
|
|
408
427
|
alias: ["comprehensive", "backup-all"],
|
|
409
428
|
type: "boolean",
|
|
410
|
-
description:
|
|
429
|
+
description:
|
|
430
|
+
"🚀 Create comprehensive backup of ALL databases and ALL storage buckets",
|
|
411
431
|
})
|
|
412
432
|
.option("trackingDatabaseId", {
|
|
413
433
|
alias: ["tracking-db"],
|
|
414
434
|
type: "string",
|
|
415
|
-
description:
|
|
435
|
+
description:
|
|
436
|
+
"Database ID to use for centralized backup tracking (interactive prompt if not specified)",
|
|
416
437
|
})
|
|
417
438
|
.option("parallelDownloads", {
|
|
418
439
|
type: "number",
|
|
419
440
|
default: 10,
|
|
420
|
-
description:
|
|
441
|
+
description:
|
|
442
|
+
"Number of parallel file downloads for bucket backups (default: 10)",
|
|
421
443
|
})
|
|
422
444
|
.option("writeData", {
|
|
423
445
|
type: "boolean",
|
|
424
|
-
description:
|
|
446
|
+
description:
|
|
447
|
+
"Output converted import data to files for validation before importing",
|
|
425
448
|
})
|
|
426
449
|
.option("push", {
|
|
427
450
|
type: "boolean",
|
|
@@ -456,7 +479,8 @@ const argv = yargs(hideBin(process.argv))
|
|
|
456
479
|
})
|
|
457
480
|
.option("transfer", {
|
|
458
481
|
type: "boolean",
|
|
459
|
-
description:
|
|
482
|
+
description:
|
|
483
|
+
"Transfer documents and files between databases, collections, or projects",
|
|
460
484
|
})
|
|
461
485
|
.option("fromDbId", {
|
|
462
486
|
alias: ["fromDb", "sourceDbId", "sourceDb"],
|
|
@@ -500,7 +524,8 @@ const argv = yargs(hideBin(process.argv))
|
|
|
500
524
|
})
|
|
501
525
|
.option("setup", {
|
|
502
526
|
type: "boolean",
|
|
503
|
-
description:
|
|
527
|
+
description:
|
|
528
|
+
"Initialize project with configuration files and directory structure",
|
|
504
529
|
})
|
|
505
530
|
.option("updateFunctionSpec", {
|
|
506
531
|
type: "boolean",
|
|
@@ -527,27 +552,32 @@ const argv = yargs(hideBin(process.argv))
|
|
|
527
552
|
.option("migrateConfig", {
|
|
528
553
|
alias: ["migrate"],
|
|
529
554
|
type: "boolean",
|
|
530
|
-
description:
|
|
555
|
+
description:
|
|
556
|
+
"Migrate appwriteConfig.ts to .appwrite structure with YAML configuration",
|
|
531
557
|
})
|
|
532
558
|
.option("generateConstants", {
|
|
533
559
|
alias: ["constants"],
|
|
534
|
-
type: "boolean",
|
|
535
|
-
description:
|
|
560
|
+
type: "boolean",
|
|
561
|
+
description:
|
|
562
|
+
"Generate cross-language constants file with database, collection, bucket, and function IDs",
|
|
536
563
|
})
|
|
537
564
|
.option("constantsLanguages", {
|
|
538
565
|
type: "string",
|
|
539
|
-
description:
|
|
566
|
+
description:
|
|
567
|
+
"Comma-separated list of languages for constants (typescript,javascript,python,php,dart,json,env)",
|
|
540
568
|
default: "typescript",
|
|
541
569
|
})
|
|
542
570
|
.option("constantsOutput", {
|
|
543
571
|
type: "string",
|
|
544
|
-
description:
|
|
572
|
+
description:
|
|
573
|
+
"Output directory for generated constants files (default: config-folder/constants)",
|
|
545
574
|
default: "auto",
|
|
546
575
|
})
|
|
547
576
|
.option("migrateCollectionsToTables", {
|
|
548
577
|
alias: ["migrate-collections"],
|
|
549
578
|
type: "boolean",
|
|
550
|
-
description:
|
|
579
|
+
description:
|
|
580
|
+
"Migrate collections to tables format for TablesDB API compatibility",
|
|
551
581
|
})
|
|
552
582
|
.option("useSession", {
|
|
553
583
|
alias: ["session"],
|
|
@@ -563,7 +593,7 @@ const argv = yargs(hideBin(process.argv))
|
|
|
563
593
|
async function main() {
|
|
564
594
|
const startTime = Date.now();
|
|
565
595
|
const operationStats: Record<string, number> = {};
|
|
566
|
-
|
|
596
|
+
|
|
567
597
|
// Early session detection for better user guidance
|
|
568
598
|
const availableSessions = getAvailableSessions();
|
|
569
599
|
let hasAnyValidSessions = availableSessions.length > 0;
|
|
@@ -576,17 +606,43 @@ async function main() {
|
|
|
576
606
|
let directConfig: any = undefined;
|
|
577
607
|
|
|
578
608
|
// Show authentication status on startup if no config provided
|
|
579
|
-
if (
|
|
609
|
+
if (
|
|
610
|
+
!argv.config &&
|
|
611
|
+
!argv.endpoint &&
|
|
612
|
+
!argv.projectId &&
|
|
613
|
+
!argv.apiKey &&
|
|
614
|
+
!argv.useSession &&
|
|
615
|
+
!argv.sessionCookie
|
|
616
|
+
) {
|
|
580
617
|
if (hasAnyValidSessions) {
|
|
581
|
-
MessageFormatter.info(
|
|
582
|
-
|
|
583
|
-
|
|
618
|
+
MessageFormatter.info(
|
|
619
|
+
`Found ${availableSessions.length} available session(s)`,
|
|
620
|
+
{ prefix: "Auth" }
|
|
621
|
+
);
|
|
622
|
+
availableSessions.forEach((session) => {
|
|
623
|
+
MessageFormatter.info(
|
|
624
|
+
` \u2022 ${session.projectId} (${session.email || "unknown"}) at ${
|
|
625
|
+
session.endpoint
|
|
626
|
+
}`,
|
|
627
|
+
{ prefix: "Auth" }
|
|
628
|
+
);
|
|
584
629
|
});
|
|
585
|
-
MessageFormatter.info(
|
|
630
|
+
MessageFormatter.info(
|
|
631
|
+
"Use --session to enable session authentication",
|
|
632
|
+
{ prefix: "Auth" }
|
|
633
|
+
);
|
|
586
634
|
} else {
|
|
587
|
-
MessageFormatter.info("No active Appwrite sessions found", {
|
|
588
|
-
|
|
589
|
-
|
|
635
|
+
MessageFormatter.info("No active Appwrite sessions found", {
|
|
636
|
+
prefix: "Auth",
|
|
637
|
+
});
|
|
638
|
+
MessageFormatter.info(
|
|
639
|
+
"\u2022 Run 'appwrite login' to authenticate with session",
|
|
640
|
+
{ prefix: "Auth" }
|
|
641
|
+
);
|
|
642
|
+
MessageFormatter.info(
|
|
643
|
+
"\u2022 Or provide --apiKey for API key authentication",
|
|
644
|
+
{ prefix: "Auth" }
|
|
645
|
+
);
|
|
590
646
|
}
|
|
591
647
|
}
|
|
592
648
|
|
|
@@ -596,12 +652,21 @@ async function main() {
|
|
|
596
652
|
const projectConfig = loadAppwriteProjectConfig(projectConfigPath);
|
|
597
653
|
if (projectConfig) {
|
|
598
654
|
directConfig = projectConfigToAppwriteConfig(projectConfig);
|
|
599
|
-
MessageFormatter.info(
|
|
655
|
+
MessageFormatter.info(
|
|
656
|
+
`Loaded project configuration from ${projectConfigPath}`,
|
|
657
|
+
{ prefix: "CLI" }
|
|
658
|
+
);
|
|
600
659
|
}
|
|
601
660
|
}
|
|
602
661
|
|
|
603
662
|
// Priority 2: CLI arguments override project config
|
|
604
|
-
if (
|
|
663
|
+
if (
|
|
664
|
+
argv.endpoint ||
|
|
665
|
+
argv.projectId ||
|
|
666
|
+
argv.apiKey ||
|
|
667
|
+
argv.useSession ||
|
|
668
|
+
argv.sessionCookie
|
|
669
|
+
) {
|
|
605
670
|
directConfig = {
|
|
606
671
|
...directConfig,
|
|
607
672
|
appwriteEndpoint: argv.endpoint || directConfig?.appwriteEndpoint,
|
|
@@ -614,31 +679,64 @@ async function main() {
|
|
|
614
679
|
let sessionAuthAvailable = false;
|
|
615
680
|
|
|
616
681
|
if (directConfig?.appwriteEndpoint && directConfig?.appwriteProject) {
|
|
617
|
-
sessionAuthAvailable = hasSessionAuth(
|
|
682
|
+
sessionAuthAvailable = hasSessionAuth(
|
|
683
|
+
directConfig.appwriteEndpoint,
|
|
684
|
+
directConfig.appwriteProject
|
|
685
|
+
);
|
|
618
686
|
}
|
|
619
687
|
|
|
620
688
|
if (argv.useSession || argv.sessionCookie) {
|
|
621
689
|
if (argv.sessionCookie) {
|
|
622
690
|
// Explicit session cookie provided
|
|
623
|
-
MessageFormatter.info(
|
|
691
|
+
MessageFormatter.info(
|
|
692
|
+
"Using explicit session cookie for authentication",
|
|
693
|
+
{ prefix: "Auth" }
|
|
694
|
+
);
|
|
624
695
|
} else if (sessionAuthAvailable) {
|
|
625
|
-
MessageFormatter.info(
|
|
696
|
+
MessageFormatter.info(
|
|
697
|
+
"Session authentication detected and will be used",
|
|
698
|
+
{ prefix: "Auth" }
|
|
699
|
+
);
|
|
626
700
|
} else {
|
|
627
|
-
MessageFormatter.warning(
|
|
701
|
+
MessageFormatter.warning(
|
|
702
|
+
"Session authentication requested but no valid session found",
|
|
703
|
+
{ prefix: "Auth" }
|
|
704
|
+
);
|
|
628
705
|
const availableSessions = getAvailableSessions();
|
|
629
706
|
if (availableSessions.length > 0) {
|
|
630
|
-
MessageFormatter.info(
|
|
631
|
-
|
|
707
|
+
MessageFormatter.info(
|
|
708
|
+
`Available sessions: ${availableSessions
|
|
709
|
+
.map((s) => `${s.projectId} (${s.email || "unknown"})`)
|
|
710
|
+
.join(", ")}`,
|
|
711
|
+
{ prefix: "Auth" }
|
|
712
|
+
);
|
|
713
|
+
MessageFormatter.info(
|
|
714
|
+
"Use --session flag to enable session authentication",
|
|
715
|
+
{ prefix: "Auth" }
|
|
716
|
+
);
|
|
632
717
|
} else {
|
|
633
|
-
MessageFormatter.warning(
|
|
718
|
+
MessageFormatter.warning(
|
|
719
|
+
"No Appwrite CLI sessions found. Please run 'appwrite login' first.",
|
|
720
|
+
{ prefix: "Auth" }
|
|
721
|
+
);
|
|
634
722
|
}
|
|
635
|
-
MessageFormatter.error(
|
|
723
|
+
MessageFormatter.error(
|
|
724
|
+
"Session authentication requested but not available",
|
|
725
|
+
undefined,
|
|
726
|
+
{ prefix: "Auth" }
|
|
727
|
+
);
|
|
636
728
|
return; // Exit early if session auth was requested but not available
|
|
637
729
|
}
|
|
638
730
|
} else if (sessionAuthAvailable && !argv.apiKey) {
|
|
639
731
|
// Auto-detect session authentication when no API key is provided
|
|
640
|
-
MessageFormatter.info(
|
|
641
|
-
|
|
732
|
+
MessageFormatter.info(
|
|
733
|
+
"Session authentication detected - no API key required",
|
|
734
|
+
{ prefix: "Auth" }
|
|
735
|
+
);
|
|
736
|
+
MessageFormatter.info(
|
|
737
|
+
"Use --session flag to explicitly enable session authentication",
|
|
738
|
+
{ prefix: "Auth" }
|
|
739
|
+
);
|
|
642
740
|
}
|
|
643
741
|
|
|
644
742
|
// Enhanced session authentication support:
|
|
@@ -647,22 +745,40 @@ async function main() {
|
|
|
647
745
|
// 3. Auto-detect session authentication when possible
|
|
648
746
|
let finalDirectConfig = directConfig;
|
|
649
747
|
|
|
650
|
-
if (
|
|
651
|
-
|
|
748
|
+
if (
|
|
749
|
+
(argv.useSession || argv.sessionCookie) &&
|
|
750
|
+
(!directConfig ||
|
|
751
|
+
!directConfig.appwriteEndpoint ||
|
|
752
|
+
!directConfig.appwriteProject)
|
|
753
|
+
) {
|
|
652
754
|
// Don't pass incomplete directConfig - let UtilsController load YAML config normally
|
|
653
755
|
finalDirectConfig = null;
|
|
654
|
-
} else if (
|
|
756
|
+
} else if (
|
|
757
|
+
finalDirectConfig &&
|
|
758
|
+
!finalDirectConfig.appwriteKey &&
|
|
759
|
+
!argv.useSession &&
|
|
760
|
+
!argv.sessionCookie
|
|
761
|
+
) {
|
|
655
762
|
// Auto-detect session authentication when no API key provided
|
|
656
763
|
if (sessionAuthAvailable) {
|
|
657
|
-
MessageFormatter.info(
|
|
658
|
-
|
|
764
|
+
MessageFormatter.info(
|
|
765
|
+
"No API key provided, but session authentication is available",
|
|
766
|
+
{ prefix: "Auth" }
|
|
767
|
+
);
|
|
768
|
+
MessageFormatter.info(
|
|
769
|
+
"Automatically using session authentication (add --session to suppress this message)",
|
|
770
|
+
{ prefix: "Auth" }
|
|
771
|
+
);
|
|
659
772
|
// Implicitly enable session authentication
|
|
660
773
|
argv.useSession = true;
|
|
661
774
|
}
|
|
662
775
|
}
|
|
663
776
|
|
|
664
777
|
// Create controller with session authentication support using singleton
|
|
665
|
-
const controller = UtilsController.getInstance(
|
|
778
|
+
const controller = UtilsController.getInstance(
|
|
779
|
+
process.cwd(),
|
|
780
|
+
finalDirectConfig
|
|
781
|
+
);
|
|
666
782
|
|
|
667
783
|
// Pass session authentication options to the controller
|
|
668
784
|
const initOptions: any = {};
|
|
@@ -687,43 +803,57 @@ async function main() {
|
|
|
687
803
|
}
|
|
688
804
|
|
|
689
805
|
if (argv.generateConstants) {
|
|
690
|
-
const { ConstantsGenerator } = await import(
|
|
691
|
-
|
|
692
|
-
|
|
806
|
+
const { ConstantsGenerator } = await import(
|
|
807
|
+
"./utils/constantsGenerator.js"
|
|
808
|
+
);
|
|
809
|
+
type SupportedLanguage =
|
|
810
|
+
import("./utils/constantsGenerator.js").SupportedLanguage;
|
|
811
|
+
|
|
693
812
|
if (!controller.config) {
|
|
694
|
-
MessageFormatter.error("No Appwrite configuration found", undefined, {
|
|
813
|
+
MessageFormatter.error("No Appwrite configuration found", undefined, {
|
|
814
|
+
prefix: "Constants",
|
|
815
|
+
});
|
|
695
816
|
return;
|
|
696
817
|
}
|
|
697
818
|
|
|
698
|
-
const languages = argv
|
|
699
|
-
|
|
819
|
+
const languages = argv
|
|
820
|
+
.constantsLanguages!.split(",")
|
|
821
|
+
.map((l) => l.trim()) as SupportedLanguage[];
|
|
822
|
+
|
|
700
823
|
// Determine output directory - use config folder/constants by default, or custom path if specified
|
|
701
824
|
let outputDir: string;
|
|
702
825
|
if (argv.constantsOutput === "auto") {
|
|
703
826
|
// Default case: use config directory + constants, fallback to current directory
|
|
704
827
|
const configPath = controller.getAppwriteFolderPath();
|
|
705
|
-
outputDir = configPath
|
|
828
|
+
outputDir = configPath
|
|
706
829
|
? path.join(configPath, "constants")
|
|
707
830
|
: path.join(process.cwd(), "constants");
|
|
708
831
|
} else {
|
|
709
832
|
// Custom output directory specified
|
|
710
833
|
outputDir = argv.constantsOutput!;
|
|
711
834
|
}
|
|
712
|
-
|
|
713
|
-
MessageFormatter.info(
|
|
714
|
-
|
|
835
|
+
|
|
836
|
+
MessageFormatter.info(
|
|
837
|
+
`Generating constants for languages: ${languages.join(", ")}`,
|
|
838
|
+
{ prefix: "Constants" }
|
|
839
|
+
);
|
|
840
|
+
|
|
715
841
|
const generator = new ConstantsGenerator(controller.config);
|
|
716
842
|
await generator.generateFiles(languages, outputDir);
|
|
717
|
-
|
|
843
|
+
|
|
718
844
|
operationStats.generatedConstants = languages.length;
|
|
719
|
-
MessageFormatter.success(`Constants generated in ${outputDir}`, {
|
|
845
|
+
MessageFormatter.success(`Constants generated in ${outputDir}`, {
|
|
846
|
+
prefix: "Constants",
|
|
847
|
+
});
|
|
720
848
|
return;
|
|
721
849
|
}
|
|
722
850
|
|
|
723
851
|
if (argv.migrateCollectionsToTables) {
|
|
724
852
|
try {
|
|
725
853
|
if (!controller.config) {
|
|
726
|
-
MessageFormatter.error("No Appwrite configuration found", undefined, {
|
|
854
|
+
MessageFormatter.error("No Appwrite configuration found", undefined, {
|
|
855
|
+
prefix: "Migration",
|
|
856
|
+
});
|
|
727
857
|
return;
|
|
728
858
|
}
|
|
729
859
|
|
|
@@ -735,8 +865,15 @@ async function main() {
|
|
|
735
865
|
if (fs.existsSync(defaultPath)) {
|
|
736
866
|
configPath = defaultPath;
|
|
737
867
|
} else {
|
|
738
|
-
MessageFormatter.error(
|
|
739
|
-
|
|
868
|
+
MessageFormatter.error(
|
|
869
|
+
"Could not determine configuration folder path",
|
|
870
|
+
undefined,
|
|
871
|
+
{ prefix: "Migration" }
|
|
872
|
+
);
|
|
873
|
+
MessageFormatter.info(
|
|
874
|
+
"Make sure you have a .appwrite/ folder in your current directory",
|
|
875
|
+
{ prefix: "Migration" }
|
|
876
|
+
);
|
|
740
877
|
return;
|
|
741
878
|
}
|
|
742
879
|
}
|
|
@@ -744,32 +881,62 @@ async function main() {
|
|
|
744
881
|
// Check if migration conditions are met
|
|
745
882
|
const migrationCheck = checkMigrationConditions(configPath);
|
|
746
883
|
if (!migrationCheck.allowed) {
|
|
747
|
-
MessageFormatter.error(
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
884
|
+
MessageFormatter.error(
|
|
885
|
+
`Migration not allowed: ${migrationCheck.reason}`,
|
|
886
|
+
undefined,
|
|
887
|
+
{ prefix: "Migration" }
|
|
888
|
+
);
|
|
889
|
+
MessageFormatter.info("Migration requirements:", {
|
|
890
|
+
prefix: "Migration",
|
|
891
|
+
});
|
|
892
|
+
MessageFormatter.info(
|
|
893
|
+
" • Configuration must be loaded (use --config or have .appwrite/ folder)",
|
|
894
|
+
{ prefix: "Migration" }
|
|
895
|
+
);
|
|
896
|
+
MessageFormatter.info(
|
|
897
|
+
" • collections/ folder must exist with YAML files",
|
|
898
|
+
{ prefix: "Migration" }
|
|
899
|
+
);
|
|
900
|
+
MessageFormatter.info(
|
|
901
|
+
" • tables/ folder must not exist or be empty",
|
|
902
|
+
{ prefix: "Migration" }
|
|
903
|
+
);
|
|
752
904
|
return;
|
|
753
905
|
}
|
|
754
906
|
|
|
755
|
-
const { migrateCollectionsToTables } = await import(
|
|
907
|
+
const { migrateCollectionsToTables } = await import(
|
|
908
|
+
"./config/configMigration.js"
|
|
909
|
+
);
|
|
756
910
|
|
|
757
|
-
MessageFormatter.info("Starting collections to tables migration...", {
|
|
911
|
+
MessageFormatter.info("Starting collections to tables migration...", {
|
|
912
|
+
prefix: "Migration",
|
|
913
|
+
});
|
|
758
914
|
const result = migrateCollectionsToTables(controller.config, {
|
|
759
915
|
strategy: "full_migration",
|
|
760
916
|
validateResult: true,
|
|
761
|
-
dryRun: false
|
|
917
|
+
dryRun: false,
|
|
762
918
|
});
|
|
763
919
|
|
|
764
920
|
if (result.success) {
|
|
765
921
|
operationStats.migratedCollections = result.changes.length;
|
|
766
|
-
MessageFormatter.success(
|
|
922
|
+
MessageFormatter.success(
|
|
923
|
+
"Collections migration completed successfully",
|
|
924
|
+
{ prefix: "Migration" }
|
|
925
|
+
);
|
|
767
926
|
} else {
|
|
768
|
-
MessageFormatter.error(
|
|
927
|
+
MessageFormatter.error(
|
|
928
|
+
`Migration failed: ${result.errors.join(", ")}`,
|
|
929
|
+
undefined,
|
|
930
|
+
{ prefix: "Migration" }
|
|
931
|
+
);
|
|
769
932
|
process.exit(1);
|
|
770
933
|
}
|
|
771
934
|
} catch (error) {
|
|
772
|
-
MessageFormatter.error(
|
|
935
|
+
MessageFormatter.error(
|
|
936
|
+
"Migration failed",
|
|
937
|
+
error instanceof Error ? error : new Error(String(error)),
|
|
938
|
+
{ prefix: "Migration" }
|
|
939
|
+
);
|
|
773
940
|
process.exit(1);
|
|
774
941
|
}
|
|
775
942
|
return;
|
|
@@ -780,17 +947,41 @@ async function main() {
|
|
|
780
947
|
const availableSessions = getAvailableSessions();
|
|
781
948
|
|
|
782
949
|
if (availableSessions.length > 0) {
|
|
783
|
-
MessageFormatter.error("No Appwrite configuration found", undefined, {
|
|
784
|
-
|
|
785
|
-
|
|
786
|
-
MessageFormatter.info("
|
|
787
|
-
|
|
950
|
+
MessageFormatter.error("No Appwrite configuration found", undefined, {
|
|
951
|
+
prefix: "CLI",
|
|
952
|
+
});
|
|
953
|
+
MessageFormatter.info("Available authentication options:", {
|
|
954
|
+
prefix: "Auth",
|
|
955
|
+
});
|
|
956
|
+
MessageFormatter.info("• Session authentication: Add --session flag", {
|
|
957
|
+
prefix: "Auth",
|
|
958
|
+
});
|
|
959
|
+
MessageFormatter.info(
|
|
960
|
+
"• API key authentication: Add --apiKey YOUR_API_KEY",
|
|
961
|
+
{ prefix: "Auth" }
|
|
962
|
+
);
|
|
963
|
+
MessageFormatter.info(
|
|
964
|
+
`• Available sessions: ${availableSessions
|
|
965
|
+
.map((s) => `${s.projectId} (${s.email || "unknown"})`)
|
|
966
|
+
.join(", ")}`,
|
|
967
|
+
{ prefix: "Auth" }
|
|
968
|
+
);
|
|
788
969
|
} else {
|
|
789
|
-
MessageFormatter.error("No Appwrite configuration found", undefined, {
|
|
970
|
+
MessageFormatter.error("No Appwrite configuration found", undefined, {
|
|
971
|
+
prefix: "CLI",
|
|
972
|
+
});
|
|
790
973
|
MessageFormatter.info("Authentication options:", { prefix: "Auth" });
|
|
791
|
-
MessageFormatter.info(
|
|
792
|
-
|
|
793
|
-
|
|
974
|
+
MessageFormatter.info(
|
|
975
|
+
"• Login with Appwrite CLI: Run 'appwrite login' then use --session flag",
|
|
976
|
+
{ prefix: "Auth" }
|
|
977
|
+
);
|
|
978
|
+
MessageFormatter.info("• Use API key: Add --apiKey YOUR_API_KEY", {
|
|
979
|
+
prefix: "Auth",
|
|
980
|
+
});
|
|
981
|
+
MessageFormatter.info(
|
|
982
|
+
"• Create config file: Run with --setup to initialize project configuration",
|
|
983
|
+
{ prefix: "Auth" }
|
|
984
|
+
);
|
|
794
985
|
}
|
|
795
986
|
return;
|
|
796
987
|
}
|
|
@@ -803,14 +994,16 @@ async function main() {
|
|
|
803
994
|
const { listBackups } = await import("./shared/backupTracking.js");
|
|
804
995
|
|
|
805
996
|
if (!controller.config) {
|
|
806
|
-
MessageFormatter.error("No Appwrite configuration found", undefined, {
|
|
997
|
+
MessageFormatter.error("No Appwrite configuration found", undefined, {
|
|
998
|
+
prefix: "Backups",
|
|
999
|
+
});
|
|
807
1000
|
return;
|
|
808
1001
|
}
|
|
809
1002
|
|
|
810
1003
|
const { adapter } = await AdapterFactory.create({
|
|
811
1004
|
appwriteEndpoint: controller.config.appwriteEndpoint,
|
|
812
1005
|
appwriteProject: controller.config.appwriteProject,
|
|
813
|
-
appwriteKey: controller.config.appwriteKey
|
|
1006
|
+
appwriteKey: controller.config.appwriteKey,
|
|
814
1007
|
});
|
|
815
1008
|
|
|
816
1009
|
const databases = parsedArgv.dbIds
|
|
@@ -825,7 +1018,10 @@ async function main() {
|
|
|
825
1018
|
for (const db of databases!) {
|
|
826
1019
|
const backups = await listBackups(adapter, db.$id);
|
|
827
1020
|
|
|
828
|
-
MessageFormatter.info(
|
|
1021
|
+
MessageFormatter.info(
|
|
1022
|
+
`\nBackups for database: ${db.name} (${db.$id})`,
|
|
1023
|
+
{ prefix: "Backups" }
|
|
1024
|
+
);
|
|
829
1025
|
|
|
830
1026
|
if (backups.length === 0) {
|
|
831
1027
|
MessageFormatter.info(" No backups found", { prefix: "Backups" });
|
|
@@ -834,7 +1030,11 @@ async function main() {
|
|
|
834
1030
|
const date = new Date(backup.$createdAt).toLocaleString();
|
|
835
1031
|
const size = MessageFormatter.formatBytes(backup.sizeBytes);
|
|
836
1032
|
MessageFormatter.info(
|
|
837
|
-
` ${
|
|
1033
|
+
` ${
|
|
1034
|
+
index + 1
|
|
1035
|
+
}. ${date} - ${backup.format.toUpperCase()} - ${size} - ${
|
|
1036
|
+
backup.collections
|
|
1037
|
+
} collections, ${backup.documents} documents`,
|
|
838
1038
|
{ prefix: "Backups" }
|
|
839
1039
|
);
|
|
840
1040
|
});
|
|
@@ -893,12 +1093,20 @@ async function main() {
|
|
|
893
1093
|
}
|
|
894
1094
|
|
|
895
1095
|
// Add default databases if not specified (only if we need them for operations)
|
|
896
|
-
const needsDatabases =
|
|
897
|
-
|
|
898
|
-
|
|
899
|
-
|
|
1096
|
+
const needsDatabases =
|
|
1097
|
+
options.doBackup ||
|
|
1098
|
+
options.wipeDatabase ||
|
|
1099
|
+
options.wipeDocumentStorage ||
|
|
1100
|
+
options.wipeUsers ||
|
|
1101
|
+
options.wipeCollections ||
|
|
1102
|
+
options.importData ||
|
|
1103
|
+
parsedArgv.sync ||
|
|
1104
|
+
parsedArgv.transfer;
|
|
900
1105
|
|
|
901
|
-
if (
|
|
1106
|
+
if (
|
|
1107
|
+
needsDatabases &&
|
|
1108
|
+
(!options.databases || options.databases.length === 0)
|
|
1109
|
+
) {
|
|
902
1110
|
const allDatabases = await fetchAllDatabases(controller.database!);
|
|
903
1111
|
options.databases = allDatabases;
|
|
904
1112
|
}
|
|
@@ -906,7 +1114,9 @@ async function main() {
|
|
|
906
1114
|
// Add default collections if not specified
|
|
907
1115
|
if (!options.collections || options.collections.length === 0) {
|
|
908
1116
|
if (controller.config && controller.config.collections) {
|
|
909
|
-
options.collections = controller.config.collections.map(
|
|
1117
|
+
options.collections = controller.config.collections.map(
|
|
1118
|
+
(c: any) => c.name
|
|
1119
|
+
);
|
|
910
1120
|
} else {
|
|
911
1121
|
options.collections = [];
|
|
912
1122
|
}
|
|
@@ -914,7 +1124,9 @@ async function main() {
|
|
|
914
1124
|
|
|
915
1125
|
// Comprehensive backup (all databases + all buckets)
|
|
916
1126
|
if (parsedArgv.comprehensiveBackup) {
|
|
917
|
-
const { comprehensiveBackup } = await import(
|
|
1127
|
+
const { comprehensiveBackup } = await import(
|
|
1128
|
+
"./backups/operations/comprehensiveBackup.js"
|
|
1129
|
+
);
|
|
918
1130
|
const { AdapterFactory } = await import("./adapters/AdapterFactory.js");
|
|
919
1131
|
|
|
920
1132
|
// Get tracking database ID (interactive prompt if not specified)
|
|
@@ -925,42 +1137,55 @@ async function main() {
|
|
|
925
1137
|
const allDatabases = await fetchAllDatabases(controller.database!);
|
|
926
1138
|
|
|
927
1139
|
if (allDatabases.length === 0) {
|
|
928
|
-
MessageFormatter.error(
|
|
1140
|
+
MessageFormatter.error(
|
|
1141
|
+
"No databases found. Cannot create comprehensive backup without a tracking database.",
|
|
1142
|
+
undefined,
|
|
1143
|
+
{ prefix: "Backup" }
|
|
1144
|
+
);
|
|
929
1145
|
return;
|
|
930
1146
|
}
|
|
931
1147
|
|
|
932
1148
|
if (allDatabases.length === 1) {
|
|
933
1149
|
trackingDatabaseId = allDatabases[0].$id;
|
|
934
|
-
MessageFormatter.info(
|
|
1150
|
+
MessageFormatter.info(
|
|
1151
|
+
`Using only available database for tracking: ${allDatabases[0].name} (${trackingDatabaseId})`,
|
|
1152
|
+
{ prefix: "Backup" }
|
|
1153
|
+
);
|
|
935
1154
|
} else {
|
|
936
1155
|
// Interactive selection
|
|
937
1156
|
const inquirer = (await import("inquirer")).default;
|
|
938
|
-
const answer = await inquirer.prompt([
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
|
|
942
|
-
|
|
943
|
-
|
|
944
|
-
|
|
945
|
-
|
|
946
|
-
|
|
1157
|
+
const answer = await inquirer.prompt([
|
|
1158
|
+
{
|
|
1159
|
+
type: "list",
|
|
1160
|
+
name: "trackingDb",
|
|
1161
|
+
message: "Select database to store backup tracking metadata:",
|
|
1162
|
+
choices: allDatabases.map((db) => ({
|
|
1163
|
+
name: `${db.name} (${db.$id})`,
|
|
1164
|
+
value: db.$id,
|
|
1165
|
+
})),
|
|
1166
|
+
},
|
|
1167
|
+
]);
|
|
947
1168
|
trackingDatabaseId = answer.trackingDb;
|
|
948
1169
|
}
|
|
949
1170
|
}
|
|
950
1171
|
|
|
951
1172
|
// Ensure trackingDatabaseId is defined before proceeding
|
|
952
1173
|
if (!trackingDatabaseId) {
|
|
953
|
-
throw new Error(
|
|
1174
|
+
throw new Error(
|
|
1175
|
+
"Tracking database ID is required for comprehensive backup"
|
|
1176
|
+
);
|
|
954
1177
|
}
|
|
955
1178
|
|
|
956
|
-
MessageFormatter.info(`Using tracking database: ${trackingDatabaseId}`, {
|
|
1179
|
+
MessageFormatter.info(`Using tracking database: ${trackingDatabaseId}`, {
|
|
1180
|
+
prefix: "Backup",
|
|
1181
|
+
});
|
|
957
1182
|
|
|
958
1183
|
// Create adapter for backup tracking
|
|
959
1184
|
const { adapter } = await AdapterFactory.create({
|
|
960
1185
|
appwriteEndpoint: controller.config!.appwriteEndpoint,
|
|
961
1186
|
appwriteProject: controller.config!.appwriteProject,
|
|
962
1187
|
appwriteKey: controller.config!.appwriteKey,
|
|
963
|
-
sessionCookie: controller.config!.sessionCookie
|
|
1188
|
+
sessionCookie: controller.config!.sessionCookie,
|
|
964
1189
|
});
|
|
965
1190
|
|
|
966
1191
|
const result = await comprehensiveBackup(
|
|
@@ -970,11 +1195,11 @@ async function main() {
|
|
|
970
1195
|
adapter,
|
|
971
1196
|
{
|
|
972
1197
|
trackingDatabaseId,
|
|
973
|
-
backupFormat: parsedArgv.backupFormat ||
|
|
1198
|
+
backupFormat: parsedArgv.backupFormat || "zip",
|
|
974
1199
|
parallelDownloads: parsedArgv.parallelDownloads || 10,
|
|
975
1200
|
onProgress: (message) => {
|
|
976
1201
|
MessageFormatter.info(message, { prefix: "Backup" });
|
|
977
|
-
}
|
|
1202
|
+
},
|
|
978
1203
|
}
|
|
979
1204
|
);
|
|
980
1205
|
|
|
@@ -983,24 +1208,44 @@ async function main() {
|
|
|
983
1208
|
operationStats.bucketsBackedUp = result.bucketBackups.length;
|
|
984
1209
|
operationStats.totalBackupSize = result.totalSizeBytes;
|
|
985
1210
|
|
|
986
|
-
if (result.status ===
|
|
987
|
-
MessageFormatter.success(
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
1211
|
+
if (result.status === "completed") {
|
|
1212
|
+
MessageFormatter.success(
|
|
1213
|
+
`Comprehensive backup completed successfully (ID: ${result.backupId})`,
|
|
1214
|
+
{ prefix: "Backup" }
|
|
1215
|
+
);
|
|
1216
|
+
} else if (result.status === "partial") {
|
|
1217
|
+
MessageFormatter.warning(
|
|
1218
|
+
`Comprehensive backup completed with errors (ID: ${result.backupId})`,
|
|
1219
|
+
{ prefix: "Backup" }
|
|
1220
|
+
);
|
|
1221
|
+
result.errors.forEach((err) =>
|
|
1222
|
+
MessageFormatter.warning(err, { prefix: "Backup" })
|
|
1223
|
+
);
|
|
991
1224
|
} else {
|
|
992
|
-
MessageFormatter.error(
|
|
993
|
-
|
|
1225
|
+
MessageFormatter.error(
|
|
1226
|
+
`Comprehensive backup failed (ID: ${result.backupId})`,
|
|
1227
|
+
undefined,
|
|
1228
|
+
{ prefix: "Backup" }
|
|
1229
|
+
);
|
|
1230
|
+
result.errors.forEach((err) =>
|
|
1231
|
+
MessageFormatter.error(err, undefined, { prefix: "Backup" })
|
|
1232
|
+
);
|
|
994
1233
|
}
|
|
995
1234
|
}
|
|
996
1235
|
|
|
997
1236
|
if (options.doBackup && options.databases) {
|
|
998
|
-
MessageFormatter.info(
|
|
1237
|
+
MessageFormatter.info(
|
|
1238
|
+
`Creating backups for ${options.databases.length} database(s) in ${parsedArgv.backupFormat} format`,
|
|
1239
|
+
{ prefix: "Backup" }
|
|
1240
|
+
);
|
|
999
1241
|
for (const db of options.databases) {
|
|
1000
|
-
await controller.backupDatabase(db, parsedArgv.backupFormat ||
|
|
1242
|
+
await controller.backupDatabase(db, parsedArgv.backupFormat || "json");
|
|
1001
1243
|
}
|
|
1002
1244
|
operationStats.backups = options.databases.length;
|
|
1003
|
-
MessageFormatter.success(
|
|
1245
|
+
MessageFormatter.success(
|
|
1246
|
+
`Backup completed for ${options.databases.length} database(s)`,
|
|
1247
|
+
{ prefix: "Backup" }
|
|
1248
|
+
);
|
|
1004
1249
|
}
|
|
1005
1250
|
|
|
1006
1251
|
if (
|
|
@@ -1010,19 +1255,22 @@ async function main() {
|
|
|
1010
1255
|
options.wipeCollections
|
|
1011
1256
|
) {
|
|
1012
1257
|
// Confirm destructive operations
|
|
1013
|
-
const databaseNames = options.databases?.map(db => db.name) || [];
|
|
1014
|
-
const confirmed = await ConfirmationDialogs.confirmDatabaseWipe(
|
|
1015
|
-
|
|
1016
|
-
|
|
1017
|
-
|
|
1018
|
-
|
|
1258
|
+
const databaseNames = options.databases?.map((db) => db.name) || [];
|
|
1259
|
+
const confirmed = await ConfirmationDialogs.confirmDatabaseWipe(
|
|
1260
|
+
databaseNames,
|
|
1261
|
+
{
|
|
1262
|
+
includeStorage: options.wipeDocumentStorage,
|
|
1263
|
+
includeUsers: options.wipeUsers,
|
|
1264
|
+
}
|
|
1265
|
+
);
|
|
1266
|
+
|
|
1019
1267
|
if (!confirmed) {
|
|
1020
1268
|
MessageFormatter.info("Operation cancelled by user", { prefix: "CLI" });
|
|
1021
1269
|
return;
|
|
1022
1270
|
}
|
|
1023
|
-
|
|
1271
|
+
|
|
1024
1272
|
let wipeStats = { databases: 0, collections: 0, users: 0, buckets: 0 };
|
|
1025
|
-
|
|
1273
|
+
|
|
1026
1274
|
if (parsedArgv.wipe === "all") {
|
|
1027
1275
|
if (options.databases) {
|
|
1028
1276
|
for (const db of options.databases) {
|
|
@@ -1061,14 +1309,15 @@ async function main() {
|
|
|
1061
1309
|
const collectionsToWipe = dbCollections.filter((c) =>
|
|
1062
1310
|
options.collections!.includes(c.$id)
|
|
1063
1311
|
);
|
|
1064
|
-
|
|
1312
|
+
|
|
1065
1313
|
// Confirm collection wipe
|
|
1066
|
-
const collectionNames = collectionsToWipe.map(c => c.name);
|
|
1067
|
-
const collectionConfirmed =
|
|
1068
|
-
|
|
1069
|
-
|
|
1070
|
-
|
|
1071
|
-
|
|
1314
|
+
const collectionNames = collectionsToWipe.map((c) => c.name);
|
|
1315
|
+
const collectionConfirmed =
|
|
1316
|
+
await ConfirmationDialogs.confirmCollectionWipe(
|
|
1317
|
+
db.name,
|
|
1318
|
+
collectionNames
|
|
1319
|
+
);
|
|
1320
|
+
|
|
1072
1321
|
if (collectionConfirmed) {
|
|
1073
1322
|
for (const collection of collectionsToWipe) {
|
|
1074
1323
|
await controller.wipeCollection(db, collection);
|
|
@@ -1077,9 +1326,14 @@ async function main() {
|
|
|
1077
1326
|
}
|
|
1078
1327
|
}
|
|
1079
1328
|
}
|
|
1080
|
-
|
|
1329
|
+
|
|
1081
1330
|
// Show wipe operation summary
|
|
1082
|
-
if (
|
|
1331
|
+
if (
|
|
1332
|
+
wipeStats.databases > 0 ||
|
|
1333
|
+
wipeStats.collections > 0 ||
|
|
1334
|
+
wipeStats.users > 0 ||
|
|
1335
|
+
wipeStats.buckets > 0
|
|
1336
|
+
) {
|
|
1083
1337
|
operationStats.wipedDatabases = wipeStats.databases;
|
|
1084
1338
|
operationStats.wipedCollections = wipeStats.collections;
|
|
1085
1339
|
operationStats.wipedUsers = wipeStats.users;
|
|
@@ -1087,16 +1341,103 @@ async function main() {
|
|
|
1087
1341
|
}
|
|
1088
1342
|
}
|
|
1089
1343
|
|
|
1090
|
-
if (parsedArgv.push) {
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1094
|
-
|
|
1095
|
-
|
|
1096
|
-
|
|
1097
|
-
|
|
1098
|
-
|
|
1099
|
-
|
|
1344
|
+
if (parsedArgv.push) {
|
|
1345
|
+
await controller.init();
|
|
1346
|
+
if (!controller.database || !controller.config) {
|
|
1347
|
+
MessageFormatter.error("Database or config not initialized", undefined, { prefix: "Push" });
|
|
1348
|
+
return;
|
|
1349
|
+
}
|
|
1350
|
+
|
|
1351
|
+
// Fetch available DBs
|
|
1352
|
+
const availableDatabases = await fetchAllDatabases(controller.database);
|
|
1353
|
+
if (availableDatabases.length === 0) {
|
|
1354
|
+
MessageFormatter.warning("No databases found in remote project", { prefix: "Push" });
|
|
1355
|
+
return;
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
// Determine selected DBs
|
|
1359
|
+
let selectedDbIds: string[] = [];
|
|
1360
|
+
if (parsedArgv.dbIds) {
|
|
1361
|
+
selectedDbIds = parsedArgv.dbIds.split(/[,\s]+/).filter(Boolean);
|
|
1362
|
+
} else {
|
|
1363
|
+
selectedDbIds = await SelectionDialogs.selectDatabases(
|
|
1364
|
+
availableDatabases,
|
|
1365
|
+
controller.config.databases || [],
|
|
1366
|
+
{ showSelectAll: false, allowNewOnly: false, defaultSelected: [] }
|
|
1367
|
+
);
|
|
1368
|
+
}
|
|
1369
|
+
|
|
1370
|
+
if (selectedDbIds.length === 0) {
|
|
1371
|
+
MessageFormatter.warning("No databases selected for push", { prefix: "Push" });
|
|
1372
|
+
return;
|
|
1373
|
+
}
|
|
1374
|
+
|
|
1375
|
+
// Build DatabaseSelection[] with tableIds per DB
|
|
1376
|
+
const databaseSelections: DatabaseSelection[] = [];
|
|
1377
|
+
const allConfigItems = controller.config.collections || controller.config.tables || [];
|
|
1378
|
+
|
|
1379
|
+
for (const dbId of selectedDbIds) {
|
|
1380
|
+
const db = availableDatabases.find(d => d.$id === dbId);
|
|
1381
|
+
if (!db) continue;
|
|
1382
|
+
|
|
1383
|
+
// Filter config items eligible for this DB according to databaseId/databaseIds rule
|
|
1384
|
+
const eligibleConfigItems = (allConfigItems as any[]).filter(item => {
|
|
1385
|
+
const one = item.databaseId as string | undefined;
|
|
1386
|
+
const many = item.databaseIds as string[] | undefined;
|
|
1387
|
+
if (Array.isArray(many) && many.length > 0) return many.includes(dbId);
|
|
1388
|
+
if (one) return one === dbId;
|
|
1389
|
+
return true; // eligible everywhere if unspecified
|
|
1390
|
+
});
|
|
1391
|
+
|
|
1392
|
+
// Fetch available tables from remote for selection context
|
|
1393
|
+
const availableTables = await fetchAllCollections(dbId, controller.database);
|
|
1394
|
+
|
|
1395
|
+
// Determine selected table IDs
|
|
1396
|
+
let selectedTableIds: string[] = [];
|
|
1397
|
+
if (parsedArgv.collectionIds) {
|
|
1398
|
+
const ids = parsedArgv.collectionIds.split(/[,\s]+/).filter(Boolean);
|
|
1399
|
+
// Only allow IDs that are in eligible config items
|
|
1400
|
+
const eligibleIds = new Set(eligibleConfigItems.map((c: any) => c.$id || c.id));
|
|
1401
|
+
selectedTableIds = ids.filter(id => eligibleIds.has(id));
|
|
1402
|
+
} else {
|
|
1403
|
+
selectedTableIds = await SelectionDialogs.selectTablesForDatabase(
|
|
1404
|
+
dbId,
|
|
1405
|
+
db.name,
|
|
1406
|
+
availableTables,
|
|
1407
|
+
eligibleConfigItems,
|
|
1408
|
+
{ showSelectAll: false, allowNewOnly: true, defaultSelected: [] }
|
|
1409
|
+
);
|
|
1410
|
+
}
|
|
1411
|
+
|
|
1412
|
+
databaseSelections.push({
|
|
1413
|
+
databaseId: db.$id,
|
|
1414
|
+
databaseName: db.name,
|
|
1415
|
+
tableIds: selectedTableIds,
|
|
1416
|
+
tableNames: [],
|
|
1417
|
+
isNew: false,
|
|
1418
|
+
});
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1421
|
+
if (databaseSelections.every(sel => sel.tableIds.length === 0)) {
|
|
1422
|
+
MessageFormatter.warning("No tables/collections selected for push", { prefix: "Push" });
|
|
1423
|
+
return;
|
|
1424
|
+
}
|
|
1425
|
+
|
|
1426
|
+
const pushSummary: Record<string, string | number | string[]> = {
|
|
1427
|
+
databases: databaseSelections.length,
|
|
1428
|
+
collections: databaseSelections.reduce((sum, s) => sum + s.tableIds.length, 0),
|
|
1429
|
+
details: databaseSelections.map(s => `${s.databaseId}: ${s.tableIds.length} items`),
|
|
1430
|
+
};
|
|
1431
|
+
const confirmed = await ConfirmationDialogs.showOperationSummary('Push', pushSummary, { confirmationRequired: true });
|
|
1432
|
+
if (!confirmed) {
|
|
1433
|
+
MessageFormatter.info("Push operation cancelled", { prefix: "Push" });
|
|
1434
|
+
return;
|
|
1435
|
+
}
|
|
1436
|
+
|
|
1437
|
+
await controller.selectivePush(databaseSelections, []);
|
|
1438
|
+
operationStats.pushedDatabases = databaseSelections.length;
|
|
1439
|
+
operationStats.pushedCollections = databaseSelections.reduce((sum, s) => sum + s.tableIds.length, 0);
|
|
1440
|
+
} else if (parsedArgv.sync) {
|
|
1100
1441
|
// Enhanced SYNC: Pull from remote with intelligent configuration detection
|
|
1101
1442
|
if (parsedArgv.autoSync) {
|
|
1102
1443
|
// Legacy behavior: sync everything without prompts
|
|
@@ -1142,7 +1483,9 @@ async function main() {
|
|
|
1142
1483
|
await controller.getDatabasesByIds([parsedArgv.fromDbId])
|
|
1143
1484
|
)?.[0];
|
|
1144
1485
|
if (!fromDb) {
|
|
1145
|
-
MessageFormatter.error("Source database not found", undefined, {
|
|
1486
|
+
MessageFormatter.error("Source database not found", undefined, {
|
|
1487
|
+
prefix: "Transfer",
|
|
1488
|
+
});
|
|
1146
1489
|
return;
|
|
1147
1490
|
}
|
|
1148
1491
|
if (isRemote) {
|
|
@@ -1163,19 +1506,27 @@ async function main() {
|
|
|
1163
1506
|
const remoteDbs = await fetchAllDatabases(targetDatabases);
|
|
1164
1507
|
toDb = remoteDbs.find((db) => db.$id === parsedArgv.toDbId);
|
|
1165
1508
|
if (!toDb) {
|
|
1166
|
-
MessageFormatter.error("Target database not found", undefined, {
|
|
1509
|
+
MessageFormatter.error("Target database not found", undefined, {
|
|
1510
|
+
prefix: "Transfer",
|
|
1511
|
+
});
|
|
1167
1512
|
return;
|
|
1168
1513
|
}
|
|
1169
1514
|
} else {
|
|
1170
1515
|
toDb = (await controller.getDatabasesByIds([parsedArgv.toDbId]))?.[0];
|
|
1171
1516
|
if (!toDb) {
|
|
1172
|
-
MessageFormatter.error("Target database not found", undefined, {
|
|
1517
|
+
MessageFormatter.error("Target database not found", undefined, {
|
|
1518
|
+
prefix: "Transfer",
|
|
1519
|
+
});
|
|
1173
1520
|
return;
|
|
1174
1521
|
}
|
|
1175
1522
|
}
|
|
1176
1523
|
|
|
1177
1524
|
if (!fromDb || !toDb) {
|
|
1178
|
-
MessageFormatter.error(
|
|
1525
|
+
MessageFormatter.error(
|
|
1526
|
+
"Source or target database not found",
|
|
1527
|
+
undefined,
|
|
1528
|
+
{ prefix: "Transfer" }
|
|
1529
|
+
);
|
|
1179
1530
|
return;
|
|
1180
1531
|
}
|
|
1181
1532
|
}
|
|
@@ -1225,11 +1576,15 @@ async function main() {
|
|
|
1225
1576
|
await controller.transferData(transferOptions);
|
|
1226
1577
|
operationStats.transfers = 1;
|
|
1227
1578
|
}
|
|
1228
|
-
|
|
1579
|
+
|
|
1229
1580
|
// Show final operation summary if any operations were performed
|
|
1230
1581
|
if (Object.keys(operationStats).length > 0) {
|
|
1231
1582
|
const duration = Date.now() - startTime;
|
|
1232
|
-
MessageFormatter.operationSummary(
|
|
1583
|
+
MessageFormatter.operationSummary(
|
|
1584
|
+
"CLI Operations",
|
|
1585
|
+
operationStats,
|
|
1586
|
+
duration
|
|
1587
|
+
);
|
|
1233
1588
|
}
|
|
1234
1589
|
}
|
|
1235
1590
|
}
|