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
|
@@ -103,8 +103,11 @@ export declare function loadAppwriteProjectConfig(configPath?: string): Appwrite
|
|
|
103
103
|
export declare function detectApiModeFromProject(projectConfig: AppwriteProjectConfig): "legacy" | "tablesdb" | "auto";
|
|
104
104
|
/**
|
|
105
105
|
* Convert project config to AppwriteConfig format
|
|
106
|
+
* @param projectConfig The project config to convert
|
|
107
|
+
* @param configPath Optional path to the config file (for resolving relative paths)
|
|
108
|
+
* @param existingConfig Optional existing config to merge with
|
|
106
109
|
*/
|
|
107
|
-
export declare function projectConfigToAppwriteConfig(projectConfig: AppwriteProjectConfig, existingConfig?: Partial<AppwriteConfig>): Partial<AppwriteConfig>;
|
|
110
|
+
export declare function projectConfigToAppwriteConfig(projectConfig: AppwriteProjectConfig, configPath?: string, existingConfig?: Partial<AppwriteConfig>): Partial<AppwriteConfig>;
|
|
108
111
|
/**
|
|
109
112
|
* Get collection/table definitions from project config
|
|
110
113
|
*/
|
|
@@ -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
|
/**
|
|
5
5
|
* Find appwrite.json or appwrite.config.json in current directory or parents
|
|
@@ -70,9 +70,13 @@ export function detectApiModeFromProject(projectConfig) {
|
|
|
70
70
|
}
|
|
71
71
|
/**
|
|
72
72
|
* Convert project config to AppwriteConfig format
|
|
73
|
+
* @param projectConfig The project config to convert
|
|
74
|
+
* @param configPath Optional path to the config file (for resolving relative paths)
|
|
75
|
+
* @param existingConfig Optional existing config to merge with
|
|
73
76
|
*/
|
|
74
|
-
export function projectConfigToAppwriteConfig(projectConfig, existingConfig) {
|
|
77
|
+
export function projectConfigToAppwriteConfig(projectConfig, configPath, existingConfig) {
|
|
75
78
|
const apiMode = detectApiModeFromProject(projectConfig);
|
|
79
|
+
const configDir = configPath ? dirname(configPath) : process.cwd();
|
|
76
80
|
const baseConfig = {
|
|
77
81
|
...existingConfig,
|
|
78
82
|
appwriteProject: projectConfig.projectId,
|
|
@@ -82,10 +86,20 @@ export function projectConfigToAppwriteConfig(projectConfig, existingConfig) {
|
|
|
82
86
|
if (projectConfig.endpoint) {
|
|
83
87
|
baseConfig.appwriteEndpoint = projectConfig.endpoint;
|
|
84
88
|
}
|
|
85
|
-
//
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
+
// Merge databases and tablesDB arrays (modern Appwrite may have both)
|
|
90
|
+
const allDatabases = [
|
|
91
|
+
...(projectConfig.databases || []),
|
|
92
|
+
...(projectConfig.tablesDB || [])
|
|
93
|
+
];
|
|
94
|
+
// Remove duplicates by $id
|
|
95
|
+
const uniqueDatabasesMap = new Map();
|
|
96
|
+
for (const db of allDatabases) {
|
|
97
|
+
if (!uniqueDatabasesMap.has(db.$id)) {
|
|
98
|
+
uniqueDatabasesMap.set(db.$id, db);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (uniqueDatabasesMap.size > 0) {
|
|
102
|
+
baseConfig.databases = Array.from(uniqueDatabasesMap.values()).map(db => ({
|
|
89
103
|
$id: db.$id,
|
|
90
104
|
name: db.name,
|
|
91
105
|
// Add basic bucket configuration if not exists
|
|
@@ -112,6 +126,27 @@ export function projectConfigToAppwriteConfig(projectConfig, existingConfig) {
|
|
|
112
126
|
antivirus: bucket.antiVirus ?? false,
|
|
113
127
|
}));
|
|
114
128
|
}
|
|
129
|
+
// Convert functions with path normalization
|
|
130
|
+
if (projectConfig.functions) {
|
|
131
|
+
baseConfig.functions = projectConfig.functions.map(func => {
|
|
132
|
+
const normalizedFunc = {
|
|
133
|
+
$id: func.$id,
|
|
134
|
+
name: func.name,
|
|
135
|
+
runtime: func.runtime,
|
|
136
|
+
entrypoint: func.entrypoint,
|
|
137
|
+
commands: func.commands,
|
|
138
|
+
events: func.events,
|
|
139
|
+
};
|
|
140
|
+
// Convert path to dirPath and make absolute
|
|
141
|
+
if (func.path) {
|
|
142
|
+
const expandedPath = func.path;
|
|
143
|
+
normalizedFunc.dirPath = isAbsolute(expandedPath)
|
|
144
|
+
? expandedPath
|
|
145
|
+
: resolve(configDir, expandedPath);
|
|
146
|
+
}
|
|
147
|
+
return normalizedFunc;
|
|
148
|
+
});
|
|
149
|
+
}
|
|
115
150
|
return baseConfig;
|
|
116
151
|
}
|
|
117
152
|
/**
|
|
@@ -55,6 +55,7 @@ export declare class UtilsController {
|
|
|
55
55
|
strictMode?: boolean;
|
|
56
56
|
useSession?: boolean;
|
|
57
57
|
sessionCookie?: string;
|
|
58
|
+
preferJson?: boolean;
|
|
58
59
|
}): Promise<void>;
|
|
59
60
|
reloadConfig(): Promise<void>;
|
|
60
61
|
ensureDatabaseConfigBucketsExist(databases?: Models.Database[]): Promise<void>;
|
package/dist/utilsController.js
CHANGED
|
@@ -151,7 +151,7 @@ export class UtilsController {
|
|
|
151
151
|
}
|
|
152
152
|
}
|
|
153
153
|
async init(options = {}) {
|
|
154
|
-
const { validate = false, strictMode = false } = options;
|
|
154
|
+
const { validate = false, strictMode = false, preferJson = false } = options;
|
|
155
155
|
const configManager = ConfigManager.getInstance();
|
|
156
156
|
// Load config if not already loaded
|
|
157
157
|
if (!configManager.hasConfig()) {
|
|
@@ -159,6 +159,7 @@ export class UtilsController {
|
|
|
159
159
|
configDir: this.currentUserDir,
|
|
160
160
|
validate,
|
|
161
161
|
strictMode,
|
|
162
|
+
preferJson,
|
|
162
163
|
});
|
|
163
164
|
}
|
|
164
165
|
const config = configManager.getConfig();
|
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.9.2",
|
|
5
5
|
"main": "src/main.ts",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"repository": {
|
|
@@ -45,6 +45,7 @@
|
|
|
45
45
|
"commander": "^12.1.0",
|
|
46
46
|
"decimal.js": "^10.6.0",
|
|
47
47
|
"es-toolkit": "^1.39.4",
|
|
48
|
+
"find-up": "^8.0.0",
|
|
48
49
|
"ignore": "^6.0.2",
|
|
49
50
|
"inquirer": "^9.3.7",
|
|
50
51
|
"js-yaml": "^4.1.0",
|
|
@@ -126,6 +126,18 @@ export interface CreateAttributeParams {
|
|
|
126
126
|
default?: any;
|
|
127
127
|
array?: boolean;
|
|
128
128
|
encrypt?: boolean;
|
|
129
|
+
// Numeric type parameters
|
|
130
|
+
min?: number;
|
|
131
|
+
max?: number;
|
|
132
|
+
// Enum type parameters
|
|
133
|
+
elements?: string[];
|
|
134
|
+
// Relationship type parameters
|
|
135
|
+
relatedCollection?: string;
|
|
136
|
+
relationType?: 'oneToOne' | 'manyToOne' | 'oneToMany' | 'manyToMany';
|
|
137
|
+
twoWay?: boolean;
|
|
138
|
+
twoWayKey?: string;
|
|
139
|
+
onDelete?: 'setNull' | 'cascade' | 'restrict';
|
|
140
|
+
side?: 'parent' | 'child';
|
|
129
141
|
// Additional type-specific parameters
|
|
130
142
|
[key: string]: any;
|
|
131
143
|
}
|
|
@@ -483,7 +483,7 @@ export class LegacyAdapter extends BaseAdapter {
|
|
|
483
483
|
relatedCollectionId: params.relatedCollection || '',
|
|
484
484
|
type: (params.type || 'oneToOne') as RelationshipType,
|
|
485
485
|
twoWay: params.twoWay ?? false,
|
|
486
|
-
onDelete: params.onDelete || 'restrict'
|
|
486
|
+
onDelete: (params.onDelete || 'restrict') as RelationMutate
|
|
487
487
|
});
|
|
488
488
|
break;
|
|
489
489
|
|
|
@@ -534,33 +534,33 @@ export class LegacyAdapter extends BaseAdapter {
|
|
|
534
534
|
});
|
|
535
535
|
break;
|
|
536
536
|
|
|
537
|
-
case 'integer':
|
|
538
|
-
const integerAttr = existingAttr as Models.AttributeInteger;
|
|
539
|
-
result = await this.databases.updateIntegerAttribute({
|
|
540
|
-
databaseId: params.databaseId,
|
|
541
|
-
collectionId: params.tableId,
|
|
542
|
-
key: params.key,
|
|
543
|
-
required: params.required ?? integerAttr.required,
|
|
544
|
-
xdefault: params.default !== undefined ? params.default : integerAttr.default,
|
|
545
|
-
// Only include when explicitly provided to avoid resubmitting extreme values
|
|
546
|
-
...(params.min !== undefined ? { min: params.min } : {}),
|
|
547
|
-
...(params.max !== undefined ? { max: params.max } : {}),
|
|
548
|
-
});
|
|
549
|
-
break;
|
|
537
|
+
case 'integer':
|
|
538
|
+
const integerAttr = existingAttr as Models.AttributeInteger;
|
|
539
|
+
result = await this.databases.updateIntegerAttribute({
|
|
540
|
+
databaseId: params.databaseId,
|
|
541
|
+
collectionId: params.tableId,
|
|
542
|
+
key: params.key,
|
|
543
|
+
required: params.required ?? integerAttr.required,
|
|
544
|
+
xdefault: params.default !== undefined ? params.default : integerAttr.default,
|
|
545
|
+
// Only include when explicitly provided to avoid resubmitting extreme values
|
|
546
|
+
...(params.min !== undefined ? { min: params.min } : {}),
|
|
547
|
+
...(params.max !== undefined ? { max: params.max } : {}),
|
|
548
|
+
});
|
|
549
|
+
break;
|
|
550
550
|
|
|
551
|
-
case 'float':
|
|
552
|
-
case 'double':
|
|
553
|
-
const floatAttr = existingAttr as Models.AttributeFloat;
|
|
554
|
-
result = await this.databases.updateFloatAttribute({
|
|
555
|
-
databaseId: params.databaseId,
|
|
556
|
-
collectionId: params.tableId,
|
|
557
|
-
key: params.key,
|
|
558
|
-
required: params.required ?? floatAttr.required,
|
|
559
|
-
xdefault: params.default !== undefined ? params.default : floatAttr.default,
|
|
560
|
-
...(params.min !== undefined ? { min: params.min } : {}),
|
|
561
|
-
...(params.max !== undefined ? { max: params.max } : {}),
|
|
562
|
-
});
|
|
563
|
-
break;
|
|
551
|
+
case 'float':
|
|
552
|
+
case 'double':
|
|
553
|
+
const floatAttr = existingAttr as Models.AttributeFloat;
|
|
554
|
+
result = await this.databases.updateFloatAttribute({
|
|
555
|
+
databaseId: params.databaseId,
|
|
556
|
+
collectionId: params.tableId,
|
|
557
|
+
key: params.key,
|
|
558
|
+
required: params.required ?? floatAttr.required,
|
|
559
|
+
xdefault: params.default !== undefined ? params.default : floatAttr.default,
|
|
560
|
+
...(params.min !== undefined ? { min: params.min } : {}),
|
|
561
|
+
...(params.max !== undefined ? { max: params.max } : {}),
|
|
562
|
+
});
|
|
563
|
+
break;
|
|
564
564
|
|
|
565
565
|
case 'boolean':
|
|
566
566
|
const booleanAttr = existingAttr as Models.AttributeBoolean;
|
|
@@ -838,4 +838,4 @@ async bulkDeleteRows(params: BulkDeleteRowsParams): Promise<ApiResponse> {
|
|
|
838
838
|
};
|
|
839
839
|
}
|
|
840
840
|
}
|
|
841
|
-
}
|
|
841
|
+
}
|
|
@@ -448,10 +448,10 @@ export class TablesDBAdapter extends BaseAdapter {
|
|
|
448
448
|
tableId: params.tableId,
|
|
449
449
|
key: params.key,
|
|
450
450
|
relatedTableId: params.relatedCollection || '',
|
|
451
|
-
type: (params.
|
|
451
|
+
type: (params.relationType || 'oneToOne') as RelationshipType,
|
|
452
452
|
twoWay: params.twoWay ?? false,
|
|
453
453
|
twoWayKey: params.twoWayKey,
|
|
454
|
-
onDelete: params.onDelete || 'restrict'
|
|
454
|
+
onDelete: (params.onDelete || 'restrict') as RelationMutate
|
|
455
455
|
});
|
|
456
456
|
break;
|
|
457
457
|
|
|
@@ -693,7 +693,26 @@ async bulkDeleteRows(params: BulkDeleteRowsParams): Promise<ApiResponse> {
|
|
|
693
693
|
|
|
694
694
|
// Wipe mode: use Query.limit for deleting without fetching
|
|
695
695
|
if (params.rowIds.length === 0) {
|
|
696
|
-
const batchSize = params.batchSize ||
|
|
696
|
+
const batchSize = params.batchSize || 1000;
|
|
697
|
+
|
|
698
|
+
// Validation: Ensure table exists before wiping
|
|
699
|
+
try {
|
|
700
|
+
await this.getTable({
|
|
701
|
+
databaseId: params.databaseId,
|
|
702
|
+
tableId: params.tableId
|
|
703
|
+
});
|
|
704
|
+
} catch (error: any) {
|
|
705
|
+
const errorMessage = error?.message || String(error);
|
|
706
|
+
if (errorMessage.toLowerCase().includes('not found') || error?.code === 404) {
|
|
707
|
+
throw new AdapterError(
|
|
708
|
+
`Table '${params.tableId}' not found in database '${params.databaseId}'`,
|
|
709
|
+
'TABLE_NOT_FOUND',
|
|
710
|
+
error
|
|
711
|
+
);
|
|
712
|
+
}
|
|
713
|
+
throw error; // Re-throw other errors
|
|
714
|
+
}
|
|
715
|
+
|
|
697
716
|
queries = [Query.limit(batchSize)];
|
|
698
717
|
}
|
|
699
718
|
// Specific IDs mode: chunk into batches of 80-90 to stay within Appwrite limits
|
|
@@ -715,8 +734,31 @@ async bulkDeleteRows(params: BulkDeleteRowsParams): Promise<ApiResponse> {
|
|
|
715
734
|
total: params.rowIds.length || (result as any).total || 0
|
|
716
735
|
};
|
|
717
736
|
} catch (error) {
|
|
737
|
+
const errorMessage = error instanceof Error ? error.message : 'Unknown error';
|
|
738
|
+
|
|
739
|
+
// Enhanced error categorization
|
|
740
|
+
if (errorMessage.toLowerCase().includes('not found') || (error as any)?.code === 404) {
|
|
741
|
+
throw new AdapterError(
|
|
742
|
+
`Table or rows not found: ${errorMessage}`,
|
|
743
|
+
'NOT_FOUND',
|
|
744
|
+
error instanceof Error ? error : undefined
|
|
745
|
+
);
|
|
746
|
+
} else if (errorMessage.toLowerCase().includes('permission') || errorMessage.toLowerCase().includes('unauthorized') || (error as any)?.code === 403) {
|
|
747
|
+
throw new AdapterError(
|
|
748
|
+
`Permission denied for bulk delete: ${errorMessage}`,
|
|
749
|
+
'PERMISSION_DENIED',
|
|
750
|
+
error instanceof Error ? error : undefined
|
|
751
|
+
);
|
|
752
|
+
} else if (errorMessage.toLowerCase().includes('rate limit') || (error as any)?.code === 429) {
|
|
753
|
+
throw new AdapterError(
|
|
754
|
+
`Rate limit exceeded during bulk delete: ${errorMessage}`,
|
|
755
|
+
'RATE_LIMIT',
|
|
756
|
+
error instanceof Error ? error : undefined
|
|
757
|
+
);
|
|
758
|
+
}
|
|
759
|
+
|
|
718
760
|
throw new AdapterError(
|
|
719
|
-
`Failed to bulk delete rows: ${
|
|
761
|
+
`Failed to bulk delete rows: ${errorMessage}`,
|
|
720
762
|
'BULK_DELETE_ROWS_FAILED',
|
|
721
763
|
error instanceof Error ? error : undefined
|
|
722
764
|
);
|
|
@@ -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";
|
|
@@ -9,6 +10,7 @@ import { logger } from "../../shared/logging.js";
|
|
|
9
10
|
import { fetchAllDatabases } from "../../databases/methods.js";
|
|
10
11
|
import { listBuckets } from "../../storage/methods.js";
|
|
11
12
|
import { getFunction, downloadLatestFunctionDeployment } from "../../functions/methods.js";
|
|
13
|
+
import { wipeTableRows } from "../../collections/wipeOperations.js";
|
|
12
14
|
import type { InteractiveCLI } from "../../interactiveCLI.js";
|
|
13
15
|
|
|
14
16
|
export const databaseCommands = {
|
|
@@ -29,11 +31,11 @@ export const databaseCommands = {
|
|
|
29
31
|
// Push operations always use local configuration as source of truth
|
|
30
32
|
|
|
31
33
|
// Select databases
|
|
32
|
-
const selectedDatabaseIds = await SelectionDialogs.selectDatabases(
|
|
33
|
-
availableDatabases,
|
|
34
|
-
configuredDatabases,
|
|
35
|
-
{ showSelectAll: false, allowNewOnly: false, defaultSelected: [] }
|
|
36
|
-
);
|
|
34
|
+
const selectedDatabaseIds = await SelectionDialogs.selectDatabases(
|
|
35
|
+
availableDatabases,
|
|
36
|
+
configuredDatabases,
|
|
37
|
+
{ showSelectAll: false, allowNewOnly: false, defaultSelected: [] }
|
|
38
|
+
);
|
|
37
39
|
|
|
38
40
|
if (selectedDatabaseIds.length === 0) {
|
|
39
41
|
MessageFormatter.warning("No databases selected. Skipping database sync.", { prefix: "Database" });
|
|
@@ -93,12 +95,12 @@ export const databaseCommands = {
|
|
|
93
95
|
MessageFormatter.warning("No storage buckets available in remote instance.", { prefix: "Database" });
|
|
94
96
|
} else {
|
|
95
97
|
// Select buckets using SelectionDialogs
|
|
96
|
-
const selectedBucketIds = await SelectionDialogs.selectBucketsForDatabases(
|
|
97
|
-
selectedDatabaseIds,
|
|
98
|
-
availableBuckets,
|
|
99
|
-
configuredBuckets,
|
|
100
|
-
{ showSelectAll: false, groupByDatabase: true, defaultSelected: [] }
|
|
101
|
-
);
|
|
98
|
+
const selectedBucketIds = await SelectionDialogs.selectBucketsForDatabases(
|
|
99
|
+
selectedDatabaseIds,
|
|
100
|
+
availableBuckets,
|
|
101
|
+
configuredBuckets,
|
|
102
|
+
{ showSelectAll: false, groupByDatabase: true, defaultSelected: [] }
|
|
103
|
+
);
|
|
102
104
|
|
|
103
105
|
if (selectedBucketIds.length > 0) {
|
|
104
106
|
// Create BucketSelection objects
|
|
@@ -745,5 +747,133 @@ export const databaseCommands = {
|
|
|
745
747
|
}
|
|
746
748
|
}
|
|
747
749
|
MessageFormatter.success("Wipe collections operation completed", { prefix: "Wipe" });
|
|
750
|
+
},
|
|
751
|
+
|
|
752
|
+
async wipeTablesData(cli: InteractiveCLI): Promise<void> {
|
|
753
|
+
const controller = (cli as any).controller;
|
|
754
|
+
|
|
755
|
+
if (!controller?.adapter) {
|
|
756
|
+
throw new Error(
|
|
757
|
+
"Database adapter is not initialized. TablesDB operations require adapter support."
|
|
758
|
+
);
|
|
759
|
+
}
|
|
760
|
+
|
|
761
|
+
try {
|
|
762
|
+
// Step 1: Select database (single selection for clearer UX)
|
|
763
|
+
const databases = await fetchAllDatabases(controller.database);
|
|
764
|
+
|
|
765
|
+
if (!databases || databases.length === 0) {
|
|
766
|
+
MessageFormatter.warning("No databases found", { prefix: "Wipe" });
|
|
767
|
+
return;
|
|
768
|
+
}
|
|
769
|
+
|
|
770
|
+
const { selectedDatabase } = await inquirer.prompt([
|
|
771
|
+
{
|
|
772
|
+
type: "list",
|
|
773
|
+
name: "selectedDatabase",
|
|
774
|
+
message: "Select database containing tables to wipe:",
|
|
775
|
+
choices: databases.map((db: any) => ({
|
|
776
|
+
name: `${db.name} (${db.$id})`,
|
|
777
|
+
value: db
|
|
778
|
+
}))
|
|
779
|
+
}
|
|
780
|
+
]);
|
|
781
|
+
|
|
782
|
+
const database = selectedDatabase;
|
|
783
|
+
|
|
784
|
+
// Step 2: Get available tables
|
|
785
|
+
const adapter = controller.adapter;
|
|
786
|
+
const tablesResponse = await adapter.listTables({
|
|
787
|
+
databaseId: database.$id,
|
|
788
|
+
queries: [Query.limit(500)]
|
|
789
|
+
});
|
|
790
|
+
const availableTables = (tablesResponse as any).tables || [];
|
|
791
|
+
|
|
792
|
+
if (availableTables.length === 0) {
|
|
793
|
+
MessageFormatter.warning(`No tables found in database: ${database.name}`, { prefix: "Wipe" });
|
|
794
|
+
return;
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
// Step 3: Select tables using existing SelectionDialogs
|
|
798
|
+
const selectedTableIds = await SelectionDialogs.selectTablesForDatabase(
|
|
799
|
+
database.$id,
|
|
800
|
+
database.name,
|
|
801
|
+
availableTables,
|
|
802
|
+
[], // No configured tables context needed for wipe
|
|
803
|
+
{
|
|
804
|
+
showSelectAll: true,
|
|
805
|
+
allowNewOnly: false,
|
|
806
|
+
defaultSelected: []
|
|
807
|
+
}
|
|
808
|
+
);
|
|
809
|
+
|
|
810
|
+
if (selectedTableIds.length === 0) {
|
|
811
|
+
MessageFormatter.warning("No tables selected. Operation cancelled.", { prefix: "Wipe" });
|
|
812
|
+
return;
|
|
813
|
+
}
|
|
814
|
+
|
|
815
|
+
// Step 4: Show confirmation with table details
|
|
816
|
+
const selectedTables = availableTables.filter((t: any) =>
|
|
817
|
+
selectedTableIds.includes(t.$id)
|
|
818
|
+
);
|
|
819
|
+
const tableNames = selectedTables.map((t: any) => t.name);
|
|
820
|
+
|
|
821
|
+
console.log(chalk.yellow.bold("\n⚠️ WARNING: Table Row Wipe Operation"));
|
|
822
|
+
console.log(chalk.yellow("This will delete ALL ROWS from the selected tables."));
|
|
823
|
+
console.log(chalk.yellow("The table structures will remain intact.\n"));
|
|
824
|
+
console.log(chalk.cyan("Database:"), chalk.white(database.name));
|
|
825
|
+
console.log(chalk.cyan("Tables to wipe:"));
|
|
826
|
+
tableNames.forEach((name: string) => console.log(chalk.white(` • ${name}`)));
|
|
827
|
+
console.log();
|
|
828
|
+
|
|
829
|
+
const { confirmed } = await inquirer.prompt([
|
|
830
|
+
{
|
|
831
|
+
type: "confirm",
|
|
832
|
+
name: "confirmed",
|
|
833
|
+
message: chalk.red.bold("Are you ABSOLUTELY SURE you want to wipe these table rows?"),
|
|
834
|
+
default: false
|
|
835
|
+
}
|
|
836
|
+
]);
|
|
837
|
+
|
|
838
|
+
if (!confirmed) {
|
|
839
|
+
MessageFormatter.info("Wipe operation cancelled by user", { prefix: "Wipe" });
|
|
840
|
+
return;
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
// Step 5: Execute wipe using existing wipeTableRows function
|
|
844
|
+
MessageFormatter.progress("Starting table row wipe operation...", { prefix: "Wipe" });
|
|
845
|
+
|
|
846
|
+
for (const table of selectedTables) {
|
|
847
|
+
try {
|
|
848
|
+
MessageFormatter.info(`Wiping rows from table: ${table.name}`, { prefix: "Wipe" });
|
|
849
|
+
|
|
850
|
+
// Use existing wipeTableRows from wipeOperations.ts
|
|
851
|
+
await wipeTableRows(adapter, database.$id, table.$id);
|
|
852
|
+
|
|
853
|
+
MessageFormatter.success(
|
|
854
|
+
`Successfully wiped rows from table: ${table.name}`,
|
|
855
|
+
{ prefix: "Wipe" }
|
|
856
|
+
);
|
|
857
|
+
} catch (error) {
|
|
858
|
+
MessageFormatter.error(
|
|
859
|
+
`Failed to wipe table ${table.name}`,
|
|
860
|
+
error instanceof Error ? error : new Error(String(error)),
|
|
861
|
+
{ prefix: "Wipe" }
|
|
862
|
+
);
|
|
863
|
+
}
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
MessageFormatter.success(
|
|
867
|
+
`Wipe operation completed for ${selectedTables.length} table(s)`,
|
|
868
|
+
{ prefix: "Wipe" }
|
|
869
|
+
);
|
|
870
|
+
} catch (error) {
|
|
871
|
+
MessageFormatter.error(
|
|
872
|
+
"Table wipe operation failed",
|
|
873
|
+
error instanceof Error ? error : new Error(String(error)),
|
|
874
|
+
{ prefix: "Wipe" }
|
|
875
|
+
);
|
|
876
|
+
throw error;
|
|
877
|
+
}
|
|
748
878
|
}
|
|
749
879
|
};
|
|
@@ -77,6 +77,12 @@ export interface ConfigLoadOptions {
|
|
|
77
77
|
* Override session authentication (used for preserving session on reload)
|
|
78
78
|
*/
|
|
79
79
|
sessionOverride?: SessionAuthInfo;
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Prefer loading from appwrite.config.json over config.yaml
|
|
83
|
+
* @default false
|
|
84
|
+
*/
|
|
85
|
+
preferJson?: boolean;
|
|
80
86
|
}
|
|
81
87
|
|
|
82
88
|
/**
|
|
@@ -233,7 +239,10 @@ export class ConfigManager {
|
|
|
233
239
|
logger.debug("Loading config from file", { prefix: "ConfigManager", options });
|
|
234
240
|
|
|
235
241
|
// 2. Discover config file
|
|
236
|
-
const configPath = this.discoveryService.findConfig(
|
|
242
|
+
const configPath = await this.discoveryService.findConfig(
|
|
243
|
+
options.configDir || process.cwd(),
|
|
244
|
+
options.preferJson || false
|
|
245
|
+
);
|
|
237
246
|
|
|
238
247
|
if (!configPath) {
|
|
239
248
|
const searchDir = options.configDir || process.cwd();
|