appwrite-utils-cli 1.7.7 → 1.7.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/SELECTION_DIALOGS.md +146 -0
- package/dist/cli/commands/databaseCommands.js +89 -23
- package/dist/config/services/ConfigLoaderService.d.ts +7 -0
- package/dist/config/services/ConfigLoaderService.js +47 -1
- package/dist/functions/deployments.js +5 -23
- package/dist/functions/methods.js +4 -2
- package/dist/functions/pathResolution.d.ts +37 -0
- package/dist/functions/pathResolution.js +185 -0
- package/dist/functions/templates/count-docs-in-collection/README.md +54 -0
- package/dist/functions/templates/count-docs-in-collection/package.json +25 -0
- package/dist/functions/templates/count-docs-in-collection/src/main.ts +159 -0
- package/dist/functions/templates/count-docs-in-collection/src/request.ts +9 -0
- package/dist/functions/templates/count-docs-in-collection/tsconfig.json +28 -0
- package/dist/functions/templates/hono-typescript/README.md +286 -0
- package/dist/functions/templates/hono-typescript/package.json +26 -0
- package/dist/functions/templates/hono-typescript/src/adapters/request.ts +74 -0
- package/dist/functions/templates/hono-typescript/src/adapters/response.ts +106 -0
- package/dist/functions/templates/hono-typescript/src/app.ts +180 -0
- package/dist/functions/templates/hono-typescript/src/context.ts +103 -0
- package/dist/functions/templates/hono-typescript/src/index.ts +54 -0
- package/dist/functions/templates/hono-typescript/src/middleware/appwrite.ts +119 -0
- package/dist/functions/templates/hono-typescript/tsconfig.json +20 -0
- package/dist/functions/templates/typescript-node/README.md +32 -0
- package/dist/functions/templates/typescript-node/package.json +25 -0
- package/dist/functions/templates/typescript-node/src/context.ts +103 -0
- package/dist/functions/templates/typescript-node/src/index.ts +29 -0
- package/dist/functions/templates/typescript-node/tsconfig.json +28 -0
- package/dist/functions/templates/uv/README.md +31 -0
- package/dist/functions/templates/uv/pyproject.toml +30 -0
- package/dist/functions/templates/uv/src/__init__.py +0 -0
- package/dist/functions/templates/uv/src/context.py +125 -0
- package/dist/functions/templates/uv/src/index.py +46 -0
- package/dist/main.js +175 -4
- package/dist/migrations/appwriteToX.d.ts +27 -2
- package/dist/migrations/appwriteToX.js +293 -69
- package/dist/migrations/yaml/YamlImportConfigLoader.d.ts +1 -1
- package/dist/migrations/yaml/generateImportSchemas.js +23 -8
- package/dist/shared/schemaGenerator.js +25 -12
- package/dist/shared/selectionDialogs.d.ts +214 -0
- package/dist/shared/selectionDialogs.js +540 -0
- package/dist/utils/configDiscovery.d.ts +4 -4
- package/dist/utils/configDiscovery.js +66 -30
- package/dist/utils/yamlConverter.d.ts +1 -0
- package/dist/utils/yamlConverter.js +26 -3
- package/dist/utilsController.d.ts +7 -1
- package/dist/utilsController.js +198 -17
- package/package.json +4 -2
- package/scripts/copy-templates.ts +23 -0
- package/src/cli/commands/databaseCommands.ts +133 -34
- package/src/config/services/ConfigLoaderService.ts +62 -1
- package/src/functions/deployments.ts +10 -35
- package/src/functions/methods.ts +4 -2
- package/src/functions/pathResolution.ts +227 -0
- package/src/main.ts +276 -34
- package/src/migrations/appwriteToX.ts +385 -90
- package/src/migrations/yaml/generateImportSchemas.ts +26 -8
- package/src/shared/schemaGenerator.ts +29 -12
- package/src/shared/selectionDialogs.ts +745 -0
- package/src/utils/configDiscovery.ts +83 -39
- package/src/utils/yamlConverter.ts +29 -3
- package/src/utilsController.ts +250 -22
- package/dist/utils/schemaStrings.d.ts +0 -14
- package/dist/utils/schemaStrings.js +0 -428
- package/dist/utils/sessionPreservationExample.d.ts +0 -1666
- package/dist/utils/sessionPreservationExample.js +0 -101
|
@@ -157,6 +157,53 @@ const YamlCollectionSchema = z.object({
|
|
|
157
157
|
|
|
158
158
|
type YamlCollection = z.infer<typeof YamlCollectionSchema>;
|
|
159
159
|
|
|
160
|
+
// YAML Table Schema - Supports table-specific terminology
|
|
161
|
+
const YamlTableSchema = z.object({
|
|
162
|
+
name: z.string(),
|
|
163
|
+
id: z.string().optional(),
|
|
164
|
+
rowSecurity: z.boolean().default(false), // Tables use rowSecurity
|
|
165
|
+
enabled: z.boolean().default(true),
|
|
166
|
+
permissions: z.array(
|
|
167
|
+
z.object({
|
|
168
|
+
permission: z.string(),
|
|
169
|
+
target: z.string()
|
|
170
|
+
})
|
|
171
|
+
).optional().default([]),
|
|
172
|
+
columns: z.array( // Tables use columns terminology
|
|
173
|
+
z.object({
|
|
174
|
+
key: z.string(),
|
|
175
|
+
type: z.string(),
|
|
176
|
+
size: z.number().optional(),
|
|
177
|
+
required: z.boolean().default(false),
|
|
178
|
+
array: z.boolean().optional(),
|
|
179
|
+
encrypted: z.boolean().optional(), // Tables support encrypted property
|
|
180
|
+
default: z.any().optional(),
|
|
181
|
+
min: z.number().optional(),
|
|
182
|
+
max: z.number().optional(),
|
|
183
|
+
elements: z.array(z.string()).optional(),
|
|
184
|
+
relatedTable: z.string().optional(), // Tables use relatedTable
|
|
185
|
+
relationType: z.string().optional(),
|
|
186
|
+
twoWay: z.boolean().optional(),
|
|
187
|
+
twoWayKey: z.string().optional(),
|
|
188
|
+
onDelete: z.string().optional(),
|
|
189
|
+
side: z.string().optional(),
|
|
190
|
+
encrypt: z.boolean().optional(),
|
|
191
|
+
format: z.string().optional()
|
|
192
|
+
})
|
|
193
|
+
).optional().default([]),
|
|
194
|
+
indexes: z.array(
|
|
195
|
+
z.object({
|
|
196
|
+
key: z.string(),
|
|
197
|
+
type: z.string(),
|
|
198
|
+
columns: z.array(z.string()), // Tables use columns in indexes
|
|
199
|
+
orders: z.array(z.string()).optional()
|
|
200
|
+
})
|
|
201
|
+
).optional().default([]),
|
|
202
|
+
importDefs: z.array(z.any()).optional().default([])
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
type YamlTable = z.infer<typeof YamlTableSchema>;
|
|
206
|
+
|
|
160
207
|
/**
|
|
161
208
|
* Loads a YAML collection file and converts it to CollectionCreate format
|
|
162
209
|
* @param filePath Path to the YAML collection file
|
|
@@ -214,57 +261,54 @@ export const loadYamlCollection = (filePath: string): CollectionCreate | null =>
|
|
|
214
261
|
};
|
|
215
262
|
|
|
216
263
|
/**
|
|
217
|
-
* Loads a YAML table file and converts it to
|
|
264
|
+
* Loads a YAML table file and converts it to CollectionCreate format
|
|
218
265
|
* @param filePath Path to the YAML table file
|
|
219
|
-
* @returns
|
|
266
|
+
* @returns CollectionCreate object or null if loading fails
|
|
220
267
|
*/
|
|
221
|
-
export const loadYamlTable = (filePath: string):
|
|
268
|
+
export const loadYamlTable = (filePath: string): CollectionCreate | null => {
|
|
222
269
|
try {
|
|
223
270
|
const fileContent = fs.readFileSync(filePath, "utf8");
|
|
224
271
|
const yamlData = yaml.load(fileContent) as unknown;
|
|
225
272
|
|
|
226
|
-
//
|
|
227
|
-
const parsedTable =
|
|
273
|
+
// Use the new table-specific schema
|
|
274
|
+
const parsedTable = YamlTableSchema.parse(yamlData);
|
|
228
275
|
|
|
229
|
-
// Convert YAML table to
|
|
230
|
-
const table:
|
|
276
|
+
// Convert YAML table to CollectionCreate format (internal representation)
|
|
277
|
+
const table: CollectionCreate = {
|
|
231
278
|
name: parsedTable.name,
|
|
232
|
-
|
|
233
|
-
documentSecurity: parsedTable.documentSecurity
|
|
279
|
+
$id: (yamlData as any).tableId || parsedTable.id || parsedTable.name.toLowerCase().replace(/\s+/g, '_'),
|
|
280
|
+
documentSecurity: parsedTable.rowSecurity, // Convert rowSecurity to documentSecurity
|
|
234
281
|
enabled: parsedTable.enabled,
|
|
235
282
|
$permissions: parsedTable.permissions.map(p => ({
|
|
236
283
|
permission: p.permission as any,
|
|
237
284
|
target: p.target
|
|
238
285
|
})),
|
|
239
|
-
attributes: parsedTable.
|
|
240
|
-
key:
|
|
241
|
-
type:
|
|
242
|
-
size:
|
|
243
|
-
required:
|
|
244
|
-
array:
|
|
245
|
-
xdefault:
|
|
246
|
-
min:
|
|
247
|
-
max:
|
|
248
|
-
elements:
|
|
249
|
-
relatedCollection:
|
|
250
|
-
relationType:
|
|
251
|
-
twoWay:
|
|
252
|
-
twoWayKey:
|
|
253
|
-
onDelete:
|
|
254
|
-
side:
|
|
255
|
-
encrypted:
|
|
256
|
-
format:
|
|
286
|
+
attributes: parsedTable.columns.map(col => ({ // Convert columns to attributes
|
|
287
|
+
key: col.key,
|
|
288
|
+
type: col.type as any,
|
|
289
|
+
size: col.size,
|
|
290
|
+
required: col.required,
|
|
291
|
+
array: col.array,
|
|
292
|
+
xdefault: col.default,
|
|
293
|
+
min: col.min,
|
|
294
|
+
max: col.max,
|
|
295
|
+
elements: col.elements,
|
|
296
|
+
relatedCollection: col.relatedTable, // Convert relatedTable to relatedCollection
|
|
297
|
+
relationType: col.relationType as any,
|
|
298
|
+
twoWay: col.twoWay,
|
|
299
|
+
twoWayKey: col.twoWayKey,
|
|
300
|
+
onDelete: col.onDelete as any,
|
|
301
|
+
side: col.side as any,
|
|
302
|
+
encrypted: col.encrypted || col.encrypt, // Support both encrypted and encrypt
|
|
303
|
+
format: col.format
|
|
257
304
|
})),
|
|
258
305
|
indexes: parsedTable.indexes.map(idx => ({
|
|
259
306
|
key: idx.key,
|
|
260
307
|
type: idx.type as any,
|
|
261
|
-
attributes: idx.attributes
|
|
308
|
+
attributes: idx.columns, // Convert columns to attributes
|
|
262
309
|
orders: idx.orders as any
|
|
263
310
|
})),
|
|
264
|
-
importDefs: parsedTable.importDefs
|
|
265
|
-
databaseId: (yamlData as any).databaseId,
|
|
266
|
-
// Add backward compatibility field
|
|
267
|
-
$id: (yamlData as any).$id || parsedTable.id
|
|
311
|
+
importDefs: parsedTable.importDefs || []
|
|
268
312
|
};
|
|
269
313
|
|
|
270
314
|
return table;
|
|
@@ -346,7 +390,7 @@ export const discoverCollections = async (collectionsDir: string): Promise<Colle
|
|
|
346
390
|
* Result of discovering tables from a directory
|
|
347
391
|
*/
|
|
348
392
|
export interface TableDiscoveryResult {
|
|
349
|
-
tables:
|
|
393
|
+
tables: CollectionCreate[];
|
|
350
394
|
loadedNames: Set<string>;
|
|
351
395
|
conflicts: Array<{ name: string; source1: string; source2: string }>;
|
|
352
396
|
}
|
|
@@ -361,7 +405,7 @@ export const discoverTables = async (
|
|
|
361
405
|
tablesDir: string,
|
|
362
406
|
existingNames: Set<string> = new Set()
|
|
363
407
|
): Promise<TableDiscoveryResult> => {
|
|
364
|
-
const tables:
|
|
408
|
+
const tables: CollectionCreate[] = [];
|
|
365
409
|
const loadedNames = new Set<string>();
|
|
366
410
|
const conflicts: Array<{ name: string; source1: string; source2: string }> = [];
|
|
367
411
|
|
|
@@ -380,7 +424,7 @@ export const discoverTables = async (
|
|
|
380
424
|
continue;
|
|
381
425
|
}
|
|
382
426
|
const filePath = path.join(tablesDir, file);
|
|
383
|
-
let table:
|
|
427
|
+
let table: CollectionCreate | null = null;
|
|
384
428
|
|
|
385
429
|
// Handle YAML tables
|
|
386
430
|
if (file.endsWith('.yaml') || file.endsWith('.yml')) {
|
|
@@ -391,7 +435,7 @@ export const discoverTables = async (
|
|
|
391
435
|
else if (file.endsWith('.ts')) {
|
|
392
436
|
const fileUrl = pathToFileURL(filePath).href;
|
|
393
437
|
const tableModule = (await import(fileUrl));
|
|
394
|
-
const importedTable:
|
|
438
|
+
const importedTable: CollectionCreate = tableModule.default?.default || tableModule.default || tableModule;
|
|
395
439
|
if (importedTable) {
|
|
396
440
|
table = importedTable;
|
|
397
441
|
// Ensure importDefs are properly loaded
|
|
@@ -402,7 +446,7 @@ export const discoverTables = async (
|
|
|
402
446
|
}
|
|
403
447
|
|
|
404
448
|
if (table) {
|
|
405
|
-
const tableName = table.name || table.tableId || table.$id || file;
|
|
449
|
+
const tableName = table.name || (table as any).tableId || table.$id || file;
|
|
406
450
|
|
|
407
451
|
// Check for naming conflicts with existing collections
|
|
408
452
|
if (existingNames.has(tableName)) {
|
|
@@ -415,7 +459,7 @@ export const discoverTables = async (
|
|
|
415
459
|
} else {
|
|
416
460
|
loadedNames.add(tableName);
|
|
417
461
|
// Mark as coming from tables directory
|
|
418
|
-
table._isFromTablesDir = true;
|
|
462
|
+
(table as any)._isFromTablesDir = true;
|
|
419
463
|
tables.push(table);
|
|
420
464
|
}
|
|
421
465
|
}
|
|
@@ -467,7 +511,7 @@ export const discoverLegacyDirectory = async (
|
|
|
467
511
|
...collection,
|
|
468
512
|
_isFromTablesDir: true,
|
|
469
513
|
tableId: collection.$id || collection.name.toLowerCase().replace(/\s+/g, '_')
|
|
470
|
-
};
|
|
514
|
+
} as CollectionCreate;
|
|
471
515
|
items.push(table);
|
|
472
516
|
} else {
|
|
473
517
|
items.push(collection);
|
|
@@ -8,6 +8,7 @@ export interface YamlCollectionData {
|
|
|
8
8
|
name: string;
|
|
9
9
|
id?: string;
|
|
10
10
|
documentSecurity?: boolean;
|
|
11
|
+
rowSecurity?: boolean;
|
|
11
12
|
enabled?: boolean;
|
|
12
13
|
permissions?: Array<{
|
|
13
14
|
permission: string;
|
|
@@ -86,10 +87,16 @@ export function collectionToYaml(
|
|
|
86
87
|
const yamlData: YamlCollectionData = {
|
|
87
88
|
name: collection.name,
|
|
88
89
|
id: collection.$id,
|
|
89
|
-
documentSecurity: collection.documentSecurity,
|
|
90
90
|
enabled: collection.enabled,
|
|
91
91
|
};
|
|
92
92
|
|
|
93
|
+
// Use appropriate security field based on terminology
|
|
94
|
+
if (config.useTableTerminology) {
|
|
95
|
+
yamlData.rowSecurity = collection.documentSecurity;
|
|
96
|
+
} else {
|
|
97
|
+
yamlData.documentSecurity = collection.documentSecurity;
|
|
98
|
+
}
|
|
99
|
+
|
|
93
100
|
// Convert permissions
|
|
94
101
|
if (collection.$permissions && collection.$permissions.length > 0) {
|
|
95
102
|
yamlData.permissions = collection.$permissions.map(p => ({
|
|
@@ -240,6 +247,12 @@ export function normalizeYamlData(yamlData: YamlCollectionData): YamlCollectionD
|
|
|
240
247
|
}));
|
|
241
248
|
}
|
|
242
249
|
|
|
250
|
+
// Normalize security fields - prefer documentSecurity for consistency
|
|
251
|
+
if (yamlData.rowSecurity !== undefined && yamlData.documentSecurity === undefined) {
|
|
252
|
+
normalized.documentSecurity = yamlData.rowSecurity;
|
|
253
|
+
delete normalized.rowSecurity;
|
|
254
|
+
}
|
|
255
|
+
|
|
243
256
|
return normalized;
|
|
244
257
|
}
|
|
245
258
|
|
|
@@ -248,7 +261,8 @@ export function normalizeYamlData(yamlData: YamlCollectionData): YamlCollectionD
|
|
|
248
261
|
*/
|
|
249
262
|
export function usesTableTerminology(yamlData: YamlCollectionData): boolean {
|
|
250
263
|
return !!(yamlData.columns && yamlData.columns.length > 0) ||
|
|
251
|
-
!!(yamlData.indexes?.some(idx => !!(idx as any).columns))
|
|
264
|
+
!!(yamlData.indexes?.some(idx => !!(idx as any).columns)) ||
|
|
265
|
+
yamlData.rowSecurity !== undefined;
|
|
252
266
|
}
|
|
253
267
|
|
|
254
268
|
/**
|
|
@@ -279,6 +293,12 @@ export function convertTerminology(
|
|
|
279
293
|
}));
|
|
280
294
|
}
|
|
281
295
|
|
|
296
|
+
// Convert security field
|
|
297
|
+
if (yamlData.documentSecurity !== undefined && yamlData.rowSecurity === undefined) {
|
|
298
|
+
converted.rowSecurity = yamlData.documentSecurity;
|
|
299
|
+
delete converted.documentSecurity;
|
|
300
|
+
}
|
|
301
|
+
|
|
282
302
|
return converted;
|
|
283
303
|
} else {
|
|
284
304
|
// Convert columns to attributes (normalize)
|
|
@@ -367,7 +387,6 @@ export function generateYamlTemplate(
|
|
|
367
387
|
const template: YamlCollectionData = {
|
|
368
388
|
name: entityName,
|
|
369
389
|
id: entityName.toLowerCase().replace(/\s+/g, '_'),
|
|
370
|
-
documentSecurity: false,
|
|
371
390
|
enabled: true,
|
|
372
391
|
permissions: [
|
|
373
392
|
{
|
|
@@ -390,6 +409,13 @@ export function generateYamlTemplate(
|
|
|
390
409
|
importDefs: []
|
|
391
410
|
};
|
|
392
411
|
|
|
412
|
+
// Use appropriate security field based on terminology
|
|
413
|
+
if (config.useTableTerminology) {
|
|
414
|
+
template.rowSecurity = false;
|
|
415
|
+
} else {
|
|
416
|
+
template.documentSecurity = false;
|
|
417
|
+
}
|
|
418
|
+
|
|
393
419
|
// Assign fields with correct property name
|
|
394
420
|
(template as any)[fieldsKey] = fieldsArray;
|
|
395
421
|
template.indexes = indexesArray as any;
|
package/src/utilsController.ts
CHANGED
|
@@ -12,11 +12,10 @@ import {
|
|
|
12
12
|
type Specification,
|
|
13
13
|
} from "appwrite-utils";
|
|
14
14
|
import {
|
|
15
|
-
loadConfig,
|
|
16
|
-
loadConfigWithPath,
|
|
17
15
|
findAppwriteConfig,
|
|
18
16
|
findFunctionsDir,
|
|
19
17
|
} from "./utils/loadConfigs.js";
|
|
18
|
+
import { normalizeFunctionName, validateFunctionDirectory } from './functions/pathResolution.js';
|
|
20
19
|
import { UsersController } from "./users/methods.js";
|
|
21
20
|
import { AppwriteToX } from "./migrations/appwriteToX.js";
|
|
22
21
|
import { ImportController } from "./migrations/importController.js";
|
|
@@ -72,6 +71,7 @@ import { configureLogging, updateLogger, logger } from "./shared/logging.js";
|
|
|
72
71
|
import { MessageFormatter, Messages } from "./shared/messageFormatter.js";
|
|
73
72
|
import { SchemaGenerator } from "./shared/schemaGenerator.js";
|
|
74
73
|
import { findYamlConfig } from "./config/yamlConfig.js";
|
|
74
|
+
import { createImportSchemas } from "./migrations/yaml/generateImportSchemas.js";
|
|
75
75
|
import {
|
|
76
76
|
validateCollectionsTablesConfig,
|
|
77
77
|
reportValidationResults,
|
|
@@ -80,6 +80,7 @@ import {
|
|
|
80
80
|
} from "./config/configValidation.js";
|
|
81
81
|
import { ConfigManager } from "./config/ConfigManager.js";
|
|
82
82
|
import { ClientFactory } from "./utils/ClientFactory.js";
|
|
83
|
+
import type { DatabaseSelection, BucketSelection } from "./shared/selectionDialogs.js";
|
|
83
84
|
|
|
84
85
|
export interface SetupOptions {
|
|
85
86
|
databases?: Models.Database[];
|
|
@@ -114,9 +115,33 @@ export class UtilsController {
|
|
|
114
115
|
appwriteKey?: string;
|
|
115
116
|
}
|
|
116
117
|
): UtilsController {
|
|
118
|
+
// Clear instance if currentUserDir has changed
|
|
119
|
+
if (UtilsController.instance &&
|
|
120
|
+
UtilsController.instance.currentUserDir !== currentUserDir) {
|
|
121
|
+
logger.debug(`Clearing singleton: currentUserDir changed from ${UtilsController.instance.currentUserDir} to ${currentUserDir}`, { prefix: "UtilsController" });
|
|
122
|
+
UtilsController.clearInstance();
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Clear instance if directConfig endpoint or project has changed
|
|
126
|
+
if (UtilsController.instance && directConfig) {
|
|
127
|
+
const existingConfig = UtilsController.instance.config;
|
|
128
|
+
if (existingConfig) {
|
|
129
|
+
const endpointChanged = directConfig.appwriteEndpoint &&
|
|
130
|
+
existingConfig.appwriteEndpoint !== directConfig.appwriteEndpoint;
|
|
131
|
+
const projectChanged = directConfig.appwriteProject &&
|
|
132
|
+
existingConfig.appwriteProject !== directConfig.appwriteProject;
|
|
133
|
+
|
|
134
|
+
if (endpointChanged || projectChanged) {
|
|
135
|
+
logger.debug("Clearing singleton: endpoint or project changed", { prefix: "UtilsController" });
|
|
136
|
+
UtilsController.clearInstance();
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
117
141
|
if (!UtilsController.instance) {
|
|
118
142
|
UtilsController.instance = new UtilsController(currentUserDir, directConfig);
|
|
119
143
|
}
|
|
144
|
+
|
|
120
145
|
return UtilsController.instance;
|
|
121
146
|
}
|
|
122
147
|
|
|
@@ -341,6 +366,26 @@ export class UtilsController {
|
|
|
341
366
|
return dbs.databases;
|
|
342
367
|
}
|
|
343
368
|
|
|
369
|
+
async fetchAllBuckets(): Promise<{ buckets: Models.Bucket[] }> {
|
|
370
|
+
await this.init();
|
|
371
|
+
if (!this.storage) {
|
|
372
|
+
MessageFormatter.warning("Storage not initialized - buckets will be empty", { prefix: "Controller" });
|
|
373
|
+
return { buckets: [] };
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
try {
|
|
377
|
+
const result = await this.storage.listBuckets([
|
|
378
|
+
Query.limit(1000) // Increase limit to get all buckets
|
|
379
|
+
]);
|
|
380
|
+
|
|
381
|
+
MessageFormatter.success(`Found ${result.buckets.length} buckets`, { prefix: "Controller" });
|
|
382
|
+
return result;
|
|
383
|
+
} catch (error: any) {
|
|
384
|
+
MessageFormatter.error(`Failed to fetch buckets: ${error.message || error}`, error instanceof Error ? error : undefined, { prefix: "Controller" });
|
|
385
|
+
return { buckets: [] };
|
|
386
|
+
}
|
|
387
|
+
}
|
|
388
|
+
|
|
344
389
|
async wipeOtherDatabases(databasesToKeep: Models.Database[]) {
|
|
345
390
|
await this.init();
|
|
346
391
|
if (!this.database) {
|
|
@@ -404,10 +449,17 @@ export class UtilsController {
|
|
|
404
449
|
for (const entry of entries) {
|
|
405
450
|
if (entry.isDirectory()) {
|
|
406
451
|
const functionPath = path.join(functionsDir, entry.name);
|
|
407
|
-
|
|
452
|
+
|
|
453
|
+
// Validate it's a function directory
|
|
454
|
+
if (!validateFunctionDirectory(functionPath)) {
|
|
455
|
+
continue; // Skip invalid directories
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// Match with config functions using normalized names
|
|
408
459
|
if (this.config?.functions) {
|
|
460
|
+
const normalizedEntryName = normalizeFunctionName(entry.name);
|
|
409
461
|
const matchingFunc = this.config.functions.find(
|
|
410
|
-
(f) => f.name
|
|
462
|
+
(f) => normalizeFunctionName(f.name) === normalizedEntryName
|
|
411
463
|
);
|
|
412
464
|
if (matchingFunc) {
|
|
413
465
|
functionDirMap.set(matchingFunc.name, functionPath);
|
|
@@ -569,28 +621,32 @@ export class UtilsController {
|
|
|
569
621
|
async generateSchemas() {
|
|
570
622
|
// Schema generation doesn't need Appwrite connection, just config
|
|
571
623
|
if (!this.config) {
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
|
|
578
|
-
|
|
579
|
-
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
return;
|
|
624
|
+
MessageFormatter.progress("Loading config from ConfigManager...", { prefix: "Config" });
|
|
625
|
+
try {
|
|
626
|
+
const configManager = ConfigManager.getInstance();
|
|
627
|
+
|
|
628
|
+
// Load config if not already loaded
|
|
629
|
+
if (!configManager.hasConfig()) {
|
|
630
|
+
await configManager.loadConfig({
|
|
631
|
+
configDir: this.currentUserDir,
|
|
632
|
+
validate: false,
|
|
633
|
+
strictMode: false,
|
|
634
|
+
});
|
|
584
635
|
}
|
|
585
|
-
|
|
586
|
-
|
|
636
|
+
|
|
637
|
+
this.config = configManager.getConfig();
|
|
638
|
+
MessageFormatter.info("Config loaded successfully from ConfigManager", { prefix: "Config" });
|
|
639
|
+
} catch (error) {
|
|
640
|
+
MessageFormatter.error("Failed to load config", error instanceof Error ? error : undefined, { prefix: "Config" });
|
|
587
641
|
return;
|
|
588
642
|
}
|
|
589
643
|
}
|
|
644
|
+
|
|
590
645
|
if (!this.appwriteFolderPath) {
|
|
591
646
|
MessageFormatter.error("Failed to get appwriteFolderPath", undefined, { prefix: "Controller" });
|
|
592
647
|
return;
|
|
593
648
|
}
|
|
649
|
+
|
|
594
650
|
await generateSchemas(this.config, this.appwriteFolderPath);
|
|
595
651
|
}
|
|
596
652
|
|
|
@@ -636,7 +692,9 @@ export class UtilsController {
|
|
|
636
692
|
|
|
637
693
|
async synchronizeConfigurations(
|
|
638
694
|
databases?: Models.Database[],
|
|
639
|
-
config?: AppwriteConfig
|
|
695
|
+
config?: AppwriteConfig,
|
|
696
|
+
databaseSelections?: DatabaseSelection[],
|
|
697
|
+
bucketSelections?: BucketSelection[]
|
|
640
698
|
) {
|
|
641
699
|
await this.init();
|
|
642
700
|
if (!this.storage) {
|
|
@@ -652,21 +710,191 @@ export class UtilsController {
|
|
|
652
710
|
MessageFormatter.error("Failed to get appwriteFolderPath", undefined, { prefix: "Controller" });
|
|
653
711
|
return;
|
|
654
712
|
}
|
|
713
|
+
|
|
714
|
+
// If selections are provided, filter the databases accordingly
|
|
715
|
+
let filteredDatabases = databases;
|
|
716
|
+
if (databaseSelections && databaseSelections.length > 0) {
|
|
717
|
+
// Convert selections to Models.Database format
|
|
718
|
+
filteredDatabases = [];
|
|
719
|
+
const allDatabases = databases ? databases : await fetchAllDatabases(this.database!);
|
|
720
|
+
|
|
721
|
+
for (const selection of databaseSelections) {
|
|
722
|
+
const database = allDatabases.find(db => db.$id === selection.databaseId);
|
|
723
|
+
if (database) {
|
|
724
|
+
filteredDatabases.push(database);
|
|
725
|
+
} else {
|
|
726
|
+
MessageFormatter.warning(`Database with ID ${selection.databaseId} not found`, { prefix: "Controller" });
|
|
727
|
+
}
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
MessageFormatter.info(`Syncing ${filteredDatabases.length} selected databases out of ${allDatabases.length} available`, { prefix: "Controller" });
|
|
731
|
+
}
|
|
732
|
+
|
|
655
733
|
const appwriteToX = new AppwriteToX(
|
|
656
734
|
configToUse,
|
|
657
735
|
this.appwriteFolderPath,
|
|
658
736
|
this.storage
|
|
659
737
|
);
|
|
660
|
-
await appwriteToX.toSchemas(
|
|
661
|
-
|
|
738
|
+
await appwriteToX.toSchemas(filteredDatabases);
|
|
739
|
+
|
|
662
740
|
// Update the controller's config with the synchronized collections
|
|
663
741
|
this.config = appwriteToX.updatedConfig;
|
|
664
|
-
|
|
742
|
+
|
|
665
743
|
// Write the updated config back to disk
|
|
666
744
|
const generator = new SchemaGenerator(this.config, this.appwriteFolderPath);
|
|
667
745
|
const yamlConfigPath = findYamlConfig(this.appwriteFolderPath);
|
|
668
746
|
const isYamlProject = !!yamlConfigPath;
|
|
669
747
|
await generator.updateConfig(this.config, isYamlProject);
|
|
748
|
+
|
|
749
|
+
// Regenerate JSON schemas to reflect any table terminology fixes
|
|
750
|
+
try {
|
|
751
|
+
MessageFormatter.progress("Regenerating JSON schemas...", { prefix: "Sync" });
|
|
752
|
+
await createImportSchemas(this.appwriteFolderPath);
|
|
753
|
+
MessageFormatter.success("JSON schemas regenerated successfully", { prefix: "Sync" });
|
|
754
|
+
} catch (error) {
|
|
755
|
+
// Log error but don't fail the sync process
|
|
756
|
+
const errorMessage = error instanceof Error ? error.message : String(error);
|
|
757
|
+
MessageFormatter.warning(
|
|
758
|
+
`Failed to regenerate JSON schemas, but sync completed: ${errorMessage}`,
|
|
759
|
+
{ prefix: "Sync" }
|
|
760
|
+
);
|
|
761
|
+
logger.warn("Schema regeneration failed during sync:", error);
|
|
762
|
+
}
|
|
763
|
+
}
|
|
764
|
+
|
|
765
|
+
async selectivePull(
|
|
766
|
+
databaseSelections: DatabaseSelection[],
|
|
767
|
+
bucketSelections: BucketSelection[]
|
|
768
|
+
): Promise<void> {
|
|
769
|
+
await this.init();
|
|
770
|
+
if (!this.database) {
|
|
771
|
+
MessageFormatter.error("Database not initialized", undefined, { prefix: "Controller" });
|
|
772
|
+
return;
|
|
773
|
+
}
|
|
774
|
+
|
|
775
|
+
MessageFormatter.progress("Starting selective pull (Appwrite → local config)...", { prefix: "Controller" });
|
|
776
|
+
|
|
777
|
+
// Convert database selections to Models.Database format
|
|
778
|
+
const selectedDatabases: Models.Database[] = [];
|
|
779
|
+
|
|
780
|
+
for (const dbSelection of databaseSelections) {
|
|
781
|
+
// Get the full database object from the controller
|
|
782
|
+
const databases = await fetchAllDatabases(this.database);
|
|
783
|
+
const database = databases.find(db => db.$id === dbSelection.databaseId);
|
|
784
|
+
|
|
785
|
+
if (database) {
|
|
786
|
+
selectedDatabases.push(database);
|
|
787
|
+
MessageFormatter.info(`Selected database: ${database.name} (${database.$id})`, { prefix: "Controller" });
|
|
788
|
+
|
|
789
|
+
// Log selected tables for this database
|
|
790
|
+
if (dbSelection.tableIds && dbSelection.tableIds.length > 0) {
|
|
791
|
+
MessageFormatter.info(` Tables: ${dbSelection.tableIds.join(', ')}`, { prefix: "Controller" });
|
|
792
|
+
}
|
|
793
|
+
} else {
|
|
794
|
+
MessageFormatter.warning(`Database with ID ${dbSelection.databaseId} not found`, { prefix: "Controller" });
|
|
795
|
+
}
|
|
796
|
+
}
|
|
797
|
+
|
|
798
|
+
if (selectedDatabases.length === 0) {
|
|
799
|
+
MessageFormatter.warning("No valid databases selected for pull", { prefix: "Controller" });
|
|
800
|
+
return;
|
|
801
|
+
}
|
|
802
|
+
|
|
803
|
+
// Log bucket selections if provided
|
|
804
|
+
if (bucketSelections && bucketSelections.length > 0) {
|
|
805
|
+
MessageFormatter.info(`Selected ${bucketSelections.length} buckets:`, { prefix: "Controller" });
|
|
806
|
+
for (const bucketSelection of bucketSelections) {
|
|
807
|
+
const dbInfo = bucketSelection.databaseId ? ` (DB: ${bucketSelection.databaseId})` : '';
|
|
808
|
+
MessageFormatter.info(` - ${bucketSelection.bucketName} (${bucketSelection.bucketId})${dbInfo}`, { prefix: "Controller" });
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
// Perform selective sync using the enhanced synchronizeConfigurations method
|
|
813
|
+
await this.synchronizeConfigurations(selectedDatabases, this.config, databaseSelections, bucketSelections);
|
|
814
|
+
|
|
815
|
+
MessageFormatter.success("Selective pull completed successfully! Remote config pulled to local.", { prefix: "Controller" });
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
async selectivePush(
|
|
819
|
+
databaseSelections: DatabaseSelection[],
|
|
820
|
+
bucketSelections: BucketSelection[]
|
|
821
|
+
): Promise<void> {
|
|
822
|
+
await this.init();
|
|
823
|
+
if (!this.database) {
|
|
824
|
+
MessageFormatter.error("Database not initialized", undefined, { prefix: "Controller" });
|
|
825
|
+
return;
|
|
826
|
+
}
|
|
827
|
+
|
|
828
|
+
MessageFormatter.progress("Starting selective push (local config → Appwrite)...", { prefix: "Controller" });
|
|
829
|
+
|
|
830
|
+
// Convert database selections to Models.Database format
|
|
831
|
+
const selectedDatabases: Models.Database[] = [];
|
|
832
|
+
|
|
833
|
+
for (const dbSelection of databaseSelections) {
|
|
834
|
+
// Get the full database object from the controller
|
|
835
|
+
const databases = await fetchAllDatabases(this.database);
|
|
836
|
+
const database = databases.find(db => db.$id === dbSelection.databaseId);
|
|
837
|
+
|
|
838
|
+
if (database) {
|
|
839
|
+
selectedDatabases.push(database);
|
|
840
|
+
MessageFormatter.info(`Selected database: ${database.name} (${database.$id})`, { prefix: "Controller" });
|
|
841
|
+
|
|
842
|
+
// Log selected tables for this database
|
|
843
|
+
if (dbSelection.tableIds && dbSelection.tableIds.length > 0) {
|
|
844
|
+
MessageFormatter.info(` Tables: ${dbSelection.tableIds.join(', ')}`, { prefix: "Controller" });
|
|
845
|
+
}
|
|
846
|
+
} else {
|
|
847
|
+
MessageFormatter.warning(`Database with ID ${dbSelection.databaseId} not found`, { prefix: "Controller" });
|
|
848
|
+
}
|
|
849
|
+
}
|
|
850
|
+
|
|
851
|
+
if (selectedDatabases.length === 0) {
|
|
852
|
+
MessageFormatter.warning("No valid databases selected for push", { prefix: "Controller" });
|
|
853
|
+
return;
|
|
854
|
+
}
|
|
855
|
+
|
|
856
|
+
// Log bucket selections if provided
|
|
857
|
+
if (bucketSelections && bucketSelections.length > 0) {
|
|
858
|
+
MessageFormatter.info(`Selected ${bucketSelections.length} buckets:`, { prefix: "Controller" });
|
|
859
|
+
for (const bucketSelection of bucketSelections) {
|
|
860
|
+
const dbInfo = bucketSelection.databaseId ? ` (DB: ${bucketSelection.databaseId})` : '';
|
|
861
|
+
MessageFormatter.info(` - ${bucketSelection.bucketName} (${bucketSelection.bucketId})${dbInfo}`, { prefix: "Controller" });
|
|
862
|
+
}
|
|
863
|
+
}
|
|
864
|
+
|
|
865
|
+
// PUSH OPERATION: Push local configuration to Appwrite
|
|
866
|
+
// Build selected collections/tables from databaseSelections
|
|
867
|
+
const selectedCollections: any[] = [];
|
|
868
|
+
|
|
869
|
+
// Get all collections/tables from config (they're at the root level, not nested in databases)
|
|
870
|
+
const allCollections = this.config?.collections || this.config?.tables || [];
|
|
871
|
+
|
|
872
|
+
// Collect all selected table IDs from all database selections
|
|
873
|
+
const selectedTableIds = new Set<string>();
|
|
874
|
+
for (const dbSelection of databaseSelections) {
|
|
875
|
+
for (const tableId of dbSelection.tableIds) {
|
|
876
|
+
selectedTableIds.add(tableId);
|
|
877
|
+
}
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
// Filter to only the selected table IDs
|
|
881
|
+
for (const collection of allCollections) {
|
|
882
|
+
const collectionId = collection.$id || (collection as any).id;
|
|
883
|
+
if (selectedTableIds.has(collectionId)) {
|
|
884
|
+
selectedCollections.push(collection);
|
|
885
|
+
}
|
|
886
|
+
}
|
|
887
|
+
|
|
888
|
+
MessageFormatter.info(`Pushing ${selectedCollections.length} selected tables/collections to Appwrite`, { prefix: "Controller" });
|
|
889
|
+
|
|
890
|
+
// Ensure databases exist
|
|
891
|
+
await this.ensureDatabasesExist(selectedDatabases);
|
|
892
|
+
await this.ensureDatabaseConfigBucketsExist(selectedDatabases);
|
|
893
|
+
|
|
894
|
+
// Create/update ONLY the selected collections/tables
|
|
895
|
+
await this.createOrUpdateCollectionsForDatabases(selectedDatabases, selectedCollections);
|
|
896
|
+
|
|
897
|
+
MessageFormatter.success("Selective push completed successfully! Local config pushed to Appwrite.", { prefix: "Controller" });
|
|
670
898
|
}
|
|
671
899
|
|
|
672
900
|
async syncDb(
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
import type { AppwriteConfig, Attribute } from "appwrite-utils";
|
|
2
|
-
export declare class SchemaGenerator {
|
|
3
|
-
private relationshipMap;
|
|
4
|
-
private config;
|
|
5
|
-
private appwriteFolderPath;
|
|
6
|
-
constructor(config: AppwriteConfig, appwriteFolderPath: string);
|
|
7
|
-
private resolveCollectionName;
|
|
8
|
-
updateTsSchemas(): void;
|
|
9
|
-
private extractRelationships;
|
|
10
|
-
private addRelationship;
|
|
11
|
-
generateSchemas(): void;
|
|
12
|
-
createSchemaStringV4: (name: string, attributes: Attribute[]) => string;
|
|
13
|
-
typeToZod: (attribute: Attribute) => string;
|
|
14
|
-
}
|