appwrite-utils-cli 1.8.2 → 1.8.5
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 +6 -1
- package/README.md +42 -13
- package/dist/adapters/TablesDBAdapter.js +1 -1
- package/dist/cli/commands/functionCommands.js +30 -3
- package/dist/cli/commands/schemaCommands.js +39 -4
- package/dist/cli/commands/storageCommands.d.ts +5 -0
- package/dist/cli/commands/storageCommands.js +143 -0
- package/dist/collections/attributes.js +7 -7
- package/dist/collections/methods.js +1 -1
- package/dist/collections/tableOperations.js +2 -2
- package/dist/interactiveCLI.d.ts +1 -0
- package/dist/interactiveCLI.js +30 -0
- package/dist/main.js +17 -0
- package/dist/migrations/appwriteToX.js +1 -1
- package/dist/migrations/yaml/generateImportSchemas.js +2 -2
- package/dist/setupCommands.js +6 -0
- package/dist/shared/attributeMapper.js +2 -2
- package/dist/shared/jsonSchemaGenerator.js +3 -1
- package/dist/shared/pydanticModelGenerator.d.ts +17 -0
- package/dist/shared/pydanticModelGenerator.js +615 -0
- package/dist/shared/schemaGenerator.d.ts +3 -2
- package/dist/shared/schemaGenerator.js +22 -9
- package/dist/storage/methods.js +50 -7
- package/dist/utils/configDiscovery.js +2 -3
- package/dist/utils/constantsGenerator.d.ts +20 -8
- package/dist/utils/constantsGenerator.js +37 -25
- package/dist/utils/projectConfig.js +1 -1
- package/dist/utils/yamlConverter.d.ts +2 -2
- package/dist/utils/yamlConverter.js +2 -2
- package/package.json +1 -1
- package/src/adapters/TablesDBAdapter.ts +1 -1
- package/src/cli/commands/functionCommands.ts +28 -3
- package/src/cli/commands/schemaCommands.ts +59 -22
- package/src/cli/commands/storageCommands.ts +152 -0
- package/src/collections/attributes.ts +7 -7
- package/src/collections/methods.ts +7 -7
- package/src/collections/tableOperations.ts +2 -2
- package/src/interactiveCLI.ts +42 -12
- package/src/main.ts +32 -9
- package/src/migrations/appwriteToX.ts +1 -1
- package/src/migrations/yaml/generateImportSchemas.ts +7 -7
- package/src/setupCommands.ts +6 -0
- package/src/shared/attributeMapper.ts +2 -2
- package/src/shared/jsonSchemaGenerator.ts +4 -2
- package/src/shared/pydanticModelGenerator.ts +618 -0
- package/src/shared/schemaGenerator.ts +38 -25
- package/src/storage/methods.ts +67 -23
- package/src/utils/configDiscovery.ts +40 -41
- package/src/utils/constantsGenerator.ts +43 -26
- package/src/utils/projectConfig.ts +11 -11
- package/src/utils/yamlConverter.ts +40 -40
package/dist/storage/methods.js
CHANGED
|
@@ -44,8 +44,54 @@ export const ensureDatabaseConfigBucketsExist = async (storage, config, database
|
|
|
44
44
|
const database = config.databases?.find((d) => d.$id === db.$id);
|
|
45
45
|
if (database?.bucket) {
|
|
46
46
|
try {
|
|
47
|
-
await storage.getBucket(database.bucket.$id);
|
|
48
|
-
|
|
47
|
+
const existing = await storage.getBucket(database.bucket.$id);
|
|
48
|
+
// Compare and update if needed
|
|
49
|
+
const desired = database.bucket;
|
|
50
|
+
// Build desired permissions via Permission helper
|
|
51
|
+
const permissions = [];
|
|
52
|
+
if (desired.permissions && desired.permissions.length > 0) {
|
|
53
|
+
for (const p of desired.permissions) {
|
|
54
|
+
switch (p.permission) {
|
|
55
|
+
case 'read':
|
|
56
|
+
permissions.push(Permission.read(p.target));
|
|
57
|
+
break;
|
|
58
|
+
case 'create':
|
|
59
|
+
permissions.push(Permission.create(p.target));
|
|
60
|
+
break;
|
|
61
|
+
case 'update':
|
|
62
|
+
permissions.push(Permission.update(p.target));
|
|
63
|
+
break;
|
|
64
|
+
case 'delete':
|
|
65
|
+
permissions.push(Permission.delete(p.target));
|
|
66
|
+
break;
|
|
67
|
+
case 'write':
|
|
68
|
+
permissions.push(Permission.write(p.target));
|
|
69
|
+
break;
|
|
70
|
+
default: break;
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
const diff = (existing.name !== desired.name ||
|
|
75
|
+
JSON.stringify(existing.$permissions || []) !== JSON.stringify(permissions) ||
|
|
76
|
+
!!existing.fileSecurity !== !!desired.fileSecurity ||
|
|
77
|
+
!!existing.enabled !== !!desired.enabled ||
|
|
78
|
+
(existing.maximumFileSize ?? undefined) !== (desired.maximumFileSize ?? undefined) ||
|
|
79
|
+
JSON.stringify(existing.allowedFileExtensions || []) !== JSON.stringify(desired.allowedFileExtensions || []) ||
|
|
80
|
+
String(existing.compression || 'none') !== String(desired.compression || 'none') ||
|
|
81
|
+
!!existing.encryption !== !!desired.encryption ||
|
|
82
|
+
!!existing.antivirus !== !!desired.antivirus);
|
|
83
|
+
if (diff) {
|
|
84
|
+
try {
|
|
85
|
+
await storage.updateBucket(desired.$id, desired.name, permissions, desired.fileSecurity, desired.enabled, desired.maximumFileSize, desired.allowedFileExtensions, desired.compression, desired.encryption, desired.antivirus);
|
|
86
|
+
MessageFormatter.info(`Updated bucket ${desired.$id} to match config`, { prefix: 'Buckets' });
|
|
87
|
+
}
|
|
88
|
+
catch (updateErr) {
|
|
89
|
+
MessageFormatter.warning(`Failed to update bucket ${desired.$id}: ${updateErr instanceof Error ? updateErr.message : String(updateErr)}`, { prefix: 'Buckets' });
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
MessageFormatter.debug(`Bucket ${desired.$id} up-to-date`, undefined, { prefix: 'Buckets' });
|
|
94
|
+
}
|
|
49
95
|
}
|
|
50
96
|
catch (e) {
|
|
51
97
|
const permissions = [];
|
|
@@ -76,13 +122,10 @@ export const ensureDatabaseConfigBucketsExist = async (storage, config, database
|
|
|
76
122
|
}
|
|
77
123
|
try {
|
|
78
124
|
await storage.createBucket(database.bucket.$id, database.bucket.name, permissions, database.bucket.fileSecurity, database.bucket.enabled, database.bucket.maximumFileSize, database.bucket.allowedFileExtensions, database.bucket.compression, database.bucket.encryption, database.bucket.antivirus);
|
|
79
|
-
|
|
125
|
+
MessageFormatter.success(`Bucket ${database.bucket.$id} created`, { prefix: 'Buckets' });
|
|
80
126
|
}
|
|
81
127
|
catch (createError) {
|
|
82
|
-
|
|
83
|
-
// `Failed to create bucket ${database.bucket.$id}:`,
|
|
84
|
-
// createError
|
|
85
|
-
// );
|
|
128
|
+
MessageFormatter.error(`Failed to create bucket ${database.bucket.$id}`, createError instanceof Error ? createError : new Error(String(createError)), { prefix: 'Buckets' });
|
|
86
129
|
}
|
|
87
130
|
}
|
|
88
131
|
}
|
|
@@ -152,7 +152,7 @@ const YamlTableSchema = z.object({
|
|
|
152
152
|
size: z.number().optional(),
|
|
153
153
|
required: z.boolean().default(false),
|
|
154
154
|
array: z.boolean().optional(),
|
|
155
|
-
|
|
155
|
+
encrypt: z.boolean().optional(), // Tables support encrypt property
|
|
156
156
|
default: z.any().optional(),
|
|
157
157
|
min: z.number().optional(),
|
|
158
158
|
max: z.number().optional(),
|
|
@@ -163,7 +163,6 @@ const YamlTableSchema = z.object({
|
|
|
163
163
|
twoWayKey: z.string().optional(),
|
|
164
164
|
onDelete: z.string().optional(),
|
|
165
165
|
side: z.string().optional(),
|
|
166
|
-
encrypt: z.boolean().optional(),
|
|
167
166
|
format: z.string().optional()
|
|
168
167
|
})).optional().default([]),
|
|
169
168
|
indexes: z.array(z.object({
|
|
@@ -266,7 +265,7 @@ export const loadYamlTable = (filePath) => {
|
|
|
266
265
|
twoWayKey: col.twoWayKey,
|
|
267
266
|
onDelete: col.onDelete,
|
|
268
267
|
side: col.side,
|
|
269
|
-
|
|
268
|
+
encrypt: col.encrypt,
|
|
270
269
|
format: col.format
|
|
271
270
|
})),
|
|
272
271
|
indexes: parsedTable.indexes.map(idx => ({
|
|
@@ -1,5 +1,11 @@
|
|
|
1
1
|
import { type AppwriteConfig } from "appwrite-utils";
|
|
2
2
|
export type SupportedLanguage = "typescript" | "javascript" | "python" | "php" | "dart" | "json" | "env";
|
|
3
|
+
interface Constants {
|
|
4
|
+
databases: Record<string, string>;
|
|
5
|
+
collections: Record<string, string>;
|
|
6
|
+
buckets: Record<string, string>;
|
|
7
|
+
functions: Record<string, string>;
|
|
8
|
+
}
|
|
3
9
|
export declare class ConstantsGenerator {
|
|
4
10
|
private config;
|
|
5
11
|
private constants;
|
|
@@ -8,12 +14,18 @@ export declare class ConstantsGenerator {
|
|
|
8
14
|
private toConstantName;
|
|
9
15
|
private toCamelCase;
|
|
10
16
|
private toSnakeCase;
|
|
11
|
-
generateTypeScript(): string;
|
|
12
|
-
generateJavaScript(): string;
|
|
13
|
-
generatePython(): string;
|
|
14
|
-
generatePHP(): string;
|
|
15
|
-
generateDart(): string;
|
|
16
|
-
generateJSON(): string;
|
|
17
|
-
generateEnv(): string;
|
|
18
|
-
generateFiles(languages: SupportedLanguage[], outputDir: string
|
|
17
|
+
generateTypeScript(constantsOverride?: Constants): string;
|
|
18
|
+
generateJavaScript(constantsOverride?: Constants): string;
|
|
19
|
+
generatePython(constantsOverride?: Constants): string;
|
|
20
|
+
generatePHP(constantsOverride?: Constants): string;
|
|
21
|
+
generateDart(constantsOverride?: Constants): string;
|
|
22
|
+
generateJSON(constantsOverride?: Constants): string;
|
|
23
|
+
generateEnv(constantsOverride?: Constants): string;
|
|
24
|
+
generateFiles(languages: SupportedLanguage[], outputDir: string, include?: {
|
|
25
|
+
databases?: boolean;
|
|
26
|
+
collections?: boolean;
|
|
27
|
+
buckets?: boolean;
|
|
28
|
+
functions?: boolean;
|
|
29
|
+
}): Promise<void>;
|
|
19
30
|
}
|
|
31
|
+
export {};
|
|
@@ -61,8 +61,8 @@ export class ConstantsGenerator {
|
|
|
61
61
|
toSnakeCase(name) {
|
|
62
62
|
return name.toLowerCase();
|
|
63
63
|
}
|
|
64
|
-
generateTypeScript() {
|
|
65
|
-
const { databases, collections, buckets, functions } = this.constants;
|
|
64
|
+
generateTypeScript(constantsOverride) {
|
|
65
|
+
const { databases, collections, buckets, functions } = constantsOverride || this.constants;
|
|
66
66
|
return `// Auto-generated Appwrite constants
|
|
67
67
|
// Generated on ${new Date().toISOString()}
|
|
68
68
|
|
|
@@ -95,8 +95,8 @@ export const ALL_BUCKET_IDS = Object.values(BUCKET_IDS);
|
|
|
95
95
|
export const ALL_FUNCTION_IDS = Object.values(FUNCTION_IDS);
|
|
96
96
|
`;
|
|
97
97
|
}
|
|
98
|
-
generateJavaScript() {
|
|
99
|
-
const { databases, collections, buckets, functions } = this.constants;
|
|
98
|
+
generateJavaScript(constantsOverride) {
|
|
99
|
+
const { databases, collections, buckets, functions } = constantsOverride || this.constants;
|
|
100
100
|
return `// Auto-generated Appwrite constants
|
|
101
101
|
// Generated on ${new Date().toISOString()}
|
|
102
102
|
|
|
@@ -123,8 +123,8 @@ export const ALL_BUCKET_IDS = Object.values(BUCKET_IDS);
|
|
|
123
123
|
export const ALL_FUNCTION_IDS = Object.values(FUNCTION_IDS);
|
|
124
124
|
`;
|
|
125
125
|
}
|
|
126
|
-
generatePython() {
|
|
127
|
-
const { databases, collections, buckets, functions } = this.constants;
|
|
126
|
+
generatePython(constantsOverride) {
|
|
127
|
+
const { databases, collections, buckets, functions } = constantsOverride || this.constants;
|
|
128
128
|
return `# Auto-generated Appwrite constants
|
|
129
129
|
# Generated on ${new Date().toISOString()}
|
|
130
130
|
|
|
@@ -162,8 +162,8 @@ ${Object.entries(functions).map(([key, value]) => ` "${this.toSnakeCase(key)}
|
|
|
162
162
|
}
|
|
163
163
|
`;
|
|
164
164
|
}
|
|
165
|
-
generatePHP() {
|
|
166
|
-
const { databases, collections, buckets, functions } = this.constants;
|
|
165
|
+
generatePHP(constantsOverride) {
|
|
166
|
+
const { databases, collections, buckets, functions } = constantsOverride || this.constants;
|
|
167
167
|
return `<?php
|
|
168
168
|
// Auto-generated Appwrite constants
|
|
169
169
|
// Generated on ${new Date().toISOString()}
|
|
@@ -216,8 +216,8 @@ ${Object.entries(functions).map(([key, value]) => ` '${key}' => '${value}
|
|
|
216
216
|
}
|
|
217
217
|
`;
|
|
218
218
|
}
|
|
219
|
-
generateDart() {
|
|
220
|
-
const { databases, collections, buckets, functions } = this.constants;
|
|
219
|
+
generateDart(constantsOverride) {
|
|
220
|
+
const { databases, collections, buckets, functions } = constantsOverride || this.constants;
|
|
221
221
|
return `// Auto-generated Appwrite constants
|
|
222
222
|
// Generated on ${new Date().toISOString()}
|
|
223
223
|
|
|
@@ -250,20 +250,21 @@ ${Object.entries(functions).map(([key, value]) => ` static String get ${this.to
|
|
|
250
250
|
}
|
|
251
251
|
`;
|
|
252
252
|
}
|
|
253
|
-
generateJSON() {
|
|
253
|
+
generateJSON(constantsOverride) {
|
|
254
|
+
const c = constantsOverride || this.constants;
|
|
254
255
|
return JSON.stringify({
|
|
255
256
|
meta: {
|
|
256
257
|
generated: new Date().toISOString(),
|
|
257
258
|
generator: "appwrite-utils-cli"
|
|
258
259
|
},
|
|
259
|
-
databases:
|
|
260
|
-
collections:
|
|
261
|
-
buckets:
|
|
262
|
-
functions:
|
|
260
|
+
databases: c.databases,
|
|
261
|
+
collections: c.collections,
|
|
262
|
+
buckets: c.buckets,
|
|
263
|
+
functions: c.functions
|
|
263
264
|
}, null, 2);
|
|
264
265
|
}
|
|
265
|
-
generateEnv() {
|
|
266
|
-
const { databases, collections, buckets, functions } = this.constants;
|
|
266
|
+
generateEnv(constantsOverride) {
|
|
267
|
+
const { databases, collections, buckets, functions } = constantsOverride || this.constants;
|
|
267
268
|
const lines = [
|
|
268
269
|
"# Auto-generated Appwrite constants",
|
|
269
270
|
`# Generated on ${new Date().toISOString()}`,
|
|
@@ -282,16 +283,27 @@ ${Object.entries(functions).map(([key, value]) => ` static String get ${this.to
|
|
|
282
283
|
];
|
|
283
284
|
return lines.join('\n');
|
|
284
285
|
}
|
|
285
|
-
async generateFiles(languages, outputDir) {
|
|
286
|
+
async generateFiles(languages, outputDir, include) {
|
|
286
287
|
await fs.mkdir(outputDir, { recursive: true });
|
|
288
|
+
const filterConstants = () => {
|
|
289
|
+
if (!include)
|
|
290
|
+
return this.constants;
|
|
291
|
+
return {
|
|
292
|
+
databases: include.databases === false ? {} : this.constants.databases,
|
|
293
|
+
collections: include.collections === false ? {} : this.constants.collections,
|
|
294
|
+
buckets: include.buckets === false ? {} : this.constants.buckets,
|
|
295
|
+
functions: include.functions === false ? {} : this.constants.functions,
|
|
296
|
+
};
|
|
297
|
+
};
|
|
298
|
+
const subset = filterConstants();
|
|
287
299
|
const generators = {
|
|
288
|
-
typescript: () => ({ content: this.generateTypeScript(), filename: "appwrite-constants.ts" }),
|
|
289
|
-
javascript: () => ({ content: this.generateJavaScript(), filename: "appwrite-constants.js" }),
|
|
290
|
-
python: () => ({ content: this.generatePython(), filename: "appwrite_constants.py" }),
|
|
291
|
-
php: () => ({ content: this.generatePHP(), filename: "AppwriteConstants.php" }),
|
|
292
|
-
dart: () => ({ content: this.generateDart(), filename: "appwrite_constants.dart" }),
|
|
293
|
-
json: () => ({ content: this.generateJSON(), filename: "appwrite-constants.json" }),
|
|
294
|
-
env: () => ({ content: this.generateEnv(), filename: ".env.appwrite" })
|
|
300
|
+
typescript: () => ({ content: this.generateTypeScript(subset), filename: "appwrite-constants.ts" }),
|
|
301
|
+
javascript: () => ({ content: this.generateJavaScript(subset), filename: "appwrite-constants.js" }),
|
|
302
|
+
python: () => ({ content: this.generatePython(subset), filename: "appwrite_constants.py" }),
|
|
303
|
+
php: () => ({ content: this.generatePHP(subset), filename: "AppwriteConstants.php" }),
|
|
304
|
+
dart: () => ({ content: this.generateDart(subset), filename: "appwrite_constants.dart" }),
|
|
305
|
+
json: () => ({ content: this.generateJSON(subset), filename: "appwrite-constants.json" }),
|
|
306
|
+
env: () => ({ content: this.generateEnv(subset), filename: ".env.appwrite" })
|
|
295
307
|
};
|
|
296
308
|
for (const language of languages) {
|
|
297
309
|
const generator = generators[language];
|
|
@@ -15,7 +15,7 @@ export interface YamlCollectionData {
|
|
|
15
15
|
size?: number;
|
|
16
16
|
required?: boolean;
|
|
17
17
|
array?: boolean;
|
|
18
|
-
|
|
18
|
+
encrypt?: boolean;
|
|
19
19
|
default?: any;
|
|
20
20
|
min?: number;
|
|
21
21
|
max?: number;
|
|
@@ -33,7 +33,7 @@ export interface YamlCollectionData {
|
|
|
33
33
|
size?: number;
|
|
34
34
|
required?: boolean;
|
|
35
35
|
array?: boolean;
|
|
36
|
-
|
|
36
|
+
encrypt?: boolean;
|
|
37
37
|
default?: any;
|
|
38
38
|
min?: number;
|
|
39
39
|
max?: number;
|
|
@@ -125,9 +125,9 @@ export function collectionToYaml(collection, config = {
|
|
|
125
125
|
yamlAttr.required = attr.required;
|
|
126
126
|
if (attr.array !== undefined)
|
|
127
127
|
yamlAttr.array = attr.array;
|
|
128
|
-
// Always include
|
|
128
|
+
// Always include encrypt field for string attributes (default to false)
|
|
129
129
|
if (attr.type === 'string') {
|
|
130
|
-
yamlAttr.
|
|
130
|
+
yamlAttr.encrypt = ('encrypt' in attr && attr.encrypt === true) ? true : false;
|
|
131
131
|
}
|
|
132
132
|
if ('xdefault' in attr && attr.xdefault !== undefined)
|
|
133
133
|
yamlAttr.default = attr.xdefault;
|
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.8.
|
|
4
|
+
"version": "1.8.5",
|
|
5
5
|
"main": "src/main.ts",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"repository": {
|
|
@@ -315,7 +315,7 @@ export class TablesDBAdapter extends BaseAdapter {
|
|
|
315
315
|
const type = (params.type || "").toLowerCase();
|
|
316
316
|
const required = params.required ?? false;
|
|
317
317
|
const array = params.array ?? false;
|
|
318
|
-
const encrypt = params.encrypt ??
|
|
318
|
+
const encrypt = params.encrypt ?? false;
|
|
319
319
|
const normalizedDefault =
|
|
320
320
|
params.default === null || params.default === undefined
|
|
321
321
|
? undefined
|
|
@@ -138,11 +138,36 @@ export const functionCommands = {
|
|
|
138
138
|
return;
|
|
139
139
|
}
|
|
140
140
|
|
|
141
|
-
//
|
|
141
|
+
// Offer choice of function config sources: central YAML, .fnconfig.yaml, or both
|
|
142
|
+
let sourceChoice: 'central' | 'fnconfig' | 'both' = 'both';
|
|
143
|
+
try {
|
|
144
|
+
const answer = await inquirer.prompt([
|
|
145
|
+
{
|
|
146
|
+
type: 'list',
|
|
147
|
+
name: 'source',
|
|
148
|
+
message: 'Select function config source:',
|
|
149
|
+
choices: [
|
|
150
|
+
{ name: 'config.yaml functions (central)', value: 'central' },
|
|
151
|
+
{ name: '.fnconfig.yaml (discovered per-function)', value: 'fnconfig' },
|
|
152
|
+
{ name: 'Both (merge; .fnconfig overrides)', value: 'both' },
|
|
153
|
+
],
|
|
154
|
+
default: 'both'
|
|
155
|
+
}
|
|
156
|
+
]);
|
|
157
|
+
sourceChoice = answer.source;
|
|
158
|
+
} catch {}
|
|
159
|
+
|
|
142
160
|
try {
|
|
143
161
|
const discovered = discoverFnConfigs((cli as any).currentDir);
|
|
144
|
-
const
|
|
145
|
-
(
|
|
162
|
+
const central = (cli as any).controller!.config!.functions || [];
|
|
163
|
+
if (sourceChoice === 'central') {
|
|
164
|
+
(cli as any).controller!.config!.functions = central as any;
|
|
165
|
+
} else if (sourceChoice === 'fnconfig') {
|
|
166
|
+
(cli as any).controller!.config!.functions = discovered as any;
|
|
167
|
+
} else {
|
|
168
|
+
const merged = mergeDiscoveredFunctions(central, discovered);
|
|
169
|
+
(cli as any).controller!.config!.functions = merged as any;
|
|
170
|
+
}
|
|
146
171
|
} catch {}
|
|
147
172
|
|
|
148
173
|
const functions = await (cli as any).selectFunctions(
|
|
@@ -12,19 +12,21 @@ export const schemaCommands = {
|
|
|
12
12
|
MessageFormatter.progress("Generating schemas...", { prefix: "Schemas" });
|
|
13
13
|
|
|
14
14
|
// Prompt user for schema type preference
|
|
15
|
-
const { schemaType } = await inquirer.prompt([
|
|
16
|
-
{
|
|
17
|
-
type: "list",
|
|
18
|
-
name: "schemaType",
|
|
19
|
-
message: "What type of schemas would you like to generate?",
|
|
20
|
-
choices: [
|
|
21
|
-
{ name: "TypeScript (Zod) schemas", value: "zod" },
|
|
22
|
-
{ name: "JSON schemas", value: "json" },
|
|
23
|
-
{ name: "
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
15
|
+
const { schemaType } = await inquirer.prompt([
|
|
16
|
+
{
|
|
17
|
+
type: "list",
|
|
18
|
+
name: "schemaType",
|
|
19
|
+
message: "What type of schemas would you like to generate?",
|
|
20
|
+
choices: [
|
|
21
|
+
{ name: "TypeScript (Zod) schemas", value: "zod" },
|
|
22
|
+
{ name: "JSON schemas", value: "json" },
|
|
23
|
+
{ name: "Python (Pydantic) models", value: "pydantic" },
|
|
24
|
+
{ name: "TypeScript + JSON", value: "both" },
|
|
25
|
+
{ name: "All (Zod, JSON, Pydantic)", value: "all" },
|
|
26
|
+
],
|
|
27
|
+
default: "all",
|
|
28
|
+
},
|
|
29
|
+
]);
|
|
28
30
|
|
|
29
31
|
// Get the config folder path (where the config file is located)
|
|
30
32
|
const configFolderPath = (cli as any).controller!.getAppwriteFolderPath();
|
|
@@ -33,14 +35,27 @@ export const schemaCommands = {
|
|
|
33
35
|
return;
|
|
34
36
|
}
|
|
35
37
|
|
|
36
|
-
//
|
|
37
|
-
const
|
|
38
|
-
|
|
38
|
+
// Prompt for schema output directory (optional override)
|
|
39
|
+
const defaultSchemaOut = path.join(configFolderPath, (cli as any).controller!.config?.schemaConfig?.outputDirectory || 'schemas');
|
|
40
|
+
const { schemaOutDir } = await inquirer.prompt([
|
|
41
|
+
{
|
|
42
|
+
type: 'input',
|
|
43
|
+
name: 'schemaOutDir',
|
|
44
|
+
message: 'Output directory for schemas:',
|
|
45
|
+
default: defaultSchemaOut,
|
|
46
|
+
validate: (input: string) => input && input.trim().length > 0 ? true : 'Please provide an output directory',
|
|
47
|
+
}
|
|
48
|
+
]);
|
|
49
|
+
|
|
50
|
+
// Create SchemaGenerator with the correct base path and generate schemas
|
|
51
|
+
const schemaGenerator = new SchemaGenerator((cli as any).controller!.config!, configFolderPath);
|
|
52
|
+
const outDirRel = path.isAbsolute(schemaOutDir) ? schemaOutDir : path.relative(configFolderPath, schemaOutDir);
|
|
53
|
+
await schemaGenerator.generateSchemas({ format: schemaType as any, verbose: true, outputDir: outDirRel });
|
|
39
54
|
|
|
40
55
|
MessageFormatter.success("Schema generation completed", { prefix: "Schemas" });
|
|
41
56
|
},
|
|
42
57
|
|
|
43
|
-
async generateConstants(cli: InteractiveCLI): Promise<void> {
|
|
58
|
+
async generateConstants(cli: InteractiveCLI): Promise<void> {
|
|
44
59
|
MessageFormatter.progress("Generating cross-language constants...", { prefix: "Constants" });
|
|
45
60
|
|
|
46
61
|
if (!(cli as any).controller?.config) {
|
|
@@ -48,8 +63,8 @@ export const schemaCommands = {
|
|
|
48
63
|
return;
|
|
49
64
|
}
|
|
50
65
|
|
|
51
|
-
// Prompt for languages
|
|
52
|
-
const { languages } = await inquirer.prompt([
|
|
66
|
+
// Prompt for languages
|
|
67
|
+
const { languages } = await inquirer.prompt([
|
|
53
68
|
{
|
|
54
69
|
type: "checkbox",
|
|
55
70
|
name: "languages",
|
|
@@ -70,7 +85,23 @@ export const schemaCommands = {
|
|
|
70
85
|
return true;
|
|
71
86
|
},
|
|
72
87
|
},
|
|
73
|
-
]);
|
|
88
|
+
]);
|
|
89
|
+
|
|
90
|
+
// Prompt for which constants to include
|
|
91
|
+
const { includeWhat } = await inquirer.prompt([
|
|
92
|
+
{
|
|
93
|
+
type: 'checkbox',
|
|
94
|
+
name: 'includeWhat',
|
|
95
|
+
message: 'Select which constants to generate:',
|
|
96
|
+
choices: [
|
|
97
|
+
{ name: 'Databases', value: 'databases', checked: true },
|
|
98
|
+
{ name: 'Collections/Tables', value: 'collections', checked: true },
|
|
99
|
+
{ name: 'Buckets', value: 'buckets', checked: true },
|
|
100
|
+
{ name: 'Functions', value: 'functions', checked: true },
|
|
101
|
+
],
|
|
102
|
+
validate: (input) => input.length > 0 ? true : 'Select at least one category',
|
|
103
|
+
}
|
|
104
|
+
]);
|
|
74
105
|
|
|
75
106
|
// Determine default output directory based on config location
|
|
76
107
|
const configPath = (cli as any).controller!.getAppwriteFolderPath();
|
|
@@ -98,8 +129,14 @@ export const schemaCommands = {
|
|
|
98
129
|
const { ConstantsGenerator } = await import("../../utils/constantsGenerator.js");
|
|
99
130
|
const generator = new ConstantsGenerator((cli as any).controller.config);
|
|
100
131
|
|
|
101
|
-
|
|
102
|
-
|
|
132
|
+
const include = {
|
|
133
|
+
databases: includeWhat.includes('databases'),
|
|
134
|
+
collections: includeWhat.includes('collections'),
|
|
135
|
+
buckets: includeWhat.includes('buckets'),
|
|
136
|
+
functions: includeWhat.includes('functions'),
|
|
137
|
+
};
|
|
138
|
+
MessageFormatter.info(`Generating constants for: ${languages.join(", ")}`, { prefix: "Constants" });
|
|
139
|
+
await generator.generateFiles(languages, outputDir, include);
|
|
103
140
|
|
|
104
141
|
MessageFormatter.success(`Constants generated in ${outputDir}`, { prefix: "Constants" });
|
|
105
142
|
} catch (error) {
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import inquirer from "inquirer";
|
|
2
|
+
import chalk from "chalk";
|
|
3
|
+
import { Storage, Permission, Role, Compression, type Models } from "node-appwrite";
|
|
4
|
+
import type { InteractiveCLI } from "../../interactiveCLI.js";
|
|
5
|
+
import { MessageFormatter } from "../../shared/messageFormatter.js";
|
|
6
|
+
import { listBuckets, createBucket as createBucketApi, deleteBucket as deleteBucketApi } from "../../storage/methods.js";
|
|
7
|
+
import { writeYamlConfig } from "../../config/yamlConfig.js";
|
|
8
|
+
import { ConfigManager } from "../../config/ConfigManager.js";
|
|
9
|
+
|
|
10
|
+
export const storageCommands = {
|
|
11
|
+
async createBucket(cli: InteractiveCLI): Promise<void> {
|
|
12
|
+
const storage: Storage = (cli as any).controller!.storage!;
|
|
13
|
+
if (!storage) {
|
|
14
|
+
MessageFormatter.error("Storage client not initialized", undefined, { prefix: "Buckets" });
|
|
15
|
+
return;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
const answers = await inquirer.prompt([
|
|
19
|
+
{ type: 'input', name: 'name', message: 'Bucket name:', validate: (v:string)=> v?.trim()?.length>0 || 'Required' },
|
|
20
|
+
{ type: 'input', name: 'id', message: 'Bucket ID (optional):' },
|
|
21
|
+
{ type: 'confirm', name: 'publicRead', message: 'Public read access?', default: false },
|
|
22
|
+
{ type: 'confirm', name: 'fileSecurity', message: 'Enable file-level security?', default: false },
|
|
23
|
+
{ type: 'confirm', name: 'enabled', message: 'Enable bucket?', default: true },
|
|
24
|
+
{ type: 'number', name: 'maximumFileSize', message: 'Max file size (bytes, optional):', default: undefined },
|
|
25
|
+
{ type: 'input', name: 'allowedFileExtensions', message: 'Allowed extensions (comma separated, optional):', default: '' },
|
|
26
|
+
{ type: 'list', name: 'compression', message: 'Compression:', choices: ['none','gzip','zstd'], default: 'none' },
|
|
27
|
+
{ type: 'confirm', name: 'encryption', message: 'Enable encryption?', default: false },
|
|
28
|
+
{ type: 'confirm', name: 'antivirus', message: 'Enable antivirus?', default: false },
|
|
29
|
+
]);
|
|
30
|
+
|
|
31
|
+
const permissions: string[] = [];
|
|
32
|
+
if (answers.publicRead) permissions.push(Permission.read(Role.any()));
|
|
33
|
+
|
|
34
|
+
const bucketInput: Omit<Models.Bucket, "$id" | "$createdAt" | "$updatedAt"> = {
|
|
35
|
+
name: answers.name,
|
|
36
|
+
$permissions: permissions,
|
|
37
|
+
fileSecurity: !!answers.fileSecurity,
|
|
38
|
+
enabled: !!answers.enabled,
|
|
39
|
+
maximumFileSize: answers.maximumFileSize || undefined,
|
|
40
|
+
allowedFileExtensions: String(answers.allowedFileExtensions || '')
|
|
41
|
+
.split(',')
|
|
42
|
+
.map((s) => s.trim())
|
|
43
|
+
.filter(Boolean),
|
|
44
|
+
compression: answers.compression as Compression,
|
|
45
|
+
encryption: !!answers.encryption,
|
|
46
|
+
antivirus: !!answers.antivirus,
|
|
47
|
+
} as any;
|
|
48
|
+
|
|
49
|
+
try {
|
|
50
|
+
const created = await createBucketApi(storage, bucketInput, answers.id || undefined);
|
|
51
|
+
MessageFormatter.success(`Bucket '${answers.name}' created`, { prefix: 'Buckets' });
|
|
52
|
+
|
|
53
|
+
// Update in-memory config and persist to YAML as a global bucket entry
|
|
54
|
+
const controller = (cli as any).controller!;
|
|
55
|
+
controller.config.buckets = controller.config.buckets || [];
|
|
56
|
+
controller.config.buckets.push({
|
|
57
|
+
$id: (created as any).$id || answers.id || bucketInput.name,
|
|
58
|
+
name: bucketInput.name,
|
|
59
|
+
permissions: [],
|
|
60
|
+
fileSecurity: bucketInput.fileSecurity,
|
|
61
|
+
enabled: bucketInput.enabled,
|
|
62
|
+
maximumFileSize: bucketInput.maximumFileSize,
|
|
63
|
+
allowedFileExtensions: bucketInput.allowedFileExtensions,
|
|
64
|
+
compression: bucketInput.compression,
|
|
65
|
+
encryption: bucketInput.encryption,
|
|
66
|
+
antivirus: bucketInput.antivirus,
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
const cfgMgr = ConfigManager.getInstance();
|
|
70
|
+
const cfgPath = cfgMgr.getConfigPath();
|
|
71
|
+
if (cfgPath && /\.ya?ml$/i.test(cfgPath)) {
|
|
72
|
+
await writeYamlConfig(cfgPath, controller.config);
|
|
73
|
+
MessageFormatter.info(`Added bucket to config.yaml`, { prefix: 'Buckets' });
|
|
74
|
+
} else {
|
|
75
|
+
MessageFormatter.warning(`Config is not YAML; updated in-memory only. Please update your TypeScript config manually.`, { prefix: 'Buckets' });
|
|
76
|
+
}
|
|
77
|
+
} catch (e) {
|
|
78
|
+
MessageFormatter.error('Failed to create bucket', e instanceof Error ? e : new Error(String(e)), { prefix: 'Buckets' });
|
|
79
|
+
}
|
|
80
|
+
},
|
|
81
|
+
|
|
82
|
+
async deleteBuckets(cli: InteractiveCLI): Promise<void> {
|
|
83
|
+
const storage: Storage = (cli as any).controller!.storage!;
|
|
84
|
+
if (!storage) {
|
|
85
|
+
MessageFormatter.error("Storage client not initialized", undefined, { prefix: "Buckets" });
|
|
86
|
+
return;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
try {
|
|
90
|
+
const res = await listBuckets(storage);
|
|
91
|
+
const buckets: Models.Bucket[] = res.buckets || [];
|
|
92
|
+
if (buckets.length === 0) {
|
|
93
|
+
MessageFormatter.info('No buckets found', { prefix: 'Buckets' });
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
const { toDelete } = await inquirer.prompt([
|
|
98
|
+
{
|
|
99
|
+
type: 'checkbox',
|
|
100
|
+
name: 'toDelete',
|
|
101
|
+
message: chalk.red('Select buckets to delete:'),
|
|
102
|
+
choices: buckets.map((b) => ({ name: `${b.name} (${b.$id})`, value: b.$id })),
|
|
103
|
+
pageSize: 10,
|
|
104
|
+
}
|
|
105
|
+
]);
|
|
106
|
+
|
|
107
|
+
if (!toDelete || toDelete.length === 0) {
|
|
108
|
+
MessageFormatter.info('No buckets selected', { prefix: 'Buckets' });
|
|
109
|
+
return;
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
const { confirm } = await inquirer.prompt([
|
|
113
|
+
{ type: 'confirm', name: 'confirm', message: `Delete ${toDelete.length} bucket(s)?`, default: false }
|
|
114
|
+
]);
|
|
115
|
+
if (!confirm) return;
|
|
116
|
+
|
|
117
|
+
const controller = (cli as any).controller!;
|
|
118
|
+
for (const id of toDelete) {
|
|
119
|
+
try {
|
|
120
|
+
await deleteBucketApi(storage, id);
|
|
121
|
+
MessageFormatter.success(`Deleted bucket ${id}`, { prefix: 'Buckets' });
|
|
122
|
+
|
|
123
|
+
// Remove from in-memory config (global buckets)
|
|
124
|
+
if (Array.isArray(controller.config.buckets)) {
|
|
125
|
+
controller.config.buckets = controller.config.buckets.filter((b: any) => b.$id !== id);
|
|
126
|
+
}
|
|
127
|
+
// Clear database-linked bucket references if matching
|
|
128
|
+
if (Array.isArray(controller.config.databases)) {
|
|
129
|
+
controller.config.databases = controller.config.databases.map((db: any) => ({
|
|
130
|
+
...db,
|
|
131
|
+
bucket: db.bucket && db.bucket.$id === id ? undefined : db.bucket,
|
|
132
|
+
}));
|
|
133
|
+
}
|
|
134
|
+
} catch (e) {
|
|
135
|
+
MessageFormatter.error(`Failed to delete bucket ${id}`, e instanceof Error ? e : new Error(String(e)), { prefix: 'Buckets' });
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Persist YAML changes
|
|
140
|
+
const cfgMgr = ConfigManager.getInstance();
|
|
141
|
+
const cfgPath = cfgMgr.getConfigPath();
|
|
142
|
+
if (cfgPath && /\.ya?ml$/i.test(cfgPath)) {
|
|
143
|
+
await writeYamlConfig(cfgPath, controller.config);
|
|
144
|
+
MessageFormatter.info(`Updated config.yaml after deletion`, { prefix: 'Buckets' });
|
|
145
|
+
} else {
|
|
146
|
+
MessageFormatter.warning(`Config is not YAML; updated in-memory only. Please update your TypeScript config manually.`, { prefix: 'Buckets' });
|
|
147
|
+
}
|
|
148
|
+
} catch (e) {
|
|
149
|
+
MessageFormatter.error('Failed to list buckets', e instanceof Error ? e : new Error(String(e)), { prefix: 'Buckets' });
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
};
|