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
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
# Selection Dialogs System
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
The `SelectionDialogs` class provides a comprehensive interactive selection system for the enhanced sync flow in the Appwrite Utils CLI. It enables users to select databases, tables/collections, and storage buckets with visual indicators for configured vs new items.
|
|
6
|
+
|
|
7
|
+
## Features
|
|
8
|
+
|
|
9
|
+
- **Visual Indicators**: ✅ for configured items, ○ for new items
|
|
10
|
+
- **Multi-Selection Support**: Checkbox-style selection with "Select All" functionality
|
|
11
|
+
- **Configuration Awareness**: Detects and highlights existing configurations
|
|
12
|
+
- **Grouped Display**: Organizes buckets by database for better context
|
|
13
|
+
- **Comprehensive Confirmation**: Shows detailed summary before sync execution
|
|
14
|
+
- **Graceful Error Handling**: Proper error messages and cancellation support
|
|
15
|
+
- **Type Safety**: Full TypeScript support with proper interfaces
|
|
16
|
+
|
|
17
|
+
## Main Functions
|
|
18
|
+
|
|
19
|
+
### `promptForExistingConfig(configuredItems: any[])`
|
|
20
|
+
Prompts user about existing configuration with options to:
|
|
21
|
+
- Sync existing configured items
|
|
22
|
+
- Add/remove items from configuration
|
|
23
|
+
|
|
24
|
+
### `selectDatabases(availableDatabases, configuredDatabases, options?)`
|
|
25
|
+
Interactive database selection with:
|
|
26
|
+
- Visual indicators for configured vs new databases
|
|
27
|
+
- "Select All" functionality
|
|
28
|
+
- Filtering options (new only, default selections)
|
|
29
|
+
|
|
30
|
+
### `selectTablesForDatabase(databaseId, databaseName, availableTables, configuredTables, options?)`
|
|
31
|
+
Table/collection selection for a specific database with:
|
|
32
|
+
- Database context display
|
|
33
|
+
- Table selection with indicators
|
|
34
|
+
- Multi-selection support
|
|
35
|
+
|
|
36
|
+
### `selectBucketsForDatabases(selectedDatabaseIds, availableBuckets, configuredBuckets, options?)`
|
|
37
|
+
Storage bucket selection with:
|
|
38
|
+
- Grouping by database
|
|
39
|
+
- Relevance filtering (only buckets for selected databases)
|
|
40
|
+
- Ungrouped/general storage support
|
|
41
|
+
|
|
42
|
+
### `confirmSyncSelection(selectionSummary: SyncSelectionSummary)`
|
|
43
|
+
Final confirmation dialog showing:
|
|
44
|
+
- Complete selection summary
|
|
45
|
+
- Statistics (total, new, existing items)
|
|
46
|
+
- Detailed breakdown by category
|
|
47
|
+
|
|
48
|
+
## Usage Example
|
|
49
|
+
|
|
50
|
+
```typescript
|
|
51
|
+
import { SelectionDialogs } from './shared/selectionDialogs.js';
|
|
52
|
+
import type { Models } from 'node-appwrite';
|
|
53
|
+
|
|
54
|
+
// 1. Check for existing configuration
|
|
55
|
+
const { syncExisting, modifyConfiguration } = await SelectionDialogs.promptForExistingConfig(configuredDatabases);
|
|
56
|
+
|
|
57
|
+
if (modifyConfiguration) {
|
|
58
|
+
// 2. Select databases
|
|
59
|
+
const selectedDatabaseIds = await SelectionDialogs.selectDatabases(
|
|
60
|
+
availableDatabases,
|
|
61
|
+
configuredDatabases,
|
|
62
|
+
{ showSelectAll: true }
|
|
63
|
+
);
|
|
64
|
+
|
|
65
|
+
// 3. Select tables for each database
|
|
66
|
+
const tableSelectionsMap = new Map<string, string[]>();
|
|
67
|
+
for (const databaseId of selectedDatabaseIds) {
|
|
68
|
+
const selectedTableIds = await SelectionDialogs.selectTablesForDatabase(
|
|
69
|
+
databaseId,
|
|
70
|
+
databaseName,
|
|
71
|
+
availableTables,
|
|
72
|
+
configuredTables
|
|
73
|
+
);
|
|
74
|
+
tableSelectionsMap.set(databaseId, selectedTableIds);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// 4. Select buckets
|
|
78
|
+
const selectedBucketIds = await SelectionDialogs.selectBucketsForDatabases(
|
|
79
|
+
selectedDatabaseIds,
|
|
80
|
+
availableBuckets,
|
|
81
|
+
configuredBuckets
|
|
82
|
+
);
|
|
83
|
+
|
|
84
|
+
// 5. Create selection summary and confirm
|
|
85
|
+
const selectionSummary = SelectionDialogs.createSyncSelectionSummary(
|
|
86
|
+
databaseSelections,
|
|
87
|
+
bucketSelections
|
|
88
|
+
);
|
|
89
|
+
|
|
90
|
+
const confirmed = await SelectionDialogs.confirmSyncSelection(selectionSummary);
|
|
91
|
+
|
|
92
|
+
if (confirmed) {
|
|
93
|
+
// Proceed with sync operation
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Configuration Options
|
|
99
|
+
|
|
100
|
+
### Database Selection Options
|
|
101
|
+
- `showSelectAll`: Show "Select All" option (default: true)
|
|
102
|
+
- `allowNewOnly`: Only show new/unconfigured databases (default: false)
|
|
103
|
+
- `defaultSelected`: Array of database IDs to pre-select
|
|
104
|
+
|
|
105
|
+
### Table Selection Options
|
|
106
|
+
- `showSelectAll`: Show "Select All" option (default: true)
|
|
107
|
+
- `allowNewOnly`: Only show new/unconfigured tables (default: false)
|
|
108
|
+
- `defaultSelected`: Array of table IDs to pre-select
|
|
109
|
+
- `showDatabaseContext`: Show database name in header (default: true)
|
|
110
|
+
|
|
111
|
+
### Bucket Selection Options
|
|
112
|
+
- `showSelectAll`: Show "Select All" option (default: true)
|
|
113
|
+
- `allowNewOnly`: Only show new/unconfigured buckets (default: false)
|
|
114
|
+
- `defaultSelected`: Array of bucket IDs to pre-select
|
|
115
|
+
- `groupByDatabase`: Group buckets by database (default: true)
|
|
116
|
+
|
|
117
|
+
## Interfaces
|
|
118
|
+
|
|
119
|
+
### `SyncSelectionSummary`
|
|
120
|
+
Contains complete selection information:
|
|
121
|
+
- `databases`: Array of selected databases with their tables
|
|
122
|
+
- `buckets`: Array of selected buckets
|
|
123
|
+
- `totalDatabases/Tables/Buckets`: Count of selected items
|
|
124
|
+
- `newItems/existingItems`: Breakdown of new vs existing configurations
|
|
125
|
+
|
|
126
|
+
### `DatabaseSelection`
|
|
127
|
+
Represents a selected database:
|
|
128
|
+
- `databaseId/databaseName`: Database identification
|
|
129
|
+
- `tableIds/tableNames`: Selected tables for this database
|
|
130
|
+
- `isNew`: Whether this is a new configuration
|
|
131
|
+
|
|
132
|
+
### `BucketSelection`
|
|
133
|
+
Represents a selected bucket:
|
|
134
|
+
- `bucketId/bucketName`: Bucket identification
|
|
135
|
+
- `databaseId/databaseName`: Associated database (if applicable)
|
|
136
|
+
- `isNew`: Whether this is a new configuration
|
|
137
|
+
|
|
138
|
+
## Integration
|
|
139
|
+
|
|
140
|
+
The selection dialogs are designed to integrate seamlessly with the existing CLI infrastructure:
|
|
141
|
+
|
|
142
|
+
- Uses `MessageFormatter` for consistent styling
|
|
143
|
+
- Integrates with existing logging system
|
|
144
|
+
- Follows established error handling patterns
|
|
145
|
+
- Compatible with existing configuration management
|
|
146
|
+
- Uses inquirer.js for interactive prompts
|
|
@@ -3,32 +3,95 @@ 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 { logger } from "../../shared/logging.js";
|
|
6
8
|
import { fetchAllDatabases } from "../../databases/methods.js";
|
|
7
9
|
import { listBuckets } from "../../storage/methods.js";
|
|
8
10
|
import { getFunction, downloadLatestFunctionDeployment } from "../../functions/methods.js";
|
|
9
11
|
export const databaseCommands = {
|
|
10
12
|
async syncDb(cli) {
|
|
11
13
|
MessageFormatter.progress("Pushing local configuration to Appwrite...", { prefix: "Database" });
|
|
12
|
-
const databases = await cli.selectDatabases(cli.getLocalDatabases(), chalk.blue("Select local databases to push:"), true);
|
|
13
|
-
if (!databases.length) {
|
|
14
|
-
MessageFormatter.warning("No databases selected. Skipping database sync.", { prefix: "Database" });
|
|
15
|
-
return;
|
|
16
|
-
}
|
|
17
14
|
try {
|
|
18
|
-
//
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
15
|
+
// Initialize controller
|
|
16
|
+
await cli.controller.init();
|
|
17
|
+
// Get available and configured databases
|
|
18
|
+
const availableDatabases = await fetchAllDatabases(cli.controller.database);
|
|
19
|
+
const configuredDatabases = cli.controller.config?.databases || [];
|
|
20
|
+
// Get local collections for selection
|
|
21
|
+
const localCollections = cli.getLocalCollections();
|
|
22
|
+
// Prompt about existing configuration
|
|
23
|
+
const { syncExisting, modifyConfiguration } = await SelectionDialogs.promptForExistingConfig(configuredDatabases);
|
|
24
|
+
// Select databases
|
|
25
|
+
const selectedDatabaseIds = await SelectionDialogs.selectDatabases(availableDatabases, configuredDatabases, { showSelectAll: true, allowNewOnly: !syncExisting });
|
|
26
|
+
if (selectedDatabaseIds.length === 0) {
|
|
27
|
+
MessageFormatter.warning("No databases selected. Skipping database sync.", { prefix: "Database" });
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
// Select tables/collections for each database using the existing method
|
|
31
|
+
const tableSelectionsMap = new Map();
|
|
32
|
+
const availableTablesMap = new Map();
|
|
33
|
+
for (const databaseId of selectedDatabaseIds) {
|
|
34
|
+
const database = availableDatabases.find(db => db.$id === databaseId);
|
|
35
|
+
// Use the existing selectCollectionsAndTables method
|
|
36
|
+
const selectedCollections = await cli.selectCollectionsAndTables(database, cli.controller.database, chalk.blue(`Select collections/tables to push to "${database.name}":`), true, // multiSelect
|
|
22
37
|
true // prefer local
|
|
23
38
|
);
|
|
24
|
-
|
|
39
|
+
// Map selected collections to table IDs
|
|
40
|
+
const selectedTableIds = selectedCollections.map((c) => c.$id || c.id);
|
|
41
|
+
// Store selections
|
|
42
|
+
tableSelectionsMap.set(databaseId, selectedTableIds);
|
|
43
|
+
availableTablesMap.set(databaseId, selectedCollections);
|
|
44
|
+
if (selectedCollections.length === 0) {
|
|
25
45
|
MessageFormatter.warning(`No collections selected for database "${database.name}". Skipping.`, { prefix: "Database" });
|
|
26
46
|
continue;
|
|
27
47
|
}
|
|
28
|
-
// Push selected collections to this specific database
|
|
29
|
-
await cli.controller.syncDb([database], collections);
|
|
30
|
-
MessageFormatter.success(`Pushed ${collections.length} collection(s) to database "${database.name}"`, { prefix: "Database" });
|
|
31
48
|
}
|
|
49
|
+
// Ask if user wants to select buckets
|
|
50
|
+
const { selectBuckets } = await inquirer.prompt([
|
|
51
|
+
{
|
|
52
|
+
type: "confirm",
|
|
53
|
+
name: "selectBuckets",
|
|
54
|
+
message: "Do you want to select storage buckets to sync as well?",
|
|
55
|
+
default: false,
|
|
56
|
+
},
|
|
57
|
+
]);
|
|
58
|
+
let bucketSelections = [];
|
|
59
|
+
if (selectBuckets) {
|
|
60
|
+
// Get available and configured buckets
|
|
61
|
+
try {
|
|
62
|
+
const availableBucketsResponse = await listBuckets(cli.controller.storage);
|
|
63
|
+
const availableBuckets = availableBucketsResponse.buckets || [];
|
|
64
|
+
const configuredBuckets = cli.controller.config?.buckets || [];
|
|
65
|
+
if (availableBuckets.length === 0) {
|
|
66
|
+
MessageFormatter.warning("No storage buckets available in remote instance.", { prefix: "Database" });
|
|
67
|
+
}
|
|
68
|
+
else {
|
|
69
|
+
// Select buckets using SelectionDialogs
|
|
70
|
+
const selectedBucketIds = await SelectionDialogs.selectBucketsForDatabases(selectedDatabaseIds, availableBuckets, configuredBuckets, { showSelectAll: true, groupByDatabase: true });
|
|
71
|
+
if (selectedBucketIds.length > 0) {
|
|
72
|
+
// Create BucketSelection objects
|
|
73
|
+
bucketSelections = SelectionDialogs.createBucketSelection(selectedBucketIds, availableBuckets, configuredBuckets, availableDatabases);
|
|
74
|
+
MessageFormatter.info(`Selected ${bucketSelections.length} storage bucket(s)`, { prefix: "Database" });
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
catch (error) {
|
|
79
|
+
MessageFormatter.warning("Failed to fetch storage buckets. Continuing with databases only.", { prefix: "Database" });
|
|
80
|
+
logger.warn("Storage bucket fetch failed during syncDb", { error: error instanceof Error ? error.message : String(error) });
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
// Create DatabaseSelection objects
|
|
84
|
+
const databaseSelections = SelectionDialogs.createDatabaseSelection(selectedDatabaseIds, availableDatabases, tableSelectionsMap, configuredDatabases, availableTablesMap);
|
|
85
|
+
// Show confirmation summary
|
|
86
|
+
const selectionSummary = SelectionDialogs.createSyncSelectionSummary(databaseSelections, bucketSelections);
|
|
87
|
+
const confirmed = await SelectionDialogs.confirmSyncSelection(selectionSummary);
|
|
88
|
+
if (!confirmed) {
|
|
89
|
+
MessageFormatter.info("Sync operation cancelled by user", { prefix: "Database" });
|
|
90
|
+
return;
|
|
91
|
+
}
|
|
92
|
+
// Perform selective sync using the controller
|
|
93
|
+
MessageFormatter.progress("Starting selective sync...", { prefix: "Database" });
|
|
94
|
+
await cli.controller.selectiveSync(databaseSelections, bucketSelections);
|
|
32
95
|
MessageFormatter.success("\n✅ All database configurations pushed successfully!", { prefix: "Database" });
|
|
33
96
|
// Then handle functions if requested
|
|
34
97
|
const { syncFunctions } = await inquirer.prompt([
|
|
@@ -73,19 +136,23 @@ export const databaseCommands = {
|
|
|
73
136
|
]);
|
|
74
137
|
if (syncDatabases) {
|
|
75
138
|
const remoteDatabases = await fetchAllDatabases(cli.controller.database);
|
|
76
|
-
//
|
|
77
|
-
MessageFormatter.progress("Pulling collections and generating collection files...", { prefix: "Collections" });
|
|
78
|
-
await cli.controller.synchronizeConfigurations(remoteDatabases);
|
|
79
|
-
// Also configure buckets for any new databases
|
|
139
|
+
// First, prepare the combined database list for bucket configuration
|
|
80
140
|
const localDatabases = cli.controller.config?.databases || [];
|
|
81
|
-
const
|
|
141
|
+
const allDatabases = [
|
|
142
|
+
...localDatabases,
|
|
143
|
+
...remoteDatabases.filter((rd) => !localDatabases.some((ld) => ld.name === rd.name)),
|
|
144
|
+
];
|
|
145
|
+
// Configure buckets FIRST to get user selections before writing config
|
|
146
|
+
MessageFormatter.progress("Configuring storage buckets...", { prefix: "Buckets" });
|
|
147
|
+
const configWithBuckets = await cli.configureBuckets({
|
|
82
148
|
...cli.controller.config,
|
|
83
|
-
databases:
|
|
84
|
-
...localDatabases,
|
|
85
|
-
...remoteDatabases.filter((rd) => !localDatabases.some((ld) => ld.name === rd.name)),
|
|
86
|
-
],
|
|
149
|
+
databases: allDatabases,
|
|
87
150
|
});
|
|
88
|
-
|
|
151
|
+
// Update controller config with bucket selections
|
|
152
|
+
cli.controller.config = configWithBuckets;
|
|
153
|
+
// Now synchronize configurations with the updated config that includes bucket selections
|
|
154
|
+
MessageFormatter.progress("Pulling collections and generating collection files...", { prefix: "Collections" });
|
|
155
|
+
await cli.controller.synchronizeConfigurations(remoteDatabases, configWithBuckets);
|
|
89
156
|
}
|
|
90
157
|
// Then sync functions
|
|
91
158
|
const { syncFunctions } = await inquirer.prompt([
|
package/dist/main.js
CHANGED
|
@@ -13,6 +13,8 @@ import chalk from "chalk";
|
|
|
13
13
|
import { listSpecifications } from "./functions/methods.js";
|
|
14
14
|
import { MessageFormatter } from "./shared/messageFormatter.js";
|
|
15
15
|
import { ConfirmationDialogs } from "./shared/confirmationDialogs.js";
|
|
16
|
+
import { SelectionDialogs } from "./shared/selectionDialogs.js";
|
|
17
|
+
import { logger } from "./shared/logging.js";
|
|
16
18
|
import path from "path";
|
|
17
19
|
import fs from "fs";
|
|
18
20
|
import { createRequire } from "node:module";
|
|
@@ -23,6 +25,153 @@ const require = createRequire(import.meta.url);
|
|
|
23
25
|
if (!globalThis.require) {
|
|
24
26
|
globalThis.require = require;
|
|
25
27
|
}
|
|
28
|
+
/**
|
|
29
|
+
* Enhanced sync function with intelligent configuration detection and selection dialogs
|
|
30
|
+
*/
|
|
31
|
+
async function performEnhancedSync(controller, parsedArgv) {
|
|
32
|
+
try {
|
|
33
|
+
MessageFormatter.banner("Enhanced Sync", "Intelligent configuration detection and selection");
|
|
34
|
+
if (!controller.config) {
|
|
35
|
+
MessageFormatter.error("No Appwrite configuration found", undefined, { prefix: "Sync" });
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
// Get all available databases from remote
|
|
39
|
+
const availableDatabases = await fetchAllDatabases(controller.database);
|
|
40
|
+
if (availableDatabases.length === 0) {
|
|
41
|
+
MessageFormatter.warning("No databases found in remote project", { prefix: "Sync" });
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
// Get existing configuration
|
|
45
|
+
const configuredDatabases = controller.config.databases || [];
|
|
46
|
+
const configuredBuckets = controller.config.buckets || [];
|
|
47
|
+
// Check if we have existing configuration
|
|
48
|
+
const hasExistingConfig = configuredDatabases.length > 0 || configuredBuckets.length > 0;
|
|
49
|
+
let syncExisting = false;
|
|
50
|
+
let modifyConfiguration = true;
|
|
51
|
+
if (hasExistingConfig) {
|
|
52
|
+
// Prompt about existing configuration
|
|
53
|
+
const response = await SelectionDialogs.promptForExistingConfig([
|
|
54
|
+
...configuredDatabases,
|
|
55
|
+
...configuredBuckets
|
|
56
|
+
]);
|
|
57
|
+
syncExisting = response.syncExisting;
|
|
58
|
+
modifyConfiguration = response.modifyConfiguration;
|
|
59
|
+
if (syncExisting && !modifyConfiguration) {
|
|
60
|
+
// Just sync existing configuration without changes
|
|
61
|
+
MessageFormatter.info("Syncing existing configuration without modifications", { prefix: "Sync" });
|
|
62
|
+
// Convert configured databases to DatabaseSelection format
|
|
63
|
+
const databaseSelections = configuredDatabases.map(db => ({
|
|
64
|
+
databaseId: db.$id,
|
|
65
|
+
databaseName: db.name,
|
|
66
|
+
tableIds: [], // Tables will be populated from collections config
|
|
67
|
+
tableNames: [],
|
|
68
|
+
isNew: false
|
|
69
|
+
}));
|
|
70
|
+
// Convert configured buckets to BucketSelection format
|
|
71
|
+
const bucketSelections = configuredBuckets.map(bucket => ({
|
|
72
|
+
bucketId: bucket.$id,
|
|
73
|
+
bucketName: bucket.name,
|
|
74
|
+
databaseId: undefined,
|
|
75
|
+
databaseName: undefined,
|
|
76
|
+
isNew: false
|
|
77
|
+
}));
|
|
78
|
+
const selectionSummary = SelectionDialogs.createSyncSelectionSummary(databaseSelections, bucketSelections);
|
|
79
|
+
const confirmed = await SelectionDialogs.confirmSyncSelection(selectionSummary);
|
|
80
|
+
if (!confirmed) {
|
|
81
|
+
MessageFormatter.info("Sync operation cancelled by user", { prefix: "Sync" });
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
// Perform sync with existing configuration
|
|
85
|
+
await controller.selectiveSync(databaseSelections, bucketSelections);
|
|
86
|
+
return selectionSummary;
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
if (!modifyConfiguration) {
|
|
90
|
+
MessageFormatter.info("No configuration changes requested", { prefix: "Sync" });
|
|
91
|
+
return null;
|
|
92
|
+
}
|
|
93
|
+
// Allow new items selection based on user choice
|
|
94
|
+
const allowNewOnly = !syncExisting;
|
|
95
|
+
// Select databases
|
|
96
|
+
const selectedDatabaseIds = await SelectionDialogs.selectDatabases(availableDatabases, configuredDatabases, {
|
|
97
|
+
showSelectAll: true,
|
|
98
|
+
allowNewOnly,
|
|
99
|
+
defaultSelected: syncExisting ? configuredDatabases.map(db => db.$id) : []
|
|
100
|
+
});
|
|
101
|
+
if (selectedDatabaseIds.length === 0) {
|
|
102
|
+
MessageFormatter.warning("No databases selected for sync", { prefix: "Sync" });
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
// For each selected database, get available tables and select them
|
|
106
|
+
const tableSelectionsMap = new Map();
|
|
107
|
+
const availableTablesMap = new Map();
|
|
108
|
+
for (const databaseId of selectedDatabaseIds) {
|
|
109
|
+
const database = availableDatabases.find(db => db.$id === databaseId);
|
|
110
|
+
SelectionDialogs.showProgress(`Fetching tables for database: ${database.name}`);
|
|
111
|
+
// Get available tables from remote
|
|
112
|
+
const availableTables = await fetchAllCollections(databaseId, controller.database);
|
|
113
|
+
availableTablesMap.set(databaseId, availableTables);
|
|
114
|
+
// Get configured tables for this database
|
|
115
|
+
// Note: Collections are stored globally in the config, not per database
|
|
116
|
+
const configuredTables = controller.config.collections || [];
|
|
117
|
+
// Select tables for this database
|
|
118
|
+
const selectedTableIds = await SelectionDialogs.selectTablesForDatabase(databaseId, database.name, availableTables, configuredTables, {
|
|
119
|
+
showSelectAll: true,
|
|
120
|
+
allowNewOnly,
|
|
121
|
+
defaultSelected: syncExisting ? configuredTables.map((t) => t.$id) : []
|
|
122
|
+
});
|
|
123
|
+
tableSelectionsMap.set(databaseId, selectedTableIds);
|
|
124
|
+
if (selectedTableIds.length === 0) {
|
|
125
|
+
MessageFormatter.warning(`No tables selected for database: ${database.name}`, { prefix: "Sync" });
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
// Select buckets
|
|
129
|
+
let selectedBucketIds = [];
|
|
130
|
+
// Get available buckets from remote
|
|
131
|
+
if (controller.storage) {
|
|
132
|
+
try {
|
|
133
|
+
// Note: We need to implement fetchAllBuckets or use storage.listBuckets
|
|
134
|
+
// For now, we'll use configured buckets as available
|
|
135
|
+
SelectionDialogs.showProgress("Fetching storage buckets...");
|
|
136
|
+
// Create a mock availableBuckets array - in real implementation,
|
|
137
|
+
// you'd fetch this from the Appwrite API
|
|
138
|
+
const availableBuckets = configuredBuckets; // Placeholder
|
|
139
|
+
selectedBucketIds = await SelectionDialogs.selectBucketsForDatabases(selectedDatabaseIds, availableBuckets, configuredBuckets, {
|
|
140
|
+
showSelectAll: true,
|
|
141
|
+
allowNewOnly: parsedArgv.selectBuckets ? false : allowNewOnly,
|
|
142
|
+
groupByDatabase: true,
|
|
143
|
+
defaultSelected: syncExisting ? configuredBuckets.map(b => b.$id) : []
|
|
144
|
+
});
|
|
145
|
+
}
|
|
146
|
+
catch (error) {
|
|
147
|
+
MessageFormatter.warning("Could not fetch storage buckets", { prefix: "Sync" });
|
|
148
|
+
logger.warn("Failed to fetch buckets during sync", { error });
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
// Create selection objects
|
|
152
|
+
const databaseSelections = SelectionDialogs.createDatabaseSelection(selectedDatabaseIds, availableDatabases, tableSelectionsMap, configuredDatabases, availableTablesMap);
|
|
153
|
+
const bucketSelections = SelectionDialogs.createBucketSelection(selectedBucketIds, [], // availableBuckets - would be populated from API
|
|
154
|
+
configuredBuckets, availableDatabases);
|
|
155
|
+
// Show final confirmation
|
|
156
|
+
const selectionSummary = SelectionDialogs.createSyncSelectionSummary(databaseSelections, bucketSelections);
|
|
157
|
+
const confirmed = await SelectionDialogs.confirmSyncSelection(selectionSummary);
|
|
158
|
+
if (!confirmed) {
|
|
159
|
+
MessageFormatter.info("Sync operation cancelled by user", { prefix: "Sync" });
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
// Perform the selective sync
|
|
163
|
+
await controller.selectiveSync(databaseSelections, bucketSelections);
|
|
164
|
+
MessageFormatter.success("Enhanced sync completed successfully", { prefix: "Sync" });
|
|
165
|
+
return selectionSummary;
|
|
166
|
+
}
|
|
167
|
+
catch (error) {
|
|
168
|
+
SelectionDialogs.showError("Enhanced sync failed", error instanceof Error ? error : new Error(String(error)));
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Performs selective sync with the given database and bucket selections
|
|
174
|
+
*/
|
|
26
175
|
/**
|
|
27
176
|
* Checks if the migration from collections to tables should be allowed
|
|
28
177
|
* Returns an object with:
|
|
@@ -143,6 +292,15 @@ const argv = yargs(hideBin(process.argv))
|
|
|
143
292
|
.option("sync", {
|
|
144
293
|
type: "boolean",
|
|
145
294
|
description: "Pull and synchronize your local config with the remote Appwrite project schema",
|
|
295
|
+
})
|
|
296
|
+
.option("autoSync", {
|
|
297
|
+
alias: ["auto"],
|
|
298
|
+
type: "boolean",
|
|
299
|
+
description: "Skip prompts and sync all databases, tables, and buckets (current behavior)"
|
|
300
|
+
})
|
|
301
|
+
.option("selectBuckets", {
|
|
302
|
+
type: "boolean",
|
|
303
|
+
description: "Force bucket selection dialog even if buckets are already configured"
|
|
146
304
|
})
|
|
147
305
|
.option("endpoint", {
|
|
148
306
|
type: "string",
|
|
@@ -711,10 +869,23 @@ async function main() {
|
|
|
711
869
|
operationStats.pushedCollections = controller.config?.collections?.length || 0;
|
|
712
870
|
}
|
|
713
871
|
else if (parsedArgv.sync) {
|
|
714
|
-
// SYNC: Pull from remote
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
872
|
+
// Enhanced SYNC: Pull from remote with intelligent configuration detection
|
|
873
|
+
if (parsedArgv.autoSync) {
|
|
874
|
+
// Legacy behavior: sync everything without prompts
|
|
875
|
+
MessageFormatter.info("Using auto-sync mode (legacy behavior)", { prefix: "Sync" });
|
|
876
|
+
const databases = options.databases || (await fetchAllDatabases(controller.database));
|
|
877
|
+
await controller.synchronizeConfigurations(databases);
|
|
878
|
+
operationStats.syncedDatabases = databases.length;
|
|
879
|
+
}
|
|
880
|
+
else {
|
|
881
|
+
// Enhanced sync flow with selection dialogs
|
|
882
|
+
const syncResult = await performEnhancedSync(controller, parsedArgv);
|
|
883
|
+
if (syncResult) {
|
|
884
|
+
operationStats.syncedDatabases = syncResult.databases.length;
|
|
885
|
+
operationStats.syncedCollections = syncResult.totalTables;
|
|
886
|
+
operationStats.syncedBuckets = syncResult.buckets.length;
|
|
887
|
+
}
|
|
888
|
+
}
|
|
718
889
|
}
|
|
719
890
|
if (options.generateSchemas) {
|
|
720
891
|
await controller.generateSchemas();
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { Storage, type Models } from "node-appwrite";
|
|
2
2
|
import { type AppwriteConfig } from "appwrite-utils";
|
|
3
|
+
import type { DatabaseAdapter } from "../adapters/DatabaseAdapter.js";
|
|
4
|
+
import type { DatabaseSelection, BucketSelection } from "../shared/selectionDialogs.js";
|
|
3
5
|
export declare class AppwriteToX {
|
|
4
6
|
config: AppwriteConfig;
|
|
5
7
|
storage: Storage;
|
|
@@ -102,7 +104,14 @@ export declare class AppwriteToX {
|
|
|
102
104
|
} | undefined;
|
|
103
105
|
})[]>;
|
|
104
106
|
appwriteFolderPath: string;
|
|
107
|
+
adapter?: DatabaseAdapter;
|
|
108
|
+
apiMode?: 'legacy' | 'tablesdb';
|
|
109
|
+
databaseApiModes: Map<string, "legacy" | "tablesdb">;
|
|
105
110
|
constructor(config: AppwriteConfig, appwriteFolderPath: string, storage: Storage);
|
|
111
|
+
/**
|
|
112
|
+
* Initialize adapter for database operations with API mode detection
|
|
113
|
+
*/
|
|
114
|
+
private initializeAdapter;
|
|
106
115
|
private ensureClientInitialized;
|
|
107
116
|
parsePermissionString: (permissionString: string) => {
|
|
108
117
|
permission: string;
|
|
@@ -116,6 +125,22 @@ export declare class AppwriteToX {
|
|
|
116
125
|
target: string;
|
|
117
126
|
})[];
|
|
118
127
|
updateCollectionConfigAttributes: (collection: Models.Collection) => void;
|
|
119
|
-
|
|
120
|
-
|
|
128
|
+
/**
|
|
129
|
+
* Fetch collections/tables using the appropriate adapter or legacy client
|
|
130
|
+
*/
|
|
131
|
+
private fetchCollectionsOrTables;
|
|
132
|
+
/**
|
|
133
|
+
* Get collection/table using the appropriate adapter or legacy client
|
|
134
|
+
*/
|
|
135
|
+
private getCollectionOrTable;
|
|
136
|
+
/**
|
|
137
|
+
* Detect API mode for a specific database by testing adapter capabilities
|
|
138
|
+
*/
|
|
139
|
+
private detectDatabaseApiMode;
|
|
140
|
+
/**
|
|
141
|
+
* Get API mode context for schema generation
|
|
142
|
+
*/
|
|
143
|
+
private getSchemaGeneratorApiContext;
|
|
144
|
+
appwriteSync(config: AppwriteConfig, databases?: Models.Database[], databaseSelections?: DatabaseSelection[], bucketSelections?: BucketSelection[]): Promise<void>;
|
|
145
|
+
toSchemas(databases?: Models.Database[], databaseSelections?: DatabaseSelection[], bucketSelections?: BucketSelection[]): Promise<void>;
|
|
121
146
|
}
|