appwrite-utils-cli 1.11.0 → 1.12.0
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/{src/adapters/index.ts → dist/adapters/index.d.ts} +0 -1
- package/dist/adapters/index.js +10 -0
- package/dist/backups/operations/bucketBackup.d.ts +19 -0
- package/dist/backups/operations/bucketBackup.js +197 -0
- package/dist/backups/operations/collectionBackup.d.ts +30 -0
- package/dist/backups/operations/collectionBackup.js +201 -0
- package/dist/backups/operations/comprehensiveBackup.d.ts +25 -0
- package/dist/backups/operations/comprehensiveBackup.js +238 -0
- package/dist/backups/schemas/bucketManifest.d.ts +93 -0
- package/dist/backups/schemas/bucketManifest.js +33 -0
- package/dist/backups/schemas/comprehensiveManifest.d.ts +108 -0
- package/dist/backups/schemas/comprehensiveManifest.js +32 -0
- package/dist/backups/tracking/centralizedTracking.d.ts +34 -0
- package/dist/backups/tracking/centralizedTracking.js +274 -0
- package/dist/cli/commands/configCommands.d.ts +8 -0
- package/dist/cli/commands/configCommands.js +210 -0
- package/dist/cli/commands/databaseCommands.d.ts +14 -0
- package/dist/cli/commands/databaseCommands.js +696 -0
- package/dist/cli/commands/functionCommands.d.ts +7 -0
- package/dist/cli/commands/functionCommands.js +330 -0
- package/dist/cli/commands/importFileCommands.d.ts +7 -0
- package/dist/cli/commands/importFileCommands.js +674 -0
- package/dist/cli/commands/schemaCommands.d.ts +7 -0
- package/dist/cli/commands/schemaCommands.js +169 -0
- package/dist/cli/commands/storageCommands.d.ts +5 -0
- package/dist/cli/commands/storageCommands.js +142 -0
- package/dist/cli/commands/transferCommands.d.ts +5 -0
- package/dist/cli/commands/transferCommands.js +382 -0
- package/dist/collections/columns.d.ts +13 -0
- package/dist/collections/columns.js +1339 -0
- package/dist/collections/indexes.d.ts +12 -0
- package/dist/collections/indexes.js +215 -0
- package/dist/collections/methods.d.ts +19 -0
- package/dist/collections/methods.js +605 -0
- package/dist/collections/tableOperations.d.ts +87 -0
- package/dist/collections/tableOperations.js +466 -0
- package/dist/collections/transferOperations.d.ts +8 -0
- package/dist/collections/transferOperations.js +411 -0
- package/dist/collections/wipeOperations.d.ts +17 -0
- package/dist/collections/wipeOperations.js +306 -0
- package/dist/databases/methods.d.ts +6 -0
- package/dist/databases/methods.js +35 -0
- package/dist/databases/setup.d.ts +5 -0
- package/dist/databases/setup.js +45 -0
- package/dist/examples/yamlTerminologyExample.d.ts +42 -0
- package/dist/examples/yamlTerminologyExample.js +272 -0
- package/dist/functions/deployments.d.ts +4 -0
- package/dist/functions/deployments.js +146 -0
- package/dist/functions/fnConfigDiscovery.d.ts +3 -0
- package/dist/functions/fnConfigDiscovery.js +108 -0
- package/dist/functions/methods.d.ts +16 -0
- package/dist/functions/methods.js +174 -0
- package/dist/init.d.ts +2 -0
- package/dist/init.js +57 -0
- package/dist/interactiveCLI.d.ts +36 -0
- package/dist/interactiveCLI.js +952 -0
- package/dist/main.d.ts +2 -0
- package/dist/main.js +1125 -0
- package/dist/migrations/afterImportActions.d.ts +17 -0
- package/dist/migrations/afterImportActions.js +305 -0
- package/dist/migrations/appwriteToX.d.ts +211 -0
- package/dist/migrations/appwriteToX.js +493 -0
- package/dist/migrations/comprehensiveTransfer.d.ts +147 -0
- package/dist/migrations/comprehensiveTransfer.js +1315 -0
- package/dist/migrations/dataLoader.d.ts +755 -0
- package/dist/migrations/dataLoader.js +1272 -0
- package/dist/migrations/importController.d.ts +25 -0
- package/dist/migrations/importController.js +283 -0
- package/dist/migrations/importDataActions.d.ts +50 -0
- package/dist/migrations/importDataActions.js +230 -0
- package/dist/migrations/relationships.d.ts +29 -0
- package/dist/migrations/relationships.js +203 -0
- package/dist/migrations/services/DataTransformationService.d.ts +55 -0
- package/dist/migrations/services/DataTransformationService.js +158 -0
- package/dist/migrations/services/FileHandlerService.d.ts +75 -0
- package/dist/migrations/services/FileHandlerService.js +236 -0
- package/dist/migrations/services/ImportOrchestrator.d.ts +99 -0
- package/dist/migrations/services/ImportOrchestrator.js +493 -0
- package/dist/migrations/services/RateLimitManager.d.ts +138 -0
- package/dist/migrations/services/RateLimitManager.js +279 -0
- package/dist/migrations/services/RelationshipResolver.d.ts +120 -0
- package/dist/migrations/services/RelationshipResolver.js +332 -0
- package/dist/migrations/services/UserMappingService.d.ts +109 -0
- package/dist/migrations/services/UserMappingService.js +277 -0
- package/dist/migrations/services/ValidationService.d.ts +74 -0
- package/dist/migrations/services/ValidationService.js +260 -0
- package/dist/migrations/transfer.d.ts +30 -0
- package/dist/migrations/transfer.js +661 -0
- package/dist/migrations/yaml/YamlImportConfigLoader.d.ts +131 -0
- package/dist/migrations/yaml/YamlImportConfigLoader.js +383 -0
- package/dist/migrations/yaml/YamlImportIntegration.d.ts +93 -0
- package/dist/migrations/yaml/YamlImportIntegration.js +341 -0
- package/dist/migrations/yaml/generateImportSchemas.d.ts +30 -0
- package/dist/migrations/yaml/generateImportSchemas.js +1327 -0
- package/dist/schemas/authUser.d.ts +24 -0
- package/dist/schemas/authUser.js +17 -0
- package/dist/setup.d.ts +2 -0
- package/{src/setup.ts → dist/setup.js} +0 -3
- package/dist/setupCommands.d.ts +58 -0
- package/dist/setupCommands.js +489 -0
- package/dist/setupController.d.ts +9 -0
- package/dist/setupController.js +34 -0
- package/dist/shared/backupMetadataSchema.d.ts +94 -0
- package/dist/shared/backupMetadataSchema.js +38 -0
- package/dist/shared/backupTracking.d.ts +18 -0
- package/dist/shared/backupTracking.js +176 -0
- package/dist/shared/confirmationDialogs.d.ts +75 -0
- package/dist/shared/confirmationDialogs.js +236 -0
- package/dist/shared/migrationHelpers.d.ts +61 -0
- package/dist/shared/migrationHelpers.js +145 -0
- package/{src/shared/operationLogger.ts → dist/shared/operationLogger.d.ts} +1 -11
- package/dist/shared/operationLogger.js +12 -0
- package/dist/shared/operationQueue.d.ts +40 -0
- package/dist/shared/operationQueue.js +310 -0
- package/dist/shared/operationsTable.d.ts +26 -0
- package/dist/shared/operationsTable.js +287 -0
- package/dist/shared/operationsTableSchema.d.ts +48 -0
- package/dist/shared/operationsTableSchema.js +35 -0
- package/dist/shared/progressManager.d.ts +62 -0
- package/dist/shared/progressManager.js +215 -0
- package/dist/shared/relationshipExtractor.d.ts +56 -0
- package/dist/shared/relationshipExtractor.js +138 -0
- package/dist/shared/selectionDialogs.d.ts +220 -0
- package/dist/shared/selectionDialogs.js +588 -0
- package/dist/storage/backupCompression.d.ts +20 -0
- package/dist/storage/backupCompression.js +67 -0
- package/dist/storage/methods.d.ts +44 -0
- package/dist/storage/methods.js +475 -0
- package/dist/storage/schemas.d.ts +842 -0
- package/dist/storage/schemas.js +175 -0
- package/dist/tables/indexManager.d.ts +65 -0
- package/dist/tables/indexManager.js +294 -0
- package/{src/types.ts → dist/types.d.ts} +1 -6
- package/dist/types.js +3 -0
- package/dist/users/methods.d.ts +16 -0
- package/dist/users/methods.js +276 -0
- package/dist/utils/configMigration.d.ts +1 -0
- package/dist/utils/configMigration.js +261 -0
- package/dist/utils/index.js +2 -0
- package/dist/utils/loadConfigs.d.ts +50 -0
- package/dist/utils/loadConfigs.js +357 -0
- package/dist/utils/setupFiles.d.ts +4 -0
- package/dist/utils/setupFiles.js +1190 -0
- package/dist/utilsController.d.ts +114 -0
- package/dist/utilsController.js +898 -0
- package/package.json +6 -3
- package/CHANGELOG.md +0 -35
- package/CONFIG_TODO.md +0 -1189
- package/SELECTION_DIALOGS.md +0 -146
- package/SERVICE_IMPLEMENTATION_REPORT.md +0 -462
- package/scripts/copy-templates.ts +0 -23
- package/src/backups/operations/bucketBackup.ts +0 -277
- package/src/backups/operations/collectionBackup.ts +0 -310
- package/src/backups/operations/comprehensiveBackup.ts +0 -342
- package/src/backups/schemas/bucketManifest.ts +0 -78
- package/src/backups/schemas/comprehensiveManifest.ts +0 -76
- package/src/backups/tracking/centralizedTracking.ts +0 -352
- package/src/cli/commands/configCommands.ts +0 -265
- package/src/cli/commands/databaseCommands.ts +0 -931
- package/src/cli/commands/functionCommands.ts +0 -419
- package/src/cli/commands/importFileCommands.ts +0 -815
- package/src/cli/commands/schemaCommands.ts +0 -200
- package/src/cli/commands/storageCommands.ts +0 -151
- package/src/cli/commands/transferCommands.ts +0 -454
- package/src/collections/attributes.ts.backup +0 -1555
- package/src/collections/columns.ts +0 -2025
- package/src/collections/indexes.ts +0 -350
- package/src/collections/methods.ts +0 -714
- package/src/collections/tableOperations.ts +0 -542
- package/src/collections/transferOperations.ts +0 -589
- package/src/collections/wipeOperations.ts +0 -449
- package/src/databases/methods.ts +0 -49
- package/src/databases/setup.ts +0 -77
- package/src/examples/yamlTerminologyExample.ts +0 -346
- package/src/functions/deployments.ts +0 -221
- package/src/functions/fnConfigDiscovery.ts +0 -103
- package/src/functions/methods.ts +0 -284
- package/src/init.ts +0 -62
- package/src/interactiveCLI.ts +0 -1201
- package/src/main.ts +0 -1517
- package/src/migrations/afterImportActions.ts +0 -579
- package/src/migrations/appwriteToX.ts +0 -668
- package/src/migrations/comprehensiveTransfer.ts +0 -2285
- package/src/migrations/dataLoader.ts +0 -1729
- package/src/migrations/importController.ts +0 -440
- package/src/migrations/importDataActions.ts +0 -315
- package/src/migrations/relationships.ts +0 -333
- package/src/migrations/services/DataTransformationService.ts +0 -196
- package/src/migrations/services/FileHandlerService.ts +0 -311
- package/src/migrations/services/ImportOrchestrator.ts +0 -675
- package/src/migrations/services/RateLimitManager.ts +0 -363
- package/src/migrations/services/RelationshipResolver.ts +0 -461
- package/src/migrations/services/UserMappingService.ts +0 -345
- package/src/migrations/services/ValidationService.ts +0 -349
- package/src/migrations/transfer.ts +0 -1113
- package/src/migrations/yaml/YamlImportConfigLoader.ts +0 -439
- package/src/migrations/yaml/YamlImportIntegration.ts +0 -446
- package/src/migrations/yaml/generateImportSchemas.ts +0 -1354
- package/src/schemas/authUser.ts +0 -23
- package/src/setupCommands.ts +0 -602
- package/src/setupController.ts +0 -43
- package/src/shared/backupMetadataSchema.ts +0 -93
- package/src/shared/backupTracking.ts +0 -211
- package/src/shared/confirmationDialogs.ts +0 -327
- package/src/shared/migrationHelpers.ts +0 -232
- package/src/shared/operationQueue.ts +0 -376
- package/src/shared/operationsTable.ts +0 -338
- package/src/shared/operationsTableSchema.ts +0 -60
- package/src/shared/progressManager.ts +0 -278
- package/src/shared/relationshipExtractor.ts +0 -214
- package/src/shared/selectionDialogs.ts +0 -802
- package/src/storage/backupCompression.ts +0 -88
- package/src/storage/methods.ts +0 -711
- package/src/storage/schemas.ts +0 -205
- package/src/tables/indexManager.ts +0 -409
- package/src/types/node-appwrite-tablesdb.d.ts +0 -44
- package/src/users/methods.ts +0 -358
- package/src/utils/configMigration.ts +0 -348
- package/src/utils/loadConfigs.ts +0 -457
- package/src/utils/setupFiles.ts +0 -1236
- package/src/utilsController.ts +0 -1263
- package/tests/README.md +0 -497
- package/tests/adapters/AdapterFactory.test.ts +0 -277
- package/tests/integration/syncOperations.test.ts +0 -463
- package/tests/jest.config.js +0 -25
- package/tests/migration/configMigration.test.ts +0 -546
- package/tests/setup.ts +0 -62
- package/tests/testUtils.ts +0 -340
- package/tests/utils/loadConfigs.test.ts +0 -350
- package/tests/validation/configValidation.test.ts +0 -412
- package/tsconfig.json +0 -44
- /package/{src → dist}/functions/templates/count-docs-in-collection/README.md +0 -0
- /package/{src → dist}/functions/templates/count-docs-in-collection/src/main.ts +0 -0
- /package/{src → dist}/functions/templates/count-docs-in-collection/src/request.ts +0 -0
- /package/{src → dist}/functions/templates/hono-typescript/README.md +0 -0
- /package/{src → dist}/functions/templates/hono-typescript/src/adapters/request.ts +0 -0
- /package/{src → dist}/functions/templates/hono-typescript/src/adapters/response.ts +0 -0
- /package/{src → dist}/functions/templates/hono-typescript/src/app.ts +0 -0
- /package/{src → dist}/functions/templates/hono-typescript/src/context.ts +0 -0
- /package/{src → dist}/functions/templates/hono-typescript/src/main.ts +0 -0
- /package/{src → dist}/functions/templates/hono-typescript/src/middleware/appwrite.ts +0 -0
- /package/{src → dist}/functions/templates/typescript-node/README.md +0 -0
- /package/{src → dist}/functions/templates/typescript-node/src/context.ts +0 -0
- /package/{src → dist}/functions/templates/typescript-node/src/main.ts +0 -0
- /package/{src → dist}/functions/templates/uv/README.md +0 -0
- /package/{src → dist}/functions/templates/uv/pyproject.toml +0 -0
- /package/{src → dist}/functions/templates/uv/src/__init__.py +0 -0
- /package/{src → dist}/functions/templates/uv/src/context.py +0 -0
- /package/{src → dist}/functions/templates/uv/src/main.py +0 -0
- /package/{src/utils/index.ts → dist/utils/index.d.ts} +0 -0
|
@@ -0,0 +1,696 @@
|
|
|
1
|
+
import inquirer from "inquirer";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { Query } from "node-appwrite";
|
|
5
|
+
import { MessageFormatter } from 'appwrite-utils-helpers';
|
|
6
|
+
import { ConfirmationDialogs } from "../../shared/confirmationDialogs.js";
|
|
7
|
+
import { SelectionDialogs } from "../../shared/selectionDialogs.js";
|
|
8
|
+
import { logger } from 'appwrite-utils-helpers';
|
|
9
|
+
import { fetchAllDatabases } from "../../databases/methods.js";
|
|
10
|
+
import { listBuckets } from "../../storage/methods.js";
|
|
11
|
+
import { getFunction, downloadLatestFunctionDeployment } from "../../functions/methods.js";
|
|
12
|
+
import { wipeTableRows } from "../../collections/wipeOperations.js";
|
|
13
|
+
export const databaseCommands = {
|
|
14
|
+
async syncDb(cli) {
|
|
15
|
+
MessageFormatter.progress("Pushing local configuration to Appwrite...", { prefix: "Push" });
|
|
16
|
+
try {
|
|
17
|
+
// Initialize controller
|
|
18
|
+
await cli.controller.init();
|
|
19
|
+
// Ask what to push first
|
|
20
|
+
const { pushTargets } = await inquirer.prompt([
|
|
21
|
+
{
|
|
22
|
+
type: "checkbox",
|
|
23
|
+
name: "pushTargets",
|
|
24
|
+
message: chalk.blue("What would you like to push to Appwrite?"),
|
|
25
|
+
choices: [
|
|
26
|
+
{ name: "Databases & Tables", value: "databases" },
|
|
27
|
+
{ name: "Storage Buckets", value: "buckets" },
|
|
28
|
+
{ name: "Functions", value: "functions" },
|
|
29
|
+
],
|
|
30
|
+
validate: (input) => {
|
|
31
|
+
if (input.length === 0) {
|
|
32
|
+
return "Please select at least one item to push.";
|
|
33
|
+
}
|
|
34
|
+
return true;
|
|
35
|
+
},
|
|
36
|
+
},
|
|
37
|
+
]);
|
|
38
|
+
const pushDatabases = pushTargets.includes("databases");
|
|
39
|
+
const pushBuckets = pushTargets.includes("buckets");
|
|
40
|
+
const pushFunctions = pushTargets.includes("functions");
|
|
41
|
+
let databaseSelections = [];
|
|
42
|
+
let bucketSelections = [];
|
|
43
|
+
let mergedDatabases = [];
|
|
44
|
+
// --- Databases & Tables sub-flow ---
|
|
45
|
+
if (pushDatabases) {
|
|
46
|
+
const serverDatabases = await fetchAllDatabases(cli.controller.database);
|
|
47
|
+
const configuredDatabases = cli.controller.config?.databases || [];
|
|
48
|
+
const serverDbIds = new Set(serverDatabases.map(db => db.$id));
|
|
49
|
+
mergedDatabases = [...serverDatabases];
|
|
50
|
+
for (const configDb of configuredDatabases) {
|
|
51
|
+
const dbId = configDb.$id;
|
|
52
|
+
if (dbId && !serverDbIds.has(dbId)) {
|
|
53
|
+
mergedDatabases.push({
|
|
54
|
+
$id: dbId,
|
|
55
|
+
name: configDb.name || dbId,
|
|
56
|
+
$createdAt: new Date().toISOString(),
|
|
57
|
+
$updatedAt: new Date().toISOString(),
|
|
58
|
+
enabled: true,
|
|
59
|
+
_isLocalOnly: true,
|
|
60
|
+
});
|
|
61
|
+
MessageFormatter.info(`Including local database "${configDb.name || dbId}" (not yet on server)`, { prefix: "Database" });
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
const selectedDatabaseIds = await SelectionDialogs.selectDatabases(mergedDatabases, configuredDatabases, { showSelectAll: false, allowNewOnly: false, defaultSelected: [] });
|
|
65
|
+
if (selectedDatabaseIds.length === 0) {
|
|
66
|
+
MessageFormatter.warning("No databases selected.", { prefix: "Database" });
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
const tableSelectionsMap = new Map();
|
|
70
|
+
const availableTablesMap = new Map();
|
|
71
|
+
for (const databaseId of selectedDatabaseIds) {
|
|
72
|
+
const database = mergedDatabases.find(db => db.$id === databaseId);
|
|
73
|
+
const selectedCollections = await cli.selectCollectionsAndTables(database, cli.controller.database, chalk.blue(`Select tables to push to "${database.name}":`), true, true, true);
|
|
74
|
+
const selectedTableIds = selectedCollections.map((c) => c.$id || c.id);
|
|
75
|
+
tableSelectionsMap.set(databaseId, selectedTableIds);
|
|
76
|
+
availableTablesMap.set(databaseId, selectedCollections);
|
|
77
|
+
if (selectedCollections.length === 0) {
|
|
78
|
+
MessageFormatter.warning(`No tables selected for database "${database.name}". Skipping.`, { prefix: "Database" });
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
databaseSelections = SelectionDialogs.createDatabaseSelection(selectedDatabaseIds, mergedDatabases, tableSelectionsMap, configuredDatabases, availableTablesMap);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
// --- Storage Buckets sub-flow ---
|
|
85
|
+
if (pushBuckets) {
|
|
86
|
+
try {
|
|
87
|
+
let remoteBuckets = [];
|
|
88
|
+
try {
|
|
89
|
+
const remoteBucketsResponse = await listBuckets(cli.controller.storage);
|
|
90
|
+
remoteBuckets = remoteBucketsResponse.buckets || [];
|
|
91
|
+
}
|
|
92
|
+
catch (error) {
|
|
93
|
+
MessageFormatter.warning("Could not fetch remote buckets, showing local buckets only.", { prefix: "Buckets" });
|
|
94
|
+
}
|
|
95
|
+
const configuredBuckets = cli.controller.config?.buckets || [];
|
|
96
|
+
// Merge local + remote buckets
|
|
97
|
+
const remoteBucketIds = new Set(remoteBuckets.map((b) => b.$id));
|
|
98
|
+
const localBucketIds = new Set(configuredBuckets.map((b) => b.$id));
|
|
99
|
+
const mergedBuckets = [];
|
|
100
|
+
for (const rb of remoteBuckets) {
|
|
101
|
+
mergedBuckets.push({
|
|
102
|
+
...rb,
|
|
103
|
+
_isLocalOnly: false,
|
|
104
|
+
_isRemoteOnly: !localBucketIds.has(rb.$id),
|
|
105
|
+
});
|
|
106
|
+
}
|
|
107
|
+
for (const lb of configuredBuckets) {
|
|
108
|
+
if (!remoteBucketIds.has(lb.$id)) {
|
|
109
|
+
mergedBuckets.push({
|
|
110
|
+
$id: lb.$id,
|
|
111
|
+
name: lb.name,
|
|
112
|
+
enabled: lb.enabled,
|
|
113
|
+
maximumFileSize: lb.maximumFileSize,
|
|
114
|
+
allowedFileExtensions: lb.allowedFileExtensions || [],
|
|
115
|
+
compression: lb.compression || 'none',
|
|
116
|
+
encryption: lb.encryption || false,
|
|
117
|
+
antivirus: lb.antivirus || false,
|
|
118
|
+
fileSecurity: lb.fileSecurity || false,
|
|
119
|
+
permissions: lb.permissions || [],
|
|
120
|
+
$permissions: [],
|
|
121
|
+
_isLocalOnly: true,
|
|
122
|
+
_isRemoteOnly: false,
|
|
123
|
+
});
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
if (mergedBuckets.length === 0) {
|
|
127
|
+
MessageFormatter.warning("No storage buckets found (local or remote).", { prefix: "Buckets" });
|
|
128
|
+
}
|
|
129
|
+
else {
|
|
130
|
+
const selectedBucketIds = await SelectionDialogs.selectBucketsForPush(mergedBuckets, configuredBuckets);
|
|
131
|
+
if (selectedBucketIds.length > 0) {
|
|
132
|
+
bucketSelections = SelectionDialogs.createBucketSelection(selectedBucketIds, mergedBuckets, configuredBuckets, mergedDatabases);
|
|
133
|
+
MessageFormatter.info(`Selected ${bucketSelections.length} storage bucket(s)`, { prefix: "Buckets" });
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
catch (error) {
|
|
138
|
+
MessageFormatter.warning("Failed during bucket selection.", { prefix: "Buckets" });
|
|
139
|
+
logger.warn("Bucket selection failed during syncDb", { error: error instanceof Error ? error.message : String(error) });
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
// --- Confirmation ---
|
|
143
|
+
if (databaseSelections.length > 0 || bucketSelections.length > 0) {
|
|
144
|
+
const selectionSummary = SelectionDialogs.createSyncSelectionSummary(databaseSelections, bucketSelections);
|
|
145
|
+
const confirmed = await SelectionDialogs.confirmSyncSelection(selectionSummary, 'push');
|
|
146
|
+
if (!confirmed) {
|
|
147
|
+
MessageFormatter.info("Push operation cancelled by user", { prefix: "Push" });
|
|
148
|
+
return;
|
|
149
|
+
}
|
|
150
|
+
MessageFormatter.progress("Starting selective push...", { prefix: "Push" });
|
|
151
|
+
await cli.controller.selectivePush(databaseSelections, bucketSelections);
|
|
152
|
+
MessageFormatter.success("Configuration pushed successfully!", { prefix: "Push" });
|
|
153
|
+
}
|
|
154
|
+
// --- Functions sub-flow ---
|
|
155
|
+
if (pushFunctions) {
|
|
156
|
+
if (!cli.controller.config?.functions?.length) {
|
|
157
|
+
MessageFormatter.warning("No functions defined in local config.", { prefix: "Functions" });
|
|
158
|
+
}
|
|
159
|
+
else {
|
|
160
|
+
const functions = await cli.selectFunctions(chalk.blue("Select local functions to push:"), true, true);
|
|
161
|
+
for (const func of functions) {
|
|
162
|
+
try {
|
|
163
|
+
await cli.controller.deployFunction(func.name);
|
|
164
|
+
MessageFormatter.success(`Function ${func.name} deployed successfully`, { prefix: "Functions" });
|
|
165
|
+
}
|
|
166
|
+
catch (error) {
|
|
167
|
+
MessageFormatter.error(`Failed to deploy function ${func.name}`, error instanceof Error ? error : new Error(String(error)), { prefix: "Functions" });
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
MessageFormatter.success("Push operation completed!", { prefix: "Push" });
|
|
173
|
+
}
|
|
174
|
+
catch (error) {
|
|
175
|
+
MessageFormatter.error("Failed to push local configuration", error instanceof Error ? error : new Error(String(error)), { prefix: "Push" });
|
|
176
|
+
throw error;
|
|
177
|
+
}
|
|
178
|
+
},
|
|
179
|
+
async synchronizeConfigurations(cli) {
|
|
180
|
+
MessageFormatter.progress("Synchronizing configurations...", { prefix: "Config" });
|
|
181
|
+
await cli.controller.init();
|
|
182
|
+
// Sync databases, collections, and buckets
|
|
183
|
+
const { syncDatabases } = await inquirer.prompt([
|
|
184
|
+
{
|
|
185
|
+
type: "confirm",
|
|
186
|
+
name: "syncDatabases",
|
|
187
|
+
message: "Do you want to synchronize databases, tables, and their buckets?",
|
|
188
|
+
default: true,
|
|
189
|
+
},
|
|
190
|
+
]);
|
|
191
|
+
if (syncDatabases) {
|
|
192
|
+
const remoteDatabases = await fetchAllDatabases(cli.controller.database);
|
|
193
|
+
// First, prepare the combined database list for bucket configuration
|
|
194
|
+
const localDatabases = cli.controller.config?.databases || [];
|
|
195
|
+
const allDatabases = [
|
|
196
|
+
...localDatabases,
|
|
197
|
+
...remoteDatabases.filter((rd) => !localDatabases.some((ld) => ld.name === rd.name)),
|
|
198
|
+
];
|
|
199
|
+
// Configure buckets FIRST to get user selections before writing config
|
|
200
|
+
MessageFormatter.progress("Configuring storage buckets...", { prefix: "Buckets" });
|
|
201
|
+
const configWithBuckets = await cli.configureBuckets({
|
|
202
|
+
...cli.controller.config,
|
|
203
|
+
databases: allDatabases,
|
|
204
|
+
});
|
|
205
|
+
// Update controller config with bucket selections
|
|
206
|
+
cli.controller.config = configWithBuckets;
|
|
207
|
+
// Now synchronize configurations with the updated config that includes bucket selections
|
|
208
|
+
MessageFormatter.progress("Pulling tables and generating table files...", { prefix: "Tables" });
|
|
209
|
+
await cli.controller.synchronizeConfigurations(remoteDatabases, configWithBuckets);
|
|
210
|
+
}
|
|
211
|
+
// Then sync functions
|
|
212
|
+
const { syncFunctions } = await inquirer.prompt([
|
|
213
|
+
{
|
|
214
|
+
type: "confirm",
|
|
215
|
+
name: "syncFunctions",
|
|
216
|
+
message: "Do you want to synchronize functions?",
|
|
217
|
+
default: true,
|
|
218
|
+
},
|
|
219
|
+
]);
|
|
220
|
+
if (syncFunctions) {
|
|
221
|
+
const remoteFunctions = await cli.controller.listAllFunctions();
|
|
222
|
+
const localFunctions = cli.controller.config?.functions || [];
|
|
223
|
+
const allFunctions = [
|
|
224
|
+
...remoteFunctions,
|
|
225
|
+
...localFunctions.filter((f) => !remoteFunctions.some((rf) => rf.$id === f.$id)),
|
|
226
|
+
];
|
|
227
|
+
for (const func of allFunctions) {
|
|
228
|
+
const hasLocal = localFunctions.some((lf) => lf.$id === func.$id);
|
|
229
|
+
const hasRemote = remoteFunctions.some((rf) => rf.$id === func.$id);
|
|
230
|
+
if (hasLocal && hasRemote) {
|
|
231
|
+
// Function exists in both local and remote
|
|
232
|
+
const { preference } = await inquirer.prompt([
|
|
233
|
+
{
|
|
234
|
+
type: "list",
|
|
235
|
+
name: "preference",
|
|
236
|
+
message: `Function "${func.name}" exists both locally and remotely. What would you like to do?`,
|
|
237
|
+
choices: [
|
|
238
|
+
{ name: "Keep local version (deploy to remote)", value: "local" },
|
|
239
|
+
{ name: "Use remote version (download)", value: "remote" },
|
|
240
|
+
{ name: "Update config only", value: "config" },
|
|
241
|
+
{ name: "Skip this function", value: "skip" },
|
|
242
|
+
],
|
|
243
|
+
},
|
|
244
|
+
]);
|
|
245
|
+
if (preference === "local") {
|
|
246
|
+
await cli.controller.deployFunction(func.name);
|
|
247
|
+
}
|
|
248
|
+
else if (preference === "remote") {
|
|
249
|
+
await downloadLatestFunctionDeployment(cli.controller.appwriteServer, func.$id, join(cli.controller.getAppwriteFolderPath(), "functions"));
|
|
250
|
+
}
|
|
251
|
+
else if (preference === "config") {
|
|
252
|
+
// Update config with remote function details
|
|
253
|
+
const remoteFunction = await getFunction(cli.controller.appwriteServer, func.$id);
|
|
254
|
+
const newFunction = {
|
|
255
|
+
$id: remoteFunction.$id,
|
|
256
|
+
name: remoteFunction.name,
|
|
257
|
+
runtime: remoteFunction.runtime,
|
|
258
|
+
execute: remoteFunction.execute || [],
|
|
259
|
+
events: remoteFunction.events || [],
|
|
260
|
+
schedule: remoteFunction.schedule || "",
|
|
261
|
+
timeout: remoteFunction.timeout || 15,
|
|
262
|
+
enabled: remoteFunction.enabled !== false,
|
|
263
|
+
logging: remoteFunction.logging !== false,
|
|
264
|
+
entrypoint: remoteFunction.entrypoint || "src/main.ts",
|
|
265
|
+
commands: remoteFunction.commands || "npm install",
|
|
266
|
+
scopes: remoteFunction.scopes || [],
|
|
267
|
+
installationId: remoteFunction.installationId,
|
|
268
|
+
providerRepositoryId: remoteFunction.providerRepositoryId,
|
|
269
|
+
providerBranch: remoteFunction.providerBranch,
|
|
270
|
+
providerSilentMode: remoteFunction.providerSilentMode,
|
|
271
|
+
providerRootDirectory: remoteFunction.providerRootDirectory,
|
|
272
|
+
specification: remoteFunction.specification,
|
|
273
|
+
};
|
|
274
|
+
const existingIndex = cli.controller.config.functions.findIndex((f) => f.$id === remoteFunction.$id);
|
|
275
|
+
if (existingIndex >= 0) {
|
|
276
|
+
cli.controller.config.functions[existingIndex] = newFunction;
|
|
277
|
+
}
|
|
278
|
+
else {
|
|
279
|
+
cli.controller.config.functions.push(newFunction);
|
|
280
|
+
}
|
|
281
|
+
MessageFormatter.success(`Updated config for function: ${func.name}`, { prefix: "Functions" });
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
else if (hasLocal) {
|
|
285
|
+
// Function exists only locally
|
|
286
|
+
const { action } = await inquirer.prompt([
|
|
287
|
+
{
|
|
288
|
+
type: "list",
|
|
289
|
+
name: "action",
|
|
290
|
+
message: `Function "${func.name}" exists only locally. What would you like to do?`,
|
|
291
|
+
choices: [
|
|
292
|
+
{ name: "Deploy to remote", value: "deploy" },
|
|
293
|
+
{ name: "Skip this function", value: "skip" },
|
|
294
|
+
],
|
|
295
|
+
},
|
|
296
|
+
]);
|
|
297
|
+
if (action === "deploy") {
|
|
298
|
+
await cli.controller.deployFunction(func.name);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
else if (hasRemote) {
|
|
302
|
+
// Function exists only remotely
|
|
303
|
+
const { action } = await inquirer.prompt([
|
|
304
|
+
{
|
|
305
|
+
type: "list",
|
|
306
|
+
name: "action",
|
|
307
|
+
message: `Function "${func.name}" exists only remotely. What would you like to do?`,
|
|
308
|
+
choices: [
|
|
309
|
+
{ name: "Update config only", value: "config" },
|
|
310
|
+
{ name: "Download locally", value: "download" },
|
|
311
|
+
{ name: "Skip this function", value: "skip" },
|
|
312
|
+
],
|
|
313
|
+
},
|
|
314
|
+
]);
|
|
315
|
+
if (action === "download") {
|
|
316
|
+
await downloadLatestFunctionDeployment(cli.controller.appwriteServer, func.$id, join(cli.controller.getAppwriteFolderPath(), "functions"));
|
|
317
|
+
}
|
|
318
|
+
else if (action === "config") {
|
|
319
|
+
const remoteFunction = await getFunction(cli.controller.appwriteServer, func.$id);
|
|
320
|
+
const newFunction = {
|
|
321
|
+
$id: remoteFunction.$id,
|
|
322
|
+
name: remoteFunction.name,
|
|
323
|
+
runtime: remoteFunction.runtime,
|
|
324
|
+
execute: remoteFunction.execute || [],
|
|
325
|
+
events: remoteFunction.events || [],
|
|
326
|
+
schedule: remoteFunction.schedule || "",
|
|
327
|
+
timeout: remoteFunction.timeout || 15,
|
|
328
|
+
enabled: remoteFunction.enabled !== false,
|
|
329
|
+
logging: remoteFunction.logging !== false,
|
|
330
|
+
entrypoint: remoteFunction.entrypoint || "src/main.ts",
|
|
331
|
+
commands: remoteFunction.commands || "npm install",
|
|
332
|
+
scopes: remoteFunction.scopes || [],
|
|
333
|
+
installationId: remoteFunction.installationId,
|
|
334
|
+
providerRepositoryId: remoteFunction.providerRepositoryId,
|
|
335
|
+
providerBranch: remoteFunction.providerBranch,
|
|
336
|
+
providerSilentMode: remoteFunction.providerSilentMode,
|
|
337
|
+
providerRootDirectory: remoteFunction.providerRootDirectory,
|
|
338
|
+
specification: remoteFunction.specification,
|
|
339
|
+
};
|
|
340
|
+
cli.controller.config.functions =
|
|
341
|
+
cli.controller.config.functions || [];
|
|
342
|
+
cli.controller.config.functions.push(newFunction);
|
|
343
|
+
MessageFormatter.success(`Added config for remote function: ${func.name}`, { prefix: "Functions" });
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
MessageFormatter.success("✨ Configurations synchronized successfully!", { prefix: "Config" });
|
|
349
|
+
},
|
|
350
|
+
async backupDatabase(cli) {
|
|
351
|
+
if (!cli.controller.database || !cli.controller.storage) {
|
|
352
|
+
throw new Error("Database or Storage is not initialized, is the config file correct & created?");
|
|
353
|
+
}
|
|
354
|
+
try {
|
|
355
|
+
// STEP 1: Select tracking database
|
|
356
|
+
MessageFormatter.info("Step 1/5: Select tracking database", { prefix: "Backup" });
|
|
357
|
+
const trackingDb = await this.selectTrackingDatabase(cli);
|
|
358
|
+
// STEP 2: Ensure backup tracking table exists
|
|
359
|
+
MessageFormatter.info("Step 2/5: Initializing backup tracking", { prefix: "Backup" });
|
|
360
|
+
await this.ensureBackupTrackingTable(cli, trackingDb);
|
|
361
|
+
// STEP 3: Select backup scope
|
|
362
|
+
MessageFormatter.info("Step 3/5: Select backup scope", { prefix: "Backup" });
|
|
363
|
+
const scope = await this.selectBackupScope(cli);
|
|
364
|
+
// STEP 4: Show confirmation
|
|
365
|
+
MessageFormatter.info("Step 4/5: Confirm backup plan", { prefix: "Backup" });
|
|
366
|
+
const confirmed = await this.confirmBackupPlan(scope);
|
|
367
|
+
if (!confirmed) {
|
|
368
|
+
MessageFormatter.info("Backup cancelled by user", { prefix: "Backup" });
|
|
369
|
+
return;
|
|
370
|
+
}
|
|
371
|
+
// STEP 5: Execute unified backup
|
|
372
|
+
MessageFormatter.info("Step 5/5: Executing backup", { prefix: "Backup" });
|
|
373
|
+
await this.executeUnifiedBackup(cli, trackingDb, scope);
|
|
374
|
+
MessageFormatter.success("Backup operation completed successfully", { prefix: "Backup" });
|
|
375
|
+
}
|
|
376
|
+
catch (error) {
|
|
377
|
+
MessageFormatter.error("Backup operation failed", error instanceof Error ? error : new Error(String(error)), { prefix: "Backup" });
|
|
378
|
+
throw error;
|
|
379
|
+
}
|
|
380
|
+
},
|
|
381
|
+
// Helper method: Select tracking database
|
|
382
|
+
async selectTrackingDatabase(cli) {
|
|
383
|
+
const databases = await fetchAllDatabases(cli.controller.database);
|
|
384
|
+
const { trackingDatabaseId } = await inquirer.prompt([
|
|
385
|
+
{
|
|
386
|
+
type: "list",
|
|
387
|
+
name: "trackingDatabaseId",
|
|
388
|
+
message: "Select database to store backup metadata:",
|
|
389
|
+
choices: databases.map(db => ({
|
|
390
|
+
name: `${db.name} (${db.$id})`,
|
|
391
|
+
value: db.$id
|
|
392
|
+
}))
|
|
393
|
+
}
|
|
394
|
+
]);
|
|
395
|
+
MessageFormatter.info(`Using ${trackingDatabaseId} for backup tracking`, { prefix: "Backup" });
|
|
396
|
+
return trackingDatabaseId;
|
|
397
|
+
},
|
|
398
|
+
// Helper method: Ensure backup tracking table exists
|
|
399
|
+
async ensureBackupTrackingTable(cli, trackingDatabaseId) {
|
|
400
|
+
const { createCentralizedBackupTrackingTable } = await import("../../backups/tracking/centralizedTracking.js");
|
|
401
|
+
const adapter = cli.controller.adapter;
|
|
402
|
+
await createCentralizedBackupTrackingTable(adapter, trackingDatabaseId);
|
|
403
|
+
MessageFormatter.success("Backup tracking table ready", { prefix: "Backup" });
|
|
404
|
+
},
|
|
405
|
+
// Helper method: Select backup scope
|
|
406
|
+
async selectBackupScope(cli) {
|
|
407
|
+
const { scopeType } = await inquirer.prompt([
|
|
408
|
+
{
|
|
409
|
+
type: "list",
|
|
410
|
+
name: "scopeType",
|
|
411
|
+
message: "What would you like to backup?",
|
|
412
|
+
choices: [
|
|
413
|
+
{ name: "Comprehensive (ALL databases + ALL buckets)", value: "comprehensive" },
|
|
414
|
+
{ name: "Selective databases (choose specific databases)", value: "selective-databases" },
|
|
415
|
+
{ name: "Selective tables (choose specific tables)", value: "selective-tables" }
|
|
416
|
+
]
|
|
417
|
+
}
|
|
418
|
+
]);
|
|
419
|
+
if (scopeType === "comprehensive") {
|
|
420
|
+
return { type: "comprehensive" };
|
|
421
|
+
}
|
|
422
|
+
if (scopeType === "selective-databases") {
|
|
423
|
+
const databases = await fetchAllDatabases(cli.controller.database);
|
|
424
|
+
const selectedDatabases = await cli.selectDatabases(databases, "Select databases to backup:");
|
|
425
|
+
const { includeBuckets } = await inquirer.prompt([
|
|
426
|
+
{
|
|
427
|
+
type: "confirm",
|
|
428
|
+
name: "includeBuckets",
|
|
429
|
+
message: "Include storage buckets in backup?",
|
|
430
|
+
default: false
|
|
431
|
+
}
|
|
432
|
+
]);
|
|
433
|
+
let selectedBuckets = [];
|
|
434
|
+
if (includeBuckets) {
|
|
435
|
+
const buckets = await listBuckets(cli.controller.storage);
|
|
436
|
+
const { bucketIds } = await inquirer.prompt([
|
|
437
|
+
{
|
|
438
|
+
type: "checkbox",
|
|
439
|
+
name: "bucketIds",
|
|
440
|
+
message: "Select buckets to backup:",
|
|
441
|
+
choices: buckets.buckets.map((b) => ({
|
|
442
|
+
name: `${b.name} (${b.$id})`,
|
|
443
|
+
value: b.$id
|
|
444
|
+
}))
|
|
445
|
+
}
|
|
446
|
+
]);
|
|
447
|
+
selectedBuckets = bucketIds;
|
|
448
|
+
}
|
|
449
|
+
return {
|
|
450
|
+
type: "selective-databases",
|
|
451
|
+
databases: selectedDatabases,
|
|
452
|
+
buckets: selectedBuckets
|
|
453
|
+
};
|
|
454
|
+
}
|
|
455
|
+
if (scopeType === "selective-tables") {
|
|
456
|
+
const databases = await fetchAllDatabases(cli.controller.database);
|
|
457
|
+
const selectedDatabase = await cli.selectDatabases(databases, "Select database containing tables:", false // single selection
|
|
458
|
+
);
|
|
459
|
+
if (!selectedDatabase || selectedDatabase.length === 0) {
|
|
460
|
+
throw new Error("No database selected");
|
|
461
|
+
}
|
|
462
|
+
const db = selectedDatabase[0];
|
|
463
|
+
const collections = await cli.selectCollectionsAndTables(db, cli.controller.database, "Select tables to backup:", true, true, true);
|
|
464
|
+
return {
|
|
465
|
+
type: "selective-tables",
|
|
466
|
+
databaseId: db.$id,
|
|
467
|
+
databaseName: db.name,
|
|
468
|
+
collections: collections
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
throw new Error("Invalid backup scope selected");
|
|
472
|
+
},
|
|
473
|
+
// Helper method: Confirm backup plan
|
|
474
|
+
async confirmBackupPlan(scope) {
|
|
475
|
+
let summary = "\n" + chalk.bold("Backup Plan Summary:") + "\n";
|
|
476
|
+
if (scope.type === "comprehensive") {
|
|
477
|
+
summary += " • ALL databases\n";
|
|
478
|
+
summary += " • ALL storage buckets\n";
|
|
479
|
+
}
|
|
480
|
+
else if (scope.type === "selective-databases") {
|
|
481
|
+
summary += ` • ${scope.databases.length} selected databases\n`;
|
|
482
|
+
if (scope.buckets.length > 0) {
|
|
483
|
+
summary += ` • ${scope.buckets.length} selected buckets\n`;
|
|
484
|
+
}
|
|
485
|
+
}
|
|
486
|
+
else if (scope.type === "selective-tables") {
|
|
487
|
+
summary += ` • Database: ${scope.databaseName}\n`;
|
|
488
|
+
summary += ` • ${scope.collections.length} selected tables\n`;
|
|
489
|
+
}
|
|
490
|
+
console.log(summary);
|
|
491
|
+
const { confirmed } = await inquirer.prompt([
|
|
492
|
+
{
|
|
493
|
+
type: "confirm",
|
|
494
|
+
name: "confirmed",
|
|
495
|
+
message: "Proceed with backup?",
|
|
496
|
+
default: true
|
|
497
|
+
}
|
|
498
|
+
]);
|
|
499
|
+
return confirmed;
|
|
500
|
+
},
|
|
501
|
+
// Helper method: Execute unified backup
|
|
502
|
+
async executeUnifiedBackup(cli, trackingDatabaseId, scope) {
|
|
503
|
+
if (scope.type === "comprehensive") {
|
|
504
|
+
const { comprehensiveBackup } = await import("../../backups/operations/comprehensiveBackup.js");
|
|
505
|
+
await comprehensiveBackup(cli.controller.config, cli.controller.database, cli.controller.storage, cli.controller.adapter, {
|
|
506
|
+
trackingDatabaseId,
|
|
507
|
+
backupFormat: 'zip',
|
|
508
|
+
parallelDownloads: 10,
|
|
509
|
+
onProgress: (message) => {
|
|
510
|
+
MessageFormatter.progress(message, { prefix: "Backup" });
|
|
511
|
+
}
|
|
512
|
+
});
|
|
513
|
+
}
|
|
514
|
+
else if (scope.type === "selective-databases") {
|
|
515
|
+
// Backup each selected database
|
|
516
|
+
for (const db of scope.databases) {
|
|
517
|
+
MessageFormatter.progress(`Backing up database: ${db.name}`, { prefix: "Backup" });
|
|
518
|
+
await cli.controller.backupDatabase(db);
|
|
519
|
+
}
|
|
520
|
+
// Backup selected buckets if any
|
|
521
|
+
for (const bucketId of scope.buckets) {
|
|
522
|
+
MessageFormatter.progress(`Backing up bucket: ${bucketId}`, { prefix: "Backup" });
|
|
523
|
+
const { backupBucket } = await import("../../backups/operations/bucketBackup.js");
|
|
524
|
+
await backupBucket(cli.controller.storage, bucketId, "appwrite-backups", { parallelDownloads: 10 });
|
|
525
|
+
}
|
|
526
|
+
}
|
|
527
|
+
else if (scope.type === "selective-tables") {
|
|
528
|
+
const { backupCollections } = await import("../../backups/operations/collectionBackup.js");
|
|
529
|
+
await backupCollections(cli.controller.config, cli.controller.database, cli.controller.storage, cli.controller.adapter, {
|
|
530
|
+
trackingDatabaseId,
|
|
531
|
+
databaseId: scope.databaseId,
|
|
532
|
+
collectionIds: scope.collections.map((c) => c.$id || c.id),
|
|
533
|
+
backupFormat: 'zip',
|
|
534
|
+
onProgress: (message) => {
|
|
535
|
+
MessageFormatter.progress(message, { prefix: "Backup" });
|
|
536
|
+
}
|
|
537
|
+
});
|
|
538
|
+
}
|
|
539
|
+
},
|
|
540
|
+
async wipeDatabase(cli) {
|
|
541
|
+
if (!cli.controller.database || !cli.controller.storage) {
|
|
542
|
+
throw new Error("Database or Storage is not initialized, is the config file correct & created?");
|
|
543
|
+
}
|
|
544
|
+
const databases = await fetchAllDatabases(cli.controller.database);
|
|
545
|
+
const storage = await listBuckets(cli.controller.storage);
|
|
546
|
+
const selectedDatabases = await cli.selectDatabases(databases, "Select databases to wipe:");
|
|
547
|
+
const { selectedStorage } = await inquirer.prompt([
|
|
548
|
+
{
|
|
549
|
+
type: "checkbox",
|
|
550
|
+
name: "selectedStorage",
|
|
551
|
+
message: "Select storage buckets to wipe:",
|
|
552
|
+
choices: storage.buckets.map((s) => ({ name: s.name, value: s.$id })),
|
|
553
|
+
},
|
|
554
|
+
]);
|
|
555
|
+
const { wipeUsers } = await inquirer.prompt([
|
|
556
|
+
{
|
|
557
|
+
type: "confirm",
|
|
558
|
+
name: "wipeUsers",
|
|
559
|
+
message: "Do you want to wipe users as well?",
|
|
560
|
+
default: false,
|
|
561
|
+
},
|
|
562
|
+
]);
|
|
563
|
+
const databaseNames = selectedDatabases.map((db) => db.name);
|
|
564
|
+
const confirmed = await ConfirmationDialogs.confirmDatabaseWipe(databaseNames, {
|
|
565
|
+
includeStorage: selectedStorage.length > 0,
|
|
566
|
+
includeUsers: wipeUsers
|
|
567
|
+
});
|
|
568
|
+
if (confirmed) {
|
|
569
|
+
MessageFormatter.info("Starting wipe operation...", { prefix: "Wipe" });
|
|
570
|
+
for (const db of selectedDatabases) {
|
|
571
|
+
await cli.controller.wipeDatabase(db);
|
|
572
|
+
}
|
|
573
|
+
for (const bucketId of selectedStorage) {
|
|
574
|
+
await cli.controller.wipeDocumentStorage(bucketId);
|
|
575
|
+
}
|
|
576
|
+
if (wipeUsers) {
|
|
577
|
+
await cli.controller.wipeUsers();
|
|
578
|
+
}
|
|
579
|
+
MessageFormatter.success("Wipe operation completed", { prefix: "Wipe" });
|
|
580
|
+
}
|
|
581
|
+
else {
|
|
582
|
+
MessageFormatter.info("Wipe operation cancelled", { prefix: "Wipe" });
|
|
583
|
+
}
|
|
584
|
+
},
|
|
585
|
+
async wipeCollections(cli) {
|
|
586
|
+
if (!cli.controller.database) {
|
|
587
|
+
throw new Error("Database is not initialized, is the config file correct & created?");
|
|
588
|
+
}
|
|
589
|
+
const databases = await fetchAllDatabases(cli.controller.database);
|
|
590
|
+
const selectedDatabases = await cli.selectDatabases(databases, "Select the database(s) containing the tables to wipe:", true);
|
|
591
|
+
for (const database of selectedDatabases) {
|
|
592
|
+
const collections = await cli.selectCollectionsAndTables(database, cli.controller.database, `Select tables to wipe from ${database.name}:`, true, undefined, true);
|
|
593
|
+
const collectionNames = collections.map((c) => c.name);
|
|
594
|
+
const confirmed = await ConfirmationDialogs.confirmCollectionWipe(database.name, collectionNames);
|
|
595
|
+
if (confirmed) {
|
|
596
|
+
MessageFormatter.info(`Wiping selected tables from ${database.name}...`, { prefix: "Wipe" });
|
|
597
|
+
for (const collection of collections) {
|
|
598
|
+
await cli.controller.wipeCollection(database, collection);
|
|
599
|
+
MessageFormatter.success(`Table ${collection.name} wiped successfully`, { prefix: "Wipe" });
|
|
600
|
+
}
|
|
601
|
+
}
|
|
602
|
+
else {
|
|
603
|
+
MessageFormatter.info(`Wipe operation cancelled for ${database.name}`, { prefix: "Wipe" });
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
MessageFormatter.success("Wipe tables operation completed", { prefix: "Wipe" });
|
|
607
|
+
},
|
|
608
|
+
async wipeTablesData(cli) {
|
|
609
|
+
const controller = cli.controller;
|
|
610
|
+
if (!controller?.adapter) {
|
|
611
|
+
throw new Error("Database adapter is not initialized. TablesDB operations require adapter support.");
|
|
612
|
+
}
|
|
613
|
+
try {
|
|
614
|
+
// Step 1: Select database (single selection for clearer UX)
|
|
615
|
+
const databases = await fetchAllDatabases(controller.database);
|
|
616
|
+
if (!databases || databases.length === 0) {
|
|
617
|
+
MessageFormatter.warning("No databases found", { prefix: "Wipe" });
|
|
618
|
+
return;
|
|
619
|
+
}
|
|
620
|
+
const { selectedDatabase } = await inquirer.prompt([
|
|
621
|
+
{
|
|
622
|
+
type: "list",
|
|
623
|
+
name: "selectedDatabase",
|
|
624
|
+
message: "Select database containing tables to wipe:",
|
|
625
|
+
choices: databases.map((db) => ({
|
|
626
|
+
name: `${db.name} (${db.$id})`,
|
|
627
|
+
value: db
|
|
628
|
+
}))
|
|
629
|
+
}
|
|
630
|
+
]);
|
|
631
|
+
const database = selectedDatabase;
|
|
632
|
+
// Step 2: Get available tables
|
|
633
|
+
const adapter = controller.adapter;
|
|
634
|
+
const tablesResponse = await adapter.listTables({
|
|
635
|
+
databaseId: database.$id,
|
|
636
|
+
queries: [Query.limit(500)]
|
|
637
|
+
});
|
|
638
|
+
const availableTables = tablesResponse.tables || [];
|
|
639
|
+
if (availableTables.length === 0) {
|
|
640
|
+
MessageFormatter.warning(`No tables found in database: ${database.name}`, { prefix: "Wipe" });
|
|
641
|
+
return;
|
|
642
|
+
}
|
|
643
|
+
// Step 3: Select tables using existing SelectionDialogs
|
|
644
|
+
const selectedTableIds = await SelectionDialogs.selectTablesForDatabase(database.$id, database.name, availableTables, [], // No configured tables context needed for wipe
|
|
645
|
+
{
|
|
646
|
+
showSelectAll: true,
|
|
647
|
+
allowNewOnly: false,
|
|
648
|
+
defaultSelected: []
|
|
649
|
+
});
|
|
650
|
+
if (selectedTableIds.length === 0) {
|
|
651
|
+
MessageFormatter.warning("No tables selected. Operation cancelled.", { prefix: "Wipe" });
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
654
|
+
// Step 4: Show confirmation with table details
|
|
655
|
+
const selectedTables = availableTables.filter((t) => selectedTableIds.includes(t.$id));
|
|
656
|
+
const tableNames = selectedTables.map((t) => t.name);
|
|
657
|
+
console.log(chalk.yellow.bold("\n⚠️ WARNING: Table Row Wipe Operation"));
|
|
658
|
+
console.log(chalk.yellow("This will delete ALL ROWS from the selected tables."));
|
|
659
|
+
console.log(chalk.yellow("The table structures will remain intact.\n"));
|
|
660
|
+
console.log(chalk.cyan("Database:"), chalk.white(database.name));
|
|
661
|
+
console.log(chalk.cyan("Tables to wipe:"));
|
|
662
|
+
tableNames.forEach((name) => console.log(chalk.white(` • ${name}`)));
|
|
663
|
+
console.log();
|
|
664
|
+
const { confirmed } = await inquirer.prompt([
|
|
665
|
+
{
|
|
666
|
+
type: "confirm",
|
|
667
|
+
name: "confirmed",
|
|
668
|
+
message: chalk.red.bold("Are you ABSOLUTELY SURE you want to wipe these table rows?"),
|
|
669
|
+
default: false
|
|
670
|
+
}
|
|
671
|
+
]);
|
|
672
|
+
if (!confirmed) {
|
|
673
|
+
MessageFormatter.info("Wipe operation cancelled by user", { prefix: "Wipe" });
|
|
674
|
+
return;
|
|
675
|
+
}
|
|
676
|
+
// Step 5: Execute wipe using existing wipeTableRows function
|
|
677
|
+
MessageFormatter.progress("Starting table row wipe operation...", { prefix: "Wipe" });
|
|
678
|
+
for (const table of selectedTables) {
|
|
679
|
+
try {
|
|
680
|
+
MessageFormatter.info(`Wiping rows from table: ${table.name}`, { prefix: "Wipe" });
|
|
681
|
+
// Use existing wipeTableRows from wipeOperations.ts
|
|
682
|
+
await wipeTableRows(adapter, database.$id, table.$id);
|
|
683
|
+
MessageFormatter.success(`Successfully wiped rows from table: ${table.name}`, { prefix: "Wipe" });
|
|
684
|
+
}
|
|
685
|
+
catch (error) {
|
|
686
|
+
MessageFormatter.error(`Failed to wipe table ${table.name}`, error instanceof Error ? error : new Error(String(error)), { prefix: "Wipe" });
|
|
687
|
+
}
|
|
688
|
+
}
|
|
689
|
+
MessageFormatter.success(`Wipe operation completed for ${selectedTables.length} table(s)`, { prefix: "Wipe" });
|
|
690
|
+
}
|
|
691
|
+
catch (error) {
|
|
692
|
+
MessageFormatter.error("Table wipe operation failed", error instanceof Error ? error : new Error(String(error)), { prefix: "Wipe" });
|
|
693
|
+
throw error;
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
};
|