appwrite-utils-cli 1.7.6 → 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/adapters/DatabaseAdapter.d.ts +1 -0
- package/dist/adapters/LegacyAdapter.js +15 -3
- package/dist/adapters/TablesDBAdapter.js +15 -3
- package/dist/cli/commands/databaseCommands.js +90 -23
- package/dist/collections/wipeOperations.d.ts +2 -2
- package/dist/collections/wipeOperations.js +37 -139
- 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/adapters/DatabaseAdapter.ts +2 -1
- package/src/adapters/LegacyAdapter.ts +95 -82
- package/src/adapters/TablesDBAdapter.ts +62 -47
- package/src/cli/commands/databaseCommands.ts +134 -34
- package/src/collections/wipeOperations.ts +62 -224
- 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
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
|
}
|