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,340 @@
|
|
1
|
+
import fs from 'fs';
|
2
|
+
import path from 'path';
|
3
|
+
import { tmpdir } from 'os';
|
4
|
+
import { randomBytes } from 'crypto';
|
5
|
+
import { type AppwriteConfig, type CollectionCreate } from 'appwrite-utils';
|
6
|
+
|
7
|
+
/**
|
8
|
+
* Test utilities for creating temporary directories and test fixtures
|
9
|
+
*/
|
10
|
+
export class TestUtils {
|
11
|
+
private static tempDirs: string[] = [];
|
12
|
+
|
13
|
+
/**
|
14
|
+
* Creates a temporary directory for testing
|
15
|
+
*/
|
16
|
+
static createTempDir(prefix = 'appwrite-test'): string {
|
17
|
+
const tempDir = path.join(tmpdir(), `${prefix}-${randomBytes(8).toString('hex')}`);
|
18
|
+
fs.mkdirSync(tempDir, { recursive: true });
|
19
|
+
this.tempDirs.push(tempDir);
|
20
|
+
return tempDir;
|
21
|
+
}
|
22
|
+
|
23
|
+
/**
|
24
|
+
* Cleans up all created temporary directories
|
25
|
+
*/
|
26
|
+
static cleanup(): void {
|
27
|
+
this.tempDirs.forEach(dir => {
|
28
|
+
try {
|
29
|
+
fs.rmSync(dir, { recursive: true, force: true });
|
30
|
+
} catch (error) {
|
31
|
+
// Ignore cleanup errors
|
32
|
+
}
|
33
|
+
});
|
34
|
+
this.tempDirs = [];
|
35
|
+
}
|
36
|
+
|
37
|
+
/**
|
38
|
+
* Creates a test appwrite config
|
39
|
+
*/
|
40
|
+
static createTestAppwriteConfig(overrides: Partial<AppwriteConfig> = {}): AppwriteConfig {
|
41
|
+
return {
|
42
|
+
appwriteEndpoint: 'http://localhost:8080/v1',
|
43
|
+
appwriteProject: 'test-project',
|
44
|
+
appwriteKey: 'test-key',
|
45
|
+
databases: [
|
46
|
+
{
|
47
|
+
name: 'test-database',
|
48
|
+
$id: 'test-db-id',
|
49
|
+
enabled: true,
|
50
|
+
}
|
51
|
+
],
|
52
|
+
buckets: [],
|
53
|
+
teams: [],
|
54
|
+
functions: [],
|
55
|
+
messaging: [],
|
56
|
+
collections: [],
|
57
|
+
...overrides,
|
58
|
+
};
|
59
|
+
}
|
60
|
+
|
61
|
+
/**
|
62
|
+
* Creates a test collection configuration
|
63
|
+
*/
|
64
|
+
static createTestCollection(overrides: Partial<CollectionCreate> = {}): CollectionCreate {
|
65
|
+
return {
|
66
|
+
name: 'TestCollection',
|
67
|
+
$id: 'test-collection',
|
68
|
+
documentSecurity: false,
|
69
|
+
enabled: true,
|
70
|
+
$permissions: [],
|
71
|
+
attributes: [
|
72
|
+
{
|
73
|
+
key: 'name',
|
74
|
+
type: 'string',
|
75
|
+
size: 255,
|
76
|
+
required: true,
|
77
|
+
array: false,
|
78
|
+
},
|
79
|
+
{
|
80
|
+
key: 'email',
|
81
|
+
type: 'email',
|
82
|
+
required: true,
|
83
|
+
array: false,
|
84
|
+
}
|
85
|
+
],
|
86
|
+
indexes: [
|
87
|
+
{
|
88
|
+
key: 'email_index',
|
89
|
+
type: 'unique',
|
90
|
+
attributes: ['email'],
|
91
|
+
}
|
92
|
+
],
|
93
|
+
...overrides,
|
94
|
+
};
|
95
|
+
}
|
96
|
+
|
97
|
+
/**
|
98
|
+
* Creates a test table configuration (tables API)
|
99
|
+
*/
|
100
|
+
static createTestTable(overrides: any = {}): any {
|
101
|
+
return {
|
102
|
+
name: 'TestTable',
|
103
|
+
tableId: 'test-table',
|
104
|
+
databaseId: 'test-db-id',
|
105
|
+
documentSecurity: false,
|
106
|
+
enabled: true,
|
107
|
+
$permissions: [],
|
108
|
+
attributes: [
|
109
|
+
{
|
110
|
+
key: 'title',
|
111
|
+
type: 'string',
|
112
|
+
size: 255,
|
113
|
+
required: true,
|
114
|
+
array: false,
|
115
|
+
},
|
116
|
+
{
|
117
|
+
key: 'count',
|
118
|
+
type: 'integer',
|
119
|
+
required: false,
|
120
|
+
min: 0,
|
121
|
+
max: 1000,
|
122
|
+
}
|
123
|
+
],
|
124
|
+
indexes: [
|
125
|
+
{
|
126
|
+
key: 'title_index',
|
127
|
+
type: 'key',
|
128
|
+
attributes: ['title'],
|
129
|
+
}
|
130
|
+
],
|
131
|
+
_isFromTablesDir: true,
|
132
|
+
...overrides,
|
133
|
+
};
|
134
|
+
}
|
135
|
+
|
136
|
+
/**
|
137
|
+
* Creates a test project structure with collections and/or tables
|
138
|
+
*/
|
139
|
+
static createTestProject(options: {
|
140
|
+
hasCollections?: boolean;
|
141
|
+
hasTables?: boolean;
|
142
|
+
hasConflicts?: boolean;
|
143
|
+
useYaml?: boolean;
|
144
|
+
configDir?: string;
|
145
|
+
} = {}): string {
|
146
|
+
const {
|
147
|
+
hasCollections = true,
|
148
|
+
hasTables = false,
|
149
|
+
hasConflicts = false,
|
150
|
+
useYaml = false,
|
151
|
+
configDir,
|
152
|
+
} = options;
|
153
|
+
|
154
|
+
const testDir = configDir || this.createTempDir('test-project');
|
155
|
+
|
156
|
+
// Create .appwrite directory
|
157
|
+
const appwriteDir = path.join(testDir, '.appwrite');
|
158
|
+
fs.mkdirSync(appwriteDir, { recursive: true });
|
159
|
+
|
160
|
+
// Create config file
|
161
|
+
const config = this.createTestAppwriteConfig();
|
162
|
+
if (useYaml) {
|
163
|
+
const yamlContent = `
|
164
|
+
appwriteEndpoint: ${config.appwriteEndpoint}
|
165
|
+
appwriteProject: ${config.appwriteProject}
|
166
|
+
appwriteKey: ${config.appwriteKey}
|
167
|
+
databases:
|
168
|
+
- name: ${config.databases[0].name}
|
169
|
+
$id: ${config.databases[0].$id}
|
170
|
+
enabled: ${config.databases[0].enabled}
|
171
|
+
buckets: []
|
172
|
+
teams: []
|
173
|
+
functions: []
|
174
|
+
messaging: []
|
175
|
+
`;
|
176
|
+
fs.writeFileSync(path.join(appwriteDir, 'config.yaml'), yamlContent);
|
177
|
+
} else {
|
178
|
+
const tsContent = `
|
179
|
+
import { type AppwriteConfig } from 'appwrite-utils';
|
180
|
+
|
181
|
+
const appwriteConfig: AppwriteConfig = ${JSON.stringify(config, null, 2)};
|
182
|
+
|
183
|
+
export default appwriteConfig;
|
184
|
+
`;
|
185
|
+
fs.writeFileSync(path.join(testDir, 'appwriteConfig.ts'), tsContent);
|
186
|
+
}
|
187
|
+
|
188
|
+
// Create collections directory if requested
|
189
|
+
if (hasCollections) {
|
190
|
+
const collectionsDir = path.join(testDir, 'collections');
|
191
|
+
fs.mkdirSync(collectionsDir);
|
192
|
+
|
193
|
+
const collection = this.createTestCollection();
|
194
|
+
if (useYaml) {
|
195
|
+
const yamlContent = `
|
196
|
+
name: ${collection.name}
|
197
|
+
id: ${collection.$id}
|
198
|
+
documentSecurity: ${collection.documentSecurity}
|
199
|
+
enabled: ${collection.enabled}
|
200
|
+
permissions: []
|
201
|
+
attributes:
|
202
|
+
- key: name
|
203
|
+
type: string
|
204
|
+
size: 255
|
205
|
+
required: true
|
206
|
+
- key: email
|
207
|
+
type: email
|
208
|
+
required: true
|
209
|
+
indexes:
|
210
|
+
- key: email_index
|
211
|
+
type: unique
|
212
|
+
attributes: [email]
|
213
|
+
`;
|
214
|
+
fs.writeFileSync(path.join(collectionsDir, 'TestCollection.yaml'), yamlContent);
|
215
|
+
} else {
|
216
|
+
const tsContent = `
|
217
|
+
import { type CollectionCreate } from 'appwrite-utils';
|
218
|
+
|
219
|
+
const TestCollection: CollectionCreate = ${JSON.stringify(collection, null, 2)};
|
220
|
+
|
221
|
+
export default TestCollection;
|
222
|
+
`;
|
223
|
+
fs.writeFileSync(path.join(collectionsDir, 'TestCollection.ts'), tsContent);
|
224
|
+
}
|
225
|
+
}
|
226
|
+
|
227
|
+
// Create tables directory if requested
|
228
|
+
if (hasTables) {
|
229
|
+
const tablesDir = path.join(testDir, 'tables');
|
230
|
+
fs.mkdirSync(tablesDir);
|
231
|
+
|
232
|
+
const table = this.createTestTable();
|
233
|
+
if (useYaml) {
|
234
|
+
const yamlContent = `
|
235
|
+
name: ${table.name}
|
236
|
+
tableId: ${table.tableId}
|
237
|
+
databaseId: ${table.databaseId}
|
238
|
+
documentSecurity: ${table.documentSecurity}
|
239
|
+
enabled: ${table.enabled}
|
240
|
+
permissions: []
|
241
|
+
attributes:
|
242
|
+
- key: title
|
243
|
+
type: string
|
244
|
+
size: 255
|
245
|
+
required: true
|
246
|
+
- key: count
|
247
|
+
type: integer
|
248
|
+
required: false
|
249
|
+
min: 0
|
250
|
+
max: 1000
|
251
|
+
indexes:
|
252
|
+
- key: title_index
|
253
|
+
type: key
|
254
|
+
attributes: [title]
|
255
|
+
`;
|
256
|
+
fs.writeFileSync(path.join(tablesDir, 'TestTable.yaml'), yamlContent);
|
257
|
+
} else {
|
258
|
+
const tsContent = `
|
259
|
+
import { type TableCreate } from 'appwrite-utils';
|
260
|
+
|
261
|
+
const TestTable: any = ${JSON.stringify(table, null, 2)};
|
262
|
+
|
263
|
+
export default TestTable;
|
264
|
+
`;
|
265
|
+
fs.writeFileSync(path.join(tablesDir, 'TestTable.ts'), tsContent);
|
266
|
+
}
|
267
|
+
|
268
|
+
// Create conflicting collection if requested
|
269
|
+
if (hasConflicts && hasCollections) {
|
270
|
+
const conflictTable = this.createTestTable({
|
271
|
+
name: 'TestCollection', // Same name as collection
|
272
|
+
tableId: 'test-collection-conflict'
|
273
|
+
});
|
274
|
+
const tsContent = `
|
275
|
+
const ConflictTable: any = ${JSON.stringify(conflictTable, null, 2)};
|
276
|
+
export default ConflictTable;
|
277
|
+
`;
|
278
|
+
fs.writeFileSync(path.join(tablesDir, 'ConflictTable.ts'), tsContent);
|
279
|
+
}
|
280
|
+
}
|
281
|
+
|
282
|
+
return testDir;
|
283
|
+
}
|
284
|
+
|
285
|
+
/**
|
286
|
+
* Creates mock Appwrite client responses
|
287
|
+
*/
|
288
|
+
static createMockAppwriteResponses() {
|
289
|
+
return {
|
290
|
+
databases: {
|
291
|
+
list: jest.fn().mockResolvedValue({
|
292
|
+
databases: [
|
293
|
+
{
|
294
|
+
$id: 'test-db-id',
|
295
|
+
name: 'test-database',
|
296
|
+
enabled: true,
|
297
|
+
$createdAt: new Date().toISOString(),
|
298
|
+
$updatedAt: new Date().toISOString(),
|
299
|
+
}
|
300
|
+
],
|
301
|
+
total: 1,
|
302
|
+
}),
|
303
|
+
get: jest.fn().mockResolvedValue({
|
304
|
+
$id: 'test-db-id',
|
305
|
+
name: 'test-database',
|
306
|
+
enabled: true,
|
307
|
+
$createdAt: new Date().toISOString(),
|
308
|
+
$updatedAt: new Date().toISOString(),
|
309
|
+
}),
|
310
|
+
listCollections: jest.fn().mockResolvedValue({
|
311
|
+
collections: [
|
312
|
+
{
|
313
|
+
$id: 'test-collection',
|
314
|
+
name: 'TestCollection',
|
315
|
+
enabled: true,
|
316
|
+
documentSecurity: false,
|
317
|
+
$permissions: [],
|
318
|
+
attributes: [],
|
319
|
+
indexes: [],
|
320
|
+
$createdAt: new Date().toISOString(),
|
321
|
+
$updatedAt: new Date().toISOString(),
|
322
|
+
}
|
323
|
+
],
|
324
|
+
total: 1,
|
325
|
+
}),
|
326
|
+
},
|
327
|
+
health: {
|
328
|
+
get: jest.fn().mockResolvedValue({
|
329
|
+
status: 'OK',
|
330
|
+
version: '1.6.0',
|
331
|
+
}),
|
332
|
+
},
|
333
|
+
};
|
334
|
+
}
|
335
|
+
}
|
336
|
+
|
337
|
+
// Clean up after all tests
|
338
|
+
afterAll(() => {
|
339
|
+
TestUtils.cleanup();
|
340
|
+
});
|
@@ -0,0 +1,350 @@
|
|
1
|
+
import fs from 'fs';
|
2
|
+
import path from 'path';
|
3
|
+
import { jest } from '@jest/globals';
|
4
|
+
import { TestUtils } from '../testUtils';
|
5
|
+
import { loadConfig, loadConfigWithPath, findAppwriteConfig } from '../../src/utils/loadConfigs';
|
6
|
+
import { MessageFormatter } from '../../src/shared/messageFormatter';
|
7
|
+
|
8
|
+
// Mock MessageFormatter to capture log messages
|
9
|
+
jest.mock('../../src/shared/messageFormatter', () => ({
|
10
|
+
MessageFormatter: {
|
11
|
+
success: jest.fn(),
|
12
|
+
warning: jest.fn(),
|
13
|
+
info: jest.fn(),
|
14
|
+
error: jest.fn(),
|
15
|
+
},
|
16
|
+
}));
|
17
|
+
|
18
|
+
// Mock version detection
|
19
|
+
jest.mock('../../src/utils/versionDetection', () => ({
|
20
|
+
detectAppwriteVersionCached: jest.fn().mockResolvedValue({
|
21
|
+
serverVersion: '1.6.0',
|
22
|
+
apiMode: 'database',
|
23
|
+
}),
|
24
|
+
fetchServerVersion: jest.fn().mockResolvedValue('1.6.0'),
|
25
|
+
isVersionAtLeast: jest.fn((version, target) => {
|
26
|
+
if (!version) return false;
|
27
|
+
return version >= target;
|
28
|
+
}),
|
29
|
+
}));
|
30
|
+
|
31
|
+
describe('loadConfigs - Dual Schema Support', () => {
|
32
|
+
let testDir: string;
|
33
|
+
|
34
|
+
beforeEach(() => {
|
35
|
+
jest.clearAllMocks();
|
36
|
+
});
|
37
|
+
|
38
|
+
afterEach(() => {
|
39
|
+
TestUtils.cleanup();
|
40
|
+
});
|
41
|
+
|
42
|
+
describe('findAppwriteConfig', () => {
|
43
|
+
it('should find YAML config in .appwrite directory', () => {
|
44
|
+
testDir = TestUtils.createTestProject({ useYaml: true });
|
45
|
+
const configPath = findAppwriteConfig(testDir);
|
46
|
+
expect(configPath).toBe(testDir);
|
47
|
+
});
|
48
|
+
|
49
|
+
it('should find TypeScript config as fallback', () => {
|
50
|
+
testDir = TestUtils.createTestProject({ useYaml: false });
|
51
|
+
const configPath = findAppwriteConfig(testDir);
|
52
|
+
expect(configPath).toBe(testDir);
|
53
|
+
});
|
54
|
+
|
55
|
+
it('should return null when no config found', () => {
|
56
|
+
testDir = TestUtils.createTempDir();
|
57
|
+
const configPath = findAppwriteConfig(testDir);
|
58
|
+
expect(configPath).toBeNull();
|
59
|
+
});
|
60
|
+
});
|
61
|
+
|
62
|
+
describe('Dual Folder Loading', () => {
|
63
|
+
it('should load collections from collections/ directory only', async () => {
|
64
|
+
testDir = TestUtils.createTestProject({
|
65
|
+
hasCollections: true,
|
66
|
+
hasTables: false,
|
67
|
+
});
|
68
|
+
|
69
|
+
const config = await loadConfig(testDir);
|
70
|
+
|
71
|
+
expect(config.collections).toHaveLength(1);
|
72
|
+
expect(config.collections[0].name).toBe('TestCollection');
|
73
|
+
expect(config.collections[0]._isFromTablesDir).toBeUndefined();
|
74
|
+
expect(MessageFormatter.success).toHaveBeenCalledWith(
|
75
|
+
'Loading from collections/ directory: 1 files found',
|
76
|
+
{ prefix: 'Config' }
|
77
|
+
);
|
78
|
+
});
|
79
|
+
|
80
|
+
it('should load tables from tables/ directory only', async () => {
|
81
|
+
testDir = TestUtils.createTestProject({
|
82
|
+
hasCollections: false,
|
83
|
+
hasTables: true,
|
84
|
+
});
|
85
|
+
|
86
|
+
const config = await loadConfig(testDir);
|
87
|
+
|
88
|
+
expect(config.collections).toHaveLength(1);
|
89
|
+
expect(config.collections[0].name).toBe('TestTable');
|
90
|
+
expect(config.collections[0]._isFromTablesDir).toBe(true);
|
91
|
+
expect(MessageFormatter.success).toHaveBeenCalledWith(
|
92
|
+
'Loading from tables/ directory: 1 files found',
|
93
|
+
{ prefix: 'Config' }
|
94
|
+
);
|
95
|
+
});
|
96
|
+
|
97
|
+
it('should load from both collections/ and tables/ directories', async () => {
|
98
|
+
testDir = TestUtils.createTestProject({
|
99
|
+
hasCollections: true,
|
100
|
+
hasTables: true,
|
101
|
+
});
|
102
|
+
|
103
|
+
const config = await loadConfig(testDir);
|
104
|
+
|
105
|
+
expect(config.collections).toHaveLength(2);
|
106
|
+
|
107
|
+
const collection = config.collections.find((c: any) => !c._isFromTablesDir);
|
108
|
+
const table = config.collections.find((c: any) => c._isFromTablesDir);
|
109
|
+
|
110
|
+
expect(collection).toBeDefined();
|
111
|
+
expect(collection.name).toBe('TestCollection');
|
112
|
+
|
113
|
+
expect(table).toBeDefined();
|
114
|
+
expect(table.name).toBe('TestTable');
|
115
|
+
expect(table._isFromTablesDir).toBe(true);
|
116
|
+
|
117
|
+
expect(MessageFormatter.success).toHaveBeenCalledWith(
|
118
|
+
'Loading from collections/ directory: 1 files found',
|
119
|
+
{ prefix: 'Config' }
|
120
|
+
);
|
121
|
+
expect(MessageFormatter.success).toHaveBeenCalledWith(
|
122
|
+
'Loading from tables/ directory: 1 files found',
|
123
|
+
{ prefix: 'Config' }
|
124
|
+
);
|
125
|
+
});
|
126
|
+
|
127
|
+
it('should handle naming conflicts with collections/ taking priority', async () => {
|
128
|
+
testDir = TestUtils.createTestProject({
|
129
|
+
hasCollections: true,
|
130
|
+
hasTables: true,
|
131
|
+
hasConflicts: true,
|
132
|
+
});
|
133
|
+
|
134
|
+
const config = await loadConfig(testDir);
|
135
|
+
|
136
|
+
// Should have 2 items: original collection + one table (conflict skipped)
|
137
|
+
expect(config.collections).toHaveLength(2);
|
138
|
+
|
139
|
+
// Check that the collection takes priority
|
140
|
+
const collectionItems = config.collections.filter((c: any) => !c._isFromTablesDir);
|
141
|
+
expect(collectionItems).toHaveLength(1);
|
142
|
+
expect(collectionItems[0].name).toBe('TestCollection');
|
143
|
+
|
144
|
+
// Check warning was logged
|
145
|
+
expect(MessageFormatter.warning).toHaveBeenCalledWith(
|
146
|
+
expect.stringContaining('Found 1 naming conflicts'),
|
147
|
+
{ prefix: 'Config' }
|
148
|
+
);
|
149
|
+
});
|
150
|
+
});
|
151
|
+
|
152
|
+
describe('YAML Support', () => {
|
153
|
+
it('should load YAML collections', async () => {
|
154
|
+
testDir = TestUtils.createTestProject({
|
155
|
+
hasCollections: true,
|
156
|
+
useYaml: true,
|
157
|
+
});
|
158
|
+
|
159
|
+
const config = await loadConfig(testDir);
|
160
|
+
|
161
|
+
expect(config.collections).toHaveLength(1);
|
162
|
+
expect(config.collections[0].name).toBe('TestCollection');
|
163
|
+
expect(config.collections[0].$id).toBe('test-collection');
|
164
|
+
expect(config.collections[0].attributes).toHaveLength(2);
|
165
|
+
});
|
166
|
+
|
167
|
+
it('should load YAML tables', async () => {
|
168
|
+
testDir = TestUtils.createTestProject({
|
169
|
+
hasTables: true,
|
170
|
+
useYaml: true,
|
171
|
+
});
|
172
|
+
|
173
|
+
const config = await loadConfig(testDir);
|
174
|
+
|
175
|
+
expect(config.collections).toHaveLength(1);
|
176
|
+
expect(config.collections[0].name).toBe('TestTable');
|
177
|
+
expect(config.collections[0]._isFromTablesDir).toBe(true);
|
178
|
+
});
|
179
|
+
|
180
|
+
it('should load mixed YAML and TypeScript files', async () => {
|
181
|
+
testDir = TestUtils.createTestProject({
|
182
|
+
hasCollections: true,
|
183
|
+
useYaml: false,
|
184
|
+
});
|
185
|
+
|
186
|
+
// Add a YAML table alongside TypeScript collection
|
187
|
+
const tablesDir = path.join(testDir, 'tables');
|
188
|
+
fs.mkdirSync(tablesDir);
|
189
|
+
|
190
|
+
const yamlContent = `
|
191
|
+
name: YamlTable
|
192
|
+
tableId: yaml-table
|
193
|
+
databaseId: test-db-id
|
194
|
+
documentSecurity: false
|
195
|
+
enabled: true
|
196
|
+
permissions: []
|
197
|
+
attributes:
|
198
|
+
- key: content
|
199
|
+
type: string
|
200
|
+
size: 1000
|
201
|
+
required: true
|
202
|
+
indexes: []
|
203
|
+
`;
|
204
|
+
fs.writeFileSync(path.join(tablesDir, 'YamlTable.yaml'), yamlContent);
|
205
|
+
|
206
|
+
const config = await loadConfig(testDir);
|
207
|
+
|
208
|
+
expect(config.collections).toHaveLength(2);
|
209
|
+
expect(config.collections.some((c: any) => c.name === 'TestCollection')).toBe(true);
|
210
|
+
expect(config.collections.some((c: any) => c.name === 'YamlTable')).toBe(true);
|
211
|
+
});
|
212
|
+
});
|
213
|
+
|
214
|
+
describe('Legacy Single Directory Support', () => {
|
215
|
+
it('should fall back to legacy collections/ directory when no dual directories', async () => {
|
216
|
+
testDir = TestUtils.createTempDir();
|
217
|
+
|
218
|
+
// Create config without .appwrite structure
|
219
|
+
const config = TestUtils.createTestAppwriteConfig();
|
220
|
+
const tsContent = `
|
221
|
+
import { type AppwriteConfig } from 'appwrite-utils';
|
222
|
+
const appwriteConfig: AppwriteConfig = ${JSON.stringify(config, null, 2)};
|
223
|
+
export default appwriteConfig;
|
224
|
+
`;
|
225
|
+
fs.writeFileSync(path.join(testDir, 'appwriteConfig.ts'), tsContent);
|
226
|
+
|
227
|
+
// Create only collections directory (legacy)
|
228
|
+
const collectionsDir = path.join(testDir, 'collections');
|
229
|
+
fs.mkdirSync(collectionsDir);
|
230
|
+
|
231
|
+
const collection = TestUtils.createTestCollection();
|
232
|
+
const collectionContent = `
|
233
|
+
const TestCollection = ${JSON.stringify(collection, null, 2)};
|
234
|
+
export default TestCollection;
|
235
|
+
`;
|
236
|
+
fs.writeFileSync(path.join(collectionsDir, 'TestCollection.ts'), collectionContent);
|
237
|
+
|
238
|
+
const loadedConfig = await loadConfig(testDir);
|
239
|
+
|
240
|
+
expect(loadedConfig.collections).toHaveLength(1);
|
241
|
+
expect(loadedConfig.collections[0].name).toBe('TestCollection');
|
242
|
+
expect(MessageFormatter.info).toHaveBeenCalledWith(
|
243
|
+
'Using legacy single directory: collections/',
|
244
|
+
{ prefix: 'Config' }
|
245
|
+
);
|
246
|
+
});
|
247
|
+
});
|
248
|
+
|
249
|
+
describe('loadConfigWithPath', () => {
|
250
|
+
it('should return both config and actual config path', async () => {
|
251
|
+
testDir = TestUtils.createTestProject({ useYaml: true });
|
252
|
+
|
253
|
+
const result = await loadConfigWithPath(testDir);
|
254
|
+
|
255
|
+
expect(result.config).toBeDefined();
|
256
|
+
expect(result.actualConfigPath).toContain('config.yaml');
|
257
|
+
expect(result.config.appwriteProject).toBe('test-project');
|
258
|
+
});
|
259
|
+
|
260
|
+
it('should handle .appwrite directory path directly', async () => {
|
261
|
+
testDir = TestUtils.createTestProject({ useYaml: true });
|
262
|
+
const appwriteDir = path.join(testDir, '.appwrite');
|
263
|
+
|
264
|
+
const result = await loadConfigWithPath(appwriteDir);
|
265
|
+
|
266
|
+
expect(result.config).toBeDefined();
|
267
|
+
expect(result.actualConfigPath).toContain('config.yaml');
|
268
|
+
});
|
269
|
+
});
|
270
|
+
|
271
|
+
describe('Error Handling', () => {
|
272
|
+
it('should throw error when no config found', async () => {
|
273
|
+
testDir = TestUtils.createTempDir();
|
274
|
+
|
275
|
+
await expect(loadConfig(testDir)).rejects.toThrow('No valid configuration found');
|
276
|
+
});
|
277
|
+
|
278
|
+
it('should handle malformed YAML gracefully', async () => {
|
279
|
+
testDir = TestUtils.createTestProject({ useYaml: true });
|
280
|
+
|
281
|
+
// Create malformed YAML collection
|
282
|
+
const collectionsDir = path.join(testDir, 'collections');
|
283
|
+
fs.writeFileSync(path.join(collectionsDir, 'BadCollection.yaml'), 'invalid: yaml: content:');
|
284
|
+
|
285
|
+
const config = await loadConfig(testDir);
|
286
|
+
|
287
|
+
// Should still load other valid collections
|
288
|
+
expect(config).toBeDefined();
|
289
|
+
// The malformed collection should be skipped
|
290
|
+
});
|
291
|
+
|
292
|
+
it('should handle missing TypeScript exports gracefully', async () => {
|
293
|
+
testDir = TestUtils.createTestProject({ useYaml: false });
|
294
|
+
|
295
|
+
// Create TypeScript file with no export
|
296
|
+
const collectionsDir = path.join(testDir, 'collections');
|
297
|
+
fs.writeFileSync(path.join(collectionsDir, 'NoExport.ts'), 'const collection = {};');
|
298
|
+
|
299
|
+
const config = await loadConfig(testDir);
|
300
|
+
|
301
|
+
expect(config).toBeDefined();
|
302
|
+
// Should still have the original test collection
|
303
|
+
expect(config.collections.length).toBeGreaterThanOrEqual(1);
|
304
|
+
});
|
305
|
+
});
|
306
|
+
|
307
|
+
describe('Performance and Scalability', () => {
|
308
|
+
it('should handle large numbers of collections and tables', async () => {
|
309
|
+
testDir = TestUtils.createTempDir();
|
310
|
+
|
311
|
+
// Create config
|
312
|
+
const config = TestUtils.createTestAppwriteConfig();
|
313
|
+
const tsContent = `export default ${JSON.stringify(config, null, 2)};`;
|
314
|
+
fs.writeFileSync(path.join(testDir, 'appwriteConfig.ts'), tsContent);
|
315
|
+
|
316
|
+
// Create many collections
|
317
|
+
const collectionsDir = path.join(testDir, 'collections');
|
318
|
+
fs.mkdirSync(collectionsDir);
|
319
|
+
|
320
|
+
for (let i = 0; i < 50; i++) {
|
321
|
+
const collection = TestUtils.createTestCollection({
|
322
|
+
name: `Collection${i}`,
|
323
|
+
$id: `collection-${i}`,
|
324
|
+
});
|
325
|
+
const content = `export default ${JSON.stringify(collection, null, 2)};`;
|
326
|
+
fs.writeFileSync(path.join(collectionsDir, `Collection${i}.ts`), content);
|
327
|
+
}
|
328
|
+
|
329
|
+
// Create many tables
|
330
|
+
const tablesDir = path.join(testDir, 'tables');
|
331
|
+
fs.mkdirSync(tablesDir);
|
332
|
+
|
333
|
+
for (let i = 0; i < 30; i++) {
|
334
|
+
const table = TestUtils.createTestTable({
|
335
|
+
name: `Table${i}`,
|
336
|
+
tableId: `table-${i}`,
|
337
|
+
});
|
338
|
+
const content = `export default ${JSON.stringify(table, null, 2)};`;
|
339
|
+
fs.writeFileSync(path.join(tablesDir, `Table${i}.ts`), content);
|
340
|
+
}
|
341
|
+
|
342
|
+
const startTime = Date.now();
|
343
|
+
const loadedConfig = await loadConfig(testDir);
|
344
|
+
const loadTime = Date.now() - startTime;
|
345
|
+
|
346
|
+
expect(loadedConfig.collections).toHaveLength(80); // 50 collections + 30 tables
|
347
|
+
expect(loadTime).toBeLessThan(5000); // Should load within 5 seconds
|
348
|
+
});
|
349
|
+
});
|
350
|
+
});
|