@njdamstra/appwrite-utils-cli 1.8.9
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 +19 -0
- package/README.md +1133 -0
- package/dist/adapters/AdapterFactory.d.ts +94 -0
- package/dist/adapters/AdapterFactory.js +405 -0
- package/dist/adapters/DatabaseAdapter.d.ts +233 -0
- package/dist/adapters/DatabaseAdapter.js +50 -0
- package/dist/adapters/LegacyAdapter.d.ts +50 -0
- package/dist/adapters/LegacyAdapter.js +612 -0
- package/dist/adapters/TablesDBAdapter.d.ts +45 -0
- package/dist/adapters/TablesDBAdapter.js +571 -0
- package/dist/adapters/index.d.ts +11 -0
- package/dist/adapters/index.js +12 -0
- 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 +166 -0
- package/dist/cli/commands/databaseCommands.d.ts +13 -0
- package/dist/cli/commands/databaseCommands.js +554 -0
- package/dist/cli/commands/functionCommands.d.ts +7 -0
- package/dist/cli/commands/functionCommands.js +330 -0
- package/dist/cli/commands/schemaCommands.d.ts +7 -0
- package/dist/cli/commands/schemaCommands.js +169 -0
- package/dist/cli/commands/storageCommands.d.ts +5 -0
- package/dist/cli/commands/storageCommands.js +143 -0
- package/dist/cli/commands/transferCommands.d.ts +5 -0
- package/dist/cli/commands/transferCommands.js +384 -0
- package/dist/collections/attributes.d.ts +13 -0
- package/dist/collections/attributes.js +1364 -0
- package/dist/collections/indexes.d.ts +12 -0
- package/dist/collections/indexes.js +217 -0
- package/dist/collections/methods.d.ts +19 -0
- package/dist/collections/methods.js +682 -0
- package/dist/collections/tableOperations.d.ts +86 -0
- package/dist/collections/tableOperations.js +434 -0
- package/dist/collections/transferOperations.d.ts +8 -0
- package/dist/collections/transferOperations.js +412 -0
- package/dist/collections/wipeOperations.d.ts +16 -0
- package/dist/collections/wipeOperations.js +233 -0
- package/dist/config/ConfigManager.d.ts +445 -0
- package/dist/config/ConfigManager.js +625 -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/index.d.ts +8 -0
- package/dist/config/index.js +7 -0
- package/dist/config/services/ConfigDiscoveryService.d.ts +126 -0
- package/dist/config/services/ConfigDiscoveryService.js +374 -0
- package/dist/config/services/ConfigLoaderService.d.ts +129 -0
- package/dist/config/services/ConfigLoaderService.js +540 -0
- package/dist/config/services/ConfigMergeService.d.ts +208 -0
- package/dist/config/services/ConfigMergeService.js +308 -0
- package/dist/config/services/ConfigValidationService.d.ts +214 -0
- package/dist/config/services/ConfigValidationService.js +310 -0
- package/dist/config/services/SessionAuthService.d.ts +225 -0
- package/dist/config/services/SessionAuthService.js +456 -0
- package/dist/config/services/__tests__/ConfigMergeService.test.d.ts +1 -0
- package/dist/config/services/__tests__/ConfigMergeService.test.js +271 -0
- package/dist/config/services/index.d.ts +13 -0
- package/dist/config/services/index.js +10 -0
- package/dist/config/yamlConfig.d.ts +722 -0
- package/dist/config/yamlConfig.js +702 -0
- package/dist/databases/methods.d.ts +6 -0
- package/dist/databases/methods.js +35 -0
- package/dist/databases/setup.d.ts +5 -0
- package/dist/databases/setup.js +45 -0
- package/dist/examples/yamlTerminologyExample.d.ts +42 -0
- package/dist/examples/yamlTerminologyExample.js +272 -0
- package/dist/functions/deployments.d.ts +4 -0
- package/dist/functions/deployments.js +146 -0
- package/dist/functions/fnConfigDiscovery.d.ts +3 -0
- package/dist/functions/fnConfigDiscovery.js +108 -0
- package/dist/functions/methods.d.ts +16 -0
- package/dist/functions/methods.js +162 -0
- package/dist/functions/pathResolution.d.ts +37 -0
- package/dist/functions/pathResolution.js +185 -0
- package/dist/functions/templates/count-docs-in-collection/README.md +54 -0
- package/dist/functions/templates/count-docs-in-collection/src/main.ts +159 -0
- package/dist/functions/templates/count-docs-in-collection/src/request.ts +9 -0
- package/dist/functions/templates/hono-typescript/README.md +286 -0
- package/dist/functions/templates/hono-typescript/src/adapters/request.ts +74 -0
- package/dist/functions/templates/hono-typescript/src/adapters/response.ts +106 -0
- package/dist/functions/templates/hono-typescript/src/app.ts +180 -0
- package/dist/functions/templates/hono-typescript/src/context.ts +103 -0
- package/dist/functions/templates/hono-typescript/src/index.ts +54 -0
- package/dist/functions/templates/hono-typescript/src/middleware/appwrite.ts +119 -0
- package/dist/functions/templates/typescript-node/README.md +32 -0
- package/dist/functions/templates/typescript-node/src/context.ts +103 -0
- package/dist/functions/templates/typescript-node/src/index.ts +29 -0
- package/dist/functions/templates/uv/README.md +31 -0
- package/dist/functions/templates/uv/pyproject.toml +30 -0
- package/dist/functions/templates/uv/src/__init__.py +0 -0
- package/dist/functions/templates/uv/src/context.py +125 -0
- package/dist/functions/templates/uv/src/index.py +46 -0
- package/dist/init.d.ts +2 -0
- package/dist/init.js +57 -0
- package/dist/interactiveCLI.d.ts +31 -0
- package/dist/interactiveCLI.js +898 -0
- package/dist/main.d.ts +2 -0
- package/dist/main.js +1172 -0
- package/dist/migrations/afterImportActions.d.ts +17 -0
- package/dist/migrations/afterImportActions.js +306 -0
- package/dist/migrations/appwriteToX.d.ts +211 -0
- package/dist/migrations/appwriteToX.js +491 -0
- package/dist/migrations/comprehensiveTransfer.d.ts +147 -0
- package/dist/migrations/comprehensiveTransfer.js +1317 -0
- package/dist/migrations/dataLoader.d.ts +753 -0
- package/dist/migrations/dataLoader.js +1250 -0
- package/dist/migrations/importController.d.ts +23 -0
- package/dist/migrations/importController.js +268 -0
- package/dist/migrations/importDataActions.d.ts +50 -0
- package/dist/migrations/importDataActions.js +230 -0
- package/dist/migrations/relationships.d.ts +29 -0
- package/dist/migrations/relationships.js +204 -0
- package/dist/migrations/services/DataTransformationService.d.ts +55 -0
- package/dist/migrations/services/DataTransformationService.js +158 -0
- package/dist/migrations/services/FileHandlerService.d.ts +75 -0
- package/dist/migrations/services/FileHandlerService.js +236 -0
- package/dist/migrations/services/ImportOrchestrator.d.ts +97 -0
- package/dist/migrations/services/ImportOrchestrator.js +485 -0
- package/dist/migrations/services/RateLimitManager.d.ts +138 -0
- package/dist/migrations/services/RateLimitManager.js +279 -0
- package/dist/migrations/services/RelationshipResolver.d.ts +120 -0
- package/dist/migrations/services/RelationshipResolver.js +332 -0
- package/dist/migrations/services/UserMappingService.d.ts +109 -0
- package/dist/migrations/services/UserMappingService.js +277 -0
- package/dist/migrations/services/ValidationService.d.ts +74 -0
- package/dist/migrations/services/ValidationService.js +260 -0
- package/dist/migrations/transfer.d.ts +26 -0
- package/dist/migrations/transfer.js +608 -0
- package/dist/migrations/yaml/YamlImportConfigLoader.d.ts +131 -0
- package/dist/migrations/yaml/YamlImportConfigLoader.js +383 -0
- package/dist/migrations/yaml/YamlImportIntegration.d.ts +93 -0
- package/dist/migrations/yaml/YamlImportIntegration.js +341 -0
- package/dist/migrations/yaml/generateImportSchemas.d.ts +30 -0
- package/dist/migrations/yaml/generateImportSchemas.js +1327 -0
- package/dist/schemas/authUser.d.ts +24 -0
- package/dist/schemas/authUser.js +17 -0
- package/dist/setup.d.ts +2 -0
- package/dist/setup.js +5 -0
- package/dist/setupCommands.d.ts +58 -0
- package/dist/setupCommands.js +490 -0
- package/dist/setupController.d.ts +9 -0
- package/dist/setupController.js +34 -0
- package/dist/shared/attributeMapper.d.ts +20 -0
- package/dist/shared/attributeMapper.js +203 -0
- 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.d.ts +75 -0
- package/dist/shared/confirmationDialogs.js +236 -0
- package/dist/shared/errorUtils.d.ts +54 -0
- package/dist/shared/errorUtils.js +95 -0
- package/dist/shared/functionManager.d.ts +48 -0
- package/dist/shared/functionManager.js +336 -0
- package/dist/shared/indexManager.d.ts +24 -0
- package/dist/shared/indexManager.js +151 -0
- package/dist/shared/jsonSchemaGenerator.d.ts +50 -0
- package/dist/shared/jsonSchemaGenerator.js +290 -0
- package/dist/shared/logging.d.ts +61 -0
- package/dist/shared/logging.js +116 -0
- package/dist/shared/messageFormatter.d.ts +39 -0
- package/dist/shared/messageFormatter.js +162 -0
- package/dist/shared/migrationHelpers.d.ts +61 -0
- package/dist/shared/migrationHelpers.js +145 -0
- package/dist/shared/operationLogger.d.ts +10 -0
- package/dist/shared/operationLogger.js +12 -0
- package/dist/shared/operationQueue.d.ts +40 -0
- package/dist/shared/operationQueue.js +311 -0
- 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/progressManager.d.ts +62 -0
- package/dist/shared/progressManager.js +215 -0
- package/dist/shared/pydanticModelGenerator.d.ts +17 -0
- package/dist/shared/pydanticModelGenerator.js +615 -0
- package/dist/shared/relationshipExtractor.d.ts +56 -0
- package/dist/shared/relationshipExtractor.js +138 -0
- package/dist/shared/schemaGenerator.d.ts +40 -0
- package/dist/shared/schemaGenerator.js +556 -0
- package/dist/shared/selectionDialogs.d.ts +214 -0
- package/dist/shared/selectionDialogs.js +544 -0
- package/dist/storage/backupCompression.d.ts +20 -0
- package/dist/storage/backupCompression.js +67 -0
- package/dist/storage/methods.d.ts +32 -0
- package/dist/storage/methods.js +472 -0
- package/dist/storage/schemas.d.ts +842 -0
- package/dist/storage/schemas.js +175 -0
- package/dist/types.d.ts +4 -0
- package/dist/types.js +3 -0
- package/dist/users/methods.d.ts +16 -0
- package/dist/users/methods.js +277 -0
- package/dist/utils/ClientFactory.d.ts +87 -0
- package/dist/utils/ClientFactory.js +212 -0
- package/dist/utils/configDiscovery.d.ts +78 -0
- package/dist/utils/configDiscovery.js +472 -0
- package/dist/utils/configMigration.d.ts +1 -0
- package/dist/utils/configMigration.js +261 -0
- package/dist/utils/constantsGenerator.d.ts +31 -0
- package/dist/utils/constantsGenerator.js +321 -0
- package/dist/utils/dataConverters.d.ts +46 -0
- package/dist/utils/dataConverters.js +139 -0
- package/dist/utils/directoryUtils.d.ts +22 -0
- package/dist/utils/directoryUtils.js +59 -0
- package/dist/utils/getClientFromConfig.d.ts +39 -0
- package/dist/utils/getClientFromConfig.js +199 -0
- package/dist/utils/helperFunctions.d.ts +63 -0
- package/dist/utils/helperFunctions.js +156 -0
- package/dist/utils/index.d.ts +2 -0
- package/dist/utils/index.js +2 -0
- package/dist/utils/loadConfigs.d.ts +50 -0
- package/dist/utils/loadConfigs.js +358 -0
- 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.d.ts +2 -0
- package/dist/utils/retryFailedPromises.js +23 -0
- package/dist/utils/sessionAuth.d.ts +48 -0
- package/dist/utils/sessionAuth.js +164 -0
- package/dist/utils/setupFiles.d.ts +4 -0
- package/dist/utils/setupFiles.js +1192 -0
- package/dist/utils/typeGuards.d.ts +35 -0
- package/dist/utils/typeGuards.js +57 -0
- package/dist/utils/validationRules.d.ts +43 -0
- package/dist/utils/validationRules.js +42 -0
- package/dist/utils/versionDetection.d.ts +58 -0
- package/dist/utils/versionDetection.js +251 -0
- package/dist/utils/yamlConverter.d.ts +100 -0
- package/dist/utils/yamlConverter.js +428 -0
- package/dist/utils/yamlLoader.d.ts +70 -0
- package/dist/utils/yamlLoader.js +267 -0
- package/dist/utilsController.d.ts +106 -0
- package/dist/utilsController.js +863 -0
- package/package.json +75 -0
- package/scripts/copy-templates.ts +23 -0
- package/src/adapters/AdapterFactory.ts +510 -0
- package/src/adapters/DatabaseAdapter.ts +306 -0
- package/src/adapters/LegacyAdapter.ts +841 -0
- package/src/adapters/TablesDBAdapter.ts +773 -0
- package/src/adapters/index.ts +37 -0
- 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 +201 -0
- package/src/cli/commands/databaseCommands.ts +749 -0
- package/src/cli/commands/functionCommands.ts +418 -0
- package/src/cli/commands/schemaCommands.ts +200 -0
- package/src/cli/commands/storageCommands.ts +152 -0
- package/src/cli/commands/transferCommands.ts +457 -0
- package/src/collections/attributes.ts +2054 -0
- package/src/collections/attributes.ts.backup +1555 -0
- package/src/collections/indexes.ts +352 -0
- package/src/collections/methods.ts +745 -0
- package/src/collections/tableOperations.ts +506 -0
- package/src/collections/transferOperations.ts +590 -0
- package/src/collections/wipeOperations.ts +346 -0
- package/src/config/ConfigManager.ts +808 -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/index.ts +10 -0
- package/src/config/services/ConfigDiscoveryService.ts +463 -0
- package/src/config/services/ConfigLoaderService.ts +740 -0
- package/src/config/services/ConfigMergeService.ts +388 -0
- package/src/config/services/ConfigValidationService.ts +394 -0
- package/src/config/services/SessionAuthService.ts +565 -0
- package/src/config/services/__tests__/ConfigMergeService.test.ts +351 -0
- package/src/config/services/index.ts +29 -0
- package/src/config/yamlConfig.ts +761 -0
- package/src/databases/methods.ts +49 -0
- package/src/databases/setup.ts +77 -0
- package/src/examples/yamlTerminologyExample.ts +346 -0
- package/src/functions/deployments.ts +220 -0
- package/src/functions/fnConfigDiscovery.ts +103 -0
- package/src/functions/methods.ts +271 -0
- package/src/functions/pathResolution.ts +227 -0
- package/src/functions/templates/count-docs-in-collection/README.md +54 -0
- package/src/functions/templates/count-docs-in-collection/src/main.ts +159 -0
- package/src/functions/templates/count-docs-in-collection/src/request.ts +9 -0
- package/src/functions/templates/hono-typescript/README.md +286 -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/typescript-node/README.md +32 -0
- package/src/functions/templates/typescript-node/src/context.ts +103 -0
- package/src/functions/templates/typescript-node/src/index.ts +29 -0
- package/src/functions/templates/uv/README.md +31 -0
- package/src/functions/templates/uv/pyproject.toml +30 -0
- package/src/functions/templates/uv/src/__init__.py +0 -0
- package/src/functions/templates/uv/src/context.py +125 -0
- package/src/functions/templates/uv/src/index.py +46 -0
- package/src/init.ts +62 -0
- package/src/interactiveCLI.ts +1136 -0
- package/src/main.ts +1661 -0
- package/src/migrations/afterImportActions.ts +580 -0
- package/src/migrations/appwriteToX.ts +664 -0
- package/src/migrations/comprehensiveTransfer.ts +2285 -0
- package/src/migrations/dataLoader.ts +1702 -0
- package/src/migrations/importController.ts +428 -0
- package/src/migrations/importDataActions.ts +315 -0
- package/src/migrations/relationships.ts +334 -0
- package/src/migrations/services/DataTransformationService.ts +196 -0
- package/src/migrations/services/FileHandlerService.ts +311 -0
- package/src/migrations/services/ImportOrchestrator.ts +666 -0
- package/src/migrations/services/RateLimitManager.ts +363 -0
- package/src/migrations/services/RelationshipResolver.ts +461 -0
- package/src/migrations/services/UserMappingService.ts +345 -0
- package/src/migrations/services/ValidationService.ts +349 -0
- package/src/migrations/transfer.ts +1068 -0
- package/src/migrations/yaml/YamlImportConfigLoader.ts +439 -0
- package/src/migrations/yaml/YamlImportIntegration.ts +446 -0
- package/src/migrations/yaml/generateImportSchemas.ts +1354 -0
- package/src/schemas/authUser.ts +23 -0
- package/src/setup.ts +8 -0
- package/src/setupCommands.ts +603 -0
- package/src/setupController.ts +43 -0
- package/src/shared/attributeMapper.ts +229 -0
- package/src/shared/backupMetadataSchema.ts +93 -0
- package/src/shared/backupTracking.ts +211 -0
- package/src/shared/confirmationDialogs.ts +327 -0
- package/src/shared/errorUtils.ts +110 -0
- package/src/shared/functionManager.ts +525 -0
- package/src/shared/indexManager.ts +254 -0
- package/src/shared/jsonSchemaGenerator.ts +383 -0
- package/src/shared/logging.ts +149 -0
- package/src/shared/messageFormatter.ts +208 -0
- package/src/shared/migrationHelpers.ts +232 -0
- package/src/shared/operationLogger.ts +20 -0
- package/src/shared/operationQueue.ts +377 -0
- package/src/shared/operationsTable.ts +338 -0
- package/src/shared/operationsTableSchema.ts +60 -0
- package/src/shared/progressManager.ts +278 -0
- package/src/shared/pydanticModelGenerator.ts +618 -0
- package/src/shared/relationshipExtractor.ts +214 -0
- package/src/shared/schemaGenerator.ts +644 -0
- package/src/shared/selectionDialogs.ts +749 -0
- package/src/storage/backupCompression.ts +88 -0
- package/src/storage/methods.ts +698 -0
- package/src/storage/schemas.ts +205 -0
- package/src/types/node-appwrite-tablesdb.d.ts +44 -0
- package/src/types.ts +9 -0
- package/src/users/methods.ts +359 -0
- package/src/utils/ClientFactory.ts +240 -0
- package/src/utils/configDiscovery.ts +557 -0
- package/src/utils/configMigration.ts +348 -0
- package/src/utils/constantsGenerator.ts +369 -0
- package/src/utils/dataConverters.ts +159 -0
- package/src/utils/directoryUtils.ts +61 -0
- package/src/utils/getClientFromConfig.ts +257 -0
- package/src/utils/helperFunctions.ts +228 -0
- package/src/utils/index.ts +2 -0
- package/src/utils/loadConfigs.ts +449 -0
- package/src/utils/pathResolvers.ts +81 -0
- package/src/utils/projectConfig.ts +299 -0
- package/src/utils/retryFailedPromises.ts +29 -0
- package/src/utils/sessionAuth.ts +230 -0
- package/src/utils/setupFiles.ts +1238 -0
- package/src/utils/typeGuards.ts +65 -0
- package/src/utils/validationRules.ts +88 -0
- package/src/utils/versionDetection.ts +292 -0
- package/src/utils/yamlConverter.ts +542 -0
- package/src/utils/yamlLoader.ts +371 -0
- package/src/utilsController.ts +1203 -0
- 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/tsconfig.json +44 -0
package/dist/main.js
ADDED
|
@@ -0,0 +1,1172 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import yargs from "yargs";
|
|
3
|
+
import {} from "yargs";
|
|
4
|
+
import { hideBin } from "yargs/helpers";
|
|
5
|
+
import { InteractiveCLI } from "./interactiveCLI.js";
|
|
6
|
+
import { UtilsController } from "./utilsController.js";
|
|
7
|
+
import { Databases, Storage } from "node-appwrite";
|
|
8
|
+
import { getClient } from "./utils/getClientFromConfig.js";
|
|
9
|
+
import { fetchAllDatabases } from "./databases/methods.js";
|
|
10
|
+
import { setupDirsFiles } from "./utils/setupFiles.js";
|
|
11
|
+
import { fetchAllCollections } from "./collections/methods.js";
|
|
12
|
+
import chalk from "chalk";
|
|
13
|
+
import { listSpecifications } from "./functions/methods.js";
|
|
14
|
+
import { MessageFormatter } from "./shared/messageFormatter.js";
|
|
15
|
+
import { ConfirmationDialogs } from "./shared/confirmationDialogs.js";
|
|
16
|
+
import { SelectionDialogs } from "./shared/selectionDialogs.js";
|
|
17
|
+
import { logger } from "./shared/logging.js";
|
|
18
|
+
import path from "path";
|
|
19
|
+
import fs from "fs";
|
|
20
|
+
import { createRequire } from "node:module";
|
|
21
|
+
import { loadAppwriteProjectConfig, findAppwriteProjectConfig, projectConfigToAppwriteConfig, } from "./utils/projectConfig.js";
|
|
22
|
+
import { hasSessionAuth, getAvailableSessions, getAuthenticationStatus, } from "./utils/sessionAuth.js";
|
|
23
|
+
import { findYamlConfig, loadYamlConfigWithSession, } from "./config/yamlConfig.js";
|
|
24
|
+
const require = createRequire(import.meta.url);
|
|
25
|
+
if (!globalThis.require) {
|
|
26
|
+
globalThis.require = require;
|
|
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, 'pull');
|
|
80
|
+
if (!confirmed) {
|
|
81
|
+
MessageFormatter.info("Pull operation cancelled by user", { prefix: "Sync" });
|
|
82
|
+
return null;
|
|
83
|
+
}
|
|
84
|
+
// Perform sync with existing configuration (pull from remote)
|
|
85
|
+
await controller.selectivePull(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: false,
|
|
98
|
+
allowNewOnly,
|
|
99
|
+
defaultSelected: []
|
|
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: false,
|
|
120
|
+
allowNewOnly,
|
|
121
|
+
defaultSelected: []
|
|
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: false,
|
|
141
|
+
allowNewOnly: parsedArgv.selectBuckets ? false : allowNewOnly,
|
|
142
|
+
groupByDatabase: true,
|
|
143
|
+
defaultSelected: []
|
|
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, 'pull');
|
|
158
|
+
if (!confirmed) {
|
|
159
|
+
MessageFormatter.info("Pull operation cancelled by user", { prefix: "Sync" });
|
|
160
|
+
return null;
|
|
161
|
+
}
|
|
162
|
+
// Perform the selective sync (pull from remote)
|
|
163
|
+
await controller.selectivePull(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
|
+
*/
|
|
175
|
+
/**
|
|
176
|
+
* Checks if the migration from collections to tables should be allowed
|
|
177
|
+
* Returns an object with:
|
|
178
|
+
* - allowed: boolean indicating if migration should proceed
|
|
179
|
+
* - reason: string explaining why migration was blocked (if not allowed)
|
|
180
|
+
*/
|
|
181
|
+
function checkMigrationConditions(configPath) {
|
|
182
|
+
const collectionsPath = path.join(configPath, "collections");
|
|
183
|
+
const tablesPath = path.join(configPath, "tables");
|
|
184
|
+
// Check if collections/ folder exists
|
|
185
|
+
if (!fs.existsSync(collectionsPath)) {
|
|
186
|
+
return {
|
|
187
|
+
allowed: false,
|
|
188
|
+
reason: "No collections/ folder found. Migration requires existing collections to migrate.",
|
|
189
|
+
};
|
|
190
|
+
}
|
|
191
|
+
// Check if collections/ folder has YAML files
|
|
192
|
+
const collectionFiles = fs
|
|
193
|
+
.readdirSync(collectionsPath)
|
|
194
|
+
.filter((file) => file.endsWith(".yaml") || file.endsWith(".yml"));
|
|
195
|
+
if (collectionFiles.length === 0) {
|
|
196
|
+
return {
|
|
197
|
+
allowed: false,
|
|
198
|
+
reason: "No YAML files found in collections/ folder. Migration requires existing collection YAML files.",
|
|
199
|
+
};
|
|
200
|
+
}
|
|
201
|
+
// Check if tables/ folder exists and has YAML files
|
|
202
|
+
if (fs.existsSync(tablesPath)) {
|
|
203
|
+
const tableFiles = fs
|
|
204
|
+
.readdirSync(tablesPath)
|
|
205
|
+
.filter((file) => file.endsWith(".yaml") || file.endsWith(".yml"));
|
|
206
|
+
if (tableFiles.length > 0) {
|
|
207
|
+
return {
|
|
208
|
+
allowed: false,
|
|
209
|
+
reason: `Tables folder already exists with ${tableFiles.length} YAML file(s). Migration appears to have already been completed.`,
|
|
210
|
+
};
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
// All conditions met
|
|
214
|
+
return { allowed: true };
|
|
215
|
+
}
|
|
216
|
+
const argv = yargs(hideBin(process.argv))
|
|
217
|
+
.option("config", {
|
|
218
|
+
type: "string",
|
|
219
|
+
description: "Path to Appwrite configuration file (appwriteConfig.ts)",
|
|
220
|
+
})
|
|
221
|
+
.option("it", {
|
|
222
|
+
alias: ["interactive", "i"],
|
|
223
|
+
type: "boolean",
|
|
224
|
+
description: "Launch interactive CLI mode with guided prompts",
|
|
225
|
+
})
|
|
226
|
+
.option("dbIds", {
|
|
227
|
+
type: "string",
|
|
228
|
+
description: "Comma-separated list of database IDs to target (e.g., 'db1,db2,db3')",
|
|
229
|
+
})
|
|
230
|
+
.option("collectionIds", {
|
|
231
|
+
alias: ["collIds", "tableIds", "tables"],
|
|
232
|
+
type: "string",
|
|
233
|
+
description: "Comma-separated list of collection/table IDs to target (e.g., 'users,posts')",
|
|
234
|
+
})
|
|
235
|
+
.option("bucketIds", {
|
|
236
|
+
type: "string",
|
|
237
|
+
description: "Comma-separated list of bucket IDs to operate on",
|
|
238
|
+
})
|
|
239
|
+
.option("wipe", {
|
|
240
|
+
choices: ["all", "docs", "users"],
|
|
241
|
+
description: "⚠️ DESTRUCTIVE: Wipe data (all: databases+storage+users, docs: documents only, users: user accounts only)",
|
|
242
|
+
})
|
|
243
|
+
.option("wipeCollections", {
|
|
244
|
+
type: "boolean",
|
|
245
|
+
description: "⚠️ DESTRUCTIVE: Wipe specific collections/tables (requires --collectionIds or --tableIds)",
|
|
246
|
+
})
|
|
247
|
+
.option("transferUsers", {
|
|
248
|
+
type: "boolean",
|
|
249
|
+
description: "Transfer users between projects",
|
|
250
|
+
})
|
|
251
|
+
.option("generate", {
|
|
252
|
+
type: "boolean",
|
|
253
|
+
description: "Generate TypeScript schemas and types from your Appwrite database schemas",
|
|
254
|
+
})
|
|
255
|
+
.option("import", {
|
|
256
|
+
type: "boolean",
|
|
257
|
+
description: "Import data from importData/ directory into your Appwrite databases",
|
|
258
|
+
})
|
|
259
|
+
.option("backup", {
|
|
260
|
+
type: "boolean",
|
|
261
|
+
description: "Create a complete backup of your databases and collections",
|
|
262
|
+
})
|
|
263
|
+
.option("backupFormat", {
|
|
264
|
+
type: "string",
|
|
265
|
+
choices: ["json", "zip"],
|
|
266
|
+
default: "json",
|
|
267
|
+
description: "Backup file format (json or zip)",
|
|
268
|
+
})
|
|
269
|
+
.option("listBackups", {
|
|
270
|
+
type: "boolean",
|
|
271
|
+
description: "List all backups for databases",
|
|
272
|
+
})
|
|
273
|
+
.option("comprehensiveBackup", {
|
|
274
|
+
alias: ["comprehensive", "backup-all"],
|
|
275
|
+
type: "boolean",
|
|
276
|
+
description: "🚀 Create comprehensive backup of ALL databases and ALL storage buckets",
|
|
277
|
+
})
|
|
278
|
+
.option("trackingDatabaseId", {
|
|
279
|
+
alias: ["tracking-db"],
|
|
280
|
+
type: "string",
|
|
281
|
+
description: "Database ID to use for centralized backup tracking (interactive prompt if not specified)",
|
|
282
|
+
})
|
|
283
|
+
.option("parallelDownloads", {
|
|
284
|
+
type: "number",
|
|
285
|
+
default: 10,
|
|
286
|
+
description: "Number of parallel file downloads for bucket backups (default: 10)",
|
|
287
|
+
})
|
|
288
|
+
.option("writeData", {
|
|
289
|
+
type: "boolean",
|
|
290
|
+
description: "Output converted import data to files for validation before importing",
|
|
291
|
+
})
|
|
292
|
+
.option("push", {
|
|
293
|
+
type: "boolean",
|
|
294
|
+
description: "Deploy your local configuration (collections, attributes, indexes) to Appwrite",
|
|
295
|
+
})
|
|
296
|
+
.option("sync", {
|
|
297
|
+
type: "boolean",
|
|
298
|
+
description: "Pull and synchronize your local config with the remote Appwrite project schema",
|
|
299
|
+
})
|
|
300
|
+
.option("autoSync", {
|
|
301
|
+
alias: ["auto"],
|
|
302
|
+
type: "boolean",
|
|
303
|
+
description: "Skip prompts and sync all databases, tables, and buckets (current behavior)"
|
|
304
|
+
})
|
|
305
|
+
.option("selectBuckets", {
|
|
306
|
+
type: "boolean",
|
|
307
|
+
description: "Force bucket selection dialog even if buckets are already configured"
|
|
308
|
+
})
|
|
309
|
+
.option("endpoint", {
|
|
310
|
+
type: "string",
|
|
311
|
+
description: "Set the Appwrite endpoint",
|
|
312
|
+
})
|
|
313
|
+
.option("projectId", {
|
|
314
|
+
type: "string",
|
|
315
|
+
description: "Set the Appwrite project ID",
|
|
316
|
+
})
|
|
317
|
+
.option("apiKey", {
|
|
318
|
+
type: "string",
|
|
319
|
+
description: "Set the Appwrite API key",
|
|
320
|
+
})
|
|
321
|
+
.option("transfer", {
|
|
322
|
+
type: "boolean",
|
|
323
|
+
description: "Transfer documents and files between databases, collections, or projects",
|
|
324
|
+
})
|
|
325
|
+
.option("fromDbId", {
|
|
326
|
+
alias: ["fromDb", "sourceDbId", "sourceDb"],
|
|
327
|
+
type: "string",
|
|
328
|
+
description: "Source database ID for transfer operations",
|
|
329
|
+
})
|
|
330
|
+
.option("toDbId", {
|
|
331
|
+
alias: ["toDb", "targetDbId", "targetDb"],
|
|
332
|
+
type: "string",
|
|
333
|
+
description: "Target database ID for transfer operations",
|
|
334
|
+
})
|
|
335
|
+
.option("fromCollectionId", {
|
|
336
|
+
alias: ["fromCollId", "fromColl"],
|
|
337
|
+
type: "string",
|
|
338
|
+
description: "Set the source collection ID for transfer",
|
|
339
|
+
})
|
|
340
|
+
.option("toCollectionId", {
|
|
341
|
+
alias: ["toCollId", "toColl"],
|
|
342
|
+
type: "string",
|
|
343
|
+
description: "Set the destination collection ID for transfer",
|
|
344
|
+
})
|
|
345
|
+
.option("fromBucketId", {
|
|
346
|
+
type: "string",
|
|
347
|
+
description: "Set the source bucket ID for transfer",
|
|
348
|
+
})
|
|
349
|
+
.option("toBucketId", {
|
|
350
|
+
type: "string",
|
|
351
|
+
description: "Set the destination bucket ID for transfer",
|
|
352
|
+
})
|
|
353
|
+
.option("remoteEndpoint", {
|
|
354
|
+
type: "string",
|
|
355
|
+
description: "Set the remote Appwrite endpoint for transfer",
|
|
356
|
+
})
|
|
357
|
+
.option("remoteProjectId", {
|
|
358
|
+
type: "string",
|
|
359
|
+
description: "Set the remote Appwrite project ID for transfer",
|
|
360
|
+
})
|
|
361
|
+
.option("remoteApiKey", {
|
|
362
|
+
type: "string",
|
|
363
|
+
description: "Set the remote Appwrite API key for transfer",
|
|
364
|
+
})
|
|
365
|
+
.option("setup", {
|
|
366
|
+
type: "boolean",
|
|
367
|
+
description: "Initialize project with configuration files and directory structure",
|
|
368
|
+
})
|
|
369
|
+
.option("updateFunctionSpec", {
|
|
370
|
+
type: "boolean",
|
|
371
|
+
description: "Update function specifications",
|
|
372
|
+
})
|
|
373
|
+
.option("functionId", {
|
|
374
|
+
type: "string",
|
|
375
|
+
description: "Function ID to update",
|
|
376
|
+
})
|
|
377
|
+
.option("specification", {
|
|
378
|
+
type: "string",
|
|
379
|
+
description: "New function specification (e.g., 's-1vcpu-1gb')",
|
|
380
|
+
choices: [
|
|
381
|
+
"s-0.5vcpu-512mb",
|
|
382
|
+
"s-1vcpu-1gb",
|
|
383
|
+
"s-2vcpu-2gb",
|
|
384
|
+
"s-2vcpu-4gb",
|
|
385
|
+
"s-4vcpu-4gb",
|
|
386
|
+
"s-4vcpu-8gb",
|
|
387
|
+
"s-8vcpu-4gb",
|
|
388
|
+
"s-8vcpu-8gb",
|
|
389
|
+
],
|
|
390
|
+
})
|
|
391
|
+
.option("migrateConfig", {
|
|
392
|
+
alias: ["migrate"],
|
|
393
|
+
type: "boolean",
|
|
394
|
+
description: "Migrate appwriteConfig.ts to .appwrite structure with YAML configuration",
|
|
395
|
+
})
|
|
396
|
+
.option("generateConstants", {
|
|
397
|
+
alias: ["constants"],
|
|
398
|
+
type: "boolean",
|
|
399
|
+
description: "Generate cross-language constants file with database, collection, bucket, and function IDs",
|
|
400
|
+
})
|
|
401
|
+
.option("constantsLanguages", {
|
|
402
|
+
type: "string",
|
|
403
|
+
description: "Comma-separated list of languages for constants (typescript,javascript,python,php,dart,json,env)",
|
|
404
|
+
default: "typescript",
|
|
405
|
+
})
|
|
406
|
+
.option("constantsOutput", {
|
|
407
|
+
type: "string",
|
|
408
|
+
description: "Output directory for generated constants files (default: config-folder/constants)",
|
|
409
|
+
default: "auto",
|
|
410
|
+
})
|
|
411
|
+
.option("constantsInclude", {
|
|
412
|
+
type: "string",
|
|
413
|
+
description: "Comma-separated categories to include: databases,collections,buckets,functions",
|
|
414
|
+
})
|
|
415
|
+
.option("generateSchemas", {
|
|
416
|
+
type: "boolean",
|
|
417
|
+
description: "Generate schemas/models without interactive prompts",
|
|
418
|
+
})
|
|
419
|
+
.option("schemaFormat", {
|
|
420
|
+
type: "string",
|
|
421
|
+
choices: ["zod", "json", "pydantic", "both", "all"],
|
|
422
|
+
description: "Schema format: zod, json, pydantic, both (zod+json), or all",
|
|
423
|
+
})
|
|
424
|
+
.option("schemaOutDir", {
|
|
425
|
+
type: "string",
|
|
426
|
+
description: "Output directory for generated schemas (absolute path respected)",
|
|
427
|
+
})
|
|
428
|
+
.option("migrateCollectionsToTables", {
|
|
429
|
+
alias: ["migrate-collections"],
|
|
430
|
+
type: "boolean",
|
|
431
|
+
description: "Migrate collections to tables format for TablesDB API compatibility",
|
|
432
|
+
})
|
|
433
|
+
.option("useSession", {
|
|
434
|
+
alias: ["session"],
|
|
435
|
+
type: "boolean",
|
|
436
|
+
description: "Use Appwrite CLI session authentication instead of API key",
|
|
437
|
+
})
|
|
438
|
+
.option("sessionCookie", {
|
|
439
|
+
type: "string",
|
|
440
|
+
description: "Explicit session cookie to use for authentication",
|
|
441
|
+
})
|
|
442
|
+
.parse();
|
|
443
|
+
async function main() {
|
|
444
|
+
const startTime = Date.now();
|
|
445
|
+
const operationStats = {};
|
|
446
|
+
// Early session detection for better user guidance
|
|
447
|
+
const availableSessions = getAvailableSessions();
|
|
448
|
+
let hasAnyValidSessions = availableSessions.length > 0;
|
|
449
|
+
if (argv.it) {
|
|
450
|
+
const cli = new InteractiveCLI(process.cwd());
|
|
451
|
+
await cli.run();
|
|
452
|
+
}
|
|
453
|
+
else {
|
|
454
|
+
// Enhanced config creation with session and project file support
|
|
455
|
+
let directConfig = undefined;
|
|
456
|
+
// Show authentication status on startup if no config provided
|
|
457
|
+
if (!argv.config &&
|
|
458
|
+
!argv.endpoint &&
|
|
459
|
+
!argv.projectId &&
|
|
460
|
+
!argv.apiKey &&
|
|
461
|
+
!argv.useSession &&
|
|
462
|
+
!argv.sessionCookie) {
|
|
463
|
+
if (hasAnyValidSessions) {
|
|
464
|
+
MessageFormatter.info(`Found ${availableSessions.length} available session(s)`, { prefix: "Auth" });
|
|
465
|
+
availableSessions.forEach((session) => {
|
|
466
|
+
MessageFormatter.info(` \u2022 ${session.projectId} (${session.email || "unknown"}) at ${session.endpoint}`, { prefix: "Auth" });
|
|
467
|
+
});
|
|
468
|
+
MessageFormatter.info("Use --session to enable session authentication", { prefix: "Auth" });
|
|
469
|
+
}
|
|
470
|
+
else {
|
|
471
|
+
MessageFormatter.info("No active Appwrite sessions found", {
|
|
472
|
+
prefix: "Auth",
|
|
473
|
+
});
|
|
474
|
+
MessageFormatter.info("\u2022 Run 'appwrite login' to authenticate with session", { prefix: "Auth" });
|
|
475
|
+
MessageFormatter.info("\u2022 Or provide --apiKey for API key authentication", { prefix: "Auth" });
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
// Priority 1: Check for appwrite.json project configuration
|
|
479
|
+
const projectConfigPath = findAppwriteProjectConfig(process.cwd());
|
|
480
|
+
if (projectConfigPath) {
|
|
481
|
+
const projectConfig = loadAppwriteProjectConfig(projectConfigPath);
|
|
482
|
+
if (projectConfig) {
|
|
483
|
+
directConfig = projectConfigToAppwriteConfig(projectConfig);
|
|
484
|
+
MessageFormatter.info(`Loaded project configuration from ${projectConfigPath}`, { prefix: "CLI" });
|
|
485
|
+
}
|
|
486
|
+
}
|
|
487
|
+
// Priority 2: CLI arguments override project config
|
|
488
|
+
if (argv.endpoint ||
|
|
489
|
+
argv.projectId ||
|
|
490
|
+
argv.apiKey ||
|
|
491
|
+
argv.useSession ||
|
|
492
|
+
argv.sessionCookie) {
|
|
493
|
+
directConfig = {
|
|
494
|
+
...directConfig,
|
|
495
|
+
appwriteEndpoint: argv.endpoint || directConfig?.appwriteEndpoint,
|
|
496
|
+
appwriteProject: argv.projectId || directConfig?.appwriteProject,
|
|
497
|
+
appwriteKey: argv.apiKey || directConfig?.appwriteKey,
|
|
498
|
+
};
|
|
499
|
+
}
|
|
500
|
+
// Priority 3: Session authentication support with improved detection
|
|
501
|
+
let sessionAuthAvailable = false;
|
|
502
|
+
if (directConfig?.appwriteEndpoint && directConfig?.appwriteProject) {
|
|
503
|
+
sessionAuthAvailable = hasSessionAuth(directConfig.appwriteEndpoint, directConfig.appwriteProject);
|
|
504
|
+
}
|
|
505
|
+
if (argv.useSession || argv.sessionCookie) {
|
|
506
|
+
if (argv.sessionCookie) {
|
|
507
|
+
// Explicit session cookie provided
|
|
508
|
+
MessageFormatter.info("Using explicit session cookie for authentication", { prefix: "Auth" });
|
|
509
|
+
}
|
|
510
|
+
else if (sessionAuthAvailable) {
|
|
511
|
+
MessageFormatter.info("Session authentication detected and will be used", { prefix: "Auth" });
|
|
512
|
+
}
|
|
513
|
+
else {
|
|
514
|
+
MessageFormatter.warning("Session authentication requested but no valid session found", { prefix: "Auth" });
|
|
515
|
+
const availableSessions = getAvailableSessions();
|
|
516
|
+
if (availableSessions.length > 0) {
|
|
517
|
+
MessageFormatter.info(`Available sessions: ${availableSessions
|
|
518
|
+
.map((s) => `${s.projectId} (${s.email || "unknown"})`)
|
|
519
|
+
.join(", ")}`, { prefix: "Auth" });
|
|
520
|
+
MessageFormatter.info("Use --session flag to enable session authentication", { prefix: "Auth" });
|
|
521
|
+
}
|
|
522
|
+
else {
|
|
523
|
+
MessageFormatter.warning("No Appwrite CLI sessions found. Please run 'appwrite login' first.", { prefix: "Auth" });
|
|
524
|
+
}
|
|
525
|
+
MessageFormatter.error("Session authentication requested but not available", undefined, { prefix: "Auth" });
|
|
526
|
+
return; // Exit early if session auth was requested but not available
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
else if (sessionAuthAvailable && !argv.apiKey) {
|
|
530
|
+
// Auto-detect session authentication when no API key is provided
|
|
531
|
+
MessageFormatter.info("Session authentication detected - no API key required", { prefix: "Auth" });
|
|
532
|
+
MessageFormatter.info("Use --session flag to explicitly enable session authentication", { prefix: "Auth" });
|
|
533
|
+
}
|
|
534
|
+
// Enhanced session authentication support:
|
|
535
|
+
// 1. If session auth is explicitly requested via flags, use it
|
|
536
|
+
// 2. If no API key is provided but sessions are available, offer to use session auth
|
|
537
|
+
// 3. Auto-detect session authentication when possible
|
|
538
|
+
let finalDirectConfig = directConfig;
|
|
539
|
+
if ((argv.useSession || argv.sessionCookie) &&
|
|
540
|
+
(!directConfig ||
|
|
541
|
+
!directConfig.appwriteEndpoint ||
|
|
542
|
+
!directConfig.appwriteProject)) {
|
|
543
|
+
// Don't pass incomplete directConfig - let UtilsController load YAML config normally
|
|
544
|
+
finalDirectConfig = null;
|
|
545
|
+
}
|
|
546
|
+
else if (finalDirectConfig &&
|
|
547
|
+
!finalDirectConfig.appwriteKey &&
|
|
548
|
+
!argv.useSession &&
|
|
549
|
+
!argv.sessionCookie) {
|
|
550
|
+
// Auto-detect session authentication when no API key provided
|
|
551
|
+
if (sessionAuthAvailable) {
|
|
552
|
+
MessageFormatter.info("No API key provided, but session authentication is available", { prefix: "Auth" });
|
|
553
|
+
MessageFormatter.info("Automatically using session authentication (add --session to suppress this message)", { prefix: "Auth" });
|
|
554
|
+
// Implicitly enable session authentication
|
|
555
|
+
argv.useSession = true;
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
// Create controller with session authentication support using singleton
|
|
559
|
+
const controller = UtilsController.getInstance(process.cwd(), finalDirectConfig);
|
|
560
|
+
// Pass session authentication options to the controller
|
|
561
|
+
const initOptions = {};
|
|
562
|
+
if (argv.useSession || argv.sessionCookie) {
|
|
563
|
+
initOptions.useSession = true;
|
|
564
|
+
if (argv.sessionCookie) {
|
|
565
|
+
initOptions.sessionCookie = argv.sessionCookie;
|
|
566
|
+
}
|
|
567
|
+
}
|
|
568
|
+
await controller.init(initOptions);
|
|
569
|
+
if (argv.setup) {
|
|
570
|
+
await setupDirsFiles(false, process.cwd());
|
|
571
|
+
return;
|
|
572
|
+
}
|
|
573
|
+
if (argv.migrateConfig) {
|
|
574
|
+
const { migrateConfig } = await import("./utils/configMigration.js");
|
|
575
|
+
await migrateConfig(process.cwd());
|
|
576
|
+
return;
|
|
577
|
+
}
|
|
578
|
+
if (argv.generateConstants) {
|
|
579
|
+
const { ConstantsGenerator } = await import("./utils/constantsGenerator.js");
|
|
580
|
+
if (!controller.config) {
|
|
581
|
+
MessageFormatter.error("No Appwrite configuration found", undefined, {
|
|
582
|
+
prefix: "Constants",
|
|
583
|
+
});
|
|
584
|
+
return;
|
|
585
|
+
}
|
|
586
|
+
const languages = argv
|
|
587
|
+
.constantsLanguages.split(",")
|
|
588
|
+
.map((l) => l.trim());
|
|
589
|
+
// Determine output directory - use config folder/constants by default, or custom path if specified
|
|
590
|
+
let outputDir;
|
|
591
|
+
if (argv.constantsOutput === "auto") {
|
|
592
|
+
// Default case: use config directory + constants, fallback to current directory
|
|
593
|
+
const configPath = controller.getAppwriteFolderPath();
|
|
594
|
+
outputDir = configPath
|
|
595
|
+
? path.join(configPath, "constants")
|
|
596
|
+
: path.join(process.cwd(), "constants");
|
|
597
|
+
}
|
|
598
|
+
else {
|
|
599
|
+
// Custom output directory specified
|
|
600
|
+
outputDir = argv.constantsOutput;
|
|
601
|
+
}
|
|
602
|
+
MessageFormatter.info(`Generating constants for languages: ${languages.join(", ")}`, { prefix: "Constants" });
|
|
603
|
+
const generator = new ConstantsGenerator(controller.config);
|
|
604
|
+
await generator.generateFiles(languages, outputDir);
|
|
605
|
+
operationStats.generatedConstants = languages.length;
|
|
606
|
+
MessageFormatter.success(`Constants generated in ${outputDir}`, {
|
|
607
|
+
prefix: "Constants",
|
|
608
|
+
});
|
|
609
|
+
return;
|
|
610
|
+
}
|
|
611
|
+
if (argv.migrateCollectionsToTables) {
|
|
612
|
+
try {
|
|
613
|
+
if (!controller.config) {
|
|
614
|
+
MessageFormatter.error("No Appwrite configuration found", undefined, {
|
|
615
|
+
prefix: "Migration",
|
|
616
|
+
});
|
|
617
|
+
return;
|
|
618
|
+
}
|
|
619
|
+
// Get the config path from the controller or use .appwrite in current directory
|
|
620
|
+
let configPath = controller.getAppwriteFolderPath();
|
|
621
|
+
if (!configPath) {
|
|
622
|
+
// Try .appwrite in current directory
|
|
623
|
+
const defaultPath = path.join(process.cwd(), ".appwrite");
|
|
624
|
+
if (fs.existsSync(defaultPath)) {
|
|
625
|
+
configPath = defaultPath;
|
|
626
|
+
}
|
|
627
|
+
else {
|
|
628
|
+
MessageFormatter.error("Could not determine configuration folder path", undefined, { prefix: "Migration" });
|
|
629
|
+
MessageFormatter.info("Make sure you have a .appwrite/ folder in your current directory", { prefix: "Migration" });
|
|
630
|
+
return;
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
// Check if migration conditions are met
|
|
634
|
+
const migrationCheck = checkMigrationConditions(configPath);
|
|
635
|
+
if (!migrationCheck.allowed) {
|
|
636
|
+
MessageFormatter.error(`Migration not allowed: ${migrationCheck.reason}`, undefined, { prefix: "Migration" });
|
|
637
|
+
MessageFormatter.info("Migration requirements:", {
|
|
638
|
+
prefix: "Migration",
|
|
639
|
+
});
|
|
640
|
+
MessageFormatter.info(" • Configuration must be loaded (use --config or have .appwrite/ folder)", { prefix: "Migration" });
|
|
641
|
+
MessageFormatter.info(" • collections/ folder must exist with YAML files", { prefix: "Migration" });
|
|
642
|
+
MessageFormatter.info(" • tables/ folder must not exist or be empty", { prefix: "Migration" });
|
|
643
|
+
return;
|
|
644
|
+
}
|
|
645
|
+
const { migrateCollectionsToTables } = await import("./config/configMigration.js");
|
|
646
|
+
MessageFormatter.info("Starting collections to tables migration...", {
|
|
647
|
+
prefix: "Migration",
|
|
648
|
+
});
|
|
649
|
+
const result = migrateCollectionsToTables(controller.config, {
|
|
650
|
+
strategy: "full_migration",
|
|
651
|
+
validateResult: true,
|
|
652
|
+
dryRun: false,
|
|
653
|
+
});
|
|
654
|
+
if (result.success) {
|
|
655
|
+
operationStats.migratedCollections = result.changes.length;
|
|
656
|
+
MessageFormatter.success("Collections migration completed successfully", { prefix: "Migration" });
|
|
657
|
+
}
|
|
658
|
+
else {
|
|
659
|
+
MessageFormatter.error(`Migration failed: ${result.errors.join(", ")}`, undefined, { prefix: "Migration" });
|
|
660
|
+
process.exit(1);
|
|
661
|
+
}
|
|
662
|
+
}
|
|
663
|
+
catch (error) {
|
|
664
|
+
MessageFormatter.error("Migration failed", error instanceof Error ? error : new Error(String(error)), { prefix: "Migration" });
|
|
665
|
+
process.exit(1);
|
|
666
|
+
}
|
|
667
|
+
return;
|
|
668
|
+
}
|
|
669
|
+
if (!controller.config) {
|
|
670
|
+
// Provide better guidance based on available authentication methods
|
|
671
|
+
const availableSessions = getAvailableSessions();
|
|
672
|
+
if (availableSessions.length > 0) {
|
|
673
|
+
MessageFormatter.error("No Appwrite configuration found", undefined, {
|
|
674
|
+
prefix: "CLI",
|
|
675
|
+
});
|
|
676
|
+
MessageFormatter.info("Available authentication options:", {
|
|
677
|
+
prefix: "Auth",
|
|
678
|
+
});
|
|
679
|
+
MessageFormatter.info("• Session authentication: Add --session flag", {
|
|
680
|
+
prefix: "Auth",
|
|
681
|
+
});
|
|
682
|
+
MessageFormatter.info("• API key authentication: Add --apiKey YOUR_API_KEY", { prefix: "Auth" });
|
|
683
|
+
MessageFormatter.info(`• Available sessions: ${availableSessions
|
|
684
|
+
.map((s) => `${s.projectId} (${s.email || "unknown"})`)
|
|
685
|
+
.join(", ")}`, { prefix: "Auth" });
|
|
686
|
+
}
|
|
687
|
+
else {
|
|
688
|
+
MessageFormatter.error("No Appwrite configuration found", undefined, {
|
|
689
|
+
prefix: "CLI",
|
|
690
|
+
});
|
|
691
|
+
MessageFormatter.info("Authentication options:", { prefix: "Auth" });
|
|
692
|
+
MessageFormatter.info("• Login with Appwrite CLI: Run 'appwrite login' then use --session flag", { prefix: "Auth" });
|
|
693
|
+
MessageFormatter.info("• Use API key: Add --apiKey YOUR_API_KEY", {
|
|
694
|
+
prefix: "Auth",
|
|
695
|
+
});
|
|
696
|
+
MessageFormatter.info("• Create config file: Run with --setup to initialize project configuration", { prefix: "Auth" });
|
|
697
|
+
}
|
|
698
|
+
return;
|
|
699
|
+
}
|
|
700
|
+
const parsedArgv = argv;
|
|
701
|
+
// List backups if requested
|
|
702
|
+
if (parsedArgv.listBackups) {
|
|
703
|
+
const { AdapterFactory } = await import("./adapters/AdapterFactory.js");
|
|
704
|
+
const { listBackups } = await import("./shared/backupTracking.js");
|
|
705
|
+
if (!controller.config) {
|
|
706
|
+
MessageFormatter.error("No Appwrite configuration found", undefined, {
|
|
707
|
+
prefix: "Backups",
|
|
708
|
+
});
|
|
709
|
+
return;
|
|
710
|
+
}
|
|
711
|
+
const { adapter } = await AdapterFactory.create({
|
|
712
|
+
appwriteEndpoint: controller.config.appwriteEndpoint,
|
|
713
|
+
appwriteProject: controller.config.appwriteProject,
|
|
714
|
+
appwriteKey: controller.config.appwriteKey,
|
|
715
|
+
});
|
|
716
|
+
const databases = parsedArgv.dbIds
|
|
717
|
+
? await controller.getDatabasesByIds(parsedArgv.dbIds.split(","))
|
|
718
|
+
: await fetchAllDatabases(controller.database);
|
|
719
|
+
if (!databases || databases.length === 0) {
|
|
720
|
+
MessageFormatter.info("No databases found", { prefix: "Backups" });
|
|
721
|
+
return;
|
|
722
|
+
}
|
|
723
|
+
for (const db of databases) {
|
|
724
|
+
const backups = await listBackups(adapter, db.$id);
|
|
725
|
+
MessageFormatter.info(`\nBackups for database: ${db.name} (${db.$id})`, { prefix: "Backups" });
|
|
726
|
+
if (backups.length === 0) {
|
|
727
|
+
MessageFormatter.info(" No backups found", { prefix: "Backups" });
|
|
728
|
+
}
|
|
729
|
+
else {
|
|
730
|
+
backups.forEach((backup, index) => {
|
|
731
|
+
const date = new Date(backup.$createdAt).toLocaleString();
|
|
732
|
+
const size = MessageFormatter.formatBytes(backup.sizeBytes);
|
|
733
|
+
MessageFormatter.info(` ${index + 1}. ${date} - ${backup.format.toUpperCase()} - ${size} - ${backup.collections} collections, ${backup.documents} documents`, { prefix: "Backups" });
|
|
734
|
+
});
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
return;
|
|
738
|
+
}
|
|
739
|
+
const options = {
|
|
740
|
+
databases: parsedArgv.dbIds
|
|
741
|
+
? await controller.getDatabasesByIds(parsedArgv.dbIds.split(","))
|
|
742
|
+
: undefined,
|
|
743
|
+
collections: parsedArgv.collectionIds?.split(","),
|
|
744
|
+
doBackup: parsedArgv.backup,
|
|
745
|
+
wipeDatabase: parsedArgv.wipe === "all" || parsedArgv.wipe === "docs",
|
|
746
|
+
wipeDocumentStorage: parsedArgv.wipe === "all" || parsedArgv.wipe === "storage",
|
|
747
|
+
wipeUsers: parsedArgv.wipe === "all" || parsedArgv.wipe === "users",
|
|
748
|
+
generateSchemas: parsedArgv.generate,
|
|
749
|
+
importData: parsedArgv.import,
|
|
750
|
+
shouldWriteFile: parsedArgv.writeData,
|
|
751
|
+
wipeCollections: parsedArgv.wipeCollections,
|
|
752
|
+
transferUsers: parsedArgv.transferUsers,
|
|
753
|
+
};
|
|
754
|
+
if (parsedArgv.updateFunctionSpec) {
|
|
755
|
+
if (!parsedArgv.functionId || !parsedArgv.specification) {
|
|
756
|
+
throw new Error("Function ID and specification are required for updating function specs");
|
|
757
|
+
}
|
|
758
|
+
MessageFormatter.info(`Updating function specification for ${parsedArgv.functionId} to ${parsedArgv.specification}`, { prefix: "Functions" });
|
|
759
|
+
const specifications = await listSpecifications(controller.appwriteServer);
|
|
760
|
+
if (!specifications.specifications.some((s) => s.slug === parsedArgv.specification)) {
|
|
761
|
+
MessageFormatter.error(`Specification ${parsedArgv.specification} not found`, undefined, { prefix: "Functions" });
|
|
762
|
+
return;
|
|
763
|
+
}
|
|
764
|
+
await controller.updateFunctionSpecifications(parsedArgv.functionId, parsedArgv.specification);
|
|
765
|
+
}
|
|
766
|
+
// Add default databases if not specified (only if we need them for operations)
|
|
767
|
+
const needsDatabases = options.doBackup ||
|
|
768
|
+
options.wipeDatabase ||
|
|
769
|
+
options.wipeDocumentStorage ||
|
|
770
|
+
options.wipeUsers ||
|
|
771
|
+
options.wipeCollections ||
|
|
772
|
+
options.importData ||
|
|
773
|
+
parsedArgv.sync ||
|
|
774
|
+
parsedArgv.transfer;
|
|
775
|
+
if (needsDatabases &&
|
|
776
|
+
(!options.databases || options.databases.length === 0)) {
|
|
777
|
+
const allDatabases = await fetchAllDatabases(controller.database);
|
|
778
|
+
options.databases = allDatabases;
|
|
779
|
+
}
|
|
780
|
+
// Add default collections if not specified
|
|
781
|
+
if (!options.collections || options.collections.length === 0) {
|
|
782
|
+
if (controller.config && controller.config.collections) {
|
|
783
|
+
options.collections = controller.config.collections.map((c) => c.name);
|
|
784
|
+
}
|
|
785
|
+
else {
|
|
786
|
+
options.collections = [];
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
// Comprehensive backup (all databases + all buckets)
|
|
790
|
+
if (parsedArgv.comprehensiveBackup) {
|
|
791
|
+
const { comprehensiveBackup } = await import("./backups/operations/comprehensiveBackup.js");
|
|
792
|
+
const { AdapterFactory } = await import("./adapters/AdapterFactory.js");
|
|
793
|
+
// Get tracking database ID (interactive prompt if not specified)
|
|
794
|
+
let trackingDatabaseId = parsedArgv.trackingDatabaseId;
|
|
795
|
+
if (!trackingDatabaseId) {
|
|
796
|
+
// Fetch all databases for selection
|
|
797
|
+
const allDatabases = await fetchAllDatabases(controller.database);
|
|
798
|
+
if (allDatabases.length === 0) {
|
|
799
|
+
MessageFormatter.error("No databases found. Cannot create comprehensive backup without a tracking database.", undefined, { prefix: "Backup" });
|
|
800
|
+
return;
|
|
801
|
+
}
|
|
802
|
+
if (allDatabases.length === 1) {
|
|
803
|
+
trackingDatabaseId = allDatabases[0].$id;
|
|
804
|
+
MessageFormatter.info(`Using only available database for tracking: ${allDatabases[0].name} (${trackingDatabaseId})`, { prefix: "Backup" });
|
|
805
|
+
}
|
|
806
|
+
else {
|
|
807
|
+
// Interactive selection
|
|
808
|
+
const inquirer = (await import("inquirer")).default;
|
|
809
|
+
const answer = await inquirer.prompt([
|
|
810
|
+
{
|
|
811
|
+
type: "list",
|
|
812
|
+
name: "trackingDb",
|
|
813
|
+
message: "Select database to store backup tracking metadata:",
|
|
814
|
+
choices: allDatabases.map((db) => ({
|
|
815
|
+
name: `${db.name} (${db.$id})`,
|
|
816
|
+
value: db.$id,
|
|
817
|
+
})),
|
|
818
|
+
},
|
|
819
|
+
]);
|
|
820
|
+
trackingDatabaseId = answer.trackingDb;
|
|
821
|
+
}
|
|
822
|
+
}
|
|
823
|
+
// Ensure trackingDatabaseId is defined before proceeding
|
|
824
|
+
if (!trackingDatabaseId) {
|
|
825
|
+
throw new Error("Tracking database ID is required for comprehensive backup");
|
|
826
|
+
}
|
|
827
|
+
MessageFormatter.info(`Using tracking database: ${trackingDatabaseId}`, {
|
|
828
|
+
prefix: "Backup",
|
|
829
|
+
});
|
|
830
|
+
// Create adapter for backup tracking
|
|
831
|
+
const { adapter } = await AdapterFactory.create({
|
|
832
|
+
appwriteEndpoint: controller.config.appwriteEndpoint,
|
|
833
|
+
appwriteProject: controller.config.appwriteProject,
|
|
834
|
+
appwriteKey: controller.config.appwriteKey,
|
|
835
|
+
sessionCookie: controller.config.sessionCookie,
|
|
836
|
+
});
|
|
837
|
+
const result = await comprehensiveBackup(controller.config, controller.database, controller.storage, adapter, {
|
|
838
|
+
trackingDatabaseId,
|
|
839
|
+
backupFormat: parsedArgv.backupFormat || "zip",
|
|
840
|
+
parallelDownloads: parsedArgv.parallelDownloads || 10,
|
|
841
|
+
onProgress: (message) => {
|
|
842
|
+
MessageFormatter.info(message, { prefix: "Backup" });
|
|
843
|
+
},
|
|
844
|
+
});
|
|
845
|
+
operationStats.comprehensiveBackup = 1;
|
|
846
|
+
operationStats.databasesBackedUp = result.databaseBackups.length;
|
|
847
|
+
operationStats.bucketsBackedUp = result.bucketBackups.length;
|
|
848
|
+
operationStats.totalBackupSize = result.totalSizeBytes;
|
|
849
|
+
if (result.status === "completed") {
|
|
850
|
+
MessageFormatter.success(`Comprehensive backup completed successfully (ID: ${result.backupId})`, { prefix: "Backup" });
|
|
851
|
+
}
|
|
852
|
+
else if (result.status === "partial") {
|
|
853
|
+
MessageFormatter.warning(`Comprehensive backup completed with errors (ID: ${result.backupId})`, { prefix: "Backup" });
|
|
854
|
+
result.errors.forEach((err) => MessageFormatter.warning(err, { prefix: "Backup" }));
|
|
855
|
+
}
|
|
856
|
+
else {
|
|
857
|
+
MessageFormatter.error(`Comprehensive backup failed (ID: ${result.backupId})`, undefined, { prefix: "Backup" });
|
|
858
|
+
result.errors.forEach((err) => MessageFormatter.error(err, undefined, { prefix: "Backup" }));
|
|
859
|
+
}
|
|
860
|
+
}
|
|
861
|
+
if (options.doBackup && options.databases) {
|
|
862
|
+
MessageFormatter.info(`Creating backups for ${options.databases.length} database(s) in ${parsedArgv.backupFormat} format`, { prefix: "Backup" });
|
|
863
|
+
for (const db of options.databases) {
|
|
864
|
+
await controller.backupDatabase(db, parsedArgv.backupFormat || "json");
|
|
865
|
+
}
|
|
866
|
+
operationStats.backups = options.databases.length;
|
|
867
|
+
MessageFormatter.success(`Backup completed for ${options.databases.length} database(s)`, { prefix: "Backup" });
|
|
868
|
+
}
|
|
869
|
+
if (options.wipeDatabase ||
|
|
870
|
+
options.wipeDocumentStorage ||
|
|
871
|
+
options.wipeUsers ||
|
|
872
|
+
options.wipeCollections) {
|
|
873
|
+
// Confirm destructive operations
|
|
874
|
+
const databaseNames = options.databases?.map((db) => db.name) || [];
|
|
875
|
+
const confirmed = await ConfirmationDialogs.confirmDatabaseWipe(databaseNames, {
|
|
876
|
+
includeStorage: options.wipeDocumentStorage,
|
|
877
|
+
includeUsers: options.wipeUsers,
|
|
878
|
+
});
|
|
879
|
+
if (!confirmed) {
|
|
880
|
+
MessageFormatter.info("Operation cancelled by user", { prefix: "CLI" });
|
|
881
|
+
return;
|
|
882
|
+
}
|
|
883
|
+
let wipeStats = { databases: 0, collections: 0, users: 0, buckets: 0 };
|
|
884
|
+
if (parsedArgv.wipe === "all") {
|
|
885
|
+
if (options.databases) {
|
|
886
|
+
for (const db of options.databases) {
|
|
887
|
+
await controller.wipeDatabase(db, true); // true to wipe associated buckets
|
|
888
|
+
}
|
|
889
|
+
wipeStats.databases = options.databases.length;
|
|
890
|
+
}
|
|
891
|
+
await controller.wipeUsers();
|
|
892
|
+
wipeStats.users = 1;
|
|
893
|
+
}
|
|
894
|
+
else if (parsedArgv.wipe === "docs") {
|
|
895
|
+
if (options.databases) {
|
|
896
|
+
for (const db of options.databases) {
|
|
897
|
+
await controller.wipeBucketFromDatabase(db);
|
|
898
|
+
}
|
|
899
|
+
wipeStats.databases = options.databases.length;
|
|
900
|
+
}
|
|
901
|
+
if (parsedArgv.bucketIds) {
|
|
902
|
+
const bucketIds = parsedArgv.bucketIds.split(",");
|
|
903
|
+
for (const bucketId of bucketIds) {
|
|
904
|
+
await controller.wipeDocumentStorage(bucketId);
|
|
905
|
+
}
|
|
906
|
+
wipeStats.buckets = bucketIds.length;
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
else if (parsedArgv.wipe === "users") {
|
|
910
|
+
await controller.wipeUsers();
|
|
911
|
+
wipeStats.users = 1;
|
|
912
|
+
}
|
|
913
|
+
// Handle specific collection wipes
|
|
914
|
+
if (options.wipeCollections && options.databases) {
|
|
915
|
+
for (const db of options.databases) {
|
|
916
|
+
const dbCollections = await fetchAllCollections(db.$id, controller.database);
|
|
917
|
+
const collectionsToWipe = dbCollections.filter((c) => options.collections.includes(c.$id));
|
|
918
|
+
// Confirm collection wipe
|
|
919
|
+
const collectionNames = collectionsToWipe.map((c) => c.name);
|
|
920
|
+
const collectionConfirmed = await ConfirmationDialogs.confirmCollectionWipe(db.name, collectionNames);
|
|
921
|
+
if (collectionConfirmed) {
|
|
922
|
+
for (const collection of collectionsToWipe) {
|
|
923
|
+
await controller.wipeCollection(db, collection);
|
|
924
|
+
}
|
|
925
|
+
wipeStats.collections += collectionsToWipe.length;
|
|
926
|
+
}
|
|
927
|
+
}
|
|
928
|
+
}
|
|
929
|
+
// Show wipe operation summary
|
|
930
|
+
if (wipeStats.databases > 0 ||
|
|
931
|
+
wipeStats.collections > 0 ||
|
|
932
|
+
wipeStats.users > 0 ||
|
|
933
|
+
wipeStats.buckets > 0) {
|
|
934
|
+
operationStats.wipedDatabases = wipeStats.databases;
|
|
935
|
+
operationStats.wipedCollections = wipeStats.collections;
|
|
936
|
+
operationStats.wipedUsers = wipeStats.users;
|
|
937
|
+
operationStats.wipedBuckets = wipeStats.buckets;
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
if (parsedArgv.push) {
|
|
941
|
+
await controller.init();
|
|
942
|
+
if (!controller.database || !controller.config) {
|
|
943
|
+
MessageFormatter.error("Database or config not initialized", undefined, { prefix: "Push" });
|
|
944
|
+
return;
|
|
945
|
+
}
|
|
946
|
+
// Fetch available DBs
|
|
947
|
+
const availableDatabases = await fetchAllDatabases(controller.database);
|
|
948
|
+
if (availableDatabases.length === 0) {
|
|
949
|
+
MessageFormatter.warning("No databases found in remote project", { prefix: "Push" });
|
|
950
|
+
return;
|
|
951
|
+
}
|
|
952
|
+
// Determine selected DBs
|
|
953
|
+
let selectedDbIds = [];
|
|
954
|
+
if (parsedArgv.dbIds) {
|
|
955
|
+
selectedDbIds = parsedArgv.dbIds.split(/[,\s]+/).filter(Boolean);
|
|
956
|
+
}
|
|
957
|
+
else {
|
|
958
|
+
selectedDbIds = await SelectionDialogs.selectDatabases(availableDatabases, controller.config.databases || [], { showSelectAll: false, allowNewOnly: false, defaultSelected: [] });
|
|
959
|
+
}
|
|
960
|
+
if (selectedDbIds.length === 0) {
|
|
961
|
+
MessageFormatter.warning("No databases selected for push", { prefix: "Push" });
|
|
962
|
+
return;
|
|
963
|
+
}
|
|
964
|
+
// Build DatabaseSelection[] with tableIds per DB
|
|
965
|
+
const databaseSelections = [];
|
|
966
|
+
const allConfigItems = controller.config.collections || controller.config.tables || [];
|
|
967
|
+
let lastSelectedTableIds = null;
|
|
968
|
+
for (const dbId of selectedDbIds) {
|
|
969
|
+
const db = availableDatabases.find(d => d.$id === dbId);
|
|
970
|
+
if (!db)
|
|
971
|
+
continue;
|
|
972
|
+
// Filter config items eligible for this DB according to databaseId/databaseIds rule
|
|
973
|
+
const eligibleConfigItems = allConfigItems.filter(item => {
|
|
974
|
+
const one = item.databaseId;
|
|
975
|
+
const many = item.databaseIds;
|
|
976
|
+
if (Array.isArray(many) && many.length > 0)
|
|
977
|
+
return many.includes(dbId);
|
|
978
|
+
if (one)
|
|
979
|
+
return one === dbId;
|
|
980
|
+
return true; // eligible everywhere if unspecified
|
|
981
|
+
});
|
|
982
|
+
// Fetch available tables from remote for selection context
|
|
983
|
+
const availableTables = await fetchAllCollections(dbId, controller.database);
|
|
984
|
+
// Determine selected table IDs
|
|
985
|
+
let selectedTableIds = [];
|
|
986
|
+
if (parsedArgv.collectionIds) {
|
|
987
|
+
// Non-interactive: respect provided table IDs as-is (apply to each selected DB)
|
|
988
|
+
selectedTableIds = parsedArgv.collectionIds.split(/[\,\s]+/).filter(Boolean);
|
|
989
|
+
}
|
|
990
|
+
else {
|
|
991
|
+
// If we have a previous selection, offer to reuse it
|
|
992
|
+
if (lastSelectedTableIds && lastSelectedTableIds.length > 0) {
|
|
993
|
+
const inquirer = (await import("inquirer")).default;
|
|
994
|
+
const { reuseMode } = await inquirer.prompt([
|
|
995
|
+
{
|
|
996
|
+
type: "list",
|
|
997
|
+
name: "reuseMode",
|
|
998
|
+
message: `How do you want to select tables for ${db.name}?`,
|
|
999
|
+
choices: [
|
|
1000
|
+
{ name: `Use same selection as previous (${lastSelectedTableIds.length} items)`, value: "same" },
|
|
1001
|
+
{ name: `Filter by this database (manual select)`, value: "filter" },
|
|
1002
|
+
{ name: `Show all available in this database (manual select)`, value: "all" }
|
|
1003
|
+
],
|
|
1004
|
+
default: "same"
|
|
1005
|
+
}
|
|
1006
|
+
]);
|
|
1007
|
+
if (reuseMode === "same") {
|
|
1008
|
+
selectedTableIds = [...lastSelectedTableIds];
|
|
1009
|
+
}
|
|
1010
|
+
else if (reuseMode === "all") {
|
|
1011
|
+
selectedTableIds = await SelectionDialogs.selectTablesForDatabase(dbId, db.name, availableTables, allConfigItems, { showSelectAll: false, allowNewOnly: false, defaultSelected: lastSelectedTableIds });
|
|
1012
|
+
}
|
|
1013
|
+
else {
|
|
1014
|
+
selectedTableIds = await SelectionDialogs.selectTablesForDatabase(dbId, db.name, availableTables, eligibleConfigItems, { showSelectAll: false, allowNewOnly: true, defaultSelected: lastSelectedTableIds });
|
|
1015
|
+
}
|
|
1016
|
+
}
|
|
1017
|
+
else {
|
|
1018
|
+
selectedTableIds = await SelectionDialogs.selectTablesForDatabase(dbId, db.name, availableTables, eligibleConfigItems, { showSelectAll: false, allowNewOnly: true, defaultSelected: [] });
|
|
1019
|
+
}
|
|
1020
|
+
}
|
|
1021
|
+
databaseSelections.push({
|
|
1022
|
+
databaseId: db.$id,
|
|
1023
|
+
databaseName: db.name,
|
|
1024
|
+
tableIds: selectedTableIds,
|
|
1025
|
+
tableNames: [],
|
|
1026
|
+
isNew: false,
|
|
1027
|
+
});
|
|
1028
|
+
if (!parsedArgv.collectionIds) {
|
|
1029
|
+
lastSelectedTableIds = selectedTableIds;
|
|
1030
|
+
}
|
|
1031
|
+
}
|
|
1032
|
+
if (databaseSelections.every(sel => sel.tableIds.length === 0)) {
|
|
1033
|
+
MessageFormatter.warning("No tables/collections selected for push", { prefix: "Push" });
|
|
1034
|
+
return;
|
|
1035
|
+
}
|
|
1036
|
+
const pushSummary = {
|
|
1037
|
+
databases: databaseSelections.length,
|
|
1038
|
+
collections: databaseSelections.reduce((sum, s) => sum + s.tableIds.length, 0),
|
|
1039
|
+
details: databaseSelections.map(s => `${s.databaseId}: ${s.tableIds.length} items`),
|
|
1040
|
+
};
|
|
1041
|
+
// Skip confirmation if both dbIds and collectionIds are provided (non-interactive)
|
|
1042
|
+
if (!(parsedArgv.dbIds && parsedArgv.collectionIds)) {
|
|
1043
|
+
const confirmed = await ConfirmationDialogs.showOperationSummary('Push', pushSummary, { confirmationRequired: true });
|
|
1044
|
+
if (!confirmed) {
|
|
1045
|
+
MessageFormatter.info("Push operation cancelled", { prefix: "Push" });
|
|
1046
|
+
return;
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
await controller.selectivePush(databaseSelections, []);
|
|
1050
|
+
operationStats.pushedDatabases = databaseSelections.length;
|
|
1051
|
+
operationStats.pushedCollections = databaseSelections.reduce((sum, s) => sum + s.tableIds.length, 0);
|
|
1052
|
+
}
|
|
1053
|
+
else if (parsedArgv.sync) {
|
|
1054
|
+
// Enhanced SYNC: Pull from remote with intelligent configuration detection
|
|
1055
|
+
if (parsedArgv.autoSync) {
|
|
1056
|
+
// Legacy behavior: sync everything without prompts
|
|
1057
|
+
MessageFormatter.info("Using auto-sync mode (legacy behavior)", { prefix: "Sync" });
|
|
1058
|
+
const databases = options.databases || (await fetchAllDatabases(controller.database));
|
|
1059
|
+
await controller.synchronizeConfigurations(databases);
|
|
1060
|
+
operationStats.syncedDatabases = databases.length;
|
|
1061
|
+
}
|
|
1062
|
+
else {
|
|
1063
|
+
// Enhanced sync flow with selection dialogs
|
|
1064
|
+
const syncResult = await performEnhancedSync(controller, parsedArgv);
|
|
1065
|
+
if (syncResult) {
|
|
1066
|
+
operationStats.syncedDatabases = syncResult.databases.length;
|
|
1067
|
+
operationStats.syncedCollections = syncResult.totalTables;
|
|
1068
|
+
operationStats.syncedBuckets = syncResult.buckets.length;
|
|
1069
|
+
}
|
|
1070
|
+
}
|
|
1071
|
+
}
|
|
1072
|
+
if (options.generateSchemas) {
|
|
1073
|
+
await controller.generateSchemas();
|
|
1074
|
+
operationStats.generatedSchemas = 1;
|
|
1075
|
+
}
|
|
1076
|
+
if (options.importData) {
|
|
1077
|
+
await controller.importData(options);
|
|
1078
|
+
operationStats.importCompleted = 1;
|
|
1079
|
+
}
|
|
1080
|
+
if (parsedArgv.transfer) {
|
|
1081
|
+
const isRemote = !!parsedArgv.remoteEndpoint;
|
|
1082
|
+
let fromDb, toDb;
|
|
1083
|
+
let targetDatabases;
|
|
1084
|
+
let targetStorage;
|
|
1085
|
+
// Only fetch databases if database IDs are provided
|
|
1086
|
+
if (parsedArgv.fromDbId && parsedArgv.toDbId) {
|
|
1087
|
+
MessageFormatter.info(`Starting database transfer from ${parsedArgv.fromDbId} to ${parsedArgv.toDbId}`, { prefix: "Transfer" });
|
|
1088
|
+
fromDb = (await controller.getDatabasesByIds([parsedArgv.fromDbId]))?.[0];
|
|
1089
|
+
if (!fromDb) {
|
|
1090
|
+
MessageFormatter.error("Source database not found", undefined, {
|
|
1091
|
+
prefix: "Transfer",
|
|
1092
|
+
});
|
|
1093
|
+
return;
|
|
1094
|
+
}
|
|
1095
|
+
if (isRemote) {
|
|
1096
|
+
if (!parsedArgv.remoteEndpoint ||
|
|
1097
|
+
!parsedArgv.remoteProjectId ||
|
|
1098
|
+
!parsedArgv.remoteApiKey) {
|
|
1099
|
+
throw new Error("Remote transfer details are missing");
|
|
1100
|
+
}
|
|
1101
|
+
const remoteClient = getClient(parsedArgv.remoteEndpoint, parsedArgv.remoteProjectId, parsedArgv.remoteApiKey);
|
|
1102
|
+
targetDatabases = new Databases(remoteClient);
|
|
1103
|
+
targetStorage = new Storage(remoteClient);
|
|
1104
|
+
const remoteDbs = await fetchAllDatabases(targetDatabases);
|
|
1105
|
+
toDb = remoteDbs.find((db) => db.$id === parsedArgv.toDbId);
|
|
1106
|
+
if (!toDb) {
|
|
1107
|
+
MessageFormatter.error("Target database not found", undefined, {
|
|
1108
|
+
prefix: "Transfer",
|
|
1109
|
+
});
|
|
1110
|
+
return;
|
|
1111
|
+
}
|
|
1112
|
+
}
|
|
1113
|
+
else {
|
|
1114
|
+
toDb = (await controller.getDatabasesByIds([parsedArgv.toDbId]))?.[0];
|
|
1115
|
+
if (!toDb) {
|
|
1116
|
+
MessageFormatter.error("Target database not found", undefined, {
|
|
1117
|
+
prefix: "Transfer",
|
|
1118
|
+
});
|
|
1119
|
+
return;
|
|
1120
|
+
}
|
|
1121
|
+
}
|
|
1122
|
+
if (!fromDb || !toDb) {
|
|
1123
|
+
MessageFormatter.error("Source or target database not found", undefined, { prefix: "Transfer" });
|
|
1124
|
+
return;
|
|
1125
|
+
}
|
|
1126
|
+
}
|
|
1127
|
+
// Handle storage setup
|
|
1128
|
+
let sourceBucket, targetBucket;
|
|
1129
|
+
if (parsedArgv.fromBucketId) {
|
|
1130
|
+
sourceBucket = await controller.storage?.getBucket(parsedArgv.fromBucketId);
|
|
1131
|
+
}
|
|
1132
|
+
if (parsedArgv.toBucketId) {
|
|
1133
|
+
if (isRemote) {
|
|
1134
|
+
if (!targetStorage) {
|
|
1135
|
+
const remoteClient = getClient(parsedArgv.remoteEndpoint, parsedArgv.remoteProjectId, parsedArgv.remoteApiKey);
|
|
1136
|
+
targetStorage = new Storage(remoteClient);
|
|
1137
|
+
}
|
|
1138
|
+
targetBucket = await targetStorage?.getBucket(parsedArgv.toBucketId);
|
|
1139
|
+
}
|
|
1140
|
+
else {
|
|
1141
|
+
targetBucket = await controller.storage?.getBucket(parsedArgv.toBucketId);
|
|
1142
|
+
}
|
|
1143
|
+
}
|
|
1144
|
+
// Validate that at least one transfer type is specified
|
|
1145
|
+
if (!fromDb && !sourceBucket && !options.transferUsers) {
|
|
1146
|
+
throw new Error("No source database or bucket specified for transfer");
|
|
1147
|
+
}
|
|
1148
|
+
const transferOptions = {
|
|
1149
|
+
isRemote,
|
|
1150
|
+
fromDb,
|
|
1151
|
+
targetDb: toDb,
|
|
1152
|
+
transferEndpoint: parsedArgv.remoteEndpoint,
|
|
1153
|
+
transferProject: parsedArgv.remoteProjectId,
|
|
1154
|
+
transferKey: parsedArgv.remoteApiKey,
|
|
1155
|
+
sourceBucket: sourceBucket,
|
|
1156
|
+
targetBucket: targetBucket,
|
|
1157
|
+
transferUsers: options.transferUsers,
|
|
1158
|
+
};
|
|
1159
|
+
await controller.transferData(transferOptions);
|
|
1160
|
+
operationStats.transfers = 1;
|
|
1161
|
+
}
|
|
1162
|
+
// Show final operation summary if any operations were performed
|
|
1163
|
+
if (Object.keys(operationStats).length > 0) {
|
|
1164
|
+
const duration = Date.now() - startTime;
|
|
1165
|
+
MessageFormatter.operationSummary("CLI Operations", operationStats, duration);
|
|
1166
|
+
}
|
|
1167
|
+
}
|
|
1168
|
+
}
|
|
1169
|
+
main().catch((error) => {
|
|
1170
|
+
MessageFormatter.error("CLI execution failed", error, { prefix: "CLI" });
|
|
1171
|
+
process.exit(1);
|
|
1172
|
+
});
|