appwrite-utils-cli 1.8.9 → 1.9.2
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/dist/adapters/DatabaseAdapter.d.ts +9 -0
- package/dist/adapters/LegacyAdapter.js +1 -1
- package/dist/adapters/TablesDBAdapter.js +29 -4
- package/dist/cli/commands/databaseCommands.d.ts +1 -0
- package/dist/cli/commands/databaseCommands.js +90 -0
- package/dist/config/ConfigManager.d.ts +5 -0
- package/dist/config/ConfigManager.js +1 -1
- package/dist/config/services/ConfigDiscoveryService.d.ts +43 -47
- package/dist/config/services/ConfigDiscoveryService.js +155 -207
- package/dist/config/services/ConfigLoaderService.js +2 -7
- package/dist/config/yamlConfig.d.ts +2 -2
- package/dist/functions/methods.js +14 -2
- package/dist/main.js +9 -1
- package/dist/migrations/appwriteToX.d.ts +1 -1
- package/dist/migrations/dataLoader.d.ts +3 -3
- package/dist/shared/functionManager.js +14 -2
- package/dist/storage/schemas.d.ts +4 -4
- package/dist/utils/projectConfig.d.ts +4 -1
- package/dist/utils/projectConfig.js +41 -6
- package/dist/utilsController.d.ts +1 -0
- package/dist/utilsController.js +2 -1
- package/package.json +2 -1
- package/src/adapters/DatabaseAdapter.ts +12 -0
- package/src/adapters/LegacyAdapter.ts +28 -28
- package/src/adapters/TablesDBAdapter.ts +46 -4
- package/src/cli/commands/databaseCommands.ts +141 -11
- package/src/config/ConfigManager.ts +10 -1
- package/src/config/services/ConfigDiscoveryService.ts +180 -233
- package/src/config/services/ConfigLoaderService.ts +2 -10
- package/src/functions/methods.ts +15 -2
- package/src/main.ts +213 -204
- package/src/shared/functionManager.ts +15 -3
- package/src/utils/projectConfig.ts +57 -16
- package/src/utilsController.ts +73 -72
package/src/main.ts
CHANGED
|
@@ -41,8 +41,9 @@ if (!(globalThis as any).require) {
|
|
|
41
41
|
(globalThis as any).require = require;
|
|
42
42
|
}
|
|
43
43
|
|
|
44
|
-
interface CliOptions {
|
|
44
|
+
interface CliOptions {
|
|
45
45
|
config?: string;
|
|
46
|
+
appwriteConfig?: boolean;
|
|
46
47
|
it?: boolean;
|
|
47
48
|
dbIds?: string;
|
|
48
49
|
collectionIds?: string;
|
|
@@ -86,13 +87,13 @@ interface CliOptions {
|
|
|
86
87
|
session?: string;
|
|
87
88
|
listBackups?: boolean;
|
|
88
89
|
autoSync?: boolean;
|
|
89
|
-
selectBuckets?: boolean;
|
|
90
|
-
// New schema/constant CLI flags
|
|
91
|
-
generateSchemas?: boolean;
|
|
92
|
-
schemaFormat?: 'zod' | 'json' | 'pydantic' | 'both' | 'all';
|
|
93
|
-
schemaOutDir?: string;
|
|
94
|
-
constantsInclude?: string;
|
|
95
|
-
}
|
|
90
|
+
selectBuckets?: boolean;
|
|
91
|
+
// New schema/constant CLI flags
|
|
92
|
+
generateSchemas?: boolean;
|
|
93
|
+
schemaFormat?: 'zod' | 'json' | 'pydantic' | 'both' | 'all';
|
|
94
|
+
schemaOutDir?: string;
|
|
95
|
+
constantsInclude?: string;
|
|
96
|
+
}
|
|
96
97
|
|
|
97
98
|
type ParsedArgv = ArgumentsCamelCase<CliOptions>;
|
|
98
99
|
|
|
@@ -185,15 +186,15 @@ async function performEnhancedSync(
|
|
|
185
186
|
const allowNewOnly = !syncExisting;
|
|
186
187
|
|
|
187
188
|
// Select databases
|
|
188
|
-
const selectedDatabaseIds = await SelectionDialogs.selectDatabases(
|
|
189
|
-
availableDatabases,
|
|
190
|
-
configuredDatabases,
|
|
191
|
-
{
|
|
192
|
-
showSelectAll: false,
|
|
193
|
-
allowNewOnly,
|
|
194
|
-
defaultSelected: []
|
|
195
|
-
}
|
|
196
|
-
);
|
|
189
|
+
const selectedDatabaseIds = await SelectionDialogs.selectDatabases(
|
|
190
|
+
availableDatabases,
|
|
191
|
+
configuredDatabases,
|
|
192
|
+
{
|
|
193
|
+
showSelectAll: false,
|
|
194
|
+
allowNewOnly,
|
|
195
|
+
defaultSelected: []
|
|
196
|
+
}
|
|
197
|
+
);
|
|
197
198
|
|
|
198
199
|
if (selectedDatabaseIds.length === 0) {
|
|
199
200
|
MessageFormatter.warning("No databases selected for sync", { prefix: "Sync" });
|
|
@@ -218,17 +219,17 @@ async function performEnhancedSync(
|
|
|
218
219
|
const configuredTables = controller.config.collections || [];
|
|
219
220
|
|
|
220
221
|
// Select tables for this database
|
|
221
|
-
const selectedTableIds = await SelectionDialogs.selectTablesForDatabase(
|
|
222
|
-
databaseId,
|
|
223
|
-
database.name,
|
|
224
|
-
availableTables,
|
|
225
|
-
configuredTables,
|
|
226
|
-
{
|
|
227
|
-
showSelectAll: false,
|
|
228
|
-
allowNewOnly,
|
|
229
|
-
defaultSelected: []
|
|
230
|
-
}
|
|
231
|
-
);
|
|
222
|
+
const selectedTableIds = await SelectionDialogs.selectTablesForDatabase(
|
|
223
|
+
databaseId,
|
|
224
|
+
database.name,
|
|
225
|
+
availableTables,
|
|
226
|
+
configuredTables,
|
|
227
|
+
{
|
|
228
|
+
showSelectAll: false,
|
|
229
|
+
allowNewOnly,
|
|
230
|
+
defaultSelected: []
|
|
231
|
+
}
|
|
232
|
+
);
|
|
232
233
|
|
|
233
234
|
tableSelectionsMap.set(databaseId, selectedTableIds);
|
|
234
235
|
|
|
@@ -251,17 +252,17 @@ async function performEnhancedSync(
|
|
|
251
252
|
// you'd fetch this from the Appwrite API
|
|
252
253
|
const availableBuckets = configuredBuckets; // Placeholder
|
|
253
254
|
|
|
254
|
-
selectedBucketIds = await SelectionDialogs.selectBucketsForDatabases(
|
|
255
|
-
selectedDatabaseIds,
|
|
256
|
-
availableBuckets,
|
|
257
|
-
configuredBuckets,
|
|
258
|
-
{
|
|
259
|
-
showSelectAll: false,
|
|
260
|
-
allowNewOnly: parsedArgv.selectBuckets ? false : allowNewOnly,
|
|
261
|
-
groupByDatabase: true,
|
|
262
|
-
defaultSelected: []
|
|
263
|
-
}
|
|
264
|
-
);
|
|
255
|
+
selectedBucketIds = await SelectionDialogs.selectBucketsForDatabases(
|
|
256
|
+
selectedDatabaseIds,
|
|
257
|
+
availableBuckets,
|
|
258
|
+
configuredBuckets,
|
|
259
|
+
{
|
|
260
|
+
showSelectAll: false,
|
|
261
|
+
allowNewOnly: parsedArgv.selectBuckets ? false : allowNewOnly,
|
|
262
|
+
groupByDatabase: true,
|
|
263
|
+
defaultSelected: []
|
|
264
|
+
}
|
|
265
|
+
);
|
|
265
266
|
} catch (error) {
|
|
266
267
|
MessageFormatter.warning("Could not fetch storage buckets", { prefix: "Sync" });
|
|
267
268
|
logger.warn("Failed to fetch buckets during sync", { error });
|
|
@@ -370,6 +371,11 @@ const argv = yargs(hideBin(process.argv))
|
|
|
370
371
|
type: "string",
|
|
371
372
|
description: "Path to Appwrite configuration file (appwriteConfig.ts)",
|
|
372
373
|
})
|
|
374
|
+
.option("appwriteConfig", {
|
|
375
|
+
alias: ["appwrite-config", "use-appwrite-config"],
|
|
376
|
+
type: "boolean",
|
|
377
|
+
description: "Prefer loading from appwrite.config.json instead of config.yaml",
|
|
378
|
+
})
|
|
373
379
|
.option("it", {
|
|
374
380
|
alias: ["interactive", "i"],
|
|
375
381
|
type: "boolean",
|
|
@@ -572,30 +578,30 @@ const argv = yargs(hideBin(process.argv))
|
|
|
572
578
|
"Comma-separated list of languages for constants (typescript,javascript,python,php,dart,json,env)",
|
|
573
579
|
default: "typescript",
|
|
574
580
|
})
|
|
575
|
-
.option("constantsOutput", {
|
|
576
|
-
type: "string",
|
|
577
|
-
description:
|
|
578
|
-
"Output directory for generated constants files (default: config-folder/constants)",
|
|
579
|
-
default: "auto",
|
|
580
|
-
})
|
|
581
|
-
.option("constantsInclude", {
|
|
582
|
-
type: "string",
|
|
583
|
-
description:
|
|
584
|
-
"Comma-separated categories to include: databases,collections,buckets,functions",
|
|
585
|
-
})
|
|
586
|
-
.option("generateSchemas", {
|
|
587
|
-
type: "boolean",
|
|
588
|
-
description: "Generate schemas/models without interactive prompts",
|
|
589
|
-
})
|
|
590
|
-
.option("schemaFormat", {
|
|
591
|
-
type: "string",
|
|
592
|
-
choices: ["zod", "json", "pydantic", "both", "all"],
|
|
593
|
-
description: "Schema format: zod, json, pydantic, both (zod+json), or all",
|
|
594
|
-
})
|
|
595
|
-
.option("schemaOutDir", {
|
|
596
|
-
type: "string",
|
|
597
|
-
description: "Output directory for generated schemas (absolute path respected)",
|
|
598
|
-
})
|
|
581
|
+
.option("constantsOutput", {
|
|
582
|
+
type: "string",
|
|
583
|
+
description:
|
|
584
|
+
"Output directory for generated constants files (default: config-folder/constants)",
|
|
585
|
+
default: "auto",
|
|
586
|
+
})
|
|
587
|
+
.option("constantsInclude", {
|
|
588
|
+
type: "string",
|
|
589
|
+
description:
|
|
590
|
+
"Comma-separated categories to include: databases,collections,buckets,functions",
|
|
591
|
+
})
|
|
592
|
+
.option("generateSchemas", {
|
|
593
|
+
type: "boolean",
|
|
594
|
+
description: "Generate schemas/models without interactive prompts",
|
|
595
|
+
})
|
|
596
|
+
.option("schemaFormat", {
|
|
597
|
+
type: "string",
|
|
598
|
+
choices: ["zod", "json", "pydantic", "both", "all"],
|
|
599
|
+
description: "Schema format: zod, json, pydantic, both (zod+json), or all",
|
|
600
|
+
})
|
|
601
|
+
.option("schemaOutDir", {
|
|
602
|
+
type: "string",
|
|
603
|
+
description: "Output directory for generated schemas (absolute path respected)",
|
|
604
|
+
})
|
|
599
605
|
.option("migrateCollectionsToTables", {
|
|
600
606
|
alias: ["migrate-collections"],
|
|
601
607
|
type: "boolean",
|
|
@@ -803,7 +809,7 @@ async function main() {
|
|
|
803
809
|
finalDirectConfig
|
|
804
810
|
);
|
|
805
811
|
|
|
806
|
-
// Pass session authentication options to the controller
|
|
812
|
+
// Pass session authentication and config options to the controller
|
|
807
813
|
const initOptions: any = {};
|
|
808
814
|
if (argv.useSession || argv.sessionCookie) {
|
|
809
815
|
initOptions.useSession = true;
|
|
@@ -811,6 +817,9 @@ async function main() {
|
|
|
811
817
|
initOptions.sessionCookie = argv.sessionCookie;
|
|
812
818
|
}
|
|
813
819
|
}
|
|
820
|
+
if (argv.appwriteConfig) {
|
|
821
|
+
initOptions.preferJson = true;
|
|
822
|
+
}
|
|
814
823
|
|
|
815
824
|
await controller.init(initOptions);
|
|
816
825
|
|
|
@@ -1364,146 +1373,146 @@ async function main() {
|
|
|
1364
1373
|
}
|
|
1365
1374
|
}
|
|
1366
1375
|
|
|
1367
|
-
if (parsedArgv.push) {
|
|
1368
|
-
await controller.init();
|
|
1369
|
-
if (!controller.database || !controller.config) {
|
|
1370
|
-
MessageFormatter.error("Database or config not initialized", undefined, { prefix: "Push" });
|
|
1371
|
-
return;
|
|
1372
|
-
}
|
|
1373
|
-
|
|
1374
|
-
// Fetch available DBs
|
|
1375
|
-
const availableDatabases = await fetchAllDatabases(controller.database);
|
|
1376
|
-
if (availableDatabases.length === 0) {
|
|
1377
|
-
MessageFormatter.warning("No databases found in remote project", { prefix: "Push" });
|
|
1378
|
-
return;
|
|
1379
|
-
}
|
|
1380
|
-
|
|
1381
|
-
// Determine selected DBs
|
|
1382
|
-
let selectedDbIds: string[] = [];
|
|
1383
|
-
if (parsedArgv.dbIds) {
|
|
1384
|
-
selectedDbIds = parsedArgv.dbIds.split(/[,\s]+/).filter(Boolean);
|
|
1385
|
-
} else {
|
|
1386
|
-
selectedDbIds = await SelectionDialogs.selectDatabases(
|
|
1387
|
-
availableDatabases,
|
|
1388
|
-
controller.config.databases || [],
|
|
1389
|
-
{ showSelectAll: false, allowNewOnly: false, defaultSelected: [] }
|
|
1390
|
-
);
|
|
1391
|
-
}
|
|
1392
|
-
|
|
1393
|
-
if (selectedDbIds.length === 0) {
|
|
1394
|
-
MessageFormatter.warning("No databases selected for push", { prefix: "Push" });
|
|
1395
|
-
return;
|
|
1396
|
-
}
|
|
1397
|
-
|
|
1398
|
-
// Build DatabaseSelection[] with tableIds per DB
|
|
1399
|
-
const databaseSelections: DatabaseSelection[] = [];
|
|
1400
|
-
const allConfigItems = controller.config.collections || controller.config.tables || [];
|
|
1401
|
-
let lastSelectedTableIds: string[] | null = null;
|
|
1402
|
-
|
|
1403
|
-
for (const dbId of selectedDbIds) {
|
|
1404
|
-
const db = availableDatabases.find(d => d.$id === dbId);
|
|
1405
|
-
if (!db) continue;
|
|
1406
|
-
|
|
1407
|
-
// Filter config items eligible for this DB according to databaseId/databaseIds rule
|
|
1408
|
-
const eligibleConfigItems = (allConfigItems as any[]).filter(item => {
|
|
1409
|
-
const one = item.databaseId as string | undefined;
|
|
1410
|
-
const many = item.databaseIds as string[] | undefined;
|
|
1411
|
-
if (Array.isArray(many) && many.length > 0) return many.includes(dbId);
|
|
1412
|
-
if (one) return one === dbId;
|
|
1413
|
-
return true; // eligible everywhere if unspecified
|
|
1414
|
-
});
|
|
1415
|
-
|
|
1416
|
-
// Fetch available tables from remote for selection context
|
|
1417
|
-
const availableTables = await fetchAllCollections(dbId, controller.database);
|
|
1418
|
-
|
|
1419
|
-
// Determine selected table IDs
|
|
1420
|
-
let selectedTableIds: string[] = [];
|
|
1421
|
-
if (parsedArgv.collectionIds) {
|
|
1422
|
-
// Non-interactive: respect provided table IDs as-is (apply to each selected DB)
|
|
1423
|
-
selectedTableIds = parsedArgv.collectionIds.split(/[\,\s]+/).filter(Boolean);
|
|
1424
|
-
} else {
|
|
1425
|
-
// If we have a previous selection, offer to reuse it
|
|
1426
|
-
if (lastSelectedTableIds && lastSelectedTableIds.length > 0) {
|
|
1427
|
-
const inquirer = (await import("inquirer")).default;
|
|
1428
|
-
const { reuseMode } = await inquirer.prompt([
|
|
1429
|
-
{
|
|
1430
|
-
type: "list",
|
|
1431
|
-
name: "reuseMode",
|
|
1432
|
-
message: `How do you want to select tables for ${db.name}?`,
|
|
1433
|
-
choices: [
|
|
1434
|
-
{ name: `Use same selection as previous (${lastSelectedTableIds.length} items)`, value: "same" },
|
|
1435
|
-
{ name: `Filter by this database (manual select)`, value: "filter" },
|
|
1436
|
-
{ name: `Show all available in this database (manual select)`, value: "all" }
|
|
1437
|
-
],
|
|
1438
|
-
default: "same"
|
|
1439
|
-
}
|
|
1440
|
-
]);
|
|
1441
|
-
|
|
1442
|
-
if (reuseMode === "same") {
|
|
1443
|
-
selectedTableIds = [...lastSelectedTableIds];
|
|
1444
|
-
} else if (reuseMode === "all") {
|
|
1445
|
-
selectedTableIds = await SelectionDialogs.selectTablesForDatabase(
|
|
1446
|
-
dbId,
|
|
1447
|
-
db.name,
|
|
1448
|
-
availableTables,
|
|
1449
|
-
allConfigItems as any[],
|
|
1450
|
-
{ showSelectAll: false, allowNewOnly: false, defaultSelected: lastSelectedTableIds }
|
|
1451
|
-
);
|
|
1452
|
-
} else {
|
|
1453
|
-
selectedTableIds = await SelectionDialogs.selectTablesForDatabase(
|
|
1454
|
-
dbId,
|
|
1455
|
-
db.name,
|
|
1456
|
-
availableTables,
|
|
1457
|
-
eligibleConfigItems,
|
|
1458
|
-
{ showSelectAll: false, allowNewOnly: true, defaultSelected: lastSelectedTableIds }
|
|
1459
|
-
);
|
|
1460
|
-
}
|
|
1461
|
-
} else {
|
|
1462
|
-
selectedTableIds = await SelectionDialogs.selectTablesForDatabase(
|
|
1463
|
-
dbId,
|
|
1464
|
-
db.name,
|
|
1465
|
-
availableTables,
|
|
1466
|
-
eligibleConfigItems,
|
|
1467
|
-
{ showSelectAll: false, allowNewOnly: true, defaultSelected: [] }
|
|
1468
|
-
);
|
|
1469
|
-
}
|
|
1470
|
-
}
|
|
1471
|
-
|
|
1472
|
-
databaseSelections.push({
|
|
1473
|
-
databaseId: db.$id,
|
|
1474
|
-
databaseName: db.name,
|
|
1475
|
-
tableIds: selectedTableIds,
|
|
1476
|
-
tableNames: [],
|
|
1477
|
-
isNew: false,
|
|
1478
|
-
});
|
|
1479
|
-
if (!parsedArgv.collectionIds) {
|
|
1480
|
-
lastSelectedTableIds = selectedTableIds;
|
|
1481
|
-
}
|
|
1482
|
-
}
|
|
1483
|
-
|
|
1484
|
-
if (databaseSelections.every(sel => sel.tableIds.length === 0)) {
|
|
1485
|
-
MessageFormatter.warning("No tables/collections selected for push", { prefix: "Push" });
|
|
1486
|
-
return;
|
|
1487
|
-
}
|
|
1488
|
-
|
|
1489
|
-
const pushSummary: Record<string, string | number | string[]> = {
|
|
1490
|
-
databases: databaseSelections.length,
|
|
1491
|
-
collections: databaseSelections.reduce((sum, s) => sum + s.tableIds.length, 0),
|
|
1492
|
-
details: databaseSelections.map(s => `${s.databaseId}: ${s.tableIds.length} items`),
|
|
1493
|
-
};
|
|
1494
|
-
// Skip confirmation if both dbIds and collectionIds are provided (non-interactive)
|
|
1495
|
-
if (!(parsedArgv.dbIds && parsedArgv.collectionIds)) {
|
|
1496
|
-
const confirmed = await ConfirmationDialogs.showOperationSummary('Push', pushSummary, { confirmationRequired: true });
|
|
1497
|
-
if (!confirmed) {
|
|
1498
|
-
MessageFormatter.info("Push operation cancelled", { prefix: "Push" });
|
|
1499
|
-
return;
|
|
1500
|
-
}
|
|
1501
|
-
}
|
|
1502
|
-
|
|
1503
|
-
await controller.selectivePush(databaseSelections, []);
|
|
1504
|
-
operationStats.pushedDatabases = databaseSelections.length;
|
|
1505
|
-
operationStats.pushedCollections = databaseSelections.reduce((sum, s) => sum + s.tableIds.length, 0);
|
|
1506
|
-
} else if (parsedArgv.sync) {
|
|
1376
|
+
if (parsedArgv.push) {
|
|
1377
|
+
await controller.init();
|
|
1378
|
+
if (!controller.database || !controller.config) {
|
|
1379
|
+
MessageFormatter.error("Database or config not initialized", undefined, { prefix: "Push" });
|
|
1380
|
+
return;
|
|
1381
|
+
}
|
|
1382
|
+
|
|
1383
|
+
// Fetch available DBs
|
|
1384
|
+
const availableDatabases = await fetchAllDatabases(controller.database);
|
|
1385
|
+
if (availableDatabases.length === 0) {
|
|
1386
|
+
MessageFormatter.warning("No databases found in remote project", { prefix: "Push" });
|
|
1387
|
+
return;
|
|
1388
|
+
}
|
|
1389
|
+
|
|
1390
|
+
// Determine selected DBs
|
|
1391
|
+
let selectedDbIds: string[] = [];
|
|
1392
|
+
if (parsedArgv.dbIds) {
|
|
1393
|
+
selectedDbIds = parsedArgv.dbIds.split(/[,\s]+/).filter(Boolean);
|
|
1394
|
+
} else {
|
|
1395
|
+
selectedDbIds = await SelectionDialogs.selectDatabases(
|
|
1396
|
+
availableDatabases,
|
|
1397
|
+
controller.config.databases || [],
|
|
1398
|
+
{ showSelectAll: false, allowNewOnly: false, defaultSelected: [] }
|
|
1399
|
+
);
|
|
1400
|
+
}
|
|
1401
|
+
|
|
1402
|
+
if (selectedDbIds.length === 0) {
|
|
1403
|
+
MessageFormatter.warning("No databases selected for push", { prefix: "Push" });
|
|
1404
|
+
return;
|
|
1405
|
+
}
|
|
1406
|
+
|
|
1407
|
+
// Build DatabaseSelection[] with tableIds per DB
|
|
1408
|
+
const databaseSelections: DatabaseSelection[] = [];
|
|
1409
|
+
const allConfigItems = controller.config.collections || controller.config.tables || [];
|
|
1410
|
+
let lastSelectedTableIds: string[] | null = null;
|
|
1411
|
+
|
|
1412
|
+
for (const dbId of selectedDbIds) {
|
|
1413
|
+
const db = availableDatabases.find(d => d.$id === dbId);
|
|
1414
|
+
if (!db) continue;
|
|
1415
|
+
|
|
1416
|
+
// Filter config items eligible for this DB according to databaseId/databaseIds rule
|
|
1417
|
+
const eligibleConfigItems = (allConfigItems as any[]).filter(item => {
|
|
1418
|
+
const one = item.databaseId as string | undefined;
|
|
1419
|
+
const many = item.databaseIds as string[] | undefined;
|
|
1420
|
+
if (Array.isArray(many) && many.length > 0) return many.includes(dbId);
|
|
1421
|
+
if (one) return one === dbId;
|
|
1422
|
+
return true; // eligible everywhere if unspecified
|
|
1423
|
+
});
|
|
1424
|
+
|
|
1425
|
+
// Fetch available tables from remote for selection context
|
|
1426
|
+
const availableTables = await fetchAllCollections(dbId, controller.database);
|
|
1427
|
+
|
|
1428
|
+
// Determine selected table IDs
|
|
1429
|
+
let selectedTableIds: string[] = [];
|
|
1430
|
+
if (parsedArgv.collectionIds) {
|
|
1431
|
+
// Non-interactive: respect provided table IDs as-is (apply to each selected DB)
|
|
1432
|
+
selectedTableIds = parsedArgv.collectionIds.split(/[\,\s]+/).filter(Boolean);
|
|
1433
|
+
} else {
|
|
1434
|
+
// If we have a previous selection, offer to reuse it
|
|
1435
|
+
if (lastSelectedTableIds && lastSelectedTableIds.length > 0) {
|
|
1436
|
+
const inquirer = (await import("inquirer")).default;
|
|
1437
|
+
const { reuseMode } = await inquirer.prompt([
|
|
1438
|
+
{
|
|
1439
|
+
type: "list",
|
|
1440
|
+
name: "reuseMode",
|
|
1441
|
+
message: `How do you want to select tables for ${db.name}?`,
|
|
1442
|
+
choices: [
|
|
1443
|
+
{ name: `Use same selection as previous (${lastSelectedTableIds.length} items)`, value: "same" },
|
|
1444
|
+
{ name: `Filter by this database (manual select)`, value: "filter" },
|
|
1445
|
+
{ name: `Show all available in this database (manual select)`, value: "all" }
|
|
1446
|
+
],
|
|
1447
|
+
default: "same"
|
|
1448
|
+
}
|
|
1449
|
+
]);
|
|
1450
|
+
|
|
1451
|
+
if (reuseMode === "same") {
|
|
1452
|
+
selectedTableIds = [...lastSelectedTableIds];
|
|
1453
|
+
} else if (reuseMode === "all") {
|
|
1454
|
+
selectedTableIds = await SelectionDialogs.selectTablesForDatabase(
|
|
1455
|
+
dbId,
|
|
1456
|
+
db.name,
|
|
1457
|
+
availableTables,
|
|
1458
|
+
allConfigItems as any[],
|
|
1459
|
+
{ showSelectAll: false, allowNewOnly: false, defaultSelected: lastSelectedTableIds }
|
|
1460
|
+
);
|
|
1461
|
+
} else {
|
|
1462
|
+
selectedTableIds = await SelectionDialogs.selectTablesForDatabase(
|
|
1463
|
+
dbId,
|
|
1464
|
+
db.name,
|
|
1465
|
+
availableTables,
|
|
1466
|
+
eligibleConfigItems,
|
|
1467
|
+
{ showSelectAll: false, allowNewOnly: true, defaultSelected: lastSelectedTableIds }
|
|
1468
|
+
);
|
|
1469
|
+
}
|
|
1470
|
+
} else {
|
|
1471
|
+
selectedTableIds = await SelectionDialogs.selectTablesForDatabase(
|
|
1472
|
+
dbId,
|
|
1473
|
+
db.name,
|
|
1474
|
+
availableTables,
|
|
1475
|
+
eligibleConfigItems,
|
|
1476
|
+
{ showSelectAll: false, allowNewOnly: true, defaultSelected: [] }
|
|
1477
|
+
);
|
|
1478
|
+
}
|
|
1479
|
+
}
|
|
1480
|
+
|
|
1481
|
+
databaseSelections.push({
|
|
1482
|
+
databaseId: db.$id,
|
|
1483
|
+
databaseName: db.name,
|
|
1484
|
+
tableIds: selectedTableIds,
|
|
1485
|
+
tableNames: [],
|
|
1486
|
+
isNew: false,
|
|
1487
|
+
});
|
|
1488
|
+
if (!parsedArgv.collectionIds) {
|
|
1489
|
+
lastSelectedTableIds = selectedTableIds;
|
|
1490
|
+
}
|
|
1491
|
+
}
|
|
1492
|
+
|
|
1493
|
+
if (databaseSelections.every(sel => sel.tableIds.length === 0)) {
|
|
1494
|
+
MessageFormatter.warning("No tables/collections selected for push", { prefix: "Push" });
|
|
1495
|
+
return;
|
|
1496
|
+
}
|
|
1497
|
+
|
|
1498
|
+
const pushSummary: Record<string, string | number | string[]> = {
|
|
1499
|
+
databases: databaseSelections.length,
|
|
1500
|
+
collections: databaseSelections.reduce((sum, s) => sum + s.tableIds.length, 0),
|
|
1501
|
+
details: databaseSelections.map(s => `${s.databaseId}: ${s.tableIds.length} items`),
|
|
1502
|
+
};
|
|
1503
|
+
// Skip confirmation if both dbIds and collectionIds are provided (non-interactive)
|
|
1504
|
+
if (!(parsedArgv.dbIds && parsedArgv.collectionIds)) {
|
|
1505
|
+
const confirmed = await ConfirmationDialogs.showOperationSummary('Push', pushSummary, { confirmationRequired: true });
|
|
1506
|
+
if (!confirmed) {
|
|
1507
|
+
MessageFormatter.info("Push operation cancelled", { prefix: "Push" });
|
|
1508
|
+
return;
|
|
1509
|
+
}
|
|
1510
|
+
}
|
|
1511
|
+
|
|
1512
|
+
await controller.selectivePush(databaseSelections, []);
|
|
1513
|
+
operationStats.pushedDatabases = databaseSelections.length;
|
|
1514
|
+
operationStats.pushedCollections = databaseSelections.reduce((sum, s) => sum + s.tableIds.length, 0);
|
|
1515
|
+
} else if (parsedArgv.sync) {
|
|
1507
1516
|
// Enhanced SYNC: Pull from remote with intelligent configuration detection
|
|
1508
1517
|
if (parsedArgv.autoSync) {
|
|
1509
1518
|
// Legacy behavior: sync everything without prompts
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { Client, Functions, Runtime, type Models } from "node-appwrite";
|
|
2
|
-
import { type AppwriteFunction } from "appwrite-utils";
|
|
2
|
+
import { type AppwriteFunction, EventTypeSchema } from "appwrite-utils";
|
|
3
3
|
import { join, relative, resolve, basename } from "node:path";
|
|
4
4
|
import fs from "node:fs";
|
|
5
5
|
import chalk from "chalk";
|
|
@@ -10,14 +10,26 @@ import { MessageFormatter } from "./messageFormatter.js";
|
|
|
10
10
|
/**
|
|
11
11
|
* Validates and filters events array for Appwrite functions
|
|
12
12
|
* - Filters out empty/invalid strings
|
|
13
|
+
* - Validates against EventTypeSchema
|
|
13
14
|
* - Limits to 100 items maximum (Appwrite limit)
|
|
14
15
|
* - Returns empty array if input is invalid
|
|
15
16
|
*/
|
|
16
17
|
const validateEvents = (events?: string[]): string[] => {
|
|
17
18
|
if (!events || !Array.isArray(events)) return [];
|
|
18
|
-
|
|
19
|
+
|
|
19
20
|
return events
|
|
20
|
-
.filter(event =>
|
|
21
|
+
.filter(event => {
|
|
22
|
+
if (!event || typeof event !== 'string' || event.trim().length === 0) {
|
|
23
|
+
return false;
|
|
24
|
+
}
|
|
25
|
+
// Validate against EventTypeSchema
|
|
26
|
+
const result = EventTypeSchema.safeParse(event);
|
|
27
|
+
if (!result.success) {
|
|
28
|
+
MessageFormatter.warning(`Invalid event type "${event}" will be filtered out`, { prefix: "Functions" });
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
31
|
+
return true;
|
|
32
|
+
})
|
|
21
33
|
.slice(0, 100);
|
|
22
34
|
};
|
|
23
35
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { existsSync, readFileSync } from "node:fs";
|
|
2
|
-
import { join, dirname } from "node:path";
|
|
2
|
+
import { join, dirname, resolve, isAbsolute } from "node:path";
|
|
3
3
|
import { MessageFormatter } from "../shared/messageFormatter.js";
|
|
4
4
|
import type { AppwriteConfig } from "appwrite-utils";
|
|
5
5
|
|
|
@@ -185,12 +185,17 @@ export function detectApiModeFromProject(projectConfig: AppwriteProjectConfig):
|
|
|
185
185
|
|
|
186
186
|
/**
|
|
187
187
|
* Convert project config to AppwriteConfig format
|
|
188
|
+
* @param projectConfig The project config to convert
|
|
189
|
+
* @param configPath Optional path to the config file (for resolving relative paths)
|
|
190
|
+
* @param existingConfig Optional existing config to merge with
|
|
188
191
|
*/
|
|
189
192
|
export function projectConfigToAppwriteConfig(
|
|
190
193
|
projectConfig: AppwriteProjectConfig,
|
|
194
|
+
configPath?: string,
|
|
191
195
|
existingConfig?: Partial<AppwriteConfig>
|
|
192
196
|
): Partial<AppwriteConfig> {
|
|
193
197
|
const apiMode = detectApiModeFromProject(projectConfig);
|
|
198
|
+
const configDir = configPath ? dirname(configPath) : process.cwd();
|
|
194
199
|
|
|
195
200
|
const baseConfig: Partial<AppwriteConfig> = {
|
|
196
201
|
...existingConfig,
|
|
@@ -203,10 +208,22 @@ export function projectConfigToAppwriteConfig(
|
|
|
203
208
|
baseConfig.appwriteEndpoint = projectConfig.endpoint;
|
|
204
209
|
}
|
|
205
210
|
|
|
206
|
-
//
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
211
|
+
// Merge databases and tablesDB arrays (modern Appwrite may have both)
|
|
212
|
+
const allDatabases = [
|
|
213
|
+
...(projectConfig.databases || []),
|
|
214
|
+
...(projectConfig.tablesDB || [])
|
|
215
|
+
];
|
|
216
|
+
|
|
217
|
+
// Remove duplicates by $id
|
|
218
|
+
const uniqueDatabasesMap = new Map();
|
|
219
|
+
for (const db of allDatabases) {
|
|
220
|
+
if (!uniqueDatabasesMap.has(db.$id)) {
|
|
221
|
+
uniqueDatabasesMap.set(db.$id, db);
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
if (uniqueDatabasesMap.size > 0) {
|
|
226
|
+
baseConfig.databases = Array.from(uniqueDatabasesMap.values()).map(db => ({
|
|
210
227
|
$id: db.$id,
|
|
211
228
|
name: db.name,
|
|
212
229
|
// Add basic bucket configuration if not exists
|
|
@@ -235,6 +252,30 @@ export function projectConfigToAppwriteConfig(
|
|
|
235
252
|
}));
|
|
236
253
|
}
|
|
237
254
|
|
|
255
|
+
// Convert functions with path normalization
|
|
256
|
+
if (projectConfig.functions) {
|
|
257
|
+
baseConfig.functions = projectConfig.functions.map(func => {
|
|
258
|
+
const normalizedFunc: any = {
|
|
259
|
+
$id: func.$id,
|
|
260
|
+
name: func.name,
|
|
261
|
+
runtime: func.runtime,
|
|
262
|
+
entrypoint: func.entrypoint,
|
|
263
|
+
commands: func.commands,
|
|
264
|
+
events: func.events,
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
// Convert path to dirPath and make absolute
|
|
268
|
+
if (func.path) {
|
|
269
|
+
const expandedPath = func.path;
|
|
270
|
+
normalizedFunc.dirPath = isAbsolute(expandedPath)
|
|
271
|
+
? expandedPath
|
|
272
|
+
: resolve(configDir, expandedPath);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
return normalizedFunc;
|
|
276
|
+
});
|
|
277
|
+
}
|
|
278
|
+
|
|
238
279
|
return baseConfig;
|
|
239
280
|
}
|
|
240
281
|
|
|
@@ -251,16 +292,16 @@ export function getCollectionsFromProject(projectConfig: AppwriteProjectConfig):
|
|
|
251
292
|
documentSecurity: table.rowSecurity || false,
|
|
252
293
|
enabled: table.enabled !== false,
|
|
253
294
|
// Convert columns to attributes for compatibility
|
|
254
|
-
attributes: table.columns.map(col => ({
|
|
255
|
-
key: col.key,
|
|
256
|
-
type: col.type,
|
|
257
|
-
required: col.required,
|
|
258
|
-
array: col.array,
|
|
259
|
-
size: col.size,
|
|
260
|
-
default: col.default,
|
|
261
|
-
encrypt: col.encrypt,
|
|
262
|
-
unique: col.unique,
|
|
263
|
-
})),
|
|
295
|
+
attributes: table.columns.map(col => ({
|
|
296
|
+
key: col.key,
|
|
297
|
+
type: col.type,
|
|
298
|
+
required: col.required,
|
|
299
|
+
array: col.array,
|
|
300
|
+
size: col.size,
|
|
301
|
+
default: col.default,
|
|
302
|
+
encrypt: col.encrypt,
|
|
303
|
+
unique: col.unique,
|
|
304
|
+
})),
|
|
264
305
|
indexes: table.indexes || [],
|
|
265
306
|
// Mark as coming from TablesDB for processing
|
|
266
307
|
_isFromTablesDir: true,
|
|
@@ -296,4 +337,4 @@ export function isTablesDBProject(projectConfig: AppwriteProjectConfig): boolean
|
|
|
296
337
|
*/
|
|
297
338
|
export function getProjectDirectoryName(projectConfig: AppwriteProjectConfig): "tables" | "collections" {
|
|
298
339
|
return isTablesDBProject(projectConfig) ? "tables" : "collections";
|
|
299
|
-
}
|
|
340
|
+
}
|