appwrite-utils-cli 1.7.8 → 1.8.1
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 +14 -199
- package/README.md +87 -30
- package/dist/adapters/AdapterFactory.js +5 -25
- package/dist/adapters/DatabaseAdapter.d.ts +17 -2
- package/dist/adapters/LegacyAdapter.d.ts +2 -1
- package/dist/adapters/LegacyAdapter.js +212 -16
- package/dist/adapters/TablesDBAdapter.d.ts +2 -12
- package/dist/adapters/TablesDBAdapter.js +261 -57
- package/dist/cli/commands/databaseCommands.js +10 -10
- package/dist/cli/commands/functionCommands.js +17 -8
- package/dist/collections/attributes.js +447 -125
- package/dist/collections/methods.js +197 -186
- package/dist/collections/tableOperations.d.ts +86 -0
- package/dist/collections/tableOperations.js +434 -0
- package/dist/collections/transferOperations.d.ts +3 -2
- package/dist/collections/transferOperations.js +93 -12
- package/dist/config/services/ConfigLoaderService.d.ts +7 -0
- package/dist/config/services/ConfigLoaderService.js +47 -1
- package/dist/config/yamlConfig.d.ts +221 -88
- package/dist/examples/yamlTerminologyExample.d.ts +1 -1
- package/dist/examples/yamlTerminologyExample.js +6 -3
- package/dist/functions/deployments.js +5 -23
- package/dist/functions/fnConfigDiscovery.d.ts +3 -0
- package/dist/functions/fnConfigDiscovery.js +108 -0
- 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/interactiveCLI.js +18 -15
- package/dist/main.js +219 -81
- package/dist/migrations/appwriteToX.d.ts +88 -23
- package/dist/migrations/comprehensiveTransfer.d.ts +2 -0
- package/dist/migrations/comprehensiveTransfer.js +83 -6
- package/dist/migrations/dataLoader.d.ts +227 -69
- package/dist/migrations/dataLoader.js +3 -3
- package/dist/migrations/importController.js +3 -3
- package/dist/migrations/relationships.d.ts +8 -2
- package/dist/migrations/services/ImportOrchestrator.js +3 -3
- package/dist/migrations/transfer.js +159 -37
- package/dist/shared/attributeMapper.d.ts +20 -0
- package/dist/shared/attributeMapper.js +203 -0
- package/dist/shared/selectionDialogs.d.ts +1 -1
- package/dist/shared/selectionDialogs.js +39 -11
- package/dist/storage/schemas.d.ts +354 -92
- package/dist/utils/configDiscovery.js +4 -3
- package/dist/utils/versionDetection.d.ts +0 -4
- package/dist/utils/versionDetection.js +41 -173
- package/dist/utils/yamlConverter.js +89 -16
- package/dist/utils/yamlLoader.d.ts +1 -1
- package/dist/utils/yamlLoader.js +6 -2
- package/dist/utilsController.d.ts +2 -1
- package/dist/utilsController.js +151 -22
- package/package.json +7 -5
- package/scripts/copy-templates.ts +23 -0
- package/src/adapters/AdapterFactory.ts +119 -143
- package/src/adapters/DatabaseAdapter.ts +18 -3
- package/src/adapters/LegacyAdapter.ts +236 -105
- package/src/adapters/TablesDBAdapter.ts +773 -643
- package/src/cli/commands/databaseCommands.ts +19 -19
- package/src/cli/commands/functionCommands.ts +23 -14
- package/src/collections/attributes.ts +2054 -1611
- package/src/collections/methods.ts +208 -293
- package/src/collections/tableOperations.ts +506 -0
- package/src/collections/transferOperations.ts +218 -144
- package/src/config/services/ConfigLoaderService.ts +62 -1
- package/src/examples/yamlTerminologyExample.ts +10 -5
- package/src/functions/deployments.ts +10 -35
- package/src/functions/fnConfigDiscovery.ts +103 -0
- package/src/functions/methods.ts +4 -2
- package/src/functions/pathResolution.ts +227 -0
- package/src/interactiveCLI.ts +25 -20
- package/src/main.ts +557 -202
- package/src/migrations/comprehensiveTransfer.ts +126 -50
- package/src/migrations/dataLoader.ts +3 -3
- package/src/migrations/importController.ts +3 -3
- package/src/migrations/services/ImportOrchestrator.ts +3 -3
- package/src/migrations/transfer.ts +148 -131
- package/src/shared/attributeMapper.ts +229 -0
- package/src/shared/selectionDialogs.ts +65 -32
- package/src/utils/configDiscovery.ts +9 -3
- package/src/utils/versionDetection.ts +74 -228
- package/src/utils/yamlConverter.ts +94 -17
- package/src/utils/yamlLoader.ts +11 -4
- package/src/utilsController.ts +202 -36
- 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
package/dist/utilsController.js
CHANGED
|
@@ -1,12 +1,13 @@
|
|
|
1
1
|
import { Client, Databases, Query, Storage, Users, } from "node-appwrite";
|
|
2
2
|
import {} from "appwrite-utils";
|
|
3
|
-
import {
|
|
3
|
+
import { findAppwriteConfig, findFunctionsDir, } from "./utils/loadConfigs.js";
|
|
4
|
+
import { normalizeFunctionName, validateFunctionDirectory } from './functions/pathResolution.js';
|
|
4
5
|
import { UsersController } from "./users/methods.js";
|
|
5
6
|
import { AppwriteToX } from "./migrations/appwriteToX.js";
|
|
6
7
|
import { ImportController } from "./migrations/importController.js";
|
|
7
8
|
import { ImportDataActions } from "./migrations/importDataActions.js";
|
|
8
9
|
import { ensureDatabasesExist, wipeOtherDatabases, ensureCollectionsExist, } from "./databases/setup.js";
|
|
9
|
-
import { createOrUpdateCollections, wipeDatabase, generateSchemas, fetchAllCollections, wipeCollection, } from "./collections/methods.js";
|
|
10
|
+
import { createOrUpdateCollections, createOrUpdateCollectionsViaAdapter, wipeDatabase, generateSchemas, fetchAllCollections, wipeCollection, } from "./collections/methods.js";
|
|
10
11
|
import { wipeAllTables, wipeTableRows } from "./collections/methods.js";
|
|
11
12
|
import { backupDatabase, ensureDatabaseConfigBucketsExist, wipeDocumentStorage, } from "./storage/methods.js";
|
|
12
13
|
import path from "path";
|
|
@@ -39,6 +40,26 @@ export class UtilsController {
|
|
|
39
40
|
* Get the UtilsController singleton instance
|
|
40
41
|
*/
|
|
41
42
|
static getInstance(currentUserDir, directConfig) {
|
|
43
|
+
// Clear instance if currentUserDir has changed
|
|
44
|
+
if (UtilsController.instance &&
|
|
45
|
+
UtilsController.instance.currentUserDir !== currentUserDir) {
|
|
46
|
+
logger.debug(`Clearing singleton: currentUserDir changed from ${UtilsController.instance.currentUserDir} to ${currentUserDir}`, { prefix: "UtilsController" });
|
|
47
|
+
UtilsController.clearInstance();
|
|
48
|
+
}
|
|
49
|
+
// Clear instance if directConfig endpoint or project has changed
|
|
50
|
+
if (UtilsController.instance && directConfig) {
|
|
51
|
+
const existingConfig = UtilsController.instance.config;
|
|
52
|
+
if (existingConfig) {
|
|
53
|
+
const endpointChanged = directConfig.appwriteEndpoint &&
|
|
54
|
+
existingConfig.appwriteEndpoint !== directConfig.appwriteEndpoint;
|
|
55
|
+
const projectChanged = directConfig.appwriteProject &&
|
|
56
|
+
existingConfig.appwriteProject !== directConfig.appwriteProject;
|
|
57
|
+
if (endpointChanged || projectChanged) {
|
|
58
|
+
logger.debug("Clearing singleton: endpoint or project changed", { prefix: "UtilsController" });
|
|
59
|
+
UtilsController.clearInstance();
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
}
|
|
42
63
|
if (!UtilsController.instance) {
|
|
43
64
|
UtilsController.instance = new UtilsController(currentUserDir, directConfig);
|
|
44
65
|
}
|
|
@@ -151,13 +172,19 @@ export class UtilsController {
|
|
|
151
172
|
this.appwriteServer = client;
|
|
152
173
|
this.adapter = adapter;
|
|
153
174
|
this.config = config;
|
|
175
|
+
// Update config.apiMode from adapter if it's auto or not set
|
|
176
|
+
if (adapter && (!config.apiMode || config.apiMode === 'auto')) {
|
|
177
|
+
this.config.apiMode = adapter.getApiMode();
|
|
178
|
+
logger.debug(`Updated config.apiMode from adapter during init: ${this.config.apiMode}`, { prefix: "UtilsController" });
|
|
179
|
+
}
|
|
154
180
|
this.database = new Databases(this.appwriteServer);
|
|
155
181
|
this.storage = new Storage(this.appwriteServer);
|
|
156
182
|
this.config.appwriteClient = this.appwriteServer;
|
|
157
183
|
// Log only on FIRST initialization to avoid spam
|
|
158
184
|
if (!this.isInitialized) {
|
|
159
185
|
const apiMode = adapter.getApiMode();
|
|
160
|
-
|
|
186
|
+
const configApiMode = this.config.apiMode;
|
|
187
|
+
MessageFormatter.info(`Database adapter initialized (apiMode: ${apiMode}, config.apiMode: ${configApiMode})`, { prefix: "Adapter" });
|
|
161
188
|
this.isInitialized = true;
|
|
162
189
|
}
|
|
163
190
|
else {
|
|
@@ -295,9 +322,14 @@ export class UtilsController {
|
|
|
295
322
|
for (const entry of entries) {
|
|
296
323
|
if (entry.isDirectory()) {
|
|
297
324
|
const functionPath = path.join(functionsDir, entry.name);
|
|
298
|
-
//
|
|
325
|
+
// Validate it's a function directory
|
|
326
|
+
if (!validateFunctionDirectory(functionPath)) {
|
|
327
|
+
continue; // Skip invalid directories
|
|
328
|
+
}
|
|
329
|
+
// Match with config functions using normalized names
|
|
299
330
|
if (this.config?.functions) {
|
|
300
|
-
const
|
|
331
|
+
const normalizedEntryName = normalizeFunctionName(entry.name);
|
|
332
|
+
const matchingFunc = this.config.functions.find((f) => normalizeFunctionName(f.name) === normalizedEntryName);
|
|
301
333
|
if (matchingFunc) {
|
|
302
334
|
functionDirMap.set(matchingFunc.name, functionPath);
|
|
303
335
|
}
|
|
@@ -417,25 +449,44 @@ export class UtilsController {
|
|
|
417
449
|
await this.init();
|
|
418
450
|
if (!this.database || !this.config)
|
|
419
451
|
throw new Error("Database or config not initialized");
|
|
420
|
-
|
|
452
|
+
// Ensure apiMode is properly set from adapter
|
|
453
|
+
if (this.adapter && (!this.config.apiMode || this.config.apiMode === 'auto')) {
|
|
454
|
+
this.config.apiMode = this.adapter.getApiMode();
|
|
455
|
+
logger.debug(`Updated config.apiMode from adapter: ${this.config.apiMode}`, { prefix: "UtilsController" });
|
|
456
|
+
}
|
|
457
|
+
// Always prefer adapter path for unified behavior. LegacyAdapter internally translates when needed.
|
|
458
|
+
if (this.adapter) {
|
|
459
|
+
logger.debug("Using adapter for createOrUpdateCollections (unified path)", {
|
|
460
|
+
prefix: "UtilsController",
|
|
461
|
+
apiMode: this.adapter.getApiMode()
|
|
462
|
+
});
|
|
463
|
+
await createOrUpdateCollectionsViaAdapter(this.adapter, database.$id, this.config, deletedCollections, collections);
|
|
464
|
+
}
|
|
465
|
+
else {
|
|
466
|
+
// Fallback if adapter is unavailable for some reason
|
|
467
|
+
logger.debug("Adapter unavailable, falling back to legacy Databases path", { prefix: "UtilsController" });
|
|
468
|
+
await createOrUpdateCollections(this.database, database.$id, this.config, deletedCollections, collections);
|
|
469
|
+
}
|
|
421
470
|
}
|
|
422
471
|
async generateSchemas() {
|
|
423
472
|
// Schema generation doesn't need Appwrite connection, just config
|
|
424
473
|
if (!this.config) {
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
474
|
+
MessageFormatter.progress("Loading config from ConfigManager...", { prefix: "Config" });
|
|
475
|
+
try {
|
|
476
|
+
const configManager = ConfigManager.getInstance();
|
|
477
|
+
// Load config if not already loaded
|
|
478
|
+
if (!configManager.hasConfig()) {
|
|
479
|
+
await configManager.loadConfig({
|
|
480
|
+
configDir: this.currentUserDir,
|
|
481
|
+
validate: false,
|
|
482
|
+
strictMode: false,
|
|
483
|
+
});
|
|
435
484
|
}
|
|
485
|
+
this.config = configManager.getConfig();
|
|
486
|
+
MessageFormatter.info("Config loaded successfully from ConfigManager", { prefix: "Config" });
|
|
436
487
|
}
|
|
437
|
-
|
|
438
|
-
MessageFormatter.error("
|
|
488
|
+
catch (error) {
|
|
489
|
+
MessageFormatter.error("Failed to load config", error instanceof Error ? error : undefined, { prefix: "Config" });
|
|
439
490
|
return;
|
|
440
491
|
}
|
|
441
492
|
}
|
|
@@ -521,13 +572,13 @@ export class UtilsController {
|
|
|
521
572
|
logger.warn("Schema regeneration failed during sync:", error);
|
|
522
573
|
}
|
|
523
574
|
}
|
|
524
|
-
async
|
|
575
|
+
async selectivePull(databaseSelections, bucketSelections) {
|
|
525
576
|
await this.init();
|
|
526
577
|
if (!this.database) {
|
|
527
578
|
MessageFormatter.error("Database not initialized", undefined, { prefix: "Controller" });
|
|
528
579
|
return;
|
|
529
580
|
}
|
|
530
|
-
MessageFormatter.progress("Starting selective
|
|
581
|
+
MessageFormatter.progress("Starting selective pull (Appwrite → local config)...", { prefix: "Controller" });
|
|
531
582
|
// Convert database selections to Models.Database format
|
|
532
583
|
const selectedDatabases = [];
|
|
533
584
|
for (const dbSelection of databaseSelections) {
|
|
@@ -547,7 +598,7 @@ export class UtilsController {
|
|
|
547
598
|
}
|
|
548
599
|
}
|
|
549
600
|
if (selectedDatabases.length === 0) {
|
|
550
|
-
MessageFormatter.warning("No valid databases selected for
|
|
601
|
+
MessageFormatter.warning("No valid databases selected for pull", { prefix: "Controller" });
|
|
551
602
|
return;
|
|
552
603
|
}
|
|
553
604
|
// Log bucket selections if provided
|
|
@@ -560,7 +611,85 @@ export class UtilsController {
|
|
|
560
611
|
}
|
|
561
612
|
// Perform selective sync using the enhanced synchronizeConfigurations method
|
|
562
613
|
await this.synchronizeConfigurations(selectedDatabases, this.config, databaseSelections, bucketSelections);
|
|
563
|
-
MessageFormatter.success("Selective
|
|
614
|
+
MessageFormatter.success("Selective pull completed successfully! Remote config pulled to local.", { prefix: "Controller" });
|
|
615
|
+
}
|
|
616
|
+
async selectivePush(databaseSelections, bucketSelections) {
|
|
617
|
+
await this.init();
|
|
618
|
+
if (!this.database) {
|
|
619
|
+
MessageFormatter.error("Database not initialized", undefined, { prefix: "Controller" });
|
|
620
|
+
return;
|
|
621
|
+
}
|
|
622
|
+
MessageFormatter.progress("Starting selective push (local config → Appwrite)...", { prefix: "Controller" });
|
|
623
|
+
// Convert database selections to Models.Database format
|
|
624
|
+
const selectedDatabases = [];
|
|
625
|
+
for (const dbSelection of databaseSelections) {
|
|
626
|
+
// Get the full database object from the controller
|
|
627
|
+
const databases = await fetchAllDatabases(this.database);
|
|
628
|
+
const database = databases.find(db => db.$id === dbSelection.databaseId);
|
|
629
|
+
if (database) {
|
|
630
|
+
selectedDatabases.push(database);
|
|
631
|
+
MessageFormatter.info(`Selected database: ${database.name} (${database.$id})`, { prefix: "Controller" });
|
|
632
|
+
// Log selected tables for this database
|
|
633
|
+
if (dbSelection.tableIds && dbSelection.tableIds.length > 0) {
|
|
634
|
+
MessageFormatter.info(` Tables: ${dbSelection.tableIds.join(', ')}`, { prefix: "Controller" });
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
else {
|
|
638
|
+
MessageFormatter.warning(`Database with ID ${dbSelection.databaseId} not found`, { prefix: "Controller" });
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
if (selectedDatabases.length === 0) {
|
|
642
|
+
MessageFormatter.warning("No valid databases selected for push", { prefix: "Controller" });
|
|
643
|
+
return;
|
|
644
|
+
}
|
|
645
|
+
// Log bucket selections if provided
|
|
646
|
+
if (bucketSelections && bucketSelections.length > 0) {
|
|
647
|
+
MessageFormatter.info(`Selected ${bucketSelections.length} buckets:`, { prefix: "Controller" });
|
|
648
|
+
for (const bucketSelection of bucketSelections) {
|
|
649
|
+
const dbInfo = bucketSelection.databaseId ? ` (DB: ${bucketSelection.databaseId})` : '';
|
|
650
|
+
MessageFormatter.info(` - ${bucketSelection.bucketName} (${bucketSelection.bucketId})${dbInfo}`, { prefix: "Controller" });
|
|
651
|
+
}
|
|
652
|
+
}
|
|
653
|
+
// PUSH OPERATION: Push local configuration to Appwrite
|
|
654
|
+
// Build database-specific collection mappings from databaseSelections
|
|
655
|
+
const databaseCollectionsMap = new Map();
|
|
656
|
+
// Get all collections/tables from config (they're at the root level, not nested in databases)
|
|
657
|
+
const allCollections = this.config?.collections || this.config?.tables || [];
|
|
658
|
+
// Create database-specific collection mapping to preserve relationships
|
|
659
|
+
for (const dbSelection of databaseSelections) {
|
|
660
|
+
const collectionsForDatabase = [];
|
|
661
|
+
MessageFormatter.info(`Processing collections for database: ${dbSelection.databaseId}`, { prefix: "Controller" });
|
|
662
|
+
// Filter collections that were selected for THIS specific database
|
|
663
|
+
for (const collection of allCollections) {
|
|
664
|
+
const collectionId = collection.$id || collection.id;
|
|
665
|
+
// Check if this collection was selected for THIS database
|
|
666
|
+
if (dbSelection.tableIds.includes(collectionId)) {
|
|
667
|
+
collectionsForDatabase.push(collection);
|
|
668
|
+
MessageFormatter.info(` - Selected collection: ${collection.name || collectionId} for database ${dbSelection.databaseId}`, { prefix: "Controller" });
|
|
669
|
+
}
|
|
670
|
+
}
|
|
671
|
+
databaseCollectionsMap.set(dbSelection.databaseId, collectionsForDatabase);
|
|
672
|
+
MessageFormatter.info(`Database ${dbSelection.databaseId}: ${collectionsForDatabase.length} collections selected`, { prefix: "Controller" });
|
|
673
|
+
}
|
|
674
|
+
// Calculate total collections for logging
|
|
675
|
+
const totalSelectedCollections = Array.from(databaseCollectionsMap.values())
|
|
676
|
+
.reduce((total, collections) => total + collections.length, 0);
|
|
677
|
+
MessageFormatter.info(`Pushing ${totalSelectedCollections} selected tables/collections to ${databaseCollectionsMap.size} databases`, { prefix: "Controller" });
|
|
678
|
+
// Ensure databases exist
|
|
679
|
+
await this.ensureDatabasesExist(selectedDatabases);
|
|
680
|
+
await this.ensureDatabaseConfigBucketsExist(selectedDatabases);
|
|
681
|
+
// Create/update collections with database-specific context
|
|
682
|
+
for (const database of selectedDatabases) {
|
|
683
|
+
const collectionsForThisDatabase = databaseCollectionsMap.get(database.$id) || [];
|
|
684
|
+
if (collectionsForThisDatabase.length > 0) {
|
|
685
|
+
MessageFormatter.info(`Pushing ${collectionsForThisDatabase.length} collections to database ${database.$id} (${database.name})`, { prefix: "Controller" });
|
|
686
|
+
await this.createOrUpdateCollections(database, undefined, collectionsForThisDatabase);
|
|
687
|
+
}
|
|
688
|
+
else {
|
|
689
|
+
MessageFormatter.info(`No collections selected for database ${database.$id} (${database.name})`, { prefix: "Controller" });
|
|
690
|
+
}
|
|
691
|
+
}
|
|
692
|
+
MessageFormatter.success("Selective push completed successfully! Local config pushed to Appwrite.", { prefix: "Controller" });
|
|
564
693
|
}
|
|
565
694
|
async syncDb(databases = [], collections = []) {
|
|
566
695
|
await this.init();
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "appwrite-utils-cli",
|
|
3
3
|
"description": "Appwrite Utility Functions to help with database management, data conversion, data import, migrations, and much more. Meant to be used as a CLI tool, I do not recommend installing this in frontend environments.",
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.8.1",
|
|
5
5
|
"main": "src/main.ts",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"repository": {
|
|
@@ -24,7 +24,9 @@
|
|
|
24
24
|
"appwrite-migrate": "./dist/main.js"
|
|
25
25
|
},
|
|
26
26
|
"scripts": {
|
|
27
|
-
"build": "bun run tsc",
|
|
27
|
+
"build": "bun run tsc && bun run copy-templates",
|
|
28
|
+
"prebuild": "rm -rf dist",
|
|
29
|
+
"copy-templates": "tsx scripts/copy-templates.ts",
|
|
28
30
|
"start": "tsx --no-cache src/main.ts",
|
|
29
31
|
"deploy": "bun run build && npm publish --access public",
|
|
30
32
|
"test": "jest",
|
|
@@ -37,10 +39,11 @@
|
|
|
37
39
|
"@types/inquirer": "^9.0.8",
|
|
38
40
|
"@types/json-schema": "^7.0.15",
|
|
39
41
|
"@types/yargs": "^17.0.33",
|
|
40
|
-
"appwrite-utils": "
|
|
42
|
+
"appwrite-utils": "file:../appwrite-utils",
|
|
41
43
|
"chalk": "^5.4.1",
|
|
42
44
|
"cli-progress": "^3.12.0",
|
|
43
45
|
"commander": "^12.1.0",
|
|
46
|
+
"decimal.js": "^10.6.0",
|
|
44
47
|
"es-toolkit": "^1.39.4",
|
|
45
48
|
"ignore": "^6.0.2",
|
|
46
49
|
"inquirer": "^9.3.7",
|
|
@@ -48,8 +51,7 @@
|
|
|
48
51
|
"jszip": "^3.10.1",
|
|
49
52
|
"luxon": "^3.6.1",
|
|
50
53
|
"nanostores": "^0.10.3",
|
|
51
|
-
"node-appwrite": "^
|
|
52
|
-
"node-appwrite-tablesdb": "npm:node-appwrite@^18.0.0",
|
|
54
|
+
"node-appwrite": "^20.2.1",
|
|
53
55
|
"p-limit": "^6.2.0",
|
|
54
56
|
"tar": "^7.4.3",
|
|
55
57
|
"tsx": "^4.20.3",
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { cpSync, mkdirSync, existsSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
|
|
4
|
+
const src = join(process.cwd(), 'src', 'functions', 'templates');
|
|
5
|
+
const dest = join(process.cwd(), 'dist', 'functions', 'templates');
|
|
6
|
+
|
|
7
|
+
// Verify source exists
|
|
8
|
+
if (!existsSync(src)) {
|
|
9
|
+
console.error('❌ Error: Template source directory not found:', src);
|
|
10
|
+
process.exit(1);
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
// Create destination directory
|
|
14
|
+
mkdirSync(dest, { recursive: true });
|
|
15
|
+
|
|
16
|
+
// Copy templates recursively
|
|
17
|
+
try {
|
|
18
|
+
cpSync(src, dest, { recursive: true });
|
|
19
|
+
console.log('✓ Templates copied to dist/functions/templates/');
|
|
20
|
+
} catch (error) {
|
|
21
|
+
console.error('❌ Failed to copy templates:', error);
|
|
22
|
+
process.exit(1);
|
|
23
|
+
}
|
|
@@ -7,14 +7,14 @@
|
|
|
7
7
|
*/
|
|
8
8
|
|
|
9
9
|
import type { AppwriteConfig } from "appwrite-utils";
|
|
10
|
-
import { detectAppwriteVersionCached, isVersionAtLeast, type ApiMode, type VersionDetectionResult } from "../utils/versionDetection.js";
|
|
11
|
-
import type { DatabaseAdapter } from './DatabaseAdapter.js';
|
|
12
|
-
import { TablesDBAdapter } from './TablesDBAdapter.js';
|
|
13
|
-
import { LegacyAdapter } from './LegacyAdapter.js';
|
|
14
|
-
import { logger } from '../shared/logging.js';
|
|
15
|
-
import {
|
|
16
|
-
import {
|
|
17
|
-
import {
|
|
10
|
+
import { detectAppwriteVersionCached, isVersionAtLeast, type ApiMode, type VersionDetectionResult } from "../utils/versionDetection.js";
|
|
11
|
+
import type { DatabaseAdapter } from './DatabaseAdapter.js';
|
|
12
|
+
import { TablesDBAdapter } from './TablesDBAdapter.js';
|
|
13
|
+
import { LegacyAdapter } from './LegacyAdapter.js';
|
|
14
|
+
import { logger } from '../shared/logging.js';
|
|
15
|
+
import { isValidSessionCookie } from '../utils/sessionAuth.js';
|
|
16
|
+
import { MessageFormatter } from '../shared/messageFormatter.js';
|
|
17
|
+
import { Client } from 'node-appwrite';
|
|
18
18
|
|
|
19
19
|
export interface AdapterFactoryConfig {
|
|
20
20
|
appwriteEndpoint: string;
|
|
@@ -201,73 +201,61 @@ export class AdapterFactory {
|
|
|
201
201
|
/**
|
|
202
202
|
* Create TablesDB adapter with dynamic import
|
|
203
203
|
*/
|
|
204
|
-
private static async createTablesDBAdapter(
|
|
205
|
-
config: AdapterFactoryConfig
|
|
206
|
-
): Promise<{ adapter: DatabaseAdapter; client: any }> {
|
|
207
|
-
const startTime = Date.now();
|
|
208
|
-
|
|
209
|
-
try {
|
|
210
|
-
logger.info('
|
|
211
|
-
endpoint: config.appwriteEndpoint,
|
|
212
|
-
operation: 'createTablesDBAdapter'
|
|
213
|
-
});
|
|
214
|
-
|
|
215
|
-
//
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
const
|
|
259
|
-
logger.info('TablesDB adapter created successfully', {
|
|
260
|
-
totalDuration,
|
|
261
|
-
importDuration,
|
|
262
|
-
endpoint: config.appwriteEndpoint,
|
|
263
|
-
operation: 'createTablesDBAdapter'
|
|
264
|
-
});
|
|
265
|
-
|
|
266
|
-
return { adapter, client };
|
|
267
|
-
|
|
268
|
-
} catch (error) {
|
|
269
|
-
const errorDuration = Date.now() - startTime;
|
|
270
|
-
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
204
|
+
private static async createTablesDBAdapter(
|
|
205
|
+
config: AdapterFactoryConfig
|
|
206
|
+
): Promise<{ adapter: DatabaseAdapter; client: any }> {
|
|
207
|
+
const startTime = Date.now();
|
|
208
|
+
|
|
209
|
+
try {
|
|
210
|
+
logger.info('Creating TablesDB adapter (static SDK imports)', {
|
|
211
|
+
endpoint: config.appwriteEndpoint,
|
|
212
|
+
operation: 'createTablesDBAdapter'
|
|
213
|
+
});
|
|
214
|
+
|
|
215
|
+
// Use pre-configured client or create session-aware client with TablesDB Client
|
|
216
|
+
let client: any;
|
|
217
|
+
if (config.preConfiguredClient) {
|
|
218
|
+
client = config.preConfiguredClient;
|
|
219
|
+
} else {
|
|
220
|
+
client = new Client()
|
|
221
|
+
.setEndpoint(config.appwriteEndpoint)
|
|
222
|
+
.setProject(config.appwriteProject);
|
|
223
|
+
|
|
224
|
+
// Set authentication method with mode headers
|
|
225
|
+
// Prefer session with admin mode, fallback to API key with default mode
|
|
226
|
+
if (config.sessionCookie && isValidSessionCookie(config.sessionCookie)) {
|
|
227
|
+
client.setSession(config.sessionCookie);
|
|
228
|
+
client.headers['X-Appwrite-Mode'] = 'admin';
|
|
229
|
+
logger.debug('Using session authentication for TablesDB adapter', {
|
|
230
|
+
project: config.appwriteProject,
|
|
231
|
+
operation: 'createTablesDBAdapter'
|
|
232
|
+
});
|
|
233
|
+
} else if (config.appwriteKey) {
|
|
234
|
+
client.setKey(config.appwriteKey);
|
|
235
|
+
client.headers['X-Appwrite-Mode'] = 'default';
|
|
236
|
+
logger.debug('Using API key authentication for TablesDB adapter', {
|
|
237
|
+
project: config.appwriteProject,
|
|
238
|
+
operation: 'createTablesDBAdapter'
|
|
239
|
+
});
|
|
240
|
+
} else {
|
|
241
|
+
throw new Error("No authentication available for adapter");
|
|
242
|
+
}
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const adapter = new TablesDBAdapter(client);
|
|
246
|
+
|
|
247
|
+
const totalDuration = Date.now() - startTime;
|
|
248
|
+
logger.info('TablesDB adapter created successfully', {
|
|
249
|
+
totalDuration,
|
|
250
|
+
endpoint: config.appwriteEndpoint,
|
|
251
|
+
operation: 'createTablesDBAdapter'
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
return { adapter, client };
|
|
255
|
+
|
|
256
|
+
} catch (error) {
|
|
257
|
+
const errorDuration = Date.now() - startTime;
|
|
258
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
271
259
|
|
|
272
260
|
MessageFormatter.warning('Failed to create TablesDB adapter - falling back to legacy', { prefix: "Adapter" });
|
|
273
261
|
|
|
@@ -286,73 +274,61 @@ export class AdapterFactory {
|
|
|
286
274
|
/**
|
|
287
275
|
* Create Legacy adapter with dynamic import
|
|
288
276
|
*/
|
|
289
|
-
private static async createLegacyAdapter(
|
|
290
|
-
config: AdapterFactoryConfig
|
|
291
|
-
): Promise<{ adapter: DatabaseAdapter; client: any }> {
|
|
292
|
-
const startTime = Date.now();
|
|
293
|
-
|
|
294
|
-
try {
|
|
295
|
-
logger.info('
|
|
296
|
-
endpoint: config.appwriteEndpoint,
|
|
297
|
-
operation: 'createLegacyAdapter'
|
|
298
|
-
});
|
|
299
|
-
|
|
300
|
-
//
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
const
|
|
344
|
-
logger.info('Legacy adapter created successfully', {
|
|
345
|
-
totalDuration,
|
|
346
|
-
importDuration,
|
|
347
|
-
endpoint: config.appwriteEndpoint,
|
|
348
|
-
operation: 'createLegacyAdapter'
|
|
349
|
-
});
|
|
350
|
-
|
|
351
|
-
return { adapter, client };
|
|
352
|
-
|
|
353
|
-
} catch (error) {
|
|
354
|
-
const errorDuration = Date.now() - startTime;
|
|
355
|
-
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
277
|
+
private static async createLegacyAdapter(
|
|
278
|
+
config: AdapterFactoryConfig
|
|
279
|
+
): Promise<{ adapter: DatabaseAdapter; client: any }> {
|
|
280
|
+
const startTime = Date.now();
|
|
281
|
+
|
|
282
|
+
try {
|
|
283
|
+
logger.info('Creating legacy adapter (static SDK imports)', {
|
|
284
|
+
endpoint: config.appwriteEndpoint,
|
|
285
|
+
operation: 'createLegacyAdapter'
|
|
286
|
+
});
|
|
287
|
+
|
|
288
|
+
// Use pre-configured client or create session-aware client with Legacy Client
|
|
289
|
+
let client: any;
|
|
290
|
+
if (config.preConfiguredClient) {
|
|
291
|
+
client = config.preConfiguredClient;
|
|
292
|
+
} else {
|
|
293
|
+
client = new Client()
|
|
294
|
+
.setEndpoint(config.appwriteEndpoint)
|
|
295
|
+
.setProject(config.appwriteProject);
|
|
296
|
+
|
|
297
|
+
// Set authentication method with mode headers
|
|
298
|
+
// Prefer session with admin mode, fallback to API key with default mode
|
|
299
|
+
if (config.sessionCookie && isValidSessionCookie(config.sessionCookie)) {
|
|
300
|
+
(client as any).setSession(config.sessionCookie);
|
|
301
|
+
client.headers['X-Appwrite-Mode'] = 'admin';
|
|
302
|
+
logger.debug('Using session authentication for Legacy adapter', {
|
|
303
|
+
project: config.appwriteProject,
|
|
304
|
+
operation: 'createLegacyAdapter'
|
|
305
|
+
});
|
|
306
|
+
} else if (config.appwriteKey) {
|
|
307
|
+
client.setKey(config.appwriteKey);
|
|
308
|
+
client.headers['X-Appwrite-Mode'] = 'default';
|
|
309
|
+
logger.debug('Using API key authentication for Legacy adapter', {
|
|
310
|
+
project: config.appwriteProject,
|
|
311
|
+
operation: 'createLegacyAdapter'
|
|
312
|
+
});
|
|
313
|
+
} else {
|
|
314
|
+
throw new Error("No authentication available for adapter");
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
|
|
318
|
+
const adapter = new LegacyAdapter(client);
|
|
319
|
+
|
|
320
|
+
const totalDuration = Date.now() - startTime;
|
|
321
|
+
logger.info('Legacy adapter created successfully', {
|
|
322
|
+
totalDuration,
|
|
323
|
+
endpoint: config.appwriteEndpoint,
|
|
324
|
+
operation: 'createLegacyAdapter'
|
|
325
|
+
});
|
|
326
|
+
|
|
327
|
+
return { adapter, client };
|
|
328
|
+
|
|
329
|
+
} catch (error) {
|
|
330
|
+
const errorDuration = Date.now() - startTime;
|
|
331
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
356
332
|
|
|
357
333
|
logger.error('Failed to load legacy Appwrite SDK', {
|
|
358
334
|
error: errorMessage,
|
|
@@ -531,4 +507,4 @@ export async function getApiCapabilities(
|
|
|
531
507
|
terminology: metadata.terminology,
|
|
532
508
|
capabilities
|
|
533
509
|
};
|
|
534
|
-
}
|
|
510
|
+
}
|