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
package/src/main.ts
CHANGED
|
@@ -1,32 +1,35 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import yargs from "yargs";
|
|
3
|
-
import { type ArgumentsCamelCase } from "yargs";
|
|
4
|
-
import { hideBin } from "yargs/helpers";
|
|
5
|
-
import { InteractiveCLI } from "./interactiveCLI.js";
|
|
6
|
-
import { UtilsController, type SetupOptions } from "./utilsController.js";
|
|
7
|
-
import type { TransferOptions } from "./migrations/transfer.js";
|
|
8
|
-
import { Databases, Storage, type Models } from "node-appwrite";
|
|
9
|
-
import { getClient } from "./utils/getClientFromConfig.js";
|
|
10
|
-
import { fetchAllDatabases } from "./databases/methods.js";
|
|
11
|
-
import { setupDirsFiles } from "./utils/setupFiles.js";
|
|
12
|
-
import { fetchAllCollections } from "./collections/methods.js";
|
|
13
|
-
import type { Specification } from "appwrite-utils";
|
|
14
|
-
import chalk from "chalk";
|
|
15
|
-
import { listSpecifications } from "./functions/methods.js";
|
|
16
|
-
import { MessageFormatter } from "./shared/messageFormatter.js";
|
|
17
|
-
import { ConfirmationDialogs } from "./shared/confirmationDialogs.js";
|
|
18
|
-
import
|
|
19
|
-
import
|
|
20
|
-
import {
|
|
21
|
-
import
|
|
22
|
-
import
|
|
23
|
-
import {
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import yargs from "yargs";
|
|
3
|
+
import { type ArgumentsCamelCase } from "yargs";
|
|
4
|
+
import { hideBin } from "yargs/helpers";
|
|
5
|
+
import { InteractiveCLI } from "./interactiveCLI.js";
|
|
6
|
+
import { UtilsController, type SetupOptions } from "./utilsController.js";
|
|
7
|
+
import type { TransferOptions } from "./migrations/transfer.js";
|
|
8
|
+
import { Databases, Storage, type Models } from "node-appwrite";
|
|
9
|
+
import { getClient } from "./utils/getClientFromConfig.js";
|
|
10
|
+
import { fetchAllDatabases } from "./databases/methods.js";
|
|
11
|
+
import { setupDirsFiles } from "./utils/setupFiles.js";
|
|
12
|
+
import { fetchAllCollections } from "./collections/methods.js";
|
|
13
|
+
import type { Specification } from "appwrite-utils";
|
|
14
|
+
import chalk from "chalk";
|
|
15
|
+
import { listSpecifications } from "./functions/methods.js";
|
|
16
|
+
import { MessageFormatter } from "./shared/messageFormatter.js";
|
|
17
|
+
import { ConfirmationDialogs } from "./shared/confirmationDialogs.js";
|
|
18
|
+
import { SelectionDialogs } from "./shared/selectionDialogs.js";
|
|
19
|
+
import { logger } from "./shared/logging.js";
|
|
20
|
+
import type { SyncSelectionSummary, DatabaseSelection, BucketSelection } from "./shared/selectionDialogs.js";
|
|
21
|
+
import path from "path";
|
|
22
|
+
import fs from "fs";
|
|
23
|
+
import { createRequire } from "node:module";
|
|
24
|
+
import { loadAppwriteProjectConfig, findAppwriteProjectConfig, projectConfigToAppwriteConfig } from "./utils/projectConfig.js";
|
|
25
|
+
import { hasSessionAuth, getAvailableSessions, getAuthenticationStatus } from "./utils/sessionAuth.js";
|
|
26
|
+
import { findYamlConfig, loadYamlConfigWithSession } from "./config/yamlConfig.js";
|
|
27
|
+
|
|
28
|
+
const require = createRequire(import.meta.url);
|
|
29
|
+
if (!(globalThis as any).require) {
|
|
30
|
+
(globalThis as any).require = require;
|
|
31
|
+
}
|
|
32
|
+
|
|
30
33
|
|
|
31
34
|
interface CliOptions {
|
|
32
35
|
config?: string;
|
|
@@ -72,10 +75,228 @@ interface CliOptions {
|
|
|
72
75
|
useSession?: boolean;
|
|
73
76
|
session?: string;
|
|
74
77
|
listBackups?: boolean;
|
|
78
|
+
autoSync?: boolean;
|
|
79
|
+
selectBuckets?: boolean;
|
|
75
80
|
}
|
|
76
81
|
|
|
77
82
|
type ParsedArgv = ArgumentsCamelCase<CliOptions>;
|
|
78
83
|
|
|
84
|
+
/**
|
|
85
|
+
* Enhanced sync function with intelligent configuration detection and selection dialogs
|
|
86
|
+
*/
|
|
87
|
+
async function performEnhancedSync(
|
|
88
|
+
controller: UtilsController,
|
|
89
|
+
parsedArgv: ParsedArgv
|
|
90
|
+
): Promise<SyncSelectionSummary | null> {
|
|
91
|
+
try {
|
|
92
|
+
MessageFormatter.banner("Enhanced Sync", "Intelligent configuration detection and selection");
|
|
93
|
+
|
|
94
|
+
if (!controller.config) {
|
|
95
|
+
MessageFormatter.error("No Appwrite configuration found", undefined, { prefix: "Sync" });
|
|
96
|
+
return null;
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Get all available databases from remote
|
|
100
|
+
const availableDatabases = await fetchAllDatabases(controller.database!);
|
|
101
|
+
if (availableDatabases.length === 0) {
|
|
102
|
+
MessageFormatter.warning("No databases found in remote project", { prefix: "Sync" });
|
|
103
|
+
return null;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Get existing configuration
|
|
107
|
+
const configuredDatabases = controller.config.databases || [];
|
|
108
|
+
const configuredBuckets = controller.config.buckets || [];
|
|
109
|
+
|
|
110
|
+
// Check if we have existing configuration
|
|
111
|
+
const hasExistingConfig = configuredDatabases.length > 0 || configuredBuckets.length > 0;
|
|
112
|
+
|
|
113
|
+
let syncExisting = false;
|
|
114
|
+
let modifyConfiguration = true;
|
|
115
|
+
|
|
116
|
+
if (hasExistingConfig) {
|
|
117
|
+
// Prompt about existing configuration
|
|
118
|
+
const response = await SelectionDialogs.promptForExistingConfig([
|
|
119
|
+
...configuredDatabases,
|
|
120
|
+
...configuredBuckets
|
|
121
|
+
]);
|
|
122
|
+
syncExisting = response.syncExisting;
|
|
123
|
+
modifyConfiguration = response.modifyConfiguration;
|
|
124
|
+
|
|
125
|
+
if (syncExisting && !modifyConfiguration) {
|
|
126
|
+
// Just sync existing configuration without changes
|
|
127
|
+
MessageFormatter.info("Syncing existing configuration without modifications", { prefix: "Sync" });
|
|
128
|
+
|
|
129
|
+
// Convert configured databases to DatabaseSelection format
|
|
130
|
+
const databaseSelections: DatabaseSelection[] = configuredDatabases.map(db => ({
|
|
131
|
+
databaseId: db.$id,
|
|
132
|
+
databaseName: db.name,
|
|
133
|
+
tableIds: [], // Tables will be populated from collections config
|
|
134
|
+
tableNames: [],
|
|
135
|
+
isNew: false
|
|
136
|
+
}));
|
|
137
|
+
|
|
138
|
+
// Convert configured buckets to BucketSelection format
|
|
139
|
+
const bucketSelections: BucketSelection[] = configuredBuckets.map(bucket => ({
|
|
140
|
+
bucketId: bucket.$id,
|
|
141
|
+
bucketName: bucket.name,
|
|
142
|
+
databaseId: undefined,
|
|
143
|
+
databaseName: undefined,
|
|
144
|
+
isNew: false
|
|
145
|
+
}));
|
|
146
|
+
|
|
147
|
+
const selectionSummary = SelectionDialogs.createSyncSelectionSummary(
|
|
148
|
+
databaseSelections,
|
|
149
|
+
bucketSelections
|
|
150
|
+
);
|
|
151
|
+
|
|
152
|
+
const confirmed = await SelectionDialogs.confirmSyncSelection(selectionSummary);
|
|
153
|
+
if (!confirmed) {
|
|
154
|
+
MessageFormatter.info("Sync operation cancelled by user", { prefix: "Sync" });
|
|
155
|
+
return null;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// Perform sync with existing configuration
|
|
159
|
+
await controller.selectiveSync(databaseSelections, bucketSelections);
|
|
160
|
+
return selectionSummary;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
if (!modifyConfiguration) {
|
|
165
|
+
MessageFormatter.info("No configuration changes requested", { prefix: "Sync" });
|
|
166
|
+
return null;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Allow new items selection based on user choice
|
|
170
|
+
const allowNewOnly = !syncExisting;
|
|
171
|
+
|
|
172
|
+
// Select databases
|
|
173
|
+
const selectedDatabaseIds = await SelectionDialogs.selectDatabases(
|
|
174
|
+
availableDatabases,
|
|
175
|
+
configuredDatabases,
|
|
176
|
+
{
|
|
177
|
+
showSelectAll: true,
|
|
178
|
+
allowNewOnly,
|
|
179
|
+
defaultSelected: syncExisting ? configuredDatabases.map(db => db.$id) : []
|
|
180
|
+
}
|
|
181
|
+
);
|
|
182
|
+
|
|
183
|
+
if (selectedDatabaseIds.length === 0) {
|
|
184
|
+
MessageFormatter.warning("No databases selected for sync", { prefix: "Sync" });
|
|
185
|
+
return null;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// For each selected database, get available tables and select them
|
|
189
|
+
const tableSelectionsMap = new Map<string, string[]>();
|
|
190
|
+
const availableTablesMap = new Map<string, any[]>();
|
|
191
|
+
|
|
192
|
+
for (const databaseId of selectedDatabaseIds) {
|
|
193
|
+
const database = availableDatabases.find(db => db.$id === databaseId)!;
|
|
194
|
+
|
|
195
|
+
SelectionDialogs.showProgress(`Fetching tables for database: ${database.name}`);
|
|
196
|
+
|
|
197
|
+
// Get available tables from remote
|
|
198
|
+
const availableTables = await fetchAllCollections(databaseId, controller.database!);
|
|
199
|
+
availableTablesMap.set(databaseId, availableTables);
|
|
200
|
+
|
|
201
|
+
// Get configured tables for this database
|
|
202
|
+
// Note: Collections are stored globally in the config, not per database
|
|
203
|
+
const configuredTables = controller.config.collections || [];
|
|
204
|
+
|
|
205
|
+
// Select tables for this database
|
|
206
|
+
const selectedTableIds = await SelectionDialogs.selectTablesForDatabase(
|
|
207
|
+
databaseId,
|
|
208
|
+
database.name,
|
|
209
|
+
availableTables,
|
|
210
|
+
configuredTables,
|
|
211
|
+
{
|
|
212
|
+
showSelectAll: true,
|
|
213
|
+
allowNewOnly,
|
|
214
|
+
defaultSelected: syncExisting ? configuredTables.map((t: any) => t.$id) : []
|
|
215
|
+
}
|
|
216
|
+
);
|
|
217
|
+
|
|
218
|
+
tableSelectionsMap.set(databaseId, selectedTableIds);
|
|
219
|
+
|
|
220
|
+
if (selectedTableIds.length === 0) {
|
|
221
|
+
MessageFormatter.warning(`No tables selected for database: ${database.name}`, { prefix: "Sync" });
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Select buckets
|
|
226
|
+
let selectedBucketIds: string[] = [];
|
|
227
|
+
|
|
228
|
+
// Get available buckets from remote
|
|
229
|
+
if (controller.storage) {
|
|
230
|
+
try {
|
|
231
|
+
// Note: We need to implement fetchAllBuckets or use storage.listBuckets
|
|
232
|
+
// For now, we'll use configured buckets as available
|
|
233
|
+
SelectionDialogs.showProgress("Fetching storage buckets...");
|
|
234
|
+
|
|
235
|
+
// Create a mock availableBuckets array - in real implementation,
|
|
236
|
+
// you'd fetch this from the Appwrite API
|
|
237
|
+
const availableBuckets = configuredBuckets; // Placeholder
|
|
238
|
+
|
|
239
|
+
selectedBucketIds = await SelectionDialogs.selectBucketsForDatabases(
|
|
240
|
+
selectedDatabaseIds,
|
|
241
|
+
availableBuckets,
|
|
242
|
+
configuredBuckets,
|
|
243
|
+
{
|
|
244
|
+
showSelectAll: true,
|
|
245
|
+
allowNewOnly: parsedArgv.selectBuckets ? false : allowNewOnly,
|
|
246
|
+
groupByDatabase: true,
|
|
247
|
+
defaultSelected: syncExisting ? configuredBuckets.map(b => b.$id) : []
|
|
248
|
+
}
|
|
249
|
+
);
|
|
250
|
+
} catch (error) {
|
|
251
|
+
MessageFormatter.warning("Could not fetch storage buckets", { prefix: "Sync" });
|
|
252
|
+
logger.warn("Failed to fetch buckets during sync", { error });
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Create selection objects
|
|
257
|
+
const databaseSelections = SelectionDialogs.createDatabaseSelection(
|
|
258
|
+
selectedDatabaseIds,
|
|
259
|
+
availableDatabases,
|
|
260
|
+
tableSelectionsMap,
|
|
261
|
+
configuredDatabases,
|
|
262
|
+
availableTablesMap
|
|
263
|
+
);
|
|
264
|
+
|
|
265
|
+
const bucketSelections = SelectionDialogs.createBucketSelection(
|
|
266
|
+
selectedBucketIds,
|
|
267
|
+
[], // availableBuckets - would be populated from API
|
|
268
|
+
configuredBuckets,
|
|
269
|
+
availableDatabases
|
|
270
|
+
);
|
|
271
|
+
|
|
272
|
+
// Show final confirmation
|
|
273
|
+
const selectionSummary = SelectionDialogs.createSyncSelectionSummary(
|
|
274
|
+
databaseSelections,
|
|
275
|
+
bucketSelections
|
|
276
|
+
);
|
|
277
|
+
|
|
278
|
+
const confirmed = await SelectionDialogs.confirmSyncSelection(selectionSummary);
|
|
279
|
+
if (!confirmed) {
|
|
280
|
+
MessageFormatter.info("Sync operation cancelled by user", { prefix: "Sync" });
|
|
281
|
+
return null;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Perform the selective sync
|
|
285
|
+
await controller.selectiveSync(databaseSelections, bucketSelections);
|
|
286
|
+
|
|
287
|
+
MessageFormatter.success("Enhanced sync completed successfully", { prefix: "Sync" });
|
|
288
|
+
return selectionSummary;
|
|
289
|
+
|
|
290
|
+
} catch (error) {
|
|
291
|
+
SelectionDialogs.showError("Enhanced sync failed", error instanceof Error ? error : new Error(String(error)));
|
|
292
|
+
return null;
|
|
293
|
+
}
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
/**
|
|
297
|
+
* Performs selective sync with the given database and bucket selections
|
|
298
|
+
*/
|
|
299
|
+
|
|
79
300
|
/**
|
|
80
301
|
* Checks if the migration from collections to tables should be allowed
|
|
81
302
|
* Returns an object with:
|
|
@@ -212,6 +433,15 @@ const argv = yargs(hideBin(process.argv))
|
|
|
212
433
|
description:
|
|
213
434
|
"Pull and synchronize your local config with the remote Appwrite project schema",
|
|
214
435
|
})
|
|
436
|
+
.option("autoSync", {
|
|
437
|
+
alias: ["auto"],
|
|
438
|
+
type: "boolean",
|
|
439
|
+
description: "Skip prompts and sync all databases, tables, and buckets (current behavior)"
|
|
440
|
+
})
|
|
441
|
+
.option("selectBuckets", {
|
|
442
|
+
type: "boolean",
|
|
443
|
+
description: "Force bucket selection dialog even if buckets are already configured"
|
|
444
|
+
})
|
|
215
445
|
.option("endpoint", {
|
|
216
446
|
type: "string",
|
|
217
447
|
description: "Set the Appwrite endpoint",
|
|
@@ -867,11 +1097,23 @@ async function main() {
|
|
|
867
1097
|
operationStats.pushedDatabases = databases.length;
|
|
868
1098
|
operationStats.pushedCollections = controller.config?.collections?.length || 0;
|
|
869
1099
|
} else if (parsedArgv.sync) {
|
|
870
|
-
// SYNC: Pull from remote
|
|
871
|
-
|
|
872
|
-
|
|
873
|
-
|
|
874
|
-
|
|
1100
|
+
// Enhanced SYNC: Pull from remote with intelligent configuration detection
|
|
1101
|
+
if (parsedArgv.autoSync) {
|
|
1102
|
+
// Legacy behavior: sync everything without prompts
|
|
1103
|
+
MessageFormatter.info("Using auto-sync mode (legacy behavior)", { prefix: "Sync" });
|
|
1104
|
+
const databases =
|
|
1105
|
+
options.databases || (await fetchAllDatabases(controller.database!));
|
|
1106
|
+
await controller.synchronizeConfigurations(databases);
|
|
1107
|
+
operationStats.syncedDatabases = databases.length;
|
|
1108
|
+
} else {
|
|
1109
|
+
// Enhanced sync flow with selection dialogs
|
|
1110
|
+
const syncResult = await performEnhancedSync(controller, parsedArgv);
|
|
1111
|
+
if (syncResult) {
|
|
1112
|
+
operationStats.syncedDatabases = syncResult.databases.length;
|
|
1113
|
+
operationStats.syncedCollections = syncResult.totalTables;
|
|
1114
|
+
operationStats.syncedBuckets = syncResult.buckets.length;
|
|
1115
|
+
}
|
|
1116
|
+
}
|
|
875
1117
|
}
|
|
876
1118
|
|
|
877
1119
|
if (options.generateSchemas) {
|