appwrite-utils-cli 1.8.9 → 1.9.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/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/main.js +9 -1
- package/dist/migrations/appwriteToX.d.ts +1 -1
- package/dist/migrations/dataLoader.d.ts +3 -3
- 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/main.ts +213 -204
- package/src/utils/projectConfig.ts +57 -16
- package/src/utilsController.ts +73 -72
|
@@ -105,6 +105,15 @@ export interface CreateAttributeParams {
|
|
|
105
105
|
default?: any;
|
|
106
106
|
array?: boolean;
|
|
107
107
|
encrypt?: boolean;
|
|
108
|
+
min?: number;
|
|
109
|
+
max?: number;
|
|
110
|
+
elements?: string[];
|
|
111
|
+
relatedCollection?: string;
|
|
112
|
+
relationType?: 'oneToOne' | 'manyToOne' | 'oneToMany' | 'manyToMany';
|
|
113
|
+
twoWay?: boolean;
|
|
114
|
+
twoWayKey?: string;
|
|
115
|
+
onDelete?: 'setNull' | 'cascade' | 'restrict';
|
|
116
|
+
side?: 'parent' | 'child';
|
|
108
117
|
[key: string]: any;
|
|
109
118
|
}
|
|
110
119
|
export interface UpdateAttributeParams {
|
|
@@ -316,7 +316,7 @@ export class LegacyAdapter extends BaseAdapter {
|
|
|
316
316
|
relatedCollectionId: params.relatedCollection || '',
|
|
317
317
|
type: (params.type || 'oneToOne'),
|
|
318
318
|
twoWay: params.twoWay ?? false,
|
|
319
|
-
onDelete: params.onDelete || 'restrict'
|
|
319
|
+
onDelete: (params.onDelete || 'restrict')
|
|
320
320
|
});
|
|
321
321
|
break;
|
|
322
322
|
default:
|
|
@@ -308,10 +308,10 @@ export class TablesDBAdapter extends BaseAdapter {
|
|
|
308
308
|
tableId: params.tableId,
|
|
309
309
|
key: params.key,
|
|
310
310
|
relatedTableId: params.relatedCollection || '',
|
|
311
|
-
type: (params.
|
|
311
|
+
type: (params.relationType || 'oneToOne'),
|
|
312
312
|
twoWay: params.twoWay ?? false,
|
|
313
313
|
twoWayKey: params.twoWayKey,
|
|
314
|
-
onDelete: params.onDelete || 'restrict'
|
|
314
|
+
onDelete: (params.onDelete || 'restrict')
|
|
315
315
|
});
|
|
316
316
|
break;
|
|
317
317
|
default:
|
|
@@ -505,7 +505,21 @@ export class TablesDBAdapter extends BaseAdapter {
|
|
|
505
505
|
let queries;
|
|
506
506
|
// Wipe mode: use Query.limit for deleting without fetching
|
|
507
507
|
if (params.rowIds.length === 0) {
|
|
508
|
-
const batchSize = params.batchSize ||
|
|
508
|
+
const batchSize = params.batchSize || 1000;
|
|
509
|
+
// Validation: Ensure table exists before wiping
|
|
510
|
+
try {
|
|
511
|
+
await this.getTable({
|
|
512
|
+
databaseId: params.databaseId,
|
|
513
|
+
tableId: params.tableId
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
catch (error) {
|
|
517
|
+
const errorMessage = error?.message || String(error);
|
|
518
|
+
if (errorMessage.toLowerCase().includes('not found') || error?.code === 404) {
|
|
519
|
+
throw new AdapterError(`Table '${params.tableId}' not found in database '${params.databaseId}'`, 'TABLE_NOT_FOUND', error);
|
|
520
|
+
}
|
|
521
|
+
throw error; // Re-throw other errors
|
|
522
|
+
}
|
|
509
523
|
queries = [Query.limit(batchSize)];
|
|
510
524
|
}
|
|
511
525
|
// Specific IDs mode: chunk into batches of 80-90 to stay within Appwrite limits
|
|
@@ -526,7 +540,18 @@ export class TablesDBAdapter extends BaseAdapter {
|
|
|
526
540
|
};
|
|
527
541
|
}
|
|
528
542
|
catch (error) {
|
|
529
|
-
|
|
543
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
544
|
+
// Enhanced error categorization
|
|
545
|
+
if (errorMessage.toLowerCase().includes('not found') || error?.code === 404) {
|
|
546
|
+
throw new AdapterError(`Table or rows not found: ${errorMessage}`, 'NOT_FOUND', error instanceof Error ? error : undefined);
|
|
547
|
+
}
|
|
548
|
+
else if (errorMessage.toLowerCase().includes('permission') || errorMessage.toLowerCase().includes('unauthorized') || error?.code === 403) {
|
|
549
|
+
throw new AdapterError(`Permission denied for bulk delete: ${errorMessage}`, 'PERMISSION_DENIED', error instanceof Error ? error : undefined);
|
|
550
|
+
}
|
|
551
|
+
else if (errorMessage.toLowerCase().includes('rate limit') || error?.code === 429) {
|
|
552
|
+
throw new AdapterError(`Rate limit exceeded during bulk delete: ${errorMessage}`, 'RATE_LIMIT', error instanceof Error ? error : undefined);
|
|
553
|
+
}
|
|
554
|
+
throw new AdapterError(`Failed to bulk delete rows: ${errorMessage}`, 'BULK_DELETE_ROWS_FAILED', error instanceof Error ? error : undefined);
|
|
530
555
|
}
|
|
531
556
|
}
|
|
532
557
|
// Metadata and Capabilities
|
|
@@ -10,4 +10,5 @@ export declare const databaseCommands: {
|
|
|
10
10
|
executeUnifiedBackup(cli: InteractiveCLI, trackingDatabaseId: string, scope: any): Promise<void>;
|
|
11
11
|
wipeDatabase(cli: InteractiveCLI): Promise<void>;
|
|
12
12
|
wipeCollections(cli: InteractiveCLI): Promise<void>;
|
|
13
|
+
wipeTablesData(cli: InteractiveCLI): Promise<void>;
|
|
13
14
|
};
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import inquirer from "inquirer";
|
|
2
2
|
import chalk from "chalk";
|
|
3
3
|
import { join } from "node:path";
|
|
4
|
+
import { Query } from "node-appwrite";
|
|
4
5
|
import { MessageFormatter } from "../../shared/messageFormatter.js";
|
|
5
6
|
import { ConfirmationDialogs } from "../../shared/confirmationDialogs.js";
|
|
6
7
|
import { SelectionDialogs } from "../../shared/selectionDialogs.js";
|
|
@@ -8,6 +9,7 @@ import { logger } from "../../shared/logging.js";
|
|
|
8
9
|
import { fetchAllDatabases } from "../../databases/methods.js";
|
|
9
10
|
import { listBuckets } from "../../storage/methods.js";
|
|
10
11
|
import { getFunction, downloadLatestFunctionDeployment } from "../../functions/methods.js";
|
|
12
|
+
import { wipeTableRows } from "../../collections/wipeOperations.js";
|
|
11
13
|
export const databaseCommands = {
|
|
12
14
|
async syncDb(cli) {
|
|
13
15
|
MessageFormatter.progress("Pushing local configuration to Appwrite...", { prefix: "Database" });
|
|
@@ -550,5 +552,93 @@ export const databaseCommands = {
|
|
|
550
552
|
}
|
|
551
553
|
}
|
|
552
554
|
MessageFormatter.success("Wipe collections operation completed", { prefix: "Wipe" });
|
|
555
|
+
},
|
|
556
|
+
async wipeTablesData(cli) {
|
|
557
|
+
const controller = cli.controller;
|
|
558
|
+
if (!controller?.adapter) {
|
|
559
|
+
throw new Error("Database adapter is not initialized. TablesDB operations require adapter support.");
|
|
560
|
+
}
|
|
561
|
+
try {
|
|
562
|
+
// Step 1: Select database (single selection for clearer UX)
|
|
563
|
+
const databases = await fetchAllDatabases(controller.database);
|
|
564
|
+
if (!databases || databases.length === 0) {
|
|
565
|
+
MessageFormatter.warning("No databases found", { prefix: "Wipe" });
|
|
566
|
+
return;
|
|
567
|
+
}
|
|
568
|
+
const { selectedDatabase } = await inquirer.prompt([
|
|
569
|
+
{
|
|
570
|
+
type: "list",
|
|
571
|
+
name: "selectedDatabase",
|
|
572
|
+
message: "Select database containing tables to wipe:",
|
|
573
|
+
choices: databases.map((db) => ({
|
|
574
|
+
name: `${db.name} (${db.$id})`,
|
|
575
|
+
value: db
|
|
576
|
+
}))
|
|
577
|
+
}
|
|
578
|
+
]);
|
|
579
|
+
const database = selectedDatabase;
|
|
580
|
+
// Step 2: Get available tables
|
|
581
|
+
const adapter = controller.adapter;
|
|
582
|
+
const tablesResponse = await adapter.listTables({
|
|
583
|
+
databaseId: database.$id,
|
|
584
|
+
queries: [Query.limit(500)]
|
|
585
|
+
});
|
|
586
|
+
const availableTables = tablesResponse.tables || [];
|
|
587
|
+
if (availableTables.length === 0) {
|
|
588
|
+
MessageFormatter.warning(`No tables found in database: ${database.name}`, { prefix: "Wipe" });
|
|
589
|
+
return;
|
|
590
|
+
}
|
|
591
|
+
// Step 3: Select tables using existing SelectionDialogs
|
|
592
|
+
const selectedTableIds = await SelectionDialogs.selectTablesForDatabase(database.$id, database.name, availableTables, [], // No configured tables context needed for wipe
|
|
593
|
+
{
|
|
594
|
+
showSelectAll: true,
|
|
595
|
+
allowNewOnly: false,
|
|
596
|
+
defaultSelected: []
|
|
597
|
+
});
|
|
598
|
+
if (selectedTableIds.length === 0) {
|
|
599
|
+
MessageFormatter.warning("No tables selected. Operation cancelled.", { prefix: "Wipe" });
|
|
600
|
+
return;
|
|
601
|
+
}
|
|
602
|
+
// Step 4: Show confirmation with table details
|
|
603
|
+
const selectedTables = availableTables.filter((t) => selectedTableIds.includes(t.$id));
|
|
604
|
+
const tableNames = selectedTables.map((t) => t.name);
|
|
605
|
+
console.log(chalk.yellow.bold("\n⚠️ WARNING: Table Row Wipe Operation"));
|
|
606
|
+
console.log(chalk.yellow("This will delete ALL ROWS from the selected tables."));
|
|
607
|
+
console.log(chalk.yellow("The table structures will remain intact.\n"));
|
|
608
|
+
console.log(chalk.cyan("Database:"), chalk.white(database.name));
|
|
609
|
+
console.log(chalk.cyan("Tables to wipe:"));
|
|
610
|
+
tableNames.forEach((name) => console.log(chalk.white(` • ${name}`)));
|
|
611
|
+
console.log();
|
|
612
|
+
const { confirmed } = await inquirer.prompt([
|
|
613
|
+
{
|
|
614
|
+
type: "confirm",
|
|
615
|
+
name: "confirmed",
|
|
616
|
+
message: chalk.red.bold("Are you ABSOLUTELY SURE you want to wipe these table rows?"),
|
|
617
|
+
default: false
|
|
618
|
+
}
|
|
619
|
+
]);
|
|
620
|
+
if (!confirmed) {
|
|
621
|
+
MessageFormatter.info("Wipe operation cancelled by user", { prefix: "Wipe" });
|
|
622
|
+
return;
|
|
623
|
+
}
|
|
624
|
+
// Step 5: Execute wipe using existing wipeTableRows function
|
|
625
|
+
MessageFormatter.progress("Starting table row wipe operation...", { prefix: "Wipe" });
|
|
626
|
+
for (const table of selectedTables) {
|
|
627
|
+
try {
|
|
628
|
+
MessageFormatter.info(`Wiping rows from table: ${table.name}`, { prefix: "Wipe" });
|
|
629
|
+
// Use existing wipeTableRows from wipeOperations.ts
|
|
630
|
+
await wipeTableRows(adapter, database.$id, table.$id);
|
|
631
|
+
MessageFormatter.success(`Successfully wiped rows from table: ${table.name}`, { prefix: "Wipe" });
|
|
632
|
+
}
|
|
633
|
+
catch (error) {
|
|
634
|
+
MessageFormatter.error(`Failed to wipe table ${table.name}`, error instanceof Error ? error : new Error(String(error)), { prefix: "Wipe" });
|
|
635
|
+
}
|
|
636
|
+
}
|
|
637
|
+
MessageFormatter.success(`Wipe operation completed for ${selectedTables.length} table(s)`, { prefix: "Wipe" });
|
|
638
|
+
}
|
|
639
|
+
catch (error) {
|
|
640
|
+
MessageFormatter.error("Table wipe operation failed", error instanceof Error ? error : new Error(String(error)), { prefix: "Wipe" });
|
|
641
|
+
throw error;
|
|
642
|
+
}
|
|
553
643
|
}
|
|
554
644
|
};
|
|
@@ -54,6 +54,11 @@ export interface ConfigLoadOptions {
|
|
|
54
54
|
* Override session authentication (used for preserving session on reload)
|
|
55
55
|
*/
|
|
56
56
|
sessionOverride?: SessionAuthInfo;
|
|
57
|
+
/**
|
|
58
|
+
* Prefer loading from appwrite.config.json over config.yaml
|
|
59
|
+
* @default false
|
|
60
|
+
*/
|
|
61
|
+
preferJson?: boolean;
|
|
57
62
|
}
|
|
58
63
|
/**
|
|
59
64
|
* Filter function for collections (accepts both Collection and CollectionCreate)
|
|
@@ -139,7 +139,7 @@ export class ConfigManager {
|
|
|
139
139
|
}
|
|
140
140
|
logger.debug("Loading config from file", { prefix: "ConfigManager", options });
|
|
141
141
|
// 2. Discover config file
|
|
142
|
-
const configPath = this.discoveryService.findConfig(options.configDir || process.cwd());
|
|
142
|
+
const configPath = await this.discoveryService.findConfig(options.configDir || process.cwd(), options.preferJson || false);
|
|
143
143
|
if (!configPath) {
|
|
144
144
|
const searchDir = options.configDir || process.cwd();
|
|
145
145
|
throw new Error(`No Appwrite configuration found in "${searchDir}".\n` +
|
|
@@ -10,74 +10,76 @@ export interface DiscoveryResult {
|
|
|
10
10
|
/**
|
|
11
11
|
* Service for discovering Appwrite configuration files and collection/table definitions.
|
|
12
12
|
*
|
|
13
|
+
* Uses find-up for intelligent searching with git repository boundary detection:
|
|
14
|
+
* 1. Finds .git directory to establish repo root boundary
|
|
15
|
+
* 2. Searches UP from current directory to repo root
|
|
16
|
+
* 3. Searches DOWN recursively within repo root
|
|
17
|
+
*
|
|
13
18
|
* Search Priority:
|
|
14
19
|
* 1. YAML configs (.appwrite/config.yaml, .appwrite/config.yml, etc.)
|
|
15
|
-
* 2.
|
|
16
|
-
* 3.
|
|
17
|
-
*
|
|
18
|
-
* Features:
|
|
19
|
-
* - Searches up directory tree (max 5 levels)
|
|
20
|
-
* - Ignores common directories (node_modules, .git, etc.)
|
|
21
|
-
* - Discovers both collections/ and tables/ directories
|
|
22
|
-
* - Recursive subdirectory scanning for .appwrite folders
|
|
20
|
+
* 2. JSON configs (appwrite.config.json, appwrite.json)
|
|
21
|
+
* 3. TypeScript configs (appwriteConfig.ts)
|
|
23
22
|
*/
|
|
24
23
|
export declare class ConfigDiscoveryService {
|
|
25
24
|
/**
|
|
26
25
|
* YAML configuration file names to search for
|
|
27
26
|
*/
|
|
28
27
|
private readonly YAML_FILENAMES;
|
|
28
|
+
/**
|
|
29
|
+
* JSON configuration file names to search for
|
|
30
|
+
*/
|
|
31
|
+
private readonly JSON_FILENAMES;
|
|
29
32
|
/**
|
|
30
33
|
* TypeScript configuration file names to search for
|
|
31
34
|
*/
|
|
32
35
|
private readonly TS_FILENAMES;
|
|
33
36
|
/**
|
|
34
|
-
*
|
|
37
|
+
* Finds the git repository root directory
|
|
38
|
+
* @param startDir The directory to start searching from
|
|
39
|
+
* @returns Path to the repository root, or startDir if no .git found
|
|
35
40
|
*/
|
|
36
|
-
private
|
|
41
|
+
private findRepoRoot;
|
|
37
42
|
/**
|
|
38
|
-
*
|
|
43
|
+
* Recursively searches downward for files matching patterns
|
|
44
|
+
* @param dir Directory to search in
|
|
45
|
+
* @param patterns File patterns to match
|
|
46
|
+
* @param maxDepth Maximum depth to search
|
|
47
|
+
* @param currentDepth Current recursion depth
|
|
48
|
+
* @returns First matching file path or null
|
|
39
49
|
*/
|
|
40
|
-
private
|
|
50
|
+
private searchDownward;
|
|
41
51
|
/**
|
|
42
|
-
* Finds any configuration file with priority
|
|
52
|
+
* Finds any configuration file with configurable priority
|
|
43
53
|
* @param startDir The directory to start searching from
|
|
54
|
+
* @param preferJson If true, prioritizes appwrite.config.json over YAML (default: false)
|
|
44
55
|
* @returns Path to the configuration file or null if not found
|
|
56
|
+
*
|
|
57
|
+
* Default priority: YAML → JSON → TypeScript
|
|
58
|
+
* With preferJson=true: JSON → YAML → TypeScript
|
|
45
59
|
*/
|
|
46
|
-
findConfig(startDir: string): string | null
|
|
60
|
+
findConfig(startDir: string, preferJson?: boolean): Promise<string | null>;
|
|
47
61
|
/**
|
|
48
62
|
* Finds YAML configuration files
|
|
49
|
-
* Searches
|
|
63
|
+
* Searches UP to repo root, then DOWN from repo root
|
|
50
64
|
* @param startDir The directory to start searching from
|
|
65
|
+
* @param repoRoot The repository root boundary
|
|
51
66
|
* @returns Path to the YAML config file or null if not found
|
|
52
67
|
*/
|
|
53
|
-
findYamlConfig(startDir: string): string | null
|
|
68
|
+
findYamlConfig(startDir: string, repoRoot?: string): Promise<string | null>;
|
|
54
69
|
/**
|
|
55
|
-
*
|
|
56
|
-
* @param
|
|
57
|
-
* @param
|
|
58
|
-
* @returns Path to
|
|
70
|
+
* Finds JSON project configuration files (appwrite.config.json, appwrite.json)
|
|
71
|
+
* @param startDir The directory to start searching from
|
|
72
|
+
* @param repoRoot The repository root boundary
|
|
73
|
+
* @returns Path to the JSON config file or null if not found
|
|
59
74
|
*/
|
|
60
|
-
|
|
75
|
+
findProjectConfig(startDir: string, repoRoot?: string): Promise<string | null>;
|
|
61
76
|
/**
|
|
62
77
|
* Finds TypeScript configuration files (appwriteConfig.ts)
|
|
63
78
|
* @param startDir The directory to start searching from
|
|
79
|
+
* @param repoRoot The repository root boundary
|
|
64
80
|
* @returns Path to the TypeScript config file or null if not found
|
|
65
81
|
*/
|
|
66
|
-
findTypeScriptConfig(startDir: string): string | null
|
|
67
|
-
/**
|
|
68
|
-
* Recursively searches for TypeScript configuration files
|
|
69
|
-
* @param dir The directory to search
|
|
70
|
-
* @param depth Current search depth
|
|
71
|
-
* @returns Path to TypeScript config or null
|
|
72
|
-
*/
|
|
73
|
-
private findTypeScriptConfigRecursive;
|
|
74
|
-
/**
|
|
75
|
-
* Finds project configuration JSON files (appwrite.json, appwrite.config.json)
|
|
76
|
-
* Searches up to 5 levels up the directory tree
|
|
77
|
-
* @param startDir The directory to start searching from
|
|
78
|
-
* @returns Path to the JSON config file or null if not found
|
|
79
|
-
*/
|
|
80
|
-
findProjectConfig(startDir?: string): string | null;
|
|
82
|
+
findTypeScriptConfig(startDir: string, repoRoot?: string): Promise<string | null>;
|
|
81
83
|
/**
|
|
82
84
|
* Discovers collection YAML files in a collections/ directory
|
|
83
85
|
* @param collectionsDir Path to the collections directory
|
|
@@ -95,32 +97,26 @@ export declare class ConfigDiscoveryService {
|
|
|
95
97
|
* @param startDir The directory to start searching from
|
|
96
98
|
* @returns Path to .appwrite directory or null if not found
|
|
97
99
|
*/
|
|
98
|
-
findAppwriteDirectory(startDir: string): string | null
|
|
100
|
+
findAppwriteDirectory(startDir: string): Promise<string | null>;
|
|
99
101
|
/**
|
|
100
102
|
* Finds the functions directory
|
|
101
103
|
* @param startDir The directory to start searching from
|
|
102
104
|
* @returns Path to functions directory or null if not found
|
|
103
105
|
*/
|
|
104
|
-
findFunctionsDirectory(startDir: string): string | null
|
|
105
|
-
/**
|
|
106
|
-
* Recursively searches for the functions directory
|
|
107
|
-
* @param dir The directory to search
|
|
108
|
-
* @param depth Current search depth
|
|
109
|
-
* @returns Path to functions directory or null
|
|
110
|
-
*/
|
|
111
|
-
private findFunctionsDirectoryRecursive;
|
|
106
|
+
findFunctionsDirectory(startDir: string): Promise<string | null>;
|
|
112
107
|
/**
|
|
113
108
|
* Gets a summary of all discoverable configuration files
|
|
114
109
|
* Useful for debugging configuration issues
|
|
115
110
|
* @param startDir The directory to start searching from
|
|
116
111
|
* @returns Object containing paths to all discovered config types
|
|
117
112
|
*/
|
|
118
|
-
getConfigurationSummary(startDir: string): {
|
|
113
|
+
getConfigurationSummary(startDir: string): Promise<{
|
|
119
114
|
yaml: string | null;
|
|
120
115
|
typescript: string | null;
|
|
121
116
|
json: string | null;
|
|
122
117
|
appwriteDirectory: string | null;
|
|
123
118
|
functionsDirectory: string | null;
|
|
124
119
|
selectedConfig: string | null;
|
|
125
|
-
|
|
120
|
+
repoRoot: string;
|
|
121
|
+
}>;
|
|
126
122
|
}
|