appwrite-utils-cli 1.7.7 → 1.7.8
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/SELECTION_DIALOGS.md +146 -0
- package/dist/cli/commands/databaseCommands.js +90 -23
- package/dist/main.js +175 -4
- package/dist/migrations/appwriteToX.d.ts +27 -2
- package/dist/migrations/appwriteToX.js +293 -69
- package/dist/migrations/yaml/YamlImportConfigLoader.d.ts +1 -1
- package/dist/migrations/yaml/generateImportSchemas.js +23 -8
- package/dist/shared/schemaGenerator.js +25 -12
- package/dist/shared/selectionDialogs.d.ts +214 -0
- package/dist/shared/selectionDialogs.js +516 -0
- package/dist/utils/configDiscovery.d.ts +4 -4
- package/dist/utils/configDiscovery.js +66 -30
- package/dist/utils/yamlConverter.d.ts +1 -0
- package/dist/utils/yamlConverter.js +26 -3
- package/dist/utilsController.d.ts +6 -1
- package/dist/utilsController.js +91 -2
- package/package.json +1 -1
- package/src/cli/commands/databaseCommands.ts +134 -34
- package/src/main.ts +276 -34
- package/src/migrations/appwriteToX.ts +385 -90
- package/src/migrations/yaml/generateImportSchemas.ts +26 -8
- package/src/shared/schemaGenerator.ts +29 -12
- package/src/shared/selectionDialogs.ts +716 -0
- package/src/utils/configDiscovery.ts +83 -39
- package/src/utils/yamlConverter.ts +29 -3
- package/src/utilsController.ts +116 -4
|
@@ -15,9 +15,15 @@ export function collectionToYaml(collection, config = {
|
|
|
15
15
|
const yamlData = {
|
|
16
16
|
name: collection.name,
|
|
17
17
|
id: collection.$id,
|
|
18
|
-
documentSecurity: collection.documentSecurity,
|
|
19
18
|
enabled: collection.enabled,
|
|
20
19
|
};
|
|
20
|
+
// Use appropriate security field based on terminology
|
|
21
|
+
if (config.useTableTerminology) {
|
|
22
|
+
yamlData.rowSecurity = collection.documentSecurity;
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
yamlData.documentSecurity = collection.documentSecurity;
|
|
26
|
+
}
|
|
21
27
|
// Convert permissions
|
|
22
28
|
if (collection.$permissions && collection.$permissions.length > 0) {
|
|
23
29
|
yamlData.permissions = collection.$permissions.map(p => ({
|
|
@@ -158,6 +164,11 @@ export function normalizeYamlData(yamlData) {
|
|
|
158
164
|
columns: undefined
|
|
159
165
|
}));
|
|
160
166
|
}
|
|
167
|
+
// Normalize security fields - prefer documentSecurity for consistency
|
|
168
|
+
if (yamlData.rowSecurity !== undefined && yamlData.documentSecurity === undefined) {
|
|
169
|
+
normalized.documentSecurity = yamlData.rowSecurity;
|
|
170
|
+
delete normalized.rowSecurity;
|
|
171
|
+
}
|
|
161
172
|
return normalized;
|
|
162
173
|
}
|
|
163
174
|
/**
|
|
@@ -165,7 +176,8 @@ export function normalizeYamlData(yamlData) {
|
|
|
165
176
|
*/
|
|
166
177
|
export function usesTableTerminology(yamlData) {
|
|
167
178
|
return !!(yamlData.columns && yamlData.columns.length > 0) ||
|
|
168
|
-
!!(yamlData.indexes?.some(idx => !!idx.columns))
|
|
179
|
+
!!(yamlData.indexes?.some(idx => !!idx.columns)) ||
|
|
180
|
+
yamlData.rowSecurity !== undefined;
|
|
169
181
|
}
|
|
170
182
|
/**
|
|
171
183
|
* Converts between attribute and column terminology
|
|
@@ -190,6 +202,11 @@ export function convertTerminology(yamlData, toTableTerminology) {
|
|
|
190
202
|
attributes: idx.attributes // Keep both for compatibility
|
|
191
203
|
}));
|
|
192
204
|
}
|
|
205
|
+
// Convert security field
|
|
206
|
+
if (yamlData.documentSecurity !== undefined && yamlData.rowSecurity === undefined) {
|
|
207
|
+
converted.rowSecurity = yamlData.documentSecurity;
|
|
208
|
+
delete converted.documentSecurity;
|
|
209
|
+
}
|
|
193
210
|
return converted;
|
|
194
211
|
}
|
|
195
212
|
else {
|
|
@@ -272,7 +289,6 @@ export function generateYamlTemplate(entityName, config) {
|
|
|
272
289
|
const template = {
|
|
273
290
|
name: entityName,
|
|
274
291
|
id: entityName.toLowerCase().replace(/\s+/g, '_'),
|
|
275
|
-
documentSecurity: false,
|
|
276
292
|
enabled: true,
|
|
277
293
|
permissions: [
|
|
278
294
|
{
|
|
@@ -294,6 +310,13 @@ export function generateYamlTemplate(entityName, config) {
|
|
|
294
310
|
],
|
|
295
311
|
importDefs: []
|
|
296
312
|
};
|
|
313
|
+
// Use appropriate security field based on terminology
|
|
314
|
+
if (config.useTableTerminology) {
|
|
315
|
+
template.rowSecurity = false;
|
|
316
|
+
}
|
|
317
|
+
else {
|
|
318
|
+
template.documentSecurity = false;
|
|
319
|
+
}
|
|
297
320
|
// Assign fields with correct property name
|
|
298
321
|
template[fieldsKey] = fieldsArray;
|
|
299
322
|
template.indexes = indexesArray;
|
|
@@ -4,6 +4,7 @@ import { type AfterImportActions, type ConverterFunctions, type ValidationRules
|
|
|
4
4
|
import { type TransferOptions } from "./migrations/transfer.js";
|
|
5
5
|
import type { DatabaseAdapter } from './adapters/DatabaseAdapter.js';
|
|
6
6
|
import { type ValidationResult } from "./config/configValidation.js";
|
|
7
|
+
import type { DatabaseSelection, BucketSelection } from "./shared/selectionDialogs.js";
|
|
7
8
|
export interface SetupOptions {
|
|
8
9
|
databases?: Models.Database[];
|
|
9
10
|
collections?: string[];
|
|
@@ -60,6 +61,9 @@ export declare class UtilsController {
|
|
|
60
61
|
ensureDatabasesExist(databases?: Models.Database[]): Promise<void>;
|
|
61
62
|
ensureCollectionsExist(database: Models.Database, collections?: Models.Collection[]): Promise<void>;
|
|
62
63
|
getDatabasesByIds(ids: string[]): Promise<Models.Database[] | undefined>;
|
|
64
|
+
fetchAllBuckets(): Promise<{
|
|
65
|
+
buckets: Models.Bucket[];
|
|
66
|
+
}>;
|
|
63
67
|
wipeOtherDatabases(databasesToKeep: Models.Database[]): Promise<void>;
|
|
64
68
|
wipeUsers(): Promise<void>;
|
|
65
69
|
backupDatabase(database: Models.Database, format?: 'json' | 'zip'): Promise<void>;
|
|
@@ -78,7 +82,8 @@ export declare class UtilsController {
|
|
|
78
82
|
}[], collections?: Models.Collection[]): Promise<void>;
|
|
79
83
|
generateSchemas(): Promise<void>;
|
|
80
84
|
importData(options?: SetupOptions): Promise<void>;
|
|
81
|
-
synchronizeConfigurations(databases?: Models.Database[], config?: AppwriteConfig): Promise<void>;
|
|
85
|
+
synchronizeConfigurations(databases?: Models.Database[], config?: AppwriteConfig, databaseSelections?: DatabaseSelection[], bucketSelections?: BucketSelection[]): Promise<void>;
|
|
86
|
+
selectiveSync(databaseSelections: DatabaseSelection[], bucketSelections: BucketSelection[]): Promise<void>;
|
|
82
87
|
syncDb(databases?: Models.Database[], collections?: Models.Collection[]): Promise<void>;
|
|
83
88
|
getAppwriteFolderPath(): string | undefined;
|
|
84
89
|
transferData(options: TransferOptions): Promise<void>;
|
package/dist/utilsController.js
CHANGED
|
@@ -25,6 +25,7 @@ import { configureLogging, updateLogger, logger } from "./shared/logging.js";
|
|
|
25
25
|
import { MessageFormatter, Messages } from "./shared/messageFormatter.js";
|
|
26
26
|
import { SchemaGenerator } from "./shared/schemaGenerator.js";
|
|
27
27
|
import { findYamlConfig } from "./config/yamlConfig.js";
|
|
28
|
+
import { createImportSchemas } from "./migrations/yaml/generateImportSchemas.js";
|
|
28
29
|
import { validateCollectionsTablesConfig, reportValidationResults, validateWithStrictMode } from "./config/configValidation.js";
|
|
29
30
|
import { ConfigManager } from "./config/ConfigManager.js";
|
|
30
31
|
import { ClientFactory } from "./utils/ClientFactory.js";
|
|
@@ -225,6 +226,24 @@ export class UtilsController {
|
|
|
225
226
|
]);
|
|
226
227
|
return dbs.databases;
|
|
227
228
|
}
|
|
229
|
+
async fetchAllBuckets() {
|
|
230
|
+
await this.init();
|
|
231
|
+
if (!this.storage) {
|
|
232
|
+
MessageFormatter.warning("Storage not initialized - buckets will be empty", { prefix: "Controller" });
|
|
233
|
+
return { buckets: [] };
|
|
234
|
+
}
|
|
235
|
+
try {
|
|
236
|
+
const result = await this.storage.listBuckets([
|
|
237
|
+
Query.limit(1000) // Increase limit to get all buckets
|
|
238
|
+
]);
|
|
239
|
+
MessageFormatter.success(`Found ${result.buckets.length} buckets`, { prefix: "Controller" });
|
|
240
|
+
return result;
|
|
241
|
+
}
|
|
242
|
+
catch (error) {
|
|
243
|
+
MessageFormatter.error(`Failed to fetch buckets: ${error.message || error}`, error instanceof Error ? error : undefined, { prefix: "Controller" });
|
|
244
|
+
return { buckets: [] };
|
|
245
|
+
}
|
|
246
|
+
}
|
|
228
247
|
async wipeOtherDatabases(databasesToKeep) {
|
|
229
248
|
await this.init();
|
|
230
249
|
if (!this.database) {
|
|
@@ -448,7 +467,7 @@ export class UtilsController {
|
|
|
448
467
|
const importController = new ImportController(this.config, this.database, this.storage, this.appwriteFolderPath, importDataActions, options, options.databases);
|
|
449
468
|
await importController.run(options.collections);
|
|
450
469
|
}
|
|
451
|
-
async synchronizeConfigurations(databases, config) {
|
|
470
|
+
async synchronizeConfigurations(databases, config, databaseSelections, bucketSelections) {
|
|
452
471
|
await this.init();
|
|
453
472
|
if (!this.storage) {
|
|
454
473
|
MessageFormatter.error("Storage not initialized", undefined, { prefix: "Controller" });
|
|
@@ -463,8 +482,25 @@ export class UtilsController {
|
|
|
463
482
|
MessageFormatter.error("Failed to get appwriteFolderPath", undefined, { prefix: "Controller" });
|
|
464
483
|
return;
|
|
465
484
|
}
|
|
485
|
+
// If selections are provided, filter the databases accordingly
|
|
486
|
+
let filteredDatabases = databases;
|
|
487
|
+
if (databaseSelections && databaseSelections.length > 0) {
|
|
488
|
+
// Convert selections to Models.Database format
|
|
489
|
+
filteredDatabases = [];
|
|
490
|
+
const allDatabases = databases ? databases : await fetchAllDatabases(this.database);
|
|
491
|
+
for (const selection of databaseSelections) {
|
|
492
|
+
const database = allDatabases.find(db => db.$id === selection.databaseId);
|
|
493
|
+
if (database) {
|
|
494
|
+
filteredDatabases.push(database);
|
|
495
|
+
}
|
|
496
|
+
else {
|
|
497
|
+
MessageFormatter.warning(`Database with ID ${selection.databaseId} not found`, { prefix: "Controller" });
|
|
498
|
+
}
|
|
499
|
+
}
|
|
500
|
+
MessageFormatter.info(`Syncing ${filteredDatabases.length} selected databases out of ${allDatabases.length} available`, { prefix: "Controller" });
|
|
501
|
+
}
|
|
466
502
|
const appwriteToX = new AppwriteToX(configToUse, this.appwriteFolderPath, this.storage);
|
|
467
|
-
await appwriteToX.toSchemas(
|
|
503
|
+
await appwriteToX.toSchemas(filteredDatabases);
|
|
468
504
|
// Update the controller's config with the synchronized collections
|
|
469
505
|
this.config = appwriteToX.updatedConfig;
|
|
470
506
|
// Write the updated config back to disk
|
|
@@ -472,6 +508,59 @@ export class UtilsController {
|
|
|
472
508
|
const yamlConfigPath = findYamlConfig(this.appwriteFolderPath);
|
|
473
509
|
const isYamlProject = !!yamlConfigPath;
|
|
474
510
|
await generator.updateConfig(this.config, isYamlProject);
|
|
511
|
+
// Regenerate JSON schemas to reflect any table terminology fixes
|
|
512
|
+
try {
|
|
513
|
+
MessageFormatter.progress("Regenerating JSON schemas...", { prefix: "Sync" });
|
|
514
|
+
await createImportSchemas(this.appwriteFolderPath);
|
|
515
|
+
MessageFormatter.success("JSON schemas regenerated successfully", { prefix: "Sync" });
|
|
516
|
+
}
|
|
517
|
+
catch (error) {
|
|
518
|
+
// Log error but don't fail the sync process
|
|
519
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
520
|
+
MessageFormatter.warning(`Failed to regenerate JSON schemas, but sync completed: ${errorMessage}`, { prefix: "Sync" });
|
|
521
|
+
logger.warn("Schema regeneration failed during sync:", error);
|
|
522
|
+
}
|
|
523
|
+
}
|
|
524
|
+
async selectiveSync(databaseSelections, bucketSelections) {
|
|
525
|
+
await this.init();
|
|
526
|
+
if (!this.database) {
|
|
527
|
+
MessageFormatter.error("Database not initialized", undefined, { prefix: "Controller" });
|
|
528
|
+
return;
|
|
529
|
+
}
|
|
530
|
+
MessageFormatter.progress("Starting selective sync...", { prefix: "Controller" });
|
|
531
|
+
// Convert database selections to Models.Database format
|
|
532
|
+
const selectedDatabases = [];
|
|
533
|
+
for (const dbSelection of databaseSelections) {
|
|
534
|
+
// Get the full database object from the controller
|
|
535
|
+
const databases = await fetchAllDatabases(this.database);
|
|
536
|
+
const database = databases.find(db => db.$id === dbSelection.databaseId);
|
|
537
|
+
if (database) {
|
|
538
|
+
selectedDatabases.push(database);
|
|
539
|
+
MessageFormatter.info(`Selected database: ${database.name} (${database.$id})`, { prefix: "Controller" });
|
|
540
|
+
// Log selected tables for this database
|
|
541
|
+
if (dbSelection.tableIds && dbSelection.tableIds.length > 0) {
|
|
542
|
+
MessageFormatter.info(` Tables: ${dbSelection.tableIds.join(', ')}`, { prefix: "Controller" });
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
else {
|
|
546
|
+
MessageFormatter.warning(`Database with ID ${dbSelection.databaseId} not found`, { prefix: "Controller" });
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
if (selectedDatabases.length === 0) {
|
|
550
|
+
MessageFormatter.warning("No valid databases selected for sync", { prefix: "Controller" });
|
|
551
|
+
return;
|
|
552
|
+
}
|
|
553
|
+
// Log bucket selections if provided
|
|
554
|
+
if (bucketSelections && bucketSelections.length > 0) {
|
|
555
|
+
MessageFormatter.info(`Selected ${bucketSelections.length} buckets:`, { prefix: "Controller" });
|
|
556
|
+
for (const bucketSelection of bucketSelections) {
|
|
557
|
+
const dbInfo = bucketSelection.databaseId ? ` (DB: ${bucketSelection.databaseId})` : '';
|
|
558
|
+
MessageFormatter.info(` - ${bucketSelection.bucketName} (${bucketSelection.bucketId})${dbInfo}`, { prefix: "Controller" });
|
|
559
|
+
}
|
|
560
|
+
}
|
|
561
|
+
// Perform selective sync using the enhanced synchronizeConfigurations method
|
|
562
|
+
await this.synchronizeConfigurations(selectedDatabases, this.config, databaseSelections, bucketSelections);
|
|
563
|
+
MessageFormatter.success("Selective sync completed successfully!", { prefix: "Controller" });
|
|
475
564
|
}
|
|
476
565
|
async syncDb(databases = [], collections = []) {
|
|
477
566
|
await this.init();
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "appwrite-utils-cli",
|
|
3
3
|
"description": "Appwrite Utility Functions to help with database management, data conversion, data import, migrations, and much more. Meant to be used as a CLI tool, I do not recommend installing this in frontend environments.",
|
|
4
|
-
"version": "1.7.
|
|
4
|
+
"version": "1.7.8",
|
|
5
5
|
"main": "src/main.ts",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"repository": {
|
|
@@ -3,6 +3,9 @@ import chalk from "chalk";
|
|
|
3
3
|
import { join } from "node:path";
|
|
4
4
|
import { MessageFormatter } from "../../shared/messageFormatter.js";
|
|
5
5
|
import { ConfirmationDialogs } from "../../shared/confirmationDialogs.js";
|
|
6
|
+
import { SelectionDialogs } from "../../shared/selectionDialogs.js";
|
|
7
|
+
import type { DatabaseSelection, BucketSelection } from "../../shared/selectionDialogs.js";
|
|
8
|
+
import { logger } from "../../shared/logging.js";
|
|
6
9
|
import { fetchAllDatabases } from "../../databases/methods.js";
|
|
7
10
|
import { listBuckets } from "../../storage/methods.js";
|
|
8
11
|
import { getFunction, downloadLatestFunctionDeployment } from "../../functions/methods.js";
|
|
@@ -12,23 +15,41 @@ export const databaseCommands = {
|
|
|
12
15
|
async syncDb(cli: InteractiveCLI): Promise<void> {
|
|
13
16
|
MessageFormatter.progress("Pushing local configuration to Appwrite...", { prefix: "Database" });
|
|
14
17
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
true
|
|
19
|
-
);
|
|
18
|
+
try {
|
|
19
|
+
// Initialize controller
|
|
20
|
+
await (cli as any).controller!.init();
|
|
20
21
|
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
}
|
|
22
|
+
// Get available and configured databases
|
|
23
|
+
const availableDatabases = await fetchAllDatabases((cli as any).controller!.database!);
|
|
24
|
+
const configuredDatabases = (cli as any).controller!.config?.databases || [];
|
|
25
25
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
26
|
+
// Get local collections for selection
|
|
27
|
+
const localCollections = (cli as any).getLocalCollections();
|
|
28
|
+
|
|
29
|
+
// Prompt about existing configuration
|
|
30
|
+
const { syncExisting, modifyConfiguration } = await SelectionDialogs.promptForExistingConfig(configuredDatabases);
|
|
31
|
+
|
|
32
|
+
// Select databases
|
|
33
|
+
const selectedDatabaseIds = await SelectionDialogs.selectDatabases(
|
|
34
|
+
availableDatabases,
|
|
35
|
+
configuredDatabases,
|
|
36
|
+
{ showSelectAll: true, allowNewOnly: !syncExisting }
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
if (selectedDatabaseIds.length === 0) {
|
|
40
|
+
MessageFormatter.warning("No databases selected. Skipping database sync.", { prefix: "Database" });
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Select tables/collections for each database using the existing method
|
|
45
|
+
const tableSelectionsMap = new Map<string, string[]>();
|
|
46
|
+
const availableTablesMap = new Map<string, any[]>();
|
|
30
47
|
|
|
31
|
-
|
|
48
|
+
for (const databaseId of selectedDatabaseIds) {
|
|
49
|
+
const database = availableDatabases.find(db => db.$id === databaseId)!;
|
|
50
|
+
|
|
51
|
+
// Use the existing selectCollectionsAndTables method
|
|
52
|
+
const selectedCollections = await (cli as any).selectCollectionsAndTables(
|
|
32
53
|
database,
|
|
33
54
|
(cli as any).controller!.database!,
|
|
34
55
|
chalk.blue(`Select collections/tables to push to "${database.name}":`),
|
|
@@ -36,19 +57,93 @@ export const databaseCommands = {
|
|
|
36
57
|
true // prefer local
|
|
37
58
|
);
|
|
38
59
|
|
|
39
|
-
|
|
60
|
+
// Map selected collections to table IDs
|
|
61
|
+
const selectedTableIds = selectedCollections.map((c: any) => c.$id || c.id);
|
|
62
|
+
|
|
63
|
+
// Store selections
|
|
64
|
+
tableSelectionsMap.set(databaseId, selectedTableIds);
|
|
65
|
+
availableTablesMap.set(databaseId, selectedCollections);
|
|
66
|
+
|
|
67
|
+
if (selectedCollections.length === 0) {
|
|
40
68
|
MessageFormatter.warning(`No collections selected for database "${database.name}". Skipping.`, { prefix: "Database" });
|
|
41
69
|
continue;
|
|
42
70
|
}
|
|
71
|
+
}
|
|
43
72
|
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
73
|
+
// Ask if user wants to select buckets
|
|
74
|
+
const { selectBuckets } = await inquirer.prompt([
|
|
75
|
+
{
|
|
76
|
+
type: "confirm",
|
|
77
|
+
name: "selectBuckets",
|
|
78
|
+
message: "Do you want to select storage buckets to sync as well?",
|
|
79
|
+
default: false,
|
|
80
|
+
},
|
|
81
|
+
]);
|
|
82
|
+
|
|
83
|
+
let bucketSelections: BucketSelection[] = [];
|
|
84
|
+
|
|
85
|
+
if (selectBuckets) {
|
|
86
|
+
// Get available and configured buckets
|
|
87
|
+
try {
|
|
88
|
+
const availableBucketsResponse = await listBuckets((cli as any).controller!.storage!);
|
|
89
|
+
const availableBuckets = availableBucketsResponse.buckets || [];
|
|
90
|
+
const configuredBuckets = (cli as any).controller!.config?.buckets || [];
|
|
91
|
+
|
|
92
|
+
if (availableBuckets.length === 0) {
|
|
93
|
+
MessageFormatter.warning("No storage buckets available in remote instance.", { prefix: "Database" });
|
|
94
|
+
} else {
|
|
95
|
+
// Select buckets using SelectionDialogs
|
|
96
|
+
const selectedBucketIds = await SelectionDialogs.selectBucketsForDatabases(
|
|
97
|
+
selectedDatabaseIds,
|
|
98
|
+
availableBuckets,
|
|
99
|
+
configuredBuckets,
|
|
100
|
+
{ showSelectAll: true, groupByDatabase: true }
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
if (selectedBucketIds.length > 0) {
|
|
104
|
+
// Create BucketSelection objects
|
|
105
|
+
bucketSelections = SelectionDialogs.createBucketSelection(
|
|
106
|
+
selectedBucketIds,
|
|
107
|
+
availableBuckets,
|
|
108
|
+
configuredBuckets,
|
|
109
|
+
availableDatabases
|
|
110
|
+
);
|
|
111
|
+
|
|
112
|
+
MessageFormatter.info(`Selected ${bucketSelections.length} storage bucket(s)`, { prefix: "Database" });
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
} catch (error) {
|
|
116
|
+
MessageFormatter.warning("Failed to fetch storage buckets. Continuing with databases only.", { prefix: "Database" });
|
|
117
|
+
logger.warn("Storage bucket fetch failed during syncDb", { error: error instanceof Error ? error.message : String(error) });
|
|
118
|
+
}
|
|
50
119
|
}
|
|
51
120
|
|
|
121
|
+
// Create DatabaseSelection objects
|
|
122
|
+
const databaseSelections = SelectionDialogs.createDatabaseSelection(
|
|
123
|
+
selectedDatabaseIds,
|
|
124
|
+
availableDatabases,
|
|
125
|
+
tableSelectionsMap,
|
|
126
|
+
configuredDatabases,
|
|
127
|
+
availableTablesMap
|
|
128
|
+
);
|
|
129
|
+
|
|
130
|
+
// Show confirmation summary
|
|
131
|
+
const selectionSummary = SelectionDialogs.createSyncSelectionSummary(
|
|
132
|
+
databaseSelections,
|
|
133
|
+
bucketSelections
|
|
134
|
+
);
|
|
135
|
+
|
|
136
|
+
const confirmed = await SelectionDialogs.confirmSyncSelection(selectionSummary);
|
|
137
|
+
|
|
138
|
+
if (!confirmed) {
|
|
139
|
+
MessageFormatter.info("Sync operation cancelled by user", { prefix: "Database" });
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// Perform selective sync using the controller
|
|
144
|
+
MessageFormatter.progress("Starting selective sync...", { prefix: "Database" });
|
|
145
|
+
await (cli as any).controller!.selectiveSync(databaseSelections, bucketSelections);
|
|
146
|
+
|
|
52
147
|
MessageFormatter.success("\n✅ All database configurations pushed successfully!", { prefix: "Database" });
|
|
53
148
|
|
|
54
149
|
// Then handle functions if requested
|
|
@@ -104,23 +199,28 @@ export const databaseCommands = {
|
|
|
104
199
|
(cli as any).controller!.database!
|
|
105
200
|
);
|
|
106
201
|
|
|
107
|
-
//
|
|
108
|
-
MessageFormatter.progress("Pulling collections and generating collection files...", { prefix: "Collections" });
|
|
109
|
-
await (cli as any).controller!.synchronizeConfigurations(remoteDatabases);
|
|
110
|
-
|
|
111
|
-
// Also configure buckets for any new databases
|
|
202
|
+
// First, prepare the combined database list for bucket configuration
|
|
112
203
|
const localDatabases = (cli as any).controller!.config?.databases || [];
|
|
113
|
-
const
|
|
204
|
+
const allDatabases = [
|
|
205
|
+
...localDatabases,
|
|
206
|
+
...remoteDatabases.filter(
|
|
207
|
+
(rd: any) => !localDatabases.some((ld: any) => ld.name === rd.name)
|
|
208
|
+
),
|
|
209
|
+
];
|
|
210
|
+
|
|
211
|
+
// Configure buckets FIRST to get user selections before writing config
|
|
212
|
+
MessageFormatter.progress("Configuring storage buckets...", { prefix: "Buckets" });
|
|
213
|
+
const configWithBuckets = await (cli as any).configureBuckets({
|
|
114
214
|
...(cli as any).controller!.config!,
|
|
115
|
-
databases:
|
|
116
|
-
...localDatabases,
|
|
117
|
-
...remoteDatabases.filter(
|
|
118
|
-
(rd: any) => !localDatabases.some((ld: any) => ld.name === rd.name)
|
|
119
|
-
),
|
|
120
|
-
],
|
|
215
|
+
databases: allDatabases,
|
|
121
216
|
});
|
|
122
217
|
|
|
123
|
-
|
|
218
|
+
// Update controller config with bucket selections
|
|
219
|
+
(cli as any).controller!.config = configWithBuckets;
|
|
220
|
+
|
|
221
|
+
// Now synchronize configurations with the updated config that includes bucket selections
|
|
222
|
+
MessageFormatter.progress("Pulling collections and generating collection files...", { prefix: "Collections" });
|
|
223
|
+
await (cli as any).controller!.synchronizeConfigurations(remoteDatabases, configWithBuckets);
|
|
124
224
|
}
|
|
125
225
|
|
|
126
226
|
// Then sync functions
|