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
@@ -0,0 +1,546 @@
|
|
1
|
+
import fs from 'fs';
|
2
|
+
import path from 'path';
|
3
|
+
import { jest } from '@jest/globals';
|
4
|
+
import { TestUtils } from '../testUtils';
|
5
|
+
|
6
|
+
// Mock config migration utilities
|
7
|
+
jest.mock('../../src/utils/configMigration', () => ({
|
8
|
+
migrateToTablesDir: jest.fn(),
|
9
|
+
migrateToCollectionsDir: jest.fn(),
|
10
|
+
detectMigrationNeeds: jest.fn(),
|
11
|
+
createBackup: jest.fn(),
|
12
|
+
validateMigration: jest.fn(),
|
13
|
+
convertCollectionToTable: jest.fn(),
|
14
|
+
convertTableToCollection: jest.fn(),
|
15
|
+
}));
|
16
|
+
|
17
|
+
import {
|
18
|
+
migrateToTablesDir,
|
19
|
+
migrateToCollectionsDir,
|
20
|
+
detectMigrationNeeds,
|
21
|
+
createBackup,
|
22
|
+
validateMigration,
|
23
|
+
convertCollectionToTable,
|
24
|
+
convertTableToCollection,
|
25
|
+
} from '../../src/utils/configMigration';
|
26
|
+
|
27
|
+
describe('Configuration Migration Tests', () => {
|
28
|
+
let testDir: string;
|
29
|
+
|
30
|
+
beforeEach(() => {
|
31
|
+
jest.clearAllMocks();
|
32
|
+
});
|
33
|
+
|
34
|
+
afterEach(() => {
|
35
|
+
TestUtils.cleanup();
|
36
|
+
});
|
37
|
+
|
38
|
+
describe('Migration Detection', () => {
|
39
|
+
it('should detect need to migrate from collections to dual schema', async () => {
|
40
|
+
testDir = TestUtils.createTestProject({ hasCollections: true });
|
41
|
+
|
42
|
+
(detectMigrationNeeds as jest.Mock).mockReturnValue({
|
43
|
+
needsMigration: true,
|
44
|
+
migrationType: 'to-dual-schema',
|
45
|
+
reason: 'Only collections/ directory found, recommend adding tables/ support',
|
46
|
+
sourceDir: 'collections',
|
47
|
+
targetDirs: ['collections', 'tables'],
|
48
|
+
});
|
49
|
+
|
50
|
+
const migrationNeeds = (detectMigrationNeeds as jest.Mock)(testDir);
|
51
|
+
|
52
|
+
expect(migrationNeeds.needsMigration).toBe(true);
|
53
|
+
expect(migrationNeeds.migrationType).toBe('to-dual-schema');
|
54
|
+
expect(detectMigrationNeeds).toHaveBeenCalledWith(testDir);
|
55
|
+
});
|
56
|
+
|
57
|
+
it('should detect need to migrate from legacy single directory to version-aware structure', async () => {
|
58
|
+
testDir = TestUtils.createTempDir();
|
59
|
+
|
60
|
+
// Create legacy structure without .appwrite directory
|
61
|
+
const config = TestUtils.createTestAppwriteConfig();
|
62
|
+
fs.writeFileSync(
|
63
|
+
path.join(testDir, 'appwriteConfig.ts'),
|
64
|
+
`export default ${JSON.stringify(config, null, 2)};`
|
65
|
+
);
|
66
|
+
|
67
|
+
const collectionsDir = path.join(testDir, 'collections');
|
68
|
+
fs.mkdirSync(collectionsDir);
|
69
|
+
|
70
|
+
(detectMigrationNeeds as jest.Mock).mockReturnValue({
|
71
|
+
needsMigration: true,
|
72
|
+
migrationType: 'to-version-aware',
|
73
|
+
reason: 'Config is outside .appwrite directory',
|
74
|
+
sourceDir: testDir,
|
75
|
+
targetDir: path.join(testDir, '.appwrite'),
|
76
|
+
});
|
77
|
+
|
78
|
+
const migrationNeeds = (detectMigrationNeeds as jest.Mock)(testDir);
|
79
|
+
|
80
|
+
expect(migrationNeeds.needsMigration).toBe(true);
|
81
|
+
expect(migrationNeeds.migrationType).toBe('to-version-aware');
|
82
|
+
});
|
83
|
+
|
84
|
+
it('should detect no migration needed for current dual schema structure', async () => {
|
85
|
+
testDir = TestUtils.createTestProject({
|
86
|
+
hasCollections: true,
|
87
|
+
hasTables: true,
|
88
|
+
useYaml: true,
|
89
|
+
});
|
90
|
+
|
91
|
+
(detectMigrationNeeds as jest.Mock).mockReturnValue({
|
92
|
+
needsMigration: false,
|
93
|
+
migrationType: 'none',
|
94
|
+
reason: 'Configuration is already using dual schema structure',
|
95
|
+
});
|
96
|
+
|
97
|
+
const migrationNeeds = (detectMigrationNeeds as jest.Mock)(testDir);
|
98
|
+
|
99
|
+
expect(migrationNeeds.needsMigration).toBe(false);
|
100
|
+
expect(migrationNeeds.migrationType).toBe('none');
|
101
|
+
});
|
102
|
+
});
|
103
|
+
|
104
|
+
describe('Migration to Tables Directory', () => {
|
105
|
+
it('should migrate collections to tables directory for TablesDB API', async () => {
|
106
|
+
testDir = TestUtils.createTestProject({ hasCollections: true });
|
107
|
+
|
108
|
+
const originalCollection = TestUtils.createTestCollection();
|
109
|
+
|
110
|
+
(migrateToTablesDir as jest.Mock).mockImplementation((sourceDir, options) => {
|
111
|
+
const tablesDir = path.join(sourceDir, 'tables');
|
112
|
+
fs.mkdirSync(tablesDir, { recursive: true });
|
113
|
+
|
114
|
+
// Convert collection to table format
|
115
|
+
const table = {
|
116
|
+
...originalCollection,
|
117
|
+
tableId: originalCollection.$id,
|
118
|
+
databaseId: 'test-db-id',
|
119
|
+
_isFromTablesDir: true,
|
120
|
+
};
|
121
|
+
|
122
|
+
fs.writeFileSync(
|
123
|
+
path.join(tablesDir, 'TestCollection.yaml'),
|
124
|
+
`name: ${table.name}\ntableId: ${table.tableId}\ndatabaseId: ${table.databaseId}\n`
|
125
|
+
);
|
126
|
+
|
127
|
+
return {
|
128
|
+
success: true,
|
129
|
+
migratedFiles: ['TestCollection.ts -> TestCollection.yaml'],
|
130
|
+
targetDir: tablesDir,
|
131
|
+
};
|
132
|
+
});
|
133
|
+
|
134
|
+
const result = (migrateToTablesDir as jest.Mock)(testDir, {
|
135
|
+
preserveOriginal: true,
|
136
|
+
convertToYaml: true,
|
137
|
+
});
|
138
|
+
|
139
|
+
expect(result.success).toBe(true);
|
140
|
+
expect(result.migratedFiles).toHaveLength(1);
|
141
|
+
|
142
|
+
const tablesDir = path.join(testDir, 'tables');
|
143
|
+
expect(fs.existsSync(tablesDir)).toBe(true);
|
144
|
+
expect(fs.existsSync(path.join(tablesDir, 'TestCollection.yaml'))).toBe(true);
|
145
|
+
});
|
146
|
+
|
147
|
+
it('should preserve original collections when requested', async () => {
|
148
|
+
testDir = TestUtils.createTestProject({ hasCollections: true });
|
149
|
+
|
150
|
+
const originalFile = path.join(testDir, 'collections', 'TestCollection.ts');
|
151
|
+
const originalContent = fs.readFileSync(originalFile, 'utf8');
|
152
|
+
|
153
|
+
(migrateToTablesDir as jest.Mock).mockImplementation((sourceDir, options) => {
|
154
|
+
if (options.preserveOriginal) {
|
155
|
+
// Don't remove original files
|
156
|
+
return {
|
157
|
+
success: true,
|
158
|
+
migratedFiles: ['TestCollection.ts'],
|
159
|
+
preservedFiles: ['TestCollection.ts'],
|
160
|
+
};
|
161
|
+
}
|
162
|
+
});
|
163
|
+
|
164
|
+
const result = (migrateToTablesDir as jest.Mock)(testDir, {
|
165
|
+
preserveOriginal: true,
|
166
|
+
});
|
167
|
+
|
168
|
+
expect(result.preservedFiles).toContain('TestCollection.ts');
|
169
|
+
expect(fs.existsSync(originalFile)).toBe(true);
|
170
|
+
|
171
|
+
const currentContent = fs.readFileSync(originalFile, 'utf8');
|
172
|
+
expect(currentContent).toBe(originalContent);
|
173
|
+
});
|
174
|
+
|
175
|
+
it('should handle collection to table conversion with proper field mapping', async () => {
|
176
|
+
const collection = TestUtils.createTestCollection({
|
177
|
+
name: 'UserProfiles',
|
178
|
+
$id: 'user-profiles',
|
179
|
+
});
|
180
|
+
|
181
|
+
(convertCollectionToTable as jest.Mock).mockReturnValue({
|
182
|
+
name: 'UserProfiles',
|
183
|
+
tableId: 'user-profiles',
|
184
|
+
databaseId: 'main-db',
|
185
|
+
documentSecurity: collection.documentSecurity,
|
186
|
+
enabled: collection.enabled,
|
187
|
+
$permissions: collection.$permissions,
|
188
|
+
attributes: collection.attributes,
|
189
|
+
indexes: collection.indexes,
|
190
|
+
importDefs: collection.importDefs,
|
191
|
+
_isFromTablesDir: true,
|
192
|
+
});
|
193
|
+
|
194
|
+
const table = (convertCollectionToTable as jest.Mock)(collection, 'main-db');
|
195
|
+
|
196
|
+
expect(table.tableId).toBe('user-profiles');
|
197
|
+
expect(table.databaseId).toBe('main-db');
|
198
|
+
expect(table._isFromTablesDir).toBe(true);
|
199
|
+
expect(table.attributes).toEqual(collection.attributes);
|
200
|
+
});
|
201
|
+
});
|
202
|
+
|
203
|
+
describe('Migration to Collections Directory', () => {
|
204
|
+
it('should migrate tables back to collections directory for Database API', async () => {
|
205
|
+
testDir = TestUtils.createTestProject({ hasTables: true });
|
206
|
+
|
207
|
+
(migrateToCollectionsDir as jest.Mock).mockImplementation((sourceDir, options) => {
|
208
|
+
const collectionsDir = path.join(sourceDir, 'collections');
|
209
|
+
fs.mkdirSync(collectionsDir, { recursive: true });
|
210
|
+
|
211
|
+
const collection = TestUtils.createTestCollection({
|
212
|
+
name: 'TestTable',
|
213
|
+
$id: 'test-table',
|
214
|
+
});
|
215
|
+
|
216
|
+
fs.writeFileSync(
|
217
|
+
path.join(collectionsDir, 'TestTable.ts'),
|
218
|
+
`export default ${JSON.stringify(collection, null, 2)};`
|
219
|
+
);
|
220
|
+
|
221
|
+
return {
|
222
|
+
success: true,
|
223
|
+
migratedFiles: ['TestTable.yaml -> TestTable.ts'],
|
224
|
+
targetDir: collectionsDir,
|
225
|
+
};
|
226
|
+
});
|
227
|
+
|
228
|
+
const result = (migrateToCollectionsDir as jest.Mock)(testDir, {
|
229
|
+
convertToTypeScript: true,
|
230
|
+
});
|
231
|
+
|
232
|
+
expect(result.success).toBe(true);
|
233
|
+
expect(result.migratedFiles).toHaveLength(1);
|
234
|
+
|
235
|
+
const collectionsDir = path.join(testDir, 'collections');
|
236
|
+
expect(fs.existsSync(collectionsDir)).toBe(true);
|
237
|
+
expect(fs.existsSync(path.join(collectionsDir, 'TestTable.ts'))).toBe(true);
|
238
|
+
});
|
239
|
+
|
240
|
+
it('should handle table to collection conversion with field mapping', async () => {
|
241
|
+
const table = TestUtils.createTestTable({
|
242
|
+
name: 'UserData',
|
243
|
+
tableId: 'user-data',
|
244
|
+
databaseId: 'main-db',
|
245
|
+
});
|
246
|
+
|
247
|
+
(convertTableToCollection as jest.Mock).mockReturnValue({
|
248
|
+
name: 'UserData',
|
249
|
+
$id: 'user-data',
|
250
|
+
documentSecurity: table.documentSecurity,
|
251
|
+
enabled: table.enabled,
|
252
|
+
$permissions: table.$permissions,
|
253
|
+
attributes: table.attributes,
|
254
|
+
indexes: table.indexes,
|
255
|
+
importDefs: table.importDefs,
|
256
|
+
});
|
257
|
+
|
258
|
+
const collection = (convertTableToCollection as jest.Mock)(table);
|
259
|
+
|
260
|
+
expect(collection.$id).toBe('user-data');
|
261
|
+
expect(collection.name).toBe('UserData');
|
262
|
+
expect(collection._isFromTablesDir).toBeUndefined();
|
263
|
+
expect(collection.attributes).toEqual(table.attributes);
|
264
|
+
});
|
265
|
+
});
|
266
|
+
|
267
|
+
describe('Migration Validation', () => {
|
268
|
+
it('should validate migration before execution', async () => {
|
269
|
+
testDir = TestUtils.createTestProject({ hasCollections: true });
|
270
|
+
|
271
|
+
(validateMigration as jest.Mock).mockReturnValue({
|
272
|
+
isValid: true,
|
273
|
+
errors: [],
|
274
|
+
warnings: [],
|
275
|
+
canProceed: true,
|
276
|
+
estimatedTime: '2 minutes',
|
277
|
+
affectedFiles: ['TestCollection.ts'],
|
278
|
+
});
|
279
|
+
|
280
|
+
const validation = (validateMigration as jest.Mock)(testDir, 'to-tables');
|
281
|
+
|
282
|
+
expect(validation.isValid).toBe(true);
|
283
|
+
expect(validation.canProceed).toBe(true);
|
284
|
+
expect(validation.affectedFiles).toHaveLength(1);
|
285
|
+
});
|
286
|
+
|
287
|
+
it('should detect validation errors that prevent migration', async () => {
|
288
|
+
testDir = TestUtils.createTestProject({ hasCollections: true });
|
289
|
+
|
290
|
+
(validateMigration as jest.Mock).mockReturnValue({
|
291
|
+
isValid: false,
|
292
|
+
errors: [
|
293
|
+
'Collection "TestCollection" has invalid attribute types for TablesDB',
|
294
|
+
'Missing required databaseId for target tables'
|
295
|
+
],
|
296
|
+
warnings: ['Migration will take significant time'],
|
297
|
+
canProceed: false,
|
298
|
+
reasons: ['Critical validation errors found'],
|
299
|
+
});
|
300
|
+
|
301
|
+
const validation = (validateMigration as jest.Mock)(testDir, 'to-tables');
|
302
|
+
|
303
|
+
expect(validation.isValid).toBe(false);
|
304
|
+
expect(validation.canProceed).toBe(false);
|
305
|
+
expect(validation.errors).toHaveLength(2);
|
306
|
+
});
|
307
|
+
|
308
|
+
it('should provide migration warnings for user review', async () => {
|
309
|
+
testDir = TestUtils.createTestProject({ hasCollections: true });
|
310
|
+
|
311
|
+
(validateMigration as jest.Mock).mockReturnValue({
|
312
|
+
isValid: true,
|
313
|
+
errors: [],
|
314
|
+
warnings: [
|
315
|
+
'Some relationships may need manual adjustment',
|
316
|
+
'Index performance may differ between APIs',
|
317
|
+
'Import definitions will be preserved but may need updates'
|
318
|
+
],
|
319
|
+
canProceed: true,
|
320
|
+
requiresUserConfirmation: true,
|
321
|
+
});
|
322
|
+
|
323
|
+
const validation = (validateMigration as jest.Mock)(testDir, 'to-tables');
|
324
|
+
|
325
|
+
expect(validation.warnings).toHaveLength(3);
|
326
|
+
expect(validation.requiresUserConfirmation).toBe(true);
|
327
|
+
});
|
328
|
+
});
|
329
|
+
|
330
|
+
describe('Migration Backup and Recovery', () => {
|
331
|
+
it('should create backup before migration', async () => {
|
332
|
+
testDir = TestUtils.createTestProject({
|
333
|
+
hasCollections: true,
|
334
|
+
hasTables: true,
|
335
|
+
});
|
336
|
+
|
337
|
+
const backupDir = path.join(testDir, '.migration-backup');
|
338
|
+
|
339
|
+
(createBackup as jest.Mock).mockImplementation((sourceDir, backupPath) => {
|
340
|
+
fs.mkdirSync(backupPath, { recursive: true });
|
341
|
+
|
342
|
+
// Copy collections
|
343
|
+
const collectionsBackup = path.join(backupPath, 'collections');
|
344
|
+
fs.mkdirSync(collectionsBackup);
|
345
|
+
fs.writeFileSync(
|
346
|
+
path.join(collectionsBackup, 'TestCollection.ts'),
|
347
|
+
'backup content'
|
348
|
+
);
|
349
|
+
|
350
|
+
return {
|
351
|
+
success: true,
|
352
|
+
backupPath,
|
353
|
+
backedUpFiles: ['collections/TestCollection.ts'],
|
354
|
+
timestamp: new Date().toISOString(),
|
355
|
+
};
|
356
|
+
});
|
357
|
+
|
358
|
+
const backup = (createBackup as jest.Mock)(testDir, backupDir);
|
359
|
+
|
360
|
+
expect(backup.success).toBe(true);
|
361
|
+
expect(backup.backedUpFiles).toContain('collections/TestCollection.ts');
|
362
|
+
expect(fs.existsSync(backupDir)).toBe(true);
|
363
|
+
});
|
364
|
+
|
365
|
+
it('should provide rollback capability after failed migration', async () => {
|
366
|
+
testDir = TestUtils.createTestProject({ hasCollections: true });
|
367
|
+
|
368
|
+
const backupDir = path.join(testDir, '.migration-backup');
|
369
|
+
|
370
|
+
// Simulate backup creation
|
371
|
+
(createBackup as jest.Mock).mockReturnValue({
|
372
|
+
success: true,
|
373
|
+
backupPath: backupDir,
|
374
|
+
backedUpFiles: ['collections/TestCollection.ts'],
|
375
|
+
});
|
376
|
+
|
377
|
+
// Simulate migration failure
|
378
|
+
(migrateToTablesDir as jest.Mock).mockImplementation(() => {
|
379
|
+
throw new Error('Migration failed - disk space insufficient');
|
380
|
+
});
|
381
|
+
|
382
|
+
try {
|
383
|
+
(migrateToTablesDir as jest.Mock)(testDir);
|
384
|
+
} catch (error) {
|
385
|
+
// Rollback should restore from backup
|
386
|
+
expect(createBackup).toHaveBeenCalled();
|
387
|
+
}
|
388
|
+
});
|
389
|
+
});
|
390
|
+
|
391
|
+
describe('Complex Migration Scenarios', () => {
|
392
|
+
it('should handle mixed YAML and TypeScript migration', async () => {
|
393
|
+
testDir = TestUtils.createTestProject({ hasCollections: true });
|
394
|
+
|
395
|
+
// Add YAML files
|
396
|
+
const collectionsDir = path.join(testDir, 'collections');
|
397
|
+
fs.writeFileSync(
|
398
|
+
path.join(collectionsDir, 'YamlCollection.yaml'),
|
399
|
+
'name: YamlCollection\nid: yaml-collection\n'
|
400
|
+
);
|
401
|
+
|
402
|
+
(migrateToTablesDir as jest.Mock).mockImplementation((sourceDir, options) => {
|
403
|
+
return {
|
404
|
+
success: true,
|
405
|
+
migratedFiles: [
|
406
|
+
'TestCollection.ts -> TestCollection.yaml',
|
407
|
+
'YamlCollection.yaml -> YamlCollection.yaml'
|
408
|
+
],
|
409
|
+
skippedFiles: [],
|
410
|
+
};
|
411
|
+
});
|
412
|
+
|
413
|
+
const result = (migrateToTablesDir as jest.Mock)(testDir, {
|
414
|
+
preserveFormat: false, // Convert all to YAML
|
415
|
+
});
|
416
|
+
|
417
|
+
expect(result.migratedFiles).toHaveLength(2);
|
418
|
+
expect(result.migratedFiles).toContain('TestCollection.ts -> TestCollection.yaml');
|
419
|
+
});
|
420
|
+
|
421
|
+
it('should handle large-scale migration with progress tracking', async () => {
|
422
|
+
testDir = TestUtils.createTempDir();
|
423
|
+
|
424
|
+
// Create many collections
|
425
|
+
const collectionsDir = path.join(testDir, 'collections');
|
426
|
+
fs.mkdirSync(collectionsDir);
|
427
|
+
|
428
|
+
for (let i = 0; i < 100; i++) {
|
429
|
+
const collection = TestUtils.createTestCollection({
|
430
|
+
name: `Collection${i}`,
|
431
|
+
$id: `collection-${i}`,
|
432
|
+
});
|
433
|
+
fs.writeFileSync(
|
434
|
+
path.join(collectionsDir, `Collection${i}.ts`),
|
435
|
+
`export default ${JSON.stringify(collection, null, 2)};`
|
436
|
+
);
|
437
|
+
}
|
438
|
+
|
439
|
+
(migrateToTablesDir as jest.Mock).mockImplementation((sourceDir, options) => {
|
440
|
+
return {
|
441
|
+
success: true,
|
442
|
+
migratedFiles: Array.from({ length: 100 }, (_, i) =>
|
443
|
+
`Collection${i}.ts -> Collection${i}.yaml`
|
444
|
+
),
|
445
|
+
totalFiles: 100,
|
446
|
+
completedFiles: 100,
|
447
|
+
estimatedTimeRemaining: '0 minutes',
|
448
|
+
};
|
449
|
+
});
|
450
|
+
|
451
|
+
const result = (migrateToTablesDir as jest.Mock)(testDir, {
|
452
|
+
batchSize: 10,
|
453
|
+
progressCallback: jest.fn(),
|
454
|
+
});
|
455
|
+
|
456
|
+
expect(result.migratedFiles).toHaveLength(100);
|
457
|
+
expect(result.completedFiles).toBe(100);
|
458
|
+
});
|
459
|
+
|
460
|
+
it('should migrate import definitions and maintain relationships', async () => {
|
461
|
+
testDir = TestUtils.createTestProject({ hasCollections: true });
|
462
|
+
|
463
|
+
const collectionWithImports = TestUtils.createTestCollection({
|
464
|
+
name: 'Users',
|
465
|
+
importDefs: [
|
466
|
+
{
|
467
|
+
sourceFile: 'users.json',
|
468
|
+
mapping: {
|
469
|
+
'name': 'fullName',
|
470
|
+
'email': 'emailAddress',
|
471
|
+
},
|
472
|
+
relationships: [
|
473
|
+
{
|
474
|
+
field: 'profileId',
|
475
|
+
targetCollection: 'Profiles',
|
476
|
+
type: 'oneToOne',
|
477
|
+
}
|
478
|
+
],
|
479
|
+
}
|
480
|
+
],
|
481
|
+
});
|
482
|
+
|
483
|
+
(migrateToTablesDir as jest.Mock).mockImplementation(() => {
|
484
|
+
return {
|
485
|
+
success: true,
|
486
|
+
migratedFiles: ['Users.ts -> Users.yaml'],
|
487
|
+
preservedImportDefs: true,
|
488
|
+
updatedRelationships: ['Users.profileId -> Profiles'],
|
489
|
+
};
|
490
|
+
});
|
491
|
+
|
492
|
+
const result = (migrateToTablesDir as jest.Mock)(testDir);
|
493
|
+
|
494
|
+
expect(result.preservedImportDefs).toBe(true);
|
495
|
+
expect(result.updatedRelationships).toHaveLength(1);
|
496
|
+
});
|
497
|
+
});
|
498
|
+
|
499
|
+
describe('Error Recovery and Edge Cases', () => {
|
500
|
+
it('should handle partial migration failures gracefully', async () => {
|
501
|
+
testDir = TestUtils.createTestProject({ hasCollections: true });
|
502
|
+
|
503
|
+
(migrateToTablesDir as jest.Mock).mockReturnValue({
|
504
|
+
success: false,
|
505
|
+
errors: [
|
506
|
+
'Failed to convert TestCollection.ts: invalid attribute type',
|
507
|
+
],
|
508
|
+
partiallyMigrated: [],
|
509
|
+
rollbackRequired: true,
|
510
|
+
});
|
511
|
+
|
512
|
+
const result = (migrateToTablesDir as jest.Mock)(testDir);
|
513
|
+
|
514
|
+
expect(result.success).toBe(false);
|
515
|
+
expect(result.rollbackRequired).toBe(true);
|
516
|
+
expect(result.errors).toHaveLength(1);
|
517
|
+
});
|
518
|
+
|
519
|
+
it('should handle file permission issues during migration', async () => {
|
520
|
+
testDir = TestUtils.createTestProject({ hasCollections: true });
|
521
|
+
|
522
|
+
(migrateToTablesDir as jest.Mock).mockImplementation(() => {
|
523
|
+
throw new Error('EACCES: permission denied, mkdir \'/readonly/tables\'');
|
524
|
+
});
|
525
|
+
|
526
|
+
expect(() => (migrateToTablesDir as jest.Mock)(testDir)).toThrow('permission denied');
|
527
|
+
});
|
528
|
+
|
529
|
+
it('should validate disk space before migration', async () => {
|
530
|
+
testDir = TestUtils.createTestProject({ hasCollections: true });
|
531
|
+
|
532
|
+
(validateMigration as jest.Mock).mockReturnValue({
|
533
|
+
isValid: false,
|
534
|
+
errors: ['Insufficient disk space for migration'],
|
535
|
+
requiredSpace: '500MB',
|
536
|
+
availableSpace: '100MB',
|
537
|
+
canProceed: false,
|
538
|
+
});
|
539
|
+
|
540
|
+
const validation = (validateMigration as jest.Mock)(testDir, 'to-tables');
|
541
|
+
|
542
|
+
expect(validation.canProceed).toBe(false);
|
543
|
+
expect(validation.errors).toContain('Insufficient disk space for migration');
|
544
|
+
});
|
545
|
+
});
|
546
|
+
});
|
package/tests/setup.ts
ADDED
@@ -0,0 +1,62 @@
|
|
1
|
+
import { jest } from '@jest/globals';
|
2
|
+
|
3
|
+
// Mock winston logger to avoid log output during tests
|
4
|
+
jest.mock('winston', () => ({
|
5
|
+
createLogger: jest.fn(() => ({
|
6
|
+
info: jest.fn(),
|
7
|
+
warn: jest.fn(),
|
8
|
+
error: jest.fn(),
|
9
|
+
debug: jest.fn(),
|
10
|
+
})),
|
11
|
+
format: {
|
12
|
+
combine: jest.fn(),
|
13
|
+
timestamp: jest.fn(),
|
14
|
+
errors: jest.fn(),
|
15
|
+
json: jest.fn(),
|
16
|
+
printf: jest.fn(),
|
17
|
+
},
|
18
|
+
transports: {
|
19
|
+
Console: jest.fn(),
|
20
|
+
File: jest.fn(),
|
21
|
+
},
|
22
|
+
}));
|
23
|
+
|
24
|
+
// Mock chalk to avoid ANSI codes in tests
|
25
|
+
jest.mock('chalk', () => ({
|
26
|
+
red: jest.fn((text) => text),
|
27
|
+
green: jest.fn((text) => text),
|
28
|
+
yellow: jest.fn((text) => text),
|
29
|
+
blue: jest.fn((text) => text),
|
30
|
+
cyan: jest.fn((text) => text),
|
31
|
+
magenta: jest.fn((text) => text),
|
32
|
+
white: jest.fn((text) => text),
|
33
|
+
gray: jest.fn((text) => text),
|
34
|
+
bold: jest.fn((text) => text),
|
35
|
+
dim: jest.fn((text) => text),
|
36
|
+
italic: jest.fn((text) => text),
|
37
|
+
underline: jest.fn((text) => text),
|
38
|
+
strikethrough: jest.fn((text) => text),
|
39
|
+
bgRed: jest.fn((text) => text),
|
40
|
+
bgGreen: jest.fn((text) => text),
|
41
|
+
bgYellow: jest.fn((text) => text),
|
42
|
+
bgBlue: jest.fn((text) => text),
|
43
|
+
bgCyan: jest.fn((text) => text),
|
44
|
+
bgMagenta: jest.fn((text) => text),
|
45
|
+
bgWhite: jest.fn((text) => text),
|
46
|
+
}));
|
47
|
+
|
48
|
+
// Mock inquirer to avoid interactive prompts during tests
|
49
|
+
jest.mock('inquirer', () => ({
|
50
|
+
prompt: jest.fn(),
|
51
|
+
}));
|
52
|
+
|
53
|
+
// Set up global test timeout
|
54
|
+
jest.setTimeout(30000);
|
55
|
+
|
56
|
+
// Clean up environment variables before each test
|
57
|
+
beforeEach(() => {
|
58
|
+
// Reset any environment variables that might affect tests
|
59
|
+
delete process.env.APPWRITE_ENDPOINT;
|
60
|
+
delete process.env.APPWRITE_PROJECT;
|
61
|
+
delete process.env.APPWRITE_KEY;
|
62
|
+
});
|