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/setupController.ts
CHANGED
@@ -3,6 +3,7 @@ import { loadConfig } from "./utils/loadConfigs.js";
|
|
3
3
|
import path from "path";
|
4
4
|
import fs from "fs";
|
5
5
|
import type { AppwriteConfig } from "appwrite-utils";
|
6
|
+
import { MessageFormatter } from "./shared/messageFormatter.js";
|
6
7
|
|
7
8
|
export class SetupController {
|
8
9
|
private currentDir: string;
|
@@ -14,7 +15,7 @@ export class SetupController {
|
|
14
15
|
|
15
16
|
async runSetup(withExampleData: boolean = false): Promise<void> {
|
16
17
|
await setupDirsFiles(withExampleData, this.currentDir);
|
17
|
-
|
18
|
+
MessageFormatter.success("Setup completed successfully", { prefix: "Setup" });
|
18
19
|
}
|
19
20
|
|
20
21
|
async loadConfig(): Promise<AppwriteConfig | null> {
|
@@ -24,7 +25,7 @@ export class SetupController {
|
|
24
25
|
this.config = await loadConfig(appwriteDir);
|
25
26
|
return this.config;
|
26
27
|
} catch (error) {
|
27
|
-
|
28
|
+
MessageFormatter.error("Error loading config", error as Error, { prefix: "Setup" });
|
28
29
|
return null;
|
29
30
|
}
|
30
31
|
}
|
@@ -0,0 +1,93 @@
|
|
1
|
+
import { z } from "zod";
|
2
|
+
|
3
|
+
/**
|
4
|
+
* Schema for centralized backup tracking table (_appwrite_backups)
|
5
|
+
*
|
6
|
+
* Tracks all backups created for databases, buckets, and comprehensive backups
|
7
|
+
*/
|
8
|
+
|
9
|
+
export type BackupType = 'database' | 'bucket' | 'comprehensive';
|
10
|
+
export type BackupFormat = 'json' | 'zip';
|
11
|
+
export type BackupStatus = 'completed' | 'partial' | 'failed';
|
12
|
+
export type RestorationStatus = 'completed' | 'partial' | 'failed' | 'not_restored';
|
13
|
+
|
14
|
+
export const BACKUP_TYPES = ['database', 'bucket', 'comprehensive'] as const;
|
15
|
+
export const BACKUP_FORMATS = ['json', 'zip'] as const;
|
16
|
+
export const BACKUP_STATUSES = ['completed', 'partial', 'failed'] as const;
|
17
|
+
export const RESTORATION_STATUSES = ['completed', 'partial', 'failed', 'not_restored'] as const;
|
18
|
+
|
19
|
+
export const BackupTypeSchema = z.enum(['database', 'bucket', 'comprehensive']);
|
20
|
+
export const BackupFormatSchema = z.enum(['json', 'zip']);
|
21
|
+
export const BackupStatusSchema = z.enum(['completed', 'partial', 'failed']);
|
22
|
+
export const RestorationStatusSchema = z.enum(['completed', 'partial', 'failed', 'not_restored']);
|
23
|
+
|
24
|
+
export interface BackupMetadata {
|
25
|
+
$id: string;
|
26
|
+
$createdAt: string;
|
27
|
+
$updatedAt: string;
|
28
|
+
|
29
|
+
// Core backup info
|
30
|
+
backupType: BackupType; // Type of backup: database, bucket, or comprehensive
|
31
|
+
backupId: string; // File ID in storage bucket for the backup file
|
32
|
+
manifestFileId?: string; // File ID for the manifest JSON file
|
33
|
+
format: BackupFormat; // 'json' or 'zip'
|
34
|
+
sizeBytes: number; // Total backup file size
|
35
|
+
|
36
|
+
// Resource identification (at least one must be present)
|
37
|
+
databaseId?: string; // Database ID (for database backups)
|
38
|
+
bucketId?: string; // Bucket ID (for bucket backups)
|
39
|
+
comprehensiveBackupId?: string; // Parent comprehensive backup ID
|
40
|
+
|
41
|
+
// Database-specific metrics
|
42
|
+
collections?: number; // Number of collections backed up
|
43
|
+
documents?: number; // Number of documents backed up
|
44
|
+
|
45
|
+
// Bucket-specific metrics
|
46
|
+
fileCount?: number; // Number of files backed up (for bucket backups)
|
47
|
+
|
48
|
+
// Status tracking
|
49
|
+
status: BackupStatus; // 'completed', 'partial', or 'failed'
|
50
|
+
error?: string; // Error message if failed or partial
|
51
|
+
|
52
|
+
// Restoration tracking
|
53
|
+
restoredAt?: string; // ISO timestamp of restoration
|
54
|
+
restorationStatus: RestorationStatus; // Restoration status
|
55
|
+
restorationError?: string; // Error message if restoration failed
|
56
|
+
}
|
57
|
+
|
58
|
+
export const BackupMetadataSchema = z.object({
|
59
|
+
$id: z.string(),
|
60
|
+
$createdAt: z.string(),
|
61
|
+
$updatedAt: z.string(),
|
62
|
+
|
63
|
+
// Core backup info
|
64
|
+
backupType: BackupTypeSchema,
|
65
|
+
backupId: z.string(),
|
66
|
+
manifestFileId: z.string().optional(),
|
67
|
+
format: BackupFormatSchema,
|
68
|
+
sizeBytes: z.number(),
|
69
|
+
|
70
|
+
// Resource identification
|
71
|
+
databaseId: z.string().optional(),
|
72
|
+
bucketId: z.string().optional(),
|
73
|
+
comprehensiveBackupId: z.string().optional(),
|
74
|
+
|
75
|
+
// Database-specific metrics
|
76
|
+
collections: z.number().optional(),
|
77
|
+
documents: z.number().optional(),
|
78
|
+
|
79
|
+
// Bucket-specific metrics
|
80
|
+
fileCount: z.number().optional(),
|
81
|
+
|
82
|
+
// Status tracking
|
83
|
+
status: BackupStatusSchema,
|
84
|
+
error: z.string().optional(),
|
85
|
+
|
86
|
+
// Restoration tracking
|
87
|
+
restoredAt: z.string().optional(),
|
88
|
+
restorationStatus: RestorationStatusSchema.default('not_restored'),
|
89
|
+
restorationError: z.string().optional()
|
90
|
+
});
|
91
|
+
|
92
|
+
export const BACKUP_TABLE_ID = "appwrite_backups";
|
93
|
+
export const BACKUP_TABLE_NAME = "Backup Tracking";
|
@@ -0,0 +1,211 @@
|
|
1
|
+
import type { DatabaseAdapter } from "../adapters/DatabaseAdapter.js";
|
2
|
+
import { logger } from "./logging.js";
|
3
|
+
import { tryAwaitWithRetry } from "../utils/helperFunctions.js";
|
4
|
+
import { Query, ID } from "node-appwrite";
|
5
|
+
import {
|
6
|
+
BACKUP_TABLE_ID,
|
7
|
+
BACKUP_TABLE_NAME,
|
8
|
+
type BackupMetadata,
|
9
|
+
BackupMetadataSchema
|
10
|
+
} from "./backupMetadataSchema.js";
|
11
|
+
|
12
|
+
/**
|
13
|
+
* Checks if backup tracking table exists in database
|
14
|
+
*/
|
15
|
+
async function tableExists(
|
16
|
+
db: DatabaseAdapter,
|
17
|
+
databaseId: string
|
18
|
+
): Promise<boolean> {
|
19
|
+
try {
|
20
|
+
await db.getTable({ databaseId, tableId: BACKUP_TABLE_ID });
|
21
|
+
return true;
|
22
|
+
} catch (error) {
|
23
|
+
return false;
|
24
|
+
}
|
25
|
+
}
|
26
|
+
|
27
|
+
/**
|
28
|
+
* Creates the backup tracking table in the specified database
|
29
|
+
*/
|
30
|
+
export async function createBackupTrackingTable(
|
31
|
+
db: DatabaseAdapter,
|
32
|
+
databaseId: string
|
33
|
+
): Promise<void> {
|
34
|
+
// Check if table already exists
|
35
|
+
const exists = await tableExists(db, databaseId);
|
36
|
+
if (exists) {
|
37
|
+
logger.debug("Backup tracking table already exists", {
|
38
|
+
databaseId,
|
39
|
+
tableId: BACKUP_TABLE_ID
|
40
|
+
});
|
41
|
+
return;
|
42
|
+
}
|
43
|
+
|
44
|
+
logger.info("Creating backup tracking table", {
|
45
|
+
databaseId,
|
46
|
+
tableId: BACKUP_TABLE_ID
|
47
|
+
});
|
48
|
+
|
49
|
+
// Create table
|
50
|
+
await tryAwaitWithRetry(async () => {
|
51
|
+
await db.createTable({
|
52
|
+
databaseId,
|
53
|
+
id: BACKUP_TABLE_ID,
|
54
|
+
name: BACKUP_TABLE_NAME
|
55
|
+
});
|
56
|
+
});
|
57
|
+
|
58
|
+
// Create attributes
|
59
|
+
const attributes = [
|
60
|
+
{
|
61
|
+
key: "backupId",
|
62
|
+
type: "string" as const,
|
63
|
+
size: 50,
|
64
|
+
required: true
|
65
|
+
},
|
66
|
+
{
|
67
|
+
key: "databaseId",
|
68
|
+
type: "string" as const,
|
69
|
+
size: 50,
|
70
|
+
required: true
|
71
|
+
},
|
72
|
+
{
|
73
|
+
key: "sizeBytes",
|
74
|
+
type: "integer" as const,
|
75
|
+
required: true
|
76
|
+
},
|
77
|
+
{
|
78
|
+
key: "collections",
|
79
|
+
type: "integer" as const,
|
80
|
+
required: true
|
81
|
+
},
|
82
|
+
{
|
83
|
+
key: "documents",
|
84
|
+
type: "integer" as const,
|
85
|
+
required: true
|
86
|
+
},
|
87
|
+
{
|
88
|
+
key: "format",
|
89
|
+
type: "enum" as const,
|
90
|
+
elements: ["json", "zip"],
|
91
|
+
required: true
|
92
|
+
},
|
93
|
+
{
|
94
|
+
key: "status",
|
95
|
+
type: "enum" as const,
|
96
|
+
elements: ["completed", "failed"],
|
97
|
+
required: true
|
98
|
+
},
|
99
|
+
{
|
100
|
+
key: "error",
|
101
|
+
type: "string" as const,
|
102
|
+
size: 10000,
|
103
|
+
required: false
|
104
|
+
}
|
105
|
+
];
|
106
|
+
|
107
|
+
for (const attr of attributes) {
|
108
|
+
await tryAwaitWithRetry(async () => {
|
109
|
+
await db.createAttribute({
|
110
|
+
databaseId,
|
111
|
+
tableId: BACKUP_TABLE_ID,
|
112
|
+
...attr
|
113
|
+
});
|
114
|
+
});
|
115
|
+
}
|
116
|
+
|
117
|
+
logger.info("Backup tracking table created successfully", {
|
118
|
+
databaseId,
|
119
|
+
tableId: BACKUP_TABLE_ID
|
120
|
+
});
|
121
|
+
}
|
122
|
+
|
123
|
+
/**
|
124
|
+
* Records backup metadata in the tracking table
|
125
|
+
*/
|
126
|
+
export async function recordBackup(
|
127
|
+
db: DatabaseAdapter,
|
128
|
+
databaseId: string,
|
129
|
+
metadata: Omit<BackupMetadata, '$id' | '$createdAt' | '$updatedAt'>
|
130
|
+
): Promise<BackupMetadata> {
|
131
|
+
// Ensure tracking table exists
|
132
|
+
await createBackupTrackingTable(db, databaseId);
|
133
|
+
|
134
|
+
// Create backup record
|
135
|
+
const result = await db.createRow({
|
136
|
+
databaseId,
|
137
|
+
tableId: BACKUP_TABLE_ID,
|
138
|
+
id: ID.unique(),
|
139
|
+
data: {
|
140
|
+
backupId: metadata.backupId,
|
141
|
+
databaseId: metadata.databaseId,
|
142
|
+
sizeBytes: metadata.sizeBytes,
|
143
|
+
collections: metadata.collections,
|
144
|
+
documents: metadata.documents,
|
145
|
+
format: metadata.format,
|
146
|
+
status: metadata.status,
|
147
|
+
error: metadata.error
|
148
|
+
}
|
149
|
+
});
|
150
|
+
|
151
|
+
logger.info("Recorded backup metadata", {
|
152
|
+
backupId: metadata.backupId,
|
153
|
+
databaseId: metadata.databaseId,
|
154
|
+
format: metadata.format
|
155
|
+
});
|
156
|
+
|
157
|
+
return result.data as BackupMetadata;
|
158
|
+
}
|
159
|
+
|
160
|
+
/**
|
161
|
+
* Lists all backups for a database, sorted by creation date (newest first)
|
162
|
+
*/
|
163
|
+
export async function listBackups(
|
164
|
+
db: DatabaseAdapter,
|
165
|
+
databaseId: string
|
166
|
+
): Promise<BackupMetadata[]> {
|
167
|
+
try {
|
168
|
+
const result = await db.listRows({
|
169
|
+
databaseId,
|
170
|
+
tableId: BACKUP_TABLE_ID,
|
171
|
+
queries: [
|
172
|
+
Query.orderDesc("$createdAt"),
|
173
|
+
Query.limit(100) // Limit to last 100 backups
|
174
|
+
]
|
175
|
+
});
|
176
|
+
|
177
|
+
return (result.rows || []) as BackupMetadata[];
|
178
|
+
} catch (error) {
|
179
|
+
// Table might not exist yet
|
180
|
+
logger.debug("No backup tracking table found", { databaseId });
|
181
|
+
return [];
|
182
|
+
}
|
183
|
+
}
|
184
|
+
|
185
|
+
/**
|
186
|
+
* Gets the most recent backup for a database
|
187
|
+
*/
|
188
|
+
export async function getLastBackup(
|
189
|
+
db: DatabaseAdapter,
|
190
|
+
databaseId: string
|
191
|
+
): Promise<BackupMetadata | null> {
|
192
|
+
try {
|
193
|
+
const result = await db.listRows({
|
194
|
+
databaseId,
|
195
|
+
tableId: BACKUP_TABLE_ID,
|
196
|
+
queries: [
|
197
|
+
Query.orderDesc("$createdAt"),
|
198
|
+
Query.limit(1)
|
199
|
+
]
|
200
|
+
});
|
201
|
+
|
202
|
+
if (result.rows && result.rows.length > 0) {
|
203
|
+
return result.rows[0] as BackupMetadata;
|
204
|
+
}
|
205
|
+
|
206
|
+
return null;
|
207
|
+
} catch (error) {
|
208
|
+
logger.debug("No backup found or table doesn't exist", { databaseId });
|
209
|
+
return null;
|
210
|
+
}
|
211
|
+
}
|
@@ -27,18 +27,18 @@ export class ConfirmationDialogs {
|
|
27
27
|
return true;
|
28
28
|
}
|
29
29
|
|
30
|
-
MessageFormatter.warning(`You are about to perform a destructive operation
|
31
|
-
|
32
|
-
|
33
|
-
|
30
|
+
MessageFormatter.warning(`You are about to perform a destructive operation:`, { skipLogging: true });
|
31
|
+
MessageFormatter.error(`Operation: ${options.operation}`, undefined, { skipLogging: true });
|
32
|
+
MessageFormatter.warning(`Targets: ${options.targets.join(", ")}`, { skipLogging: true });
|
33
|
+
|
34
34
|
if (options.consequences && options.consequences.length > 0) {
|
35
|
-
|
35
|
+
MessageFormatter.error("This will:", undefined, { skipLogging: true });
|
36
36
|
options.consequences.forEach(consequence => {
|
37
|
-
|
37
|
+
MessageFormatter.error(` • ${consequence}`, undefined, { skipLogging: true });
|
38
38
|
});
|
39
39
|
}
|
40
40
|
|
41
|
-
|
41
|
+
MessageFormatter.error("⚠️ THIS ACTION CANNOT BE UNDONE!", undefined, { skipLogging: true });
|
42
42
|
|
43
43
|
if (options.requireExplicitConfirmation && options.confirmationText) {
|
44
44
|
const { confirmation } = await inquirer.prompt([{
|
@@ -68,12 +68,12 @@ export class ConfirmationDialogs {
|
|
68
68
|
* Prompts user about creating a backup before a destructive operation
|
69
69
|
*/
|
70
70
|
static async promptForBackup(options: BackupPromptOptions): Promise<'yes' | 'no' | 'skip'> {
|
71
|
-
const message = options.backupMessage ||
|
71
|
+
const message = options.backupMessage ||
|
72
72
|
`Create a backup before performing ${options.operation} on: ${options.targets.join(", ")}?`;
|
73
73
|
|
74
|
-
|
74
|
+
MessageFormatter.info("🛡️ Backup Recommendation", { skipLogging: true });
|
75
75
|
if (options.recommendBackup !== false) {
|
76
|
-
|
76
|
+
MessageFormatter.warning("It's strongly recommended to create a backup before proceeding.", { skipLogging: true });
|
77
77
|
}
|
78
78
|
|
79
79
|
const { choice } = await inquirer.prompt([{
|
@@ -95,12 +95,12 @@ export class ConfirmationDialogs {
|
|
95
95
|
* Shows a final confirmation before proceeding with an operation
|
96
96
|
*/
|
97
97
|
static async finalConfirmation(operation: string, details?: string[]): Promise<boolean> {
|
98
|
-
|
99
|
-
|
98
|
+
MessageFormatter.success(`Ready to perform: ${operation}`, { skipLogging: true });
|
99
|
+
|
100
100
|
if (details && details.length > 0) {
|
101
|
-
|
101
|
+
MessageFormatter.debug("Details:", undefined, { skipLogging: true });
|
102
102
|
details.forEach(detail => {
|
103
|
-
|
103
|
+
MessageFormatter.debug(` • ${detail}`, undefined, { skipLogging: true });
|
104
104
|
});
|
105
105
|
}
|
106
106
|
|
@@ -215,19 +215,19 @@ export class ConfirmationDialogs {
|
|
215
215
|
|
216
216
|
Object.entries(summary).forEach(([key, value]) => {
|
217
217
|
const formattedKey = key.replace(/([A-Z])/g, ' $1').replace(/^./, str => str.toUpperCase());
|
218
|
-
|
218
|
+
|
219
219
|
if (Array.isArray(value)) {
|
220
|
-
|
220
|
+
MessageFormatter.info(`● ${formattedKey}:`, { skipLogging: true });
|
221
221
|
value.forEach(item => {
|
222
|
-
|
222
|
+
MessageFormatter.debug(` • ${item}`, undefined, { skipLogging: true });
|
223
223
|
});
|
224
224
|
} else {
|
225
|
-
|
225
|
+
MessageFormatter.info(`● ${formattedKey}: ${String(value)}`, { skipLogging: true });
|
226
226
|
}
|
227
227
|
});
|
228
228
|
|
229
229
|
if (options.warningMessage) {
|
230
|
-
|
230
|
+
MessageFormatter.warning(`⚠️ ${options.warningMessage}`, { skipLogging: true });
|
231
231
|
}
|
232
232
|
|
233
233
|
if (options.confirmationRequired !== false) {
|
@@ -0,0 +1,110 @@
|
|
1
|
+
/**
|
2
|
+
* Shared error handling utilities for consistent error processing
|
3
|
+
*/
|
4
|
+
|
5
|
+
/**
|
6
|
+
* Extracts error message from unknown error type
|
7
|
+
* @param error - Error of unknown type
|
8
|
+
* @returns String error message
|
9
|
+
*/
|
10
|
+
export function getErrorMessage(error: unknown): string {
|
11
|
+
return error instanceof Error ? error.message : String(error);
|
12
|
+
}
|
13
|
+
|
14
|
+
/**
|
15
|
+
* Normalizes unknown error to Error instance
|
16
|
+
* @param error - Error of unknown type
|
17
|
+
* @returns Normalized Error object
|
18
|
+
*/
|
19
|
+
export function normalizeError(error: unknown): Error {
|
20
|
+
return error instanceof Error ? error : new Error(String(error));
|
21
|
+
}
|
22
|
+
|
23
|
+
/**
|
24
|
+
* Checks if error is a conflict error (409)
|
25
|
+
* @param error - Error object to check
|
26
|
+
* @returns True if error is a conflict (duplicate resource)
|
27
|
+
*/
|
28
|
+
export function isConflictError(error: any): boolean {
|
29
|
+
return error?.code === 409 || (error?.message?.toLowerCase().includes('already exists') ?? false);
|
30
|
+
}
|
31
|
+
|
32
|
+
/**
|
33
|
+
* Checks if error is a Cloudflare error (522)
|
34
|
+
* @param error - Error object to check
|
35
|
+
* @returns True if error is from Cloudflare connection timeout
|
36
|
+
*/
|
37
|
+
export function isCloudflareError(error: any): boolean {
|
38
|
+
return error?.code === 522 || error?.code === "522";
|
39
|
+
}
|
40
|
+
|
41
|
+
/**
|
42
|
+
* Checks if error is authentication/authorization related
|
43
|
+
* @param error - Error object to check
|
44
|
+
* @returns True if error is auth-related (401, 403)
|
45
|
+
*/
|
46
|
+
export function isAuthError(error: any): boolean {
|
47
|
+
const code = error?.code;
|
48
|
+
return code === 401 || code === 403 || code === "401" || code === "403";
|
49
|
+
}
|
50
|
+
|
51
|
+
/**
|
52
|
+
* Error categorization helpers for smart fallback logic
|
53
|
+
*/
|
54
|
+
|
55
|
+
/**
|
56
|
+
* Checks if an error is retryable (network issues, rate limits, etc.)
|
57
|
+
* @param errorMessage - Error message to check
|
58
|
+
* @returns True if error is retryable
|
59
|
+
*/
|
60
|
+
export function isRetryableError(errorMessage: string): boolean {
|
61
|
+
const retryableErrors = [
|
62
|
+
"rate limit",
|
63
|
+
"timeout",
|
64
|
+
"network",
|
65
|
+
"temporary",
|
66
|
+
"503",
|
67
|
+
"502",
|
68
|
+
"429"
|
69
|
+
];
|
70
|
+
return retryableErrors.some(error =>
|
71
|
+
errorMessage.toLowerCase().includes(error)
|
72
|
+
);
|
73
|
+
}
|
74
|
+
|
75
|
+
/**
|
76
|
+
* Checks if an error indicates bulk operations are not supported
|
77
|
+
* @param errorMessage - Error message to check
|
78
|
+
* @returns True if bulk operations not supported
|
79
|
+
*/
|
80
|
+
export function isBulkNotSupportedError(errorMessage: string): boolean {
|
81
|
+
const notSupportedErrors = [
|
82
|
+
"method not found",
|
83
|
+
"not implemented",
|
84
|
+
"unsupported",
|
85
|
+
"not available",
|
86
|
+
"404"
|
87
|
+
];
|
88
|
+
return notSupportedErrors.some(error =>
|
89
|
+
errorMessage.toLowerCase().includes(error)
|
90
|
+
);
|
91
|
+
}
|
92
|
+
|
93
|
+
/**
|
94
|
+
* Checks if an error is critical (authentication, authorization, permissions)
|
95
|
+
* @param errorMessage - Error message to check
|
96
|
+
* @returns True if error is critical
|
97
|
+
*/
|
98
|
+
export function isCriticalError(errorMessage: string): boolean {
|
99
|
+
const criticalErrors = [
|
100
|
+
"authentication",
|
101
|
+
"authorization",
|
102
|
+
"permission",
|
103
|
+
"forbidden",
|
104
|
+
"401",
|
105
|
+
"403"
|
106
|
+
];
|
107
|
+
return criticalErrors.some(error =>
|
108
|
+
errorMessage.toLowerCase().includes(error)
|
109
|
+
);
|
110
|
+
}
|
@@ -5,6 +5,7 @@ import fs from "node:fs";
|
|
5
5
|
import chalk from "chalk";
|
6
6
|
import pLimit from "p-limit";
|
7
7
|
import { tryAwaitWithRetry } from "../utils/helperFunctions.js";
|
8
|
+
import { MessageFormatter } from "./messageFormatter.js";
|
8
9
|
|
9
10
|
/**
|
10
11
|
* Validates and filters events array for Appwrite functions
|
@@ -64,7 +65,7 @@ export class FunctionManager {
|
|
64
65
|
} = options;
|
65
66
|
|
66
67
|
if (verbose) {
|
67
|
-
|
68
|
+
MessageFormatter.info(`Searching for function: ${functionName}`, { prefix: "Functions" });
|
68
69
|
}
|
69
70
|
|
70
71
|
// Normalize function name for comparison
|
@@ -76,7 +77,7 @@ export class FunctionManager {
|
|
76
77
|
for (const path of standardPaths) {
|
77
78
|
if (await this.isValidFunctionDirectory(path)) {
|
78
79
|
if (verbose) {
|
79
|
-
|
80
|
+
MessageFormatter.success(`Found function at standard location: ${path}`, { prefix: "Functions" });
|
80
81
|
}
|
81
82
|
return path;
|
82
83
|
}
|
@@ -92,7 +93,7 @@ export class FunctionManager {
|
|
92
93
|
);
|
93
94
|
if (foundPath) {
|
94
95
|
if (verbose) {
|
95
|
-
|
96
|
+
MessageFormatter.success(`Found function via fuzzy search: ${foundPath}`, { prefix: "Functions" });
|
96
97
|
}
|
97
98
|
return foundPath;
|
98
99
|
}
|
@@ -100,7 +101,7 @@ export class FunctionManager {
|
|
100
101
|
}
|
101
102
|
|
102
103
|
if (verbose) {
|
103
|
-
|
104
|
+
MessageFormatter.warning(`Function directory not found: ${functionName}`, { prefix: "Functions" });
|
104
105
|
}
|
105
106
|
return null;
|
106
107
|
}
|
@@ -175,7 +176,7 @@ export class FunctionManager {
|
|
175
176
|
}
|
176
177
|
} catch (error) {
|
177
178
|
if (verbose) {
|
178
|
-
|
179
|
+
MessageFormatter.debug(`Skipping inaccessible directory: ${searchPath}`, undefined, { prefix: "Functions" });
|
179
180
|
}
|
180
181
|
}
|
181
182
|
|
@@ -248,9 +249,9 @@ export class FunctionManager {
|
|
248
249
|
|
249
250
|
return await functionLimit(async () => {
|
250
251
|
if (verbose) {
|
251
|
-
|
252
|
-
|
253
|
-
|
252
|
+
MessageFormatter.processing(`Deploying function: ${functionConfig.name}`, { prefix: "Functions" });
|
253
|
+
MessageFormatter.debug(`Path: ${functionPath}`, undefined, { prefix: "Functions" });
|
254
|
+
MessageFormatter.debug(`Entrypoint: ${entrypoint}`, undefined, { prefix: "Functions" });
|
254
255
|
}
|
255
256
|
|
256
257
|
// Validate function directory
|
@@ -265,7 +266,7 @@ export class FunctionManager {
|
|
265
266
|
functionExists = true;
|
266
267
|
} catch (error) {
|
267
268
|
if (verbose) {
|
268
|
-
|
269
|
+
MessageFormatter.info(`Function ${functionConfig.$id} does not exist, creating...`, { prefix: "Functions" });
|
269
270
|
}
|
270
271
|
}
|
271
272
|
|
@@ -289,7 +290,7 @@ export class FunctionManager {
|
|
289
290
|
);
|
290
291
|
|
291
292
|
if (verbose) {
|
292
|
-
|
293
|
+
MessageFormatter.success(`Function ${functionConfig.name} deployed successfully`, { prefix: "Functions" });
|
293
294
|
}
|
294
295
|
|
295
296
|
return deployment;
|
@@ -303,7 +304,7 @@ export class FunctionManager {
|
|
303
304
|
const { verbose = false } = options;
|
304
305
|
|
305
306
|
if (verbose) {
|
306
|
-
|
307
|
+
MessageFormatter.processing(`Creating function: ${functionConfig.name}`, { prefix: "Functions" });
|
307
308
|
}
|
308
309
|
|
309
310
|
return await tryAwaitWithRetry(async () => {
|
@@ -336,7 +337,7 @@ export class FunctionManager {
|
|
336
337
|
const { verbose = false } = options;
|
337
338
|
|
338
339
|
if (verbose) {
|
339
|
-
|
340
|
+
MessageFormatter.processing(`Updating function: ${functionConfig.name}`, { prefix: "Functions" });
|
340
341
|
}
|
341
342
|
|
342
343
|
return await tryAwaitWithRetry(async () => {
|
@@ -373,14 +374,14 @@ export class FunctionManager {
|
|
373
374
|
const { platform } = await import("node:os");
|
374
375
|
|
375
376
|
if (verbose) {
|
376
|
-
|
377
|
+
MessageFormatter.processing("Executing pre-deploy commands...", { prefix: "Functions" });
|
377
378
|
}
|
378
379
|
|
379
380
|
const isWindows = platform() === "win32";
|
380
381
|
|
381
382
|
for (const command of commands) {
|
382
383
|
if (verbose) {
|
383
|
-
|
384
|
+
MessageFormatter.debug(`$ ${command}`, undefined, { prefix: "Functions" });
|
384
385
|
}
|
385
386
|
|
386
387
|
try {
|
@@ -391,13 +392,13 @@ export class FunctionManager {
|
|
391
392
|
windowsHide: true,
|
392
393
|
});
|
393
394
|
} catch (error) {
|
394
|
-
|
395
|
+
MessageFormatter.error(`Failed to execute command: ${command}`, error as Error, { prefix: "Functions" });
|
395
396
|
throw error;
|
396
397
|
}
|
397
398
|
}
|
398
399
|
|
399
400
|
if (verbose) {
|
400
|
-
|
401
|
+
MessageFormatter.success("Pre-deploy commands completed", { prefix: "Functions" });
|
401
402
|
}
|
402
403
|
}
|
403
404
|
|
@@ -415,7 +416,7 @@ export class FunctionManager {
|
|
415
416
|
|
416
417
|
try {
|
417
418
|
if (verbose) {
|
418
|
-
|
419
|
+
MessageFormatter.processing("Creating deployment archive...", { prefix: "Functions" });
|
419
420
|
}
|
420
421
|
|
421
422
|
// Create tarball
|
@@ -431,9 +432,9 @@ export class FunctionManager {
|
|
431
432
|
relativePath.includes(`/${pattern.toLowerCase()}`) ||
|
432
433
|
relativePath.includes(`\\${pattern.toLowerCase()}`)
|
433
434
|
);
|
434
|
-
|
435
|
+
|
435
436
|
if (shouldIgnore && verbose) {
|
436
|
-
|
437
|
+
MessageFormatter.debug(`Ignoring: ${path}`, undefined, { prefix: "Functions" });
|
437
438
|
}
|
438
439
|
|
439
440
|
return !shouldIgnore;
|
@@ -450,7 +451,7 @@ export class FunctionManager {
|
|
450
451
|
);
|
451
452
|
|
452
453
|
if (verbose) {
|
453
|
-
|
454
|
+
MessageFormatter.processing("Uploading deployment...", { prefix: "Functions" });
|
454
455
|
}
|
455
456
|
|
456
457
|
const deployment = await tryAwaitWithRetry(async () => {
|