appwrite-utils-cli 1.5.1 → 1.6.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/CHANGELOG.md +199 -0
- package/README.md +251 -29
- package/dist/adapters/AdapterFactory.d.ts +10 -3
- package/dist/adapters/AdapterFactory.js +213 -17
- package/dist/adapters/TablesDBAdapter.js +60 -17
- 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 +160 -0
- package/dist/cli/commands/databaseCommands.d.ts +13 -0
- package/dist/cli/commands/databaseCommands.js +478 -0
- package/dist/cli/commands/functionCommands.d.ts +7 -0
- package/dist/cli/commands/functionCommands.js +289 -0
- package/dist/cli/commands/schemaCommands.d.ts +7 -0
- package/dist/cli/commands/schemaCommands.js +134 -0
- package/dist/cli/commands/transferCommands.d.ts +5 -0
- package/dist/cli/commands/transferCommands.js +384 -0
- package/dist/collections/attributes.d.ts +5 -4
- package/dist/collections/attributes.js +539 -246
- package/dist/collections/indexes.js +39 -37
- package/dist/collections/methods.d.ts +2 -16
- package/dist/collections/methods.js +90 -538
- package/dist/collections/transferOperations.d.ts +7 -0
- package/dist/collections/transferOperations.js +331 -0
- package/dist/collections/wipeOperations.d.ts +16 -0
- package/dist/collections/wipeOperations.js +328 -0
- package/dist/config/configMigration.d.ts +87 -0
- package/dist/config/configMigration.js +390 -0
- package/dist/config/configValidation.d.ts +66 -0
- package/dist/config/configValidation.js +358 -0
- package/dist/config/yamlConfig.d.ts +455 -1
- package/dist/config/yamlConfig.js +145 -52
- package/dist/databases/methods.js +3 -2
- package/dist/databases/setup.d.ts +1 -2
- package/dist/databases/setup.js +9 -87
- package/dist/examples/yamlTerminologyExample.d.ts +42 -0
- package/dist/examples/yamlTerminologyExample.js +269 -0
- package/dist/functions/deployments.js +11 -10
- package/dist/functions/methods.d.ts +1 -1
- package/dist/functions/methods.js +5 -4
- package/dist/init.js +9 -9
- package/dist/interactiveCLI.d.ts +8 -17
- package/dist/interactiveCLI.js +186 -1171
- package/dist/main.js +364 -21
- package/dist/migrations/afterImportActions.js +22 -30
- package/dist/migrations/appwriteToX.js +71 -25
- package/dist/migrations/dataLoader.js +35 -26
- package/dist/migrations/importController.js +29 -30
- package/dist/migrations/relationships.js +13 -12
- package/dist/migrations/services/ImportOrchestrator.js +16 -19
- package/dist/migrations/transfer.js +46 -46
- package/dist/migrations/yaml/YamlImportConfigLoader.d.ts +3 -1
- package/dist/migrations/yaml/YamlImportConfigLoader.js +6 -3
- package/dist/migrations/yaml/YamlImportIntegration.d.ts +9 -3
- package/dist/migrations/yaml/YamlImportIntegration.js +22 -11
- package/dist/migrations/yaml/generateImportSchemas.d.ts +14 -1
- package/dist/migrations/yaml/generateImportSchemas.js +736 -7
- package/dist/schemas/authUser.d.ts +1 -1
- package/dist/setupController.js +3 -2
- 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.js +15 -15
- package/dist/shared/errorUtils.d.ts +54 -0
- package/dist/shared/errorUtils.js +95 -0
- package/dist/shared/functionManager.js +20 -19
- package/dist/shared/indexManager.js +12 -11
- package/dist/shared/jsonSchemaGenerator.js +10 -26
- package/dist/shared/logging.d.ts +51 -0
- package/dist/shared/logging.js +70 -0
- package/dist/shared/messageFormatter.d.ts +2 -0
- package/dist/shared/messageFormatter.js +10 -0
- package/dist/shared/migrationHelpers.d.ts +6 -16
- package/dist/shared/migrationHelpers.js +24 -21
- package/dist/shared/operationLogger.d.ts +8 -1
- package/dist/shared/operationLogger.js +11 -24
- package/dist/shared/operationQueue.d.ts +28 -1
- package/dist/shared/operationQueue.js +268 -66
- package/dist/shared/operationsTable.d.ts +26 -0
- package/dist/shared/operationsTable.js +286 -0
- package/dist/shared/operationsTableSchema.d.ts +48 -0
- package/dist/shared/operationsTableSchema.js +35 -0
- package/dist/shared/relationshipExtractor.d.ts +56 -0
- package/dist/shared/relationshipExtractor.js +138 -0
- package/dist/shared/schemaGenerator.d.ts +19 -1
- package/dist/shared/schemaGenerator.js +56 -75
- package/dist/storage/backupCompression.d.ts +20 -0
- package/dist/storage/backupCompression.js +67 -0
- package/dist/storage/methods.d.ts +16 -2
- package/dist/storage/methods.js +98 -14
- package/dist/users/methods.js +9 -8
- package/dist/utils/configDiscovery.d.ts +78 -0
- package/dist/utils/configDiscovery.js +430 -0
- package/dist/utils/directoryUtils.d.ts +22 -0
- package/dist/utils/directoryUtils.js +59 -0
- package/dist/utils/getClientFromConfig.d.ts +17 -8
- package/dist/utils/getClientFromConfig.js +162 -17
- package/dist/utils/helperFunctions.d.ts +16 -2
- package/dist/utils/helperFunctions.js +19 -5
- package/dist/utils/loadConfigs.d.ts +34 -9
- package/dist/utils/loadConfigs.js +236 -316
- package/dist/utils/pathResolvers.d.ts +53 -0
- package/dist/utils/pathResolvers.js +72 -0
- package/dist/utils/projectConfig.d.ts +119 -0
- package/dist/utils/projectConfig.js +171 -0
- package/dist/utils/retryFailedPromises.js +4 -2
- package/dist/utils/sessionAuth.d.ts +48 -0
- package/dist/utils/sessionAuth.js +164 -0
- package/dist/utils/sessionPreservationExample.d.ts +1666 -0
- package/dist/utils/sessionPreservationExample.js +101 -0
- package/dist/utils/setupFiles.js +301 -41
- package/dist/utils/typeGuards.d.ts +35 -0
- package/dist/utils/typeGuards.js +57 -0
- package/dist/utils/versionDetection.js +145 -9
- package/dist/utils/yamlConverter.d.ts +53 -3
- package/dist/utils/yamlConverter.js +232 -13
- package/dist/utils/yamlLoader.d.ts +70 -0
- package/dist/utils/yamlLoader.js +263 -0
- package/dist/utilsController.d.ts +36 -3
- package/dist/utilsController.js +186 -56
- package/package.json +12 -2
- package/src/adapters/AdapterFactory.ts +263 -35
- package/src/adapters/TablesDBAdapter.ts +225 -36
- package/src/backups/operations/bucketBackup.ts +277 -0
- package/src/backups/operations/collectionBackup.ts +310 -0
- package/src/backups/operations/comprehensiveBackup.ts +342 -0
- package/src/backups/schemas/bucketManifest.ts +78 -0
- package/src/backups/schemas/comprehensiveManifest.ts +76 -0
- package/src/backups/tracking/centralizedTracking.ts +352 -0
- package/src/cli/commands/configCommands.ts +194 -0
- package/src/cli/commands/databaseCommands.ts +635 -0
- package/src/cli/commands/functionCommands.ts +379 -0
- package/src/cli/commands/schemaCommands.ts +163 -0
- package/src/cli/commands/transferCommands.ts +457 -0
- package/src/collections/attributes.ts +900 -621
- package/src/collections/attributes.ts.backup +1555 -0
- package/src/collections/indexes.ts +116 -114
- package/src/collections/methods.ts +295 -968
- package/src/collections/transferOperations.ts +516 -0
- package/src/collections/wipeOperations.ts +501 -0
- package/src/config/README.md +274 -0
- package/src/config/configMigration.ts +575 -0
- package/src/config/configValidation.ts +445 -0
- package/src/config/yamlConfig.ts +168 -55
- package/src/databases/methods.ts +3 -2
- package/src/databases/setup.ts +11 -138
- package/src/examples/yamlTerminologyExample.ts +341 -0
- package/src/functions/deployments.ts +14 -12
- package/src/functions/methods.ts +11 -11
- package/src/functions/templates/hono-typescript/README.md +286 -0
- package/src/functions/templates/hono-typescript/package.json +26 -0
- package/src/functions/templates/hono-typescript/src/adapters/request.ts +74 -0
- package/src/functions/templates/hono-typescript/src/adapters/response.ts +106 -0
- package/src/functions/templates/hono-typescript/src/app.ts +180 -0
- package/src/functions/templates/hono-typescript/src/context.ts +103 -0
- package/src/functions/templates/hono-typescript/src/index.ts +54 -0
- package/src/functions/templates/hono-typescript/src/middleware/appwrite.ts +119 -0
- package/src/functions/templates/hono-typescript/tsconfig.json +20 -0
- package/src/functions/templates/typescript-node/package.json +2 -1
- package/src/functions/templates/typescript-node/src/context.ts +103 -0
- package/src/functions/templates/typescript-node/src/index.ts +18 -12
- package/src/functions/templates/uv/pyproject.toml +1 -0
- package/src/functions/templates/uv/src/context.py +125 -0
- package/src/functions/templates/uv/src/index.py +35 -5
- package/src/init.ts +9 -11
- package/src/interactiveCLI.ts +276 -1591
- package/src/main.ts +418 -24
- package/src/migrations/afterImportActions.ts +71 -44
- package/src/migrations/appwriteToX.ts +100 -34
- package/src/migrations/dataLoader.ts +48 -34
- package/src/migrations/importController.ts +44 -39
- package/src/migrations/relationships.ts +28 -18
- package/src/migrations/services/ImportOrchestrator.ts +24 -27
- package/src/migrations/transfer.ts +159 -121
- package/src/migrations/yaml/YamlImportConfigLoader.ts +11 -4
- package/src/migrations/yaml/YamlImportIntegration.ts +47 -20
- package/src/migrations/yaml/generateImportSchemas.ts +751 -12
- package/src/setupController.ts +3 -2
- package/src/shared/backupMetadataSchema.ts +93 -0
- package/src/shared/backupTracking.ts +211 -0
- package/src/shared/confirmationDialogs.ts +19 -19
- package/src/shared/errorUtils.ts +110 -0
- package/src/shared/functionManager.ts +21 -20
- package/src/shared/indexManager.ts +12 -11
- package/src/shared/jsonSchemaGenerator.ts +38 -52
- package/src/shared/logging.ts +75 -0
- package/src/shared/messageFormatter.ts +14 -1
- package/src/shared/migrationHelpers.ts +45 -38
- package/src/shared/operationLogger.ts +11 -36
- package/src/shared/operationQueue.ts +322 -93
- package/src/shared/operationsTable.ts +338 -0
- package/src/shared/operationsTableSchema.ts +60 -0
- package/src/shared/relationshipExtractor.ts +214 -0
- package/src/shared/schemaGenerator.ts +179 -219
- package/src/storage/backupCompression.ts +88 -0
- package/src/storage/methods.ts +131 -34
- package/src/users/methods.ts +11 -9
- package/src/utils/configDiscovery.ts +502 -0
- package/src/utils/directoryUtils.ts +61 -0
- package/src/utils/getClientFromConfig.ts +205 -22
- package/src/utils/helperFunctions.ts +23 -5
- package/src/utils/loadConfigs.ts +313 -345
- package/src/utils/pathResolvers.ts +81 -0
- package/src/utils/projectConfig.ts +299 -0
- package/src/utils/retryFailedPromises.ts +4 -2
- package/src/utils/sessionAuth.ts +230 -0
- package/src/utils/setupFiles.ts +322 -54
- package/src/utils/typeGuards.ts +65 -0
- package/src/utils/versionDetection.ts +218 -64
- package/src/utils/yamlConverter.ts +296 -13
- package/src/utils/yamlLoader.ts +364 -0
- package/src/utilsController.ts +314 -110
- package/tests/README.md +497 -0
- package/tests/adapters/AdapterFactory.test.ts +277 -0
- package/tests/integration/syncOperations.test.ts +463 -0
- package/tests/jest.config.js +25 -0
- package/tests/migration/configMigration.test.ts +546 -0
- package/tests/setup.ts +62 -0
- package/tests/testUtils.ts +340 -0
- package/tests/utils/loadConfigs.test.ts +350 -0
- package/tests/validation/configValidation.test.ts +412 -0
- package/src/utils/schemaStrings.ts +0 -517
package/src/utilsController.ts
CHANGED
@@ -22,23 +22,21 @@ import { AppwriteToX } from "./migrations/appwriteToX.js";
|
|
22
22
|
import { ImportController } from "./migrations/importController.js";
|
23
23
|
import { ImportDataActions } from "./migrations/importDataActions.js";
|
24
24
|
import {
|
25
|
-
setupMigrationDatabase,
|
26
25
|
ensureDatabasesExist,
|
27
26
|
wipeOtherDatabases,
|
28
27
|
ensureCollectionsExist,
|
29
28
|
} from "./databases/setup.js";
|
30
|
-
import {
|
31
|
-
createOrUpdateCollections,
|
32
|
-
wipeDatabase,
|
33
|
-
generateSchemas,
|
34
|
-
fetchAllCollections,
|
35
|
-
wipeCollection,
|
36
|
-
} from "./collections/methods.js";
|
37
|
-
import { wipeAllTables, wipeTableRows } from "./collections/methods.js";
|
29
|
+
import {
|
30
|
+
createOrUpdateCollections,
|
31
|
+
wipeDatabase,
|
32
|
+
generateSchemas,
|
33
|
+
fetchAllCollections,
|
34
|
+
wipeCollection,
|
35
|
+
} from "./collections/methods.js";
|
36
|
+
import { wipeAllTables, wipeTableRows } from "./collections/methods.js";
|
38
37
|
import {
|
39
38
|
backupDatabase,
|
40
39
|
ensureDatabaseConfigBucketsExist,
|
41
|
-
initOrGetBackupStorage,
|
42
40
|
wipeDocumentStorage,
|
43
41
|
} from "./storage/methods.js";
|
44
42
|
import path from "path";
|
@@ -58,8 +56,11 @@ import {
|
|
58
56
|
transferUsersLocalToRemote,
|
59
57
|
type TransferOptions,
|
60
58
|
} from "./migrations/transfer.js";
|
61
|
-
import { getClient } from "./utils/getClientFromConfig.js";
|
62
|
-
import { getAdapterFromConfig } from "./utils/getClientFromConfig.js";
|
59
|
+
import { getClient, getClientWithAuth } from "./utils/getClientFromConfig.js";
|
60
|
+
import { getAdapterFromConfig } from "./utils/getClientFromConfig.js";
|
61
|
+
import type { DatabaseAdapter } from './adapters/DatabaseAdapter.js';
|
62
|
+
import { hasSessionAuth, findSessionByEndpointAndProject, isValidSessionCookie, type SessionAuthInfo } from "./utils/sessionAuth.js";
|
63
|
+
import { createSessionPreservation, type SessionPreservationOptions } from "./utils/loadConfigs.js";
|
63
64
|
import { fetchAllDatabases } from "./databases/methods.js";
|
64
65
|
import {
|
65
66
|
listFunctions,
|
@@ -72,6 +73,12 @@ import { configureLogging, updateLogger } from "./shared/logging.js";
|
|
72
73
|
import { MessageFormatter, Messages } from "./shared/messageFormatter.js";
|
73
74
|
import { SchemaGenerator } from "./shared/schemaGenerator.js";
|
74
75
|
import { findYamlConfig } from "./config/yamlConfig.js";
|
76
|
+
import {
|
77
|
+
validateCollectionsTablesConfig,
|
78
|
+
reportValidationResults,
|
79
|
+
validateWithStrictMode,
|
80
|
+
type ValidationResult
|
81
|
+
} from "./config/configValidation.js";
|
75
82
|
|
76
83
|
export interface SetupOptions {
|
77
84
|
databases?: Models.Database[];
|
@@ -95,10 +102,19 @@ export class UtilsController {
|
|
95
102
|
public appwriteServer?: Client;
|
96
103
|
public database?: Databases;
|
97
104
|
public storage?: Storage;
|
105
|
+
public adapter?: DatabaseAdapter;
|
98
106
|
public converterDefinitions: ConverterFunctions = converterFunctions;
|
99
107
|
public validityRuleDefinitions: ValidationRules = validationRules;
|
100
108
|
public afterImportActionsDefinitions: AfterImportActions = afterImportActions;
|
101
109
|
|
110
|
+
// Session preservation fields
|
111
|
+
private sessionCookie?: string;
|
112
|
+
private authMethod?: "session" | "apikey" | "auto";
|
113
|
+
private sessionMetadata?: {
|
114
|
+
email?: string;
|
115
|
+
expiresAt?: string;
|
116
|
+
};
|
117
|
+
|
102
118
|
constructor(
|
103
119
|
currentUserDir: string,
|
104
120
|
directConfig?: {
|
@@ -119,9 +135,21 @@ export class UtilsController {
|
|
119
135
|
MessageFormatter.error("Appwrite project is required", undefined, { prefix: "Config" });
|
120
136
|
hasErrors = true;
|
121
137
|
}
|
122
|
-
|
123
|
-
|
138
|
+
// Check authentication: either API key or session auth is required
|
139
|
+
const hasValidSession = directConfig.appwriteEndpoint && directConfig.appwriteProject &&
|
140
|
+
hasSessionAuth(directConfig.appwriteEndpoint, directConfig.appwriteProject);
|
141
|
+
|
142
|
+
if (!directConfig.appwriteKey && !hasValidSession) {
|
143
|
+
MessageFormatter.error(
|
144
|
+
"Authentication required: provide an API key or login with 'appwrite login'",
|
145
|
+
undefined,
|
146
|
+
{ prefix: "Config" }
|
147
|
+
);
|
124
148
|
hasErrors = true;
|
149
|
+
} else if (!directConfig.appwriteKey && hasValidSession) {
|
150
|
+
MessageFormatter.info("Using session authentication (no API key required)", { prefix: "Auth" });
|
151
|
+
} else if (directConfig.appwriteKey && hasValidSession) {
|
152
|
+
MessageFormatter.info("API key provided, session authentication also available", { prefix: "Auth" });
|
125
153
|
}
|
126
154
|
if (!hasErrors) {
|
127
155
|
// Only set config if we have all required fields
|
@@ -129,9 +157,10 @@ export class UtilsController {
|
|
129
157
|
this.config = {
|
130
158
|
appwriteEndpoint: directConfig.appwriteEndpoint!,
|
131
159
|
appwriteProject: directConfig.appwriteProject!,
|
132
|
-
appwriteKey: directConfig.appwriteKey
|
160
|
+
appwriteKey: directConfig.appwriteKey || "",
|
133
161
|
appwriteClient: null,
|
134
162
|
apiMode: "auto", // Default to auto-detect for dual API support
|
163
|
+
authMethod: "auto", // Default to auto-detect authentication method
|
135
164
|
enableBackups: false,
|
136
165
|
backupInterval: 0,
|
137
166
|
backupRetention: 0,
|
@@ -139,7 +168,6 @@ export class UtilsController {
|
|
139
168
|
enableMockData: false,
|
140
169
|
documentBucketId: "",
|
141
170
|
usersCollectionName: "",
|
142
|
-
useMigrations: true,
|
143
171
|
databases: [],
|
144
172
|
buckets: [],
|
145
173
|
functions: [],
|
@@ -165,16 +193,31 @@ export class UtilsController {
|
|
165
193
|
}
|
166
194
|
}
|
167
195
|
|
168
|
-
async init() {
|
196
|
+
async init(options: { validate?: boolean; strictMode?: boolean; useSession?: boolean; sessionCookie?: string } = {}) {
|
197
|
+
const { validate = false, strictMode = false, useSession = false, sessionCookie } = options;
|
198
|
+
|
169
199
|
if (!this.config) {
|
170
200
|
if (this.appwriteFolderPath && this.appwriteConfigPath) {
|
171
201
|
MessageFormatter.progress("Loading config from file...", { prefix: "Config" });
|
172
202
|
try {
|
173
|
-
const { config, actualConfigPath } = await loadConfigWithPath(
|
203
|
+
const { config, actualConfigPath, validation } = await loadConfigWithPath(
|
204
|
+
this.appwriteFolderPath,
|
205
|
+
{ validate, strictMode, reportValidation: false }
|
206
|
+
);
|
174
207
|
this.config = config;
|
175
208
|
MessageFormatter.info(`Loaded config from: ${actualConfigPath}`, { prefix: "Config" });
|
209
|
+
|
210
|
+
// Report validation results if validation was requested
|
211
|
+
if (validation && validate) {
|
212
|
+
reportValidationResults(validation, { verbose: false });
|
213
|
+
|
214
|
+
// In strict mode, throw if validation fails
|
215
|
+
if (strictMode && !validation.isValid) {
|
216
|
+
throw new Error(`Configuration validation failed in strict mode. Found ${validation.errors.length} validation errors.`);
|
217
|
+
}
|
218
|
+
}
|
176
219
|
} catch (error) {
|
177
|
-
MessageFormatter.error("Failed to load config from file", undefined, { prefix: "Config" });
|
220
|
+
MessageFormatter.error("Failed to load config from file", error instanceof Error ? error : undefined, { prefix: "Config" });
|
178
221
|
return;
|
179
222
|
}
|
180
223
|
} else {
|
@@ -189,15 +232,41 @@ export class UtilsController {
|
|
189
232
|
updateLogger();
|
190
233
|
}
|
191
234
|
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
235
|
+
// Use enhanced client with session authentication support
|
236
|
+
// Pass session cookie from options if provided
|
237
|
+
const clientSessionCookie = sessionCookie || this.sessionCookie;
|
238
|
+
this.appwriteServer = getClientWithAuth(
|
239
|
+
this.config.appwriteEndpoint,
|
240
|
+
this.config.appwriteProject,
|
241
|
+
this.config.appwriteKey || undefined,
|
242
|
+
clientSessionCookie
|
243
|
+
);
|
197
244
|
|
198
245
|
this.database = new Databases(this.appwriteServer);
|
199
246
|
this.storage = new Storage(this.appwriteServer);
|
200
247
|
this.config.appwriteClient = this.appwriteServer;
|
248
|
+
|
249
|
+
// Initialize adapter with version detection
|
250
|
+
try {
|
251
|
+
const { adapter, apiMode } = await getAdapterFromConfig(
|
252
|
+
this.config,
|
253
|
+
false,
|
254
|
+
clientSessionCookie
|
255
|
+
);
|
256
|
+
this.adapter = adapter;
|
257
|
+
|
258
|
+
MessageFormatter.info(`Database adapter initialized (apiMode: ${apiMode})`, {
|
259
|
+
prefix: "Adapter"
|
260
|
+
});
|
261
|
+
} catch (error) {
|
262
|
+
MessageFormatter.warning(
|
263
|
+
'Database adapter initialization failed - some features may not work',
|
264
|
+
{ prefix: "Adapter" }
|
265
|
+
);
|
266
|
+
}
|
267
|
+
|
268
|
+
// Extract and store session information after successful authentication
|
269
|
+
this.extractSessionInfo();
|
201
270
|
}
|
202
271
|
|
203
272
|
async reloadConfig() {
|
@@ -205,9 +274,15 @@ export class UtilsController {
|
|
205
274
|
MessageFormatter.error("Failed to get appwriteFolderPath", undefined, { prefix: "Controller" });
|
206
275
|
return;
|
207
276
|
}
|
208
|
-
|
277
|
+
|
278
|
+
// Preserve session authentication during config reload
|
279
|
+
const preserveAuth = this.createSessionPreservationOptions();
|
280
|
+
|
281
|
+
this.config = await loadConfig(this.appwriteFolderPath, {
|
282
|
+
preserveAuth
|
283
|
+
});
|
209
284
|
if (!this.config) {
|
210
|
-
|
285
|
+
MessageFormatter.error("Failed to load config", undefined, { prefix: "Controller" });
|
211
286
|
return;
|
212
287
|
}
|
213
288
|
|
@@ -217,25 +292,41 @@ export class UtilsController {
|
|
217
292
|
updateLogger();
|
218
293
|
}
|
219
294
|
|
220
|
-
|
221
|
-
this.appwriteServer
|
222
|
-
|
223
|
-
|
224
|
-
|
295
|
+
// Use enhanced client with session authentication support, passing preserved session
|
296
|
+
this.appwriteServer = getClientWithAuth(
|
297
|
+
this.config.appwriteEndpoint,
|
298
|
+
this.config.appwriteProject,
|
299
|
+
this.config.appwriteKey || undefined,
|
300
|
+
this.sessionCookie
|
301
|
+
);
|
225
302
|
this.database = new Databases(this.appwriteServer);
|
226
303
|
this.storage = new Storage(this.appwriteServer);
|
227
304
|
this.config.appwriteClient = this.appwriteServer;
|
228
|
-
}
|
229
305
|
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
306
|
+
// Re-initialize adapter with version detection after config reload
|
307
|
+
try {
|
308
|
+
const { adapter, apiMode } = await getAdapterFromConfig(
|
309
|
+
this.config,
|
310
|
+
false,
|
311
|
+
this.sessionCookie
|
312
|
+
);
|
313
|
+
this.adapter = adapter;
|
314
|
+
|
315
|
+
MessageFormatter.info(`Database adapter re-initialized (apiMode: ${apiMode})`, {
|
316
|
+
prefix: "Adapter"
|
317
|
+
});
|
318
|
+
} catch (error) {
|
319
|
+
MessageFormatter.warning(
|
320
|
+
'Database adapter re-initialization failed - some features may not work',
|
321
|
+
{ prefix: "Adapter" }
|
322
|
+
);
|
235
323
|
}
|
236
|
-
|
324
|
+
|
325
|
+
// Re-extract session information after reload
|
326
|
+
this.extractSessionInfo();
|
237
327
|
}
|
238
328
|
|
329
|
+
|
239
330
|
async ensureDatabaseConfigBucketsExist(databases: Models.Database[] = []) {
|
240
331
|
await this.init();
|
241
332
|
if (!this.storage) {
|
@@ -259,7 +350,6 @@ export class UtilsController {
|
|
259
350
|
MessageFormatter.error("Config not initialized", undefined, { prefix: "Controller" });
|
260
351
|
return;
|
261
352
|
}
|
262
|
-
await this.setupMigrationDatabase();
|
263
353
|
await this.ensureDatabaseConfigBucketsExist(databases);
|
264
354
|
await ensureDatabasesExist(this.config, databases);
|
265
355
|
}
|
@@ -296,37 +386,38 @@ export class UtilsController {
|
|
296
386
|
MessageFormatter.error("Database not initialized", undefined, { prefix: "Controller" });
|
297
387
|
return;
|
298
388
|
}
|
299
|
-
await wipeOtherDatabases(this.database, databasesToKeep
|
389
|
+
await wipeOtherDatabases(this.database, databasesToKeep);
|
300
390
|
}
|
301
391
|
|
302
392
|
async wipeUsers() {
|
303
393
|
await this.init();
|
304
394
|
if (!this.config || !this.database) {
|
305
|
-
|
395
|
+
MessageFormatter.error("Config or database not initialized", undefined, { prefix: "Controller" });
|
306
396
|
return;
|
307
397
|
}
|
308
398
|
const usersController = new UsersController(this.config, this.database);
|
309
399
|
await usersController.wipeUsers();
|
310
400
|
}
|
311
401
|
|
312
|
-
async backupDatabase(database: Models.Database) {
|
402
|
+
async backupDatabase(database: Models.Database, format: 'json' | 'zip' = 'json') {
|
313
403
|
await this.init();
|
314
404
|
if (!this.database || !this.storage || !this.config) {
|
315
|
-
|
405
|
+
MessageFormatter.error("Database, storage, or config not initialized", undefined, { prefix: "Controller" });
|
316
406
|
return;
|
317
407
|
}
|
318
408
|
await backupDatabase(
|
319
409
|
this.config,
|
320
410
|
this.database,
|
321
411
|
database.$id,
|
322
|
-
this.storage
|
412
|
+
this.storage,
|
413
|
+
format
|
323
414
|
);
|
324
415
|
}
|
325
416
|
|
326
417
|
async listAllFunctions() {
|
327
418
|
await this.init();
|
328
419
|
if (!this.appwriteServer) {
|
329
|
-
|
420
|
+
MessageFormatter.error("Appwrite server not initialized", undefined, { prefix: "Controller" });
|
330
421
|
return [];
|
331
422
|
}
|
332
423
|
const { functions } = await listFunctions(this.appwriteServer, [
|
@@ -342,7 +433,7 @@ export class UtilsController {
|
|
342
433
|
}
|
343
434
|
const functionsDir = findFunctionsDir(this.appwriteFolderPath);
|
344
435
|
if (!functionsDir) {
|
345
|
-
|
436
|
+
MessageFormatter.error("Failed to find functions directory", undefined, { prefix: "Controller" });
|
346
437
|
return new Map();
|
347
438
|
}
|
348
439
|
|
@@ -373,7 +464,7 @@ export class UtilsController {
|
|
373
464
|
) {
|
374
465
|
await this.init();
|
375
466
|
if (!this.appwriteServer) {
|
376
|
-
|
467
|
+
MessageFormatter.error("Appwrite server not initialized", undefined, { prefix: "Controller" });
|
377
468
|
return;
|
378
469
|
}
|
379
470
|
|
@@ -383,7 +474,7 @@ export class UtilsController {
|
|
383
474
|
);
|
384
475
|
}
|
385
476
|
if (!functionConfig) {
|
386
|
-
|
477
|
+
MessageFormatter.error(`Function ${functionName} not found in config`, undefined, { prefix: "Controller" });
|
387
478
|
return;
|
388
479
|
}
|
389
480
|
|
@@ -398,7 +489,7 @@ export class UtilsController {
|
|
398
489
|
async syncFunctions() {
|
399
490
|
await this.init();
|
400
491
|
if (!this.appwriteServer) {
|
401
|
-
|
492
|
+
MessageFormatter.error("Appwrite server not initialized", undefined, { prefix: "Controller" });
|
402
493
|
return;
|
403
494
|
}
|
404
495
|
|
@@ -415,23 +506,23 @@ export class UtilsController {
|
|
415
506
|
MessageFormatter.success("All functions synchronized successfully!", { prefix: "Functions" });
|
416
507
|
}
|
417
508
|
|
418
|
-
async wipeDatabase(database: Models.Database, wipeBucket: boolean = false) {
|
419
|
-
await this.init();
|
420
|
-
if (!this.database || !this.config) throw new Error("Database not initialized");
|
421
|
-
try {
|
422
|
-
const { adapter, apiMode } = await getAdapterFromConfig(this.config);
|
423
|
-
if (apiMode === 'tablesdb') {
|
424
|
-
await wipeAllTables(adapter, database.$id);
|
425
|
-
} else {
|
426
|
-
await wipeDatabase(this.database, database.$id);
|
427
|
-
}
|
428
|
-
} catch {
|
429
|
-
await wipeDatabase(this.database, database.$id);
|
430
|
-
}
|
431
|
-
if (wipeBucket) {
|
432
|
-
await this.wipeBucketFromDatabase(database);
|
433
|
-
}
|
434
|
-
}
|
509
|
+
async wipeDatabase(database: Models.Database, wipeBucket: boolean = false) {
|
510
|
+
await this.init();
|
511
|
+
if (!this.database || !this.config) throw new Error("Database not initialized");
|
512
|
+
try {
|
513
|
+
const { adapter, apiMode } = await getAdapterFromConfig(this.config, false, this.sessionCookie);
|
514
|
+
if (apiMode === 'tablesdb') {
|
515
|
+
await wipeAllTables(adapter, database.$id);
|
516
|
+
} else {
|
517
|
+
await wipeDatabase(this.database, database.$id);
|
518
|
+
}
|
519
|
+
} catch {
|
520
|
+
await wipeDatabase(this.database, database.$id);
|
521
|
+
}
|
522
|
+
if (wipeBucket) {
|
523
|
+
await this.wipeBucketFromDatabase(database);
|
524
|
+
}
|
525
|
+
}
|
435
526
|
|
436
527
|
async wipeBucketFromDatabase(database: Models.Database) {
|
437
528
|
// Check configured bucket in database config
|
@@ -459,23 +550,23 @@ export class UtilsController {
|
|
459
550
|
}
|
460
551
|
}
|
461
552
|
|
462
|
-
async wipeCollection(
|
463
|
-
database: Models.Database,
|
464
|
-
collection: Models.Collection
|
465
|
-
) {
|
466
|
-
await this.init();
|
467
|
-
if (!this.database || !this.config) throw new Error("Database not initialized");
|
468
|
-
try {
|
469
|
-
const { adapter, apiMode } = await getAdapterFromConfig(this.config);
|
470
|
-
if (apiMode === 'tablesdb') {
|
471
|
-
await wipeTableRows(adapter, database.$id, collection.$id);
|
472
|
-
} else {
|
473
|
-
await wipeCollection(this.database, database.$id, collection.$id);
|
474
|
-
}
|
475
|
-
} catch {
|
476
|
-
await wipeCollection(this.database, database.$id, collection.$id);
|
477
|
-
}
|
478
|
-
}
|
553
|
+
async wipeCollection(
|
554
|
+
database: Models.Database,
|
555
|
+
collection: Models.Collection
|
556
|
+
) {
|
557
|
+
await this.init();
|
558
|
+
if (!this.database || !this.config) throw new Error("Database not initialized");
|
559
|
+
try {
|
560
|
+
const { adapter, apiMode } = await getAdapterFromConfig(this.config, false, this.sessionCookie);
|
561
|
+
if (apiMode === 'tablesdb') {
|
562
|
+
await wipeTableRows(adapter, database.$id, collection.$id);
|
563
|
+
} else {
|
564
|
+
await wipeCollection(this.database, database.$id, collection.$id);
|
565
|
+
}
|
566
|
+
} catch {
|
567
|
+
await wipeCollection(this.database, database.$id, collection.$id);
|
568
|
+
}
|
569
|
+
}
|
479
570
|
|
480
571
|
async wipeDocumentStorage(bucketId: string) {
|
481
572
|
await this.init();
|
@@ -491,7 +582,6 @@ export class UtilsController {
|
|
491
582
|
if (!this.database || !this.config)
|
492
583
|
throw new Error("Database or config not initialized");
|
493
584
|
for (const database of databases) {
|
494
|
-
if (!this.config.useMigrations && database.$id === "migrations") continue;
|
495
585
|
await this.createOrUpdateCollections(database, undefined, collections);
|
496
586
|
}
|
497
587
|
}
|
@@ -514,10 +604,25 @@ export class UtilsController {
|
|
514
604
|
}
|
515
605
|
|
516
606
|
async generateSchemas() {
|
517
|
-
|
607
|
+
// Schema generation doesn't need Appwrite connection, just config
|
518
608
|
if (!this.config) {
|
519
|
-
|
520
|
-
|
609
|
+
if (this.appwriteFolderPath && this.appwriteConfigPath) {
|
610
|
+
MessageFormatter.progress("Loading config from file...", { prefix: "Config" });
|
611
|
+
try {
|
612
|
+
const { config, actualConfigPath } = await loadConfigWithPath(
|
613
|
+
this.appwriteFolderPath,
|
614
|
+
{ validate: false, strictMode: false, reportValidation: false }
|
615
|
+
);
|
616
|
+
this.config = config;
|
617
|
+
MessageFormatter.info(`Loaded config from: ${actualConfigPath}`, { prefix: "Config" });
|
618
|
+
} catch (error) {
|
619
|
+
MessageFormatter.error("Failed to load config from file", error instanceof Error ? error : undefined, { prefix: "Config" });
|
620
|
+
return;
|
621
|
+
}
|
622
|
+
} else {
|
623
|
+
MessageFormatter.error("No configuration available", undefined, { prefix: "Controller" });
|
624
|
+
return;
|
625
|
+
}
|
521
626
|
}
|
522
627
|
if (!this.appwriteFolderPath) {
|
523
628
|
MessageFormatter.error("Failed to get appwriteFolderPath", undefined, { prefix: "Controller" });
|
@@ -601,10 +706,10 @@ export class UtilsController {
|
|
601
706
|
await generator.updateConfig(this.config, isYamlProject);
|
602
707
|
}
|
603
708
|
|
604
|
-
async syncDb(
|
605
|
-
databases: Models.Database[] = [],
|
606
|
-
collections: Models.Collection[] = []
|
607
|
-
) {
|
709
|
+
async syncDb(
|
710
|
+
databases: Models.Database[] = [],
|
711
|
+
collections: Models.Collection[] = []
|
712
|
+
) {
|
608
713
|
await this.init();
|
609
714
|
if (!this.database) {
|
610
715
|
MessageFormatter.error("Database not initialized", undefined, { prefix: "Controller" });
|
@@ -614,17 +719,12 @@ export class UtilsController {
|
|
614
719
|
const allDatabases = await fetchAllDatabases(this.database);
|
615
720
|
databases = allDatabases;
|
616
721
|
}
|
617
|
-
// Ensure DBs exist
|
618
|
-
await this.ensureDatabasesExist(databases);
|
619
|
-
await this.ensureDatabaseConfigBucketsExist(databases);
|
620
|
-
|
621
|
-
|
622
|
-
|
623
|
-
(db) => (this.config?.useMigrations ?? true) ? db.name.toLowerCase() !== "migrations" : true
|
624
|
-
);
|
625
|
-
|
626
|
-
await this.createOrUpdateCollectionsForDatabases(dbsForCollections, collections);
|
627
|
-
}
|
722
|
+
// Ensure DBs exist
|
723
|
+
await this.ensureDatabasesExist(databases);
|
724
|
+
await this.ensureDatabaseConfigBucketsExist(databases);
|
725
|
+
|
726
|
+
await this.createOrUpdateCollectionsForDatabases(databases, collections);
|
727
|
+
}
|
628
728
|
|
629
729
|
getAppwriteFolderPath() {
|
630
730
|
return this.appwriteFolderPath;
|
@@ -637,7 +737,7 @@ export class UtilsController {
|
|
637
737
|
let targetDatabases: Models.Database[] = [];
|
638
738
|
|
639
739
|
if (!sourceClient) {
|
640
|
-
|
740
|
+
MessageFormatter.error("Source database not initialized", undefined, { prefix: "Controller" });
|
641
741
|
return;
|
642
742
|
}
|
643
743
|
|
@@ -647,7 +747,7 @@ export class UtilsController {
|
|
647
747
|
!options.transferProject ||
|
648
748
|
!options.transferKey
|
649
749
|
) {
|
650
|
-
|
750
|
+
MessageFormatter.error("Remote transfer options are missing", undefined, { prefix: "Controller" });
|
651
751
|
return;
|
652
752
|
}
|
653
753
|
|
@@ -675,7 +775,7 @@ export class UtilsController {
|
|
675
775
|
);
|
676
776
|
|
677
777
|
if (!fromDb || !targetDb) {
|
678
|
-
|
778
|
+
MessageFormatter.error("Source or target database not found", undefined, { prefix: "Controller" });
|
679
779
|
return;
|
680
780
|
}
|
681
781
|
|
@@ -699,13 +799,12 @@ export class UtilsController {
|
|
699
799
|
|
700
800
|
if (options.transferUsers) {
|
701
801
|
if (!options.isRemote) {
|
702
|
-
|
703
|
-
|
704
|
-
|
705
|
-
)
|
802
|
+
MessageFormatter.warning(
|
803
|
+
"User transfer is only supported for remote transfers. Skipping...",
|
804
|
+
{ prefix: "Controller" }
|
706
805
|
);
|
707
806
|
} else if (!this.appwriteServer) {
|
708
|
-
|
807
|
+
MessageFormatter.error("Appwrite server not initialized", undefined, { prefix: "Controller" });
|
709
808
|
return;
|
710
809
|
} else {
|
711
810
|
MessageFormatter.progress("Starting user transfer...", { prefix: "Transfer" });
|
@@ -789,4 +888,109 @@ export class UtilsController {
|
|
789
888
|
{ prefix: "Functions" }
|
790
889
|
);
|
791
890
|
}
|
891
|
+
|
892
|
+
/**
|
893
|
+
* Validates the current configuration for collections/tables conflicts
|
894
|
+
*/
|
895
|
+
async validateConfiguration(strictMode: boolean = false): Promise<ValidationResult> {
|
896
|
+
await this.init();
|
897
|
+
if (!this.config) {
|
898
|
+
throw new Error("Configuration not loaded");
|
899
|
+
}
|
900
|
+
|
901
|
+
MessageFormatter.progress("Validating configuration...", { prefix: "Validation" });
|
902
|
+
|
903
|
+
const validation = strictMode
|
904
|
+
? validateWithStrictMode(this.config, strictMode)
|
905
|
+
: validateCollectionsTablesConfig(this.config);
|
906
|
+
|
907
|
+
reportValidationResults(validation, { verbose: true });
|
908
|
+
|
909
|
+
if (validation.isValid) {
|
910
|
+
MessageFormatter.success("Configuration validation passed", { prefix: "Validation" });
|
911
|
+
} else {
|
912
|
+
MessageFormatter.error(`Configuration validation failed with ${validation.errors.length} errors`, undefined, { prefix: "Validation" });
|
913
|
+
}
|
914
|
+
|
915
|
+
return validation;
|
916
|
+
}
|
917
|
+
|
918
|
+
/**
|
919
|
+
* Extract session information from the current authenticated client
|
920
|
+
* This preserves session context for use across config reloads and adapter operations
|
921
|
+
*/
|
922
|
+
private extractSessionInfo(): void {
|
923
|
+
if (!this.config) {
|
924
|
+
return;
|
925
|
+
}
|
926
|
+
|
927
|
+
// Try to extract session from current config first
|
928
|
+
if (this.config.sessionCookie && isValidSessionCookie(this.config.sessionCookie)) {
|
929
|
+
this.sessionCookie = this.config.sessionCookie;
|
930
|
+
this.authMethod = "session";
|
931
|
+
this.sessionMetadata = this.config.sessionMetadata;
|
932
|
+
MessageFormatter.debug("Extracted session from config", { prefix: "Session" });
|
933
|
+
return;
|
934
|
+
}
|
935
|
+
|
936
|
+
// Fall back to finding session from Appwrite CLI preferences
|
937
|
+
const sessionAuth = findSessionByEndpointAndProject(
|
938
|
+
this.config.appwriteEndpoint,
|
939
|
+
this.config.appwriteProject
|
940
|
+
);
|
941
|
+
|
942
|
+
if (sessionAuth && isValidSessionCookie(sessionAuth.sessionCookie)) {
|
943
|
+
this.sessionCookie = sessionAuth.sessionCookie;
|
944
|
+
this.authMethod = "session";
|
945
|
+
this.sessionMetadata = {
|
946
|
+
email: sessionAuth.email
|
947
|
+
};
|
948
|
+
MessageFormatter.debug(
|
949
|
+
`Extracted session from CLI preferences for ${sessionAuth.email || 'unknown user'}`,
|
950
|
+
{ prefix: "Session" }
|
951
|
+
);
|
952
|
+
return;
|
953
|
+
}
|
954
|
+
|
955
|
+
// No session found, using API key authentication
|
956
|
+
if (this.config.appwriteKey) {
|
957
|
+
this.authMethod = "apikey";
|
958
|
+
this.sessionCookie = undefined;
|
959
|
+
this.sessionMetadata = undefined;
|
960
|
+
MessageFormatter.debug("Using API key authentication", { prefix: "Session" });
|
961
|
+
}
|
962
|
+
}
|
963
|
+
|
964
|
+
/**
|
965
|
+
* Create session preservation options for passing to loadConfig
|
966
|
+
* Returns current session state to maintain authentication across config reloads
|
967
|
+
*/
|
968
|
+
private createSessionPreservationOptions(): SessionPreservationOptions | undefined {
|
969
|
+
if (!this.sessionCookie || !isValidSessionCookie(this.sessionCookie)) {
|
970
|
+
return undefined;
|
971
|
+
}
|
972
|
+
|
973
|
+
return createSessionPreservation(
|
974
|
+
this.sessionCookie,
|
975
|
+
this.sessionMetadata?.email,
|
976
|
+
this.sessionMetadata?.expiresAt
|
977
|
+
);
|
978
|
+
}
|
979
|
+
|
980
|
+
/**
|
981
|
+
* Get current session information for debugging/logging purposes
|
982
|
+
*/
|
983
|
+
public getSessionInfo(): {
|
984
|
+
hasSession: boolean;
|
985
|
+
authMethod?: string;
|
986
|
+
email?: string;
|
987
|
+
expiresAt?: string;
|
988
|
+
} {
|
989
|
+
return {
|
990
|
+
hasSession: !!this.sessionCookie && isValidSessionCookie(this.sessionCookie),
|
991
|
+
authMethod: this.authMethod,
|
992
|
+
email: this.sessionMetadata?.email,
|
993
|
+
expiresAt: this.sessionMetadata?.expiresAt
|
994
|
+
};
|
995
|
+
}
|
792
996
|
}
|