appwrite-utils-cli 1.8.3 → 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.
Files changed (51) hide show
  1. package/CHANGELOG.md +6 -1
  2. package/README.md +42 -13
  3. package/dist/adapters/TablesDBAdapter.js +1 -1
  4. package/dist/cli/commands/functionCommands.js +30 -3
  5. package/dist/cli/commands/schemaCommands.js +39 -4
  6. package/dist/cli/commands/storageCommands.d.ts +5 -0
  7. package/dist/cli/commands/storageCommands.js +143 -0
  8. package/dist/collections/attributes.js +7 -7
  9. package/dist/collections/methods.js +1 -1
  10. package/dist/collections/tableOperations.js +2 -2
  11. package/dist/interactiveCLI.d.ts +1 -0
  12. package/dist/interactiveCLI.js +30 -0
  13. package/dist/main.js +17 -0
  14. package/dist/migrations/appwriteToX.js +1 -1
  15. package/dist/migrations/yaml/generateImportSchemas.js +2 -2
  16. package/dist/setupCommands.js +6 -0
  17. package/dist/shared/attributeMapper.js +2 -2
  18. package/dist/shared/jsonSchemaGenerator.js +3 -1
  19. package/dist/shared/pydanticModelGenerator.d.ts +17 -0
  20. package/dist/shared/pydanticModelGenerator.js +615 -0
  21. package/dist/shared/schemaGenerator.d.ts +3 -2
  22. package/dist/shared/schemaGenerator.js +22 -9
  23. package/dist/storage/methods.js +50 -7
  24. package/dist/utils/configDiscovery.js +2 -3
  25. package/dist/utils/constantsGenerator.d.ts +20 -8
  26. package/dist/utils/constantsGenerator.js +37 -25
  27. package/dist/utils/projectConfig.js +1 -1
  28. package/dist/utils/yamlConverter.d.ts +2 -2
  29. package/dist/utils/yamlConverter.js +2 -2
  30. package/package.json +1 -1
  31. package/src/adapters/TablesDBAdapter.ts +1 -1
  32. package/src/cli/commands/functionCommands.ts +28 -3
  33. package/src/cli/commands/schemaCommands.ts +59 -22
  34. package/src/cli/commands/storageCommands.ts +152 -0
  35. package/src/collections/attributes.ts +7 -7
  36. package/src/collections/methods.ts +7 -7
  37. package/src/collections/tableOperations.ts +2 -2
  38. package/src/interactiveCLI.ts +42 -12
  39. package/src/main.ts +32 -9
  40. package/src/migrations/appwriteToX.ts +1 -1
  41. package/src/migrations/yaml/generateImportSchemas.ts +7 -7
  42. package/src/setupCommands.ts +6 -0
  43. package/src/shared/attributeMapper.ts +2 -2
  44. package/src/shared/jsonSchemaGenerator.ts +4 -2
  45. package/src/shared/pydanticModelGenerator.ts +618 -0
  46. package/src/shared/schemaGenerator.ts +38 -25
  47. package/src/storage/methods.ts +67 -23
  48. package/src/utils/configDiscovery.ts +40 -41
  49. package/src/utils/constantsGenerator.ts +43 -26
  50. package/src/utils/projectConfig.ts +11 -11
  51. package/src/utils/yamlConverter.ts +40 -40
package/CHANGELOG.md CHANGED
@@ -11,4 +11,9 @@ All notable changes to this project will be documented in this file.
11
11
  - Multi-database targeting supported via `databaseIds` (alongside `databaseId`) on table/collection definitions.
12
12
  - Per-function configuration discovery via `.fnconfig.yaml` anywhere in the repository. Relative `dirPath` resolves from the file’s directory; `~` expands to homedir.
13
13
  - TablesDB/legacy detection is fetch-based; no additional SDK packages required.
14
-
14
+ - New: Python (Pydantic) model generation with modern typing (PEP 604 unions) and Appwrite alias mapping. Generates:
15
+ - `base.py` (always overwritten) with aliased $id/$createdAt/$updatedAt/$permissions and helpers
16
+ - One model per collection/table extending `BaseAppwriteModel`
17
+ - Output directory selectable during interactive flow (absolute paths respected)
18
+ - Constants generator now supports selecting which categories to include (databases, collections/tables, buckets, functions).
19
+ - Bucket management: interactive create/delete commands with YAML persistence when active; selective push now diffs and updates bucket settings (name, permissions, security, limits).
package/README.md CHANGED
@@ -34,13 +34,13 @@ Highlights:
34
34
  - **File Operations**: Complete file handling with URL downloads, local file search, and afterImportActions
35
35
  - **Backup Management**: Comprehensive backup system with progress tracking and detailed reporting
36
36
 
37
- ### Development Tools
38
- - **Database Migrations**: Full migration control with progress tracking and operation summaries
39
- - **Schema Generation**: Generate TypeScript and JSON schemas from database configurations
40
- - **Constants Generation**: Generate cross-language constants files (TypeScript, Python, PHP, Dart, JSON, Env) for database, collection, bucket, and function IDs
41
- - **Data Transfer**: Transfer data between databases, collections, and instances with real-time progress
42
- - **Configuration Sync**: Bidirectional synchronization between local YAML configs and Appwrite projects
43
- - **Function Management**: Deploy and manage Appwrite Functions with specification updates
37
+ ### Development Tools
38
+ - **Database Migrations**: Full migration control with progress tracking and operation summaries
39
+ - **Schema Generation**: Generate TypeScript (Zod), JSON, and Python (Pydantic) schemas/models from database configurations
40
+ - **Constants Generation**: Generate cross-language constants files (TypeScript, Python, PHP, Dart, JSON, Env) for database, collection, bucket, and function IDs
41
+ - **Data Transfer**: Transfer data between databases, collections, and instances with real-time progress
42
+ - **Configuration Sync**: Bidirectional synchronization between local YAML configs and Appwrite projects
43
+ - **Function Management**: Deploy and manage Appwrite Functions with specification updates
44
44
 
45
45
  ## Installation
46
46
 
@@ -213,7 +213,7 @@ logging:
213
213
 
214
214
  After installation, you can access the tool directly from your command line using the provided commands.
215
215
 
216
- ### Interactive Mode
216
+ ### Interactive Mode
217
217
 
218
218
  Run the CLI in interactive mode with enhanced visual feedback:
219
219
 
@@ -221,11 +221,28 @@ Run the CLI in interactive mode with enhanced visual feedback:
221
221
  npx --package=appwrite-utils-cli@latest appwrite-migrate --it
222
222
  ```
223
223
 
224
- This provides a professional guided experience with:
225
- - Rich visual feedback and progress tracking
226
- - Smart confirmation dialogs for destructive operations
227
- - Operation summaries with detailed statistics
228
- - Real-time progress bars with ETA calculations
224
+ This provides a professional guided experience with:
225
+ - Rich visual feedback and progress tracking
226
+ - Smart confirmation dialogs for destructive operations
227
+ - Operation summaries with detailed statistics
228
+ - Real-time progress bars with ETA calculations
229
+
230
+ ### Generate Schemas (Zod / JSON / Pydantic)
231
+
232
+ Interactive schema generation lets you pick the format and output directory:
233
+
234
+ ```bash
235
+ npx appwrite-utils-cli appwrite-migrate --it
236
+ # Choose: Generate schemas
237
+ # Select: TypeScript (Zod), JSON, Python (Pydantic), or All
238
+ # Enter output directory (absolute path respected)
239
+ ```
240
+
241
+ - Pydantic models use modern typing (str | None, list[str]) and alias mapping for Appwrite system fields:
242
+ - Base model (written as `base.py`) defines aliases for `$id`, `$createdAt`, `$updatedAt`, `$permissions`, `$databaseId`, `$collectionId`, `$sequence`.
243
+ - Each collection/table generates a model extending `BaseAppwriteModel`.
244
+ - Serialize with aliases via `model_dump(by_alias=True)` and validate from Appwrite docs via `model_validate(...)`.
245
+ - Output directory is selectable; files are written directly to your chosen path (no extra subfolder).
229
246
 
230
247
  ### Push (manual selection)
231
248
 
@@ -1102,3 +1119,15 @@ Rules:
1102
1119
  - `.fnconfig.yaml` definitions merge with central `.appwrite/config.yaml` functions; if the same `$id` exists in both, `.fnconfig.yaml` overrides
1103
1120
 
1104
1121
  Deployment uses the merged function set and resolves paths according to these rules.
1122
+ ### Generate Constants
1123
+
1124
+ Select which languages and which categories to generate (databases, collections/tables, buckets, functions):
1125
+
1126
+ ```bash
1127
+ npx appwrite-utils-cli appwrite-migrate --it
1128
+ # Choose: Generate cross-language constants
1129
+ # Select languages (TS/JS/Python/PHP/Dart/JSON/Env)
1130
+ # Select categories to include
1131
+ ```
1132
+
1133
+ Constants are written to a configurable output directory under `.appwrite/` by default.
@@ -192,7 +192,7 @@ export class TablesDBAdapter extends BaseAdapter {
192
192
  const type = (params.type || "").toLowerCase();
193
193
  const required = params.required ?? false;
194
194
  const array = params.array ?? false;
195
- const encrypt = params.encrypt ?? params.encrypted ?? false;
195
+ const encrypt = params.encrypt ?? false;
196
196
  const normalizedDefault = params.default === null || params.default === undefined
197
197
  ? undefined
198
198
  : params.default;
@@ -111,11 +111,38 @@ export const functionCommands = {
111
111
  MessageFormatter.error("Failed to initialize controller or load config", undefined, { prefix: "Functions" });
112
112
  return;
113
113
  }
114
- // Discover local .fnconfig.yaml functions and merge into controller config
114
+ // Offer choice of function config sources: central YAML, .fnconfig.yaml, or both
115
+ let sourceChoice = 'both';
116
+ try {
117
+ const answer = await inquirer.prompt([
118
+ {
119
+ type: 'list',
120
+ name: 'source',
121
+ message: 'Select function config source:',
122
+ choices: [
123
+ { name: 'config.yaml functions (central)', value: 'central' },
124
+ { name: '.fnconfig.yaml (discovered per-function)', value: 'fnconfig' },
125
+ { name: 'Both (merge; .fnconfig overrides)', value: 'both' },
126
+ ],
127
+ default: 'both'
128
+ }
129
+ ]);
130
+ sourceChoice = answer.source;
131
+ }
132
+ catch { }
115
133
  try {
116
134
  const discovered = discoverFnConfigs(cli.currentDir);
117
- const merged = mergeDiscoveredFunctions(cli.controller.config.functions || [], discovered);
118
- cli.controller.config.functions = merged;
135
+ const central = cli.controller.config.functions || [];
136
+ if (sourceChoice === 'central') {
137
+ cli.controller.config.functions = central;
138
+ }
139
+ else if (sourceChoice === 'fnconfig') {
140
+ cli.controller.config.functions = discovered;
141
+ }
142
+ else {
143
+ const merged = mergeDiscoveredFunctions(central, discovered);
144
+ cli.controller.config.functions = merged;
145
+ }
119
146
  }
120
147
  catch { }
121
148
  const functions = await cli.selectFunctions("Select function(s) to deploy:", true, true);
@@ -17,9 +17,11 @@ export const schemaCommands = {
17
17
  choices: [
18
18
  { name: "TypeScript (Zod) schemas", value: "zod" },
19
19
  { name: "JSON schemas", value: "json" },
20
- { name: "Both TypeScript and JSON schemas", value: "both" },
20
+ { name: "Python (Pydantic) models", value: "pydantic" },
21
+ { name: "TypeScript + JSON", value: "both" },
22
+ { name: "All (Zod, JSON, Pydantic)", value: "all" },
21
23
  ],
22
- default: "both",
24
+ default: "all",
23
25
  },
24
26
  ]);
25
27
  // Get the config folder path (where the config file is located)
@@ -28,9 +30,21 @@ export const schemaCommands = {
28
30
  MessageFormatter.error("Failed to get config folder path", undefined, { prefix: "Schemas" });
29
31
  return;
30
32
  }
33
+ // Prompt for schema output directory (optional override)
34
+ const defaultSchemaOut = path.join(configFolderPath, cli.controller.config?.schemaConfig?.outputDirectory || 'schemas');
35
+ const { schemaOutDir } = await inquirer.prompt([
36
+ {
37
+ type: 'input',
38
+ name: 'schemaOutDir',
39
+ message: 'Output directory for schemas:',
40
+ default: defaultSchemaOut,
41
+ validate: (input) => input && input.trim().length > 0 ? true : 'Please provide an output directory',
42
+ }
43
+ ]);
31
44
  // Create SchemaGenerator with the correct base path and generate schemas
32
45
  const schemaGenerator = new SchemaGenerator(cli.controller.config, configFolderPath);
33
- schemaGenerator.generateSchemas({ format: schemaType, verbose: true });
46
+ const outDirRel = path.isAbsolute(schemaOutDir) ? schemaOutDir : path.relative(configFolderPath, schemaOutDir);
47
+ await schemaGenerator.generateSchemas({ format: schemaType, verbose: true, outputDir: outDirRel });
34
48
  MessageFormatter.success("Schema generation completed", { prefix: "Schemas" });
35
49
  },
36
50
  async generateConstants(cli) {
@@ -62,6 +76,21 @@ export const schemaCommands = {
62
76
  },
63
77
  },
64
78
  ]);
79
+ // Prompt for which constants to include
80
+ const { includeWhat } = await inquirer.prompt([
81
+ {
82
+ type: 'checkbox',
83
+ name: 'includeWhat',
84
+ message: 'Select which constants to generate:',
85
+ choices: [
86
+ { name: 'Databases', value: 'databases', checked: true },
87
+ { name: 'Collections/Tables', value: 'collections', checked: true },
88
+ { name: 'Buckets', value: 'buckets', checked: true },
89
+ { name: 'Functions', value: 'functions', checked: true },
90
+ ],
91
+ validate: (input) => input.length > 0 ? true : 'Select at least one category',
92
+ }
93
+ ]);
65
94
  // Determine default output directory based on config location
66
95
  const configPath = cli.controller.getAppwriteFolderPath();
67
96
  const defaultOutputDir = configPath
@@ -85,8 +114,14 @@ export const schemaCommands = {
85
114
  try {
86
115
  const { ConstantsGenerator } = await import("../../utils/constantsGenerator.js");
87
116
  const generator = new ConstantsGenerator(cli.controller.config);
117
+ const include = {
118
+ databases: includeWhat.includes('databases'),
119
+ collections: includeWhat.includes('collections'),
120
+ buckets: includeWhat.includes('buckets'),
121
+ functions: includeWhat.includes('functions'),
122
+ };
88
123
  MessageFormatter.info(`Generating constants for: ${languages.join(", ")}`, { prefix: "Constants" });
89
- await generator.generateFiles(languages, outputDir);
124
+ await generator.generateFiles(languages, outputDir, include);
90
125
  MessageFormatter.success(`Constants generated in ${outputDir}`, { prefix: "Constants" });
91
126
  }
92
127
  catch (error) {
@@ -0,0 +1,5 @@
1
+ import type { InteractiveCLI } from "../../interactiveCLI.js";
2
+ export declare const storageCommands: {
3
+ createBucket(cli: InteractiveCLI): Promise<void>;
4
+ deleteBuckets(cli: InteractiveCLI): Promise<void>;
5
+ };
@@ -0,0 +1,143 @@
1
+ import inquirer from "inquirer";
2
+ import chalk from "chalk";
3
+ import { Storage, Permission, Role, Compression } from "node-appwrite";
4
+ import { MessageFormatter } from "../../shared/messageFormatter.js";
5
+ import { listBuckets, createBucket as createBucketApi, deleteBucket as deleteBucketApi } from "../../storage/methods.js";
6
+ import { writeYamlConfig } from "../../config/yamlConfig.js";
7
+ import { ConfigManager } from "../../config/ConfigManager.js";
8
+ export const storageCommands = {
9
+ async createBucket(cli) {
10
+ const storage = cli.controller.storage;
11
+ if (!storage) {
12
+ MessageFormatter.error("Storage client not initialized", undefined, { prefix: "Buckets" });
13
+ return;
14
+ }
15
+ const answers = await inquirer.prompt([
16
+ { type: 'input', name: 'name', message: 'Bucket name:', validate: (v) => v?.trim()?.length > 0 || 'Required' },
17
+ { type: 'input', name: 'id', message: 'Bucket ID (optional):' },
18
+ { type: 'confirm', name: 'publicRead', message: 'Public read access?', default: false },
19
+ { type: 'confirm', name: 'fileSecurity', message: 'Enable file-level security?', default: false },
20
+ { type: 'confirm', name: 'enabled', message: 'Enable bucket?', default: true },
21
+ { type: 'number', name: 'maximumFileSize', message: 'Max file size (bytes, optional):', default: undefined },
22
+ { type: 'input', name: 'allowedFileExtensions', message: 'Allowed extensions (comma separated, optional):', default: '' },
23
+ { type: 'list', name: 'compression', message: 'Compression:', choices: ['none', 'gzip', 'zstd'], default: 'none' },
24
+ { type: 'confirm', name: 'encryption', message: 'Enable encryption?', default: false },
25
+ { type: 'confirm', name: 'antivirus', message: 'Enable antivirus?', default: false },
26
+ ]);
27
+ const permissions = [];
28
+ if (answers.publicRead)
29
+ permissions.push(Permission.read(Role.any()));
30
+ const bucketInput = {
31
+ name: answers.name,
32
+ $permissions: permissions,
33
+ fileSecurity: !!answers.fileSecurity,
34
+ enabled: !!answers.enabled,
35
+ maximumFileSize: answers.maximumFileSize || undefined,
36
+ allowedFileExtensions: String(answers.allowedFileExtensions || '')
37
+ .split(',')
38
+ .map((s) => s.trim())
39
+ .filter(Boolean),
40
+ compression: answers.compression,
41
+ encryption: !!answers.encryption,
42
+ antivirus: !!answers.antivirus,
43
+ };
44
+ try {
45
+ const created = await createBucketApi(storage, bucketInput, answers.id || undefined);
46
+ MessageFormatter.success(`Bucket '${answers.name}' created`, { prefix: 'Buckets' });
47
+ // Update in-memory config and persist to YAML as a global bucket entry
48
+ const controller = cli.controller;
49
+ controller.config.buckets = controller.config.buckets || [];
50
+ controller.config.buckets.push({
51
+ $id: created.$id || answers.id || bucketInput.name,
52
+ name: bucketInput.name,
53
+ permissions: [],
54
+ fileSecurity: bucketInput.fileSecurity,
55
+ enabled: bucketInput.enabled,
56
+ maximumFileSize: bucketInput.maximumFileSize,
57
+ allowedFileExtensions: bucketInput.allowedFileExtensions,
58
+ compression: bucketInput.compression,
59
+ encryption: bucketInput.encryption,
60
+ antivirus: bucketInput.antivirus,
61
+ });
62
+ const cfgMgr = ConfigManager.getInstance();
63
+ const cfgPath = cfgMgr.getConfigPath();
64
+ if (cfgPath && /\.ya?ml$/i.test(cfgPath)) {
65
+ await writeYamlConfig(cfgPath, controller.config);
66
+ MessageFormatter.info(`Added bucket to config.yaml`, { prefix: 'Buckets' });
67
+ }
68
+ else {
69
+ MessageFormatter.warning(`Config is not YAML; updated in-memory only. Please update your TypeScript config manually.`, { prefix: 'Buckets' });
70
+ }
71
+ }
72
+ catch (e) {
73
+ MessageFormatter.error('Failed to create bucket', e instanceof Error ? e : new Error(String(e)), { prefix: 'Buckets' });
74
+ }
75
+ },
76
+ async deleteBuckets(cli) {
77
+ const storage = cli.controller.storage;
78
+ if (!storage) {
79
+ MessageFormatter.error("Storage client not initialized", undefined, { prefix: "Buckets" });
80
+ return;
81
+ }
82
+ try {
83
+ const res = await listBuckets(storage);
84
+ const buckets = res.buckets || [];
85
+ if (buckets.length === 0) {
86
+ MessageFormatter.info('No buckets found', { prefix: 'Buckets' });
87
+ return;
88
+ }
89
+ const { toDelete } = await inquirer.prompt([
90
+ {
91
+ type: 'checkbox',
92
+ name: 'toDelete',
93
+ message: chalk.red('Select buckets to delete:'),
94
+ choices: buckets.map((b) => ({ name: `${b.name} (${b.$id})`, value: b.$id })),
95
+ pageSize: 10,
96
+ }
97
+ ]);
98
+ if (!toDelete || toDelete.length === 0) {
99
+ MessageFormatter.info('No buckets selected', { prefix: 'Buckets' });
100
+ return;
101
+ }
102
+ const { confirm } = await inquirer.prompt([
103
+ { type: 'confirm', name: 'confirm', message: `Delete ${toDelete.length} bucket(s)?`, default: false }
104
+ ]);
105
+ if (!confirm)
106
+ return;
107
+ const controller = cli.controller;
108
+ for (const id of toDelete) {
109
+ try {
110
+ await deleteBucketApi(storage, id);
111
+ MessageFormatter.success(`Deleted bucket ${id}`, { prefix: 'Buckets' });
112
+ // Remove from in-memory config (global buckets)
113
+ if (Array.isArray(controller.config.buckets)) {
114
+ controller.config.buckets = controller.config.buckets.filter((b) => b.$id !== id);
115
+ }
116
+ // Clear database-linked bucket references if matching
117
+ if (Array.isArray(controller.config.databases)) {
118
+ controller.config.databases = controller.config.databases.map((db) => ({
119
+ ...db,
120
+ bucket: db.bucket && db.bucket.$id === id ? undefined : db.bucket,
121
+ }));
122
+ }
123
+ }
124
+ catch (e) {
125
+ MessageFormatter.error(`Failed to delete bucket ${id}`, e instanceof Error ? e : new Error(String(e)), { prefix: 'Buckets' });
126
+ }
127
+ }
128
+ // Persist YAML changes
129
+ const cfgMgr = ConfigManager.getInstance();
130
+ const cfgPath = cfgMgr.getConfigPath();
131
+ if (cfgPath && /\.ya?ml$/i.test(cfgPath)) {
132
+ await writeYamlConfig(cfgPath, controller.config);
133
+ MessageFormatter.info(`Updated config.yaml after deletion`, { prefix: 'Buckets' });
134
+ }
135
+ else {
136
+ MessageFormatter.warning(`Config is not YAML; updated in-memory only. Please update your TypeScript config manually.`, { prefix: 'Buckets' });
137
+ }
138
+ }
139
+ catch (e) {
140
+ MessageFormatter.error('Failed to list buckets', e instanceof Error ? e : new Error(String(e)), { prefix: 'Buckets' });
141
+ }
142
+ }
143
+ };
@@ -266,8 +266,8 @@ const createAttributeViaAdapter = async (db, dbId, collectionId, attribute) => {
266
266
  ...(attribute.size && { size: attribute.size }),
267
267
  ...(attribute.xdefault !== undefined &&
268
268
  !attribute.required && { default: attribute.xdefault }),
269
- ...(attribute.encrypted && {
270
- encrypt: attribute.encrypted,
269
+ ...(attribute.encrypt && {
270
+ encrypt: attribute.encrypt,
271
271
  }),
272
272
  ...(attribute.min !== undefined && {
273
273
  min: attribute.min,
@@ -334,7 +334,7 @@ const updateAttributeViaAdapter = async (db, dbId, collectionId, attribute) => {
334
334
  size: attribute.size,
335
335
  min: attribute.min,
336
336
  max: attribute.max,
337
- encrypt: attribute.encrypted ?? attribute.encrypt,
337
+ encrypt: attribute.encrypt,
338
338
  elements: attribute.elements,
339
339
  relatedCollection: attribute.relatedCollection,
340
340
  relationType: attribute.relationType,
@@ -375,13 +375,13 @@ const createLegacyAttribute = async (db, dbId, collectionId, attribute) => {
375
375
  ? attribute.xdefault
376
376
  : undefined,
377
377
  array: attribute.array || false,
378
- encrypted: attribute.encrypted,
378
+ encrypt: attribute.encrypt,
379
379
  };
380
380
  logger.debug(`Creating string attribute '${attribute.key}'`, {
381
381
  ...stringParams,
382
382
  operation: "createLegacyAttribute",
383
383
  });
384
- await db.createStringAttribute(dbId, collectionId, attribute.key, stringParams.size, stringParams.required, stringParams.defaultValue, stringParams.array, stringParams.encrypted);
384
+ await db.createStringAttribute(dbId, collectionId, attribute.key, stringParams.size, stringParams.required, stringParams.defaultValue, stringParams.array, stringParams.encrypt);
385
385
  break;
386
386
  case "integer":
387
387
  const integerParams = {
@@ -686,7 +686,7 @@ const getComparableFields = (type) => {
686
686
  const baseFields = ["key", "type", "array", "required", "xdefault"];
687
687
  switch (type) {
688
688
  case "string":
689
- return [...baseFields, "size", "encrypted"];
689
+ return [...baseFields, "size", "encrypt"];
690
690
  case "integer":
691
691
  case "double":
692
692
  case "float":
@@ -714,7 +714,7 @@ const getComparableFields = (type) => {
714
714
  "key",
715
715
  "type",
716
716
  "array",
717
- "encrypted",
717
+ "encrypt",
718
718
  "required",
719
719
  "size",
720
720
  "min",
@@ -118,7 +118,7 @@ export const fetchAndCacheCollectionByName = async (db, dbId, collectionName) =>
118
118
  };
119
119
  export const generateSchemas = async (config, appwriteFolderPath) => {
120
120
  const schemaGenerator = new SchemaGenerator(config, appwriteFolderPath);
121
- schemaGenerator.generateSchemas();
121
+ await schemaGenerator.generateSchemas();
122
122
  };
123
123
  export const createOrUpdateCollections = async (database, databaseId, config, deletedCollections, selectedCollections = []) => {
124
124
  // Clear processing state at the start of a new operation
@@ -62,7 +62,7 @@ export function normalizeAttributeToComparable(attr) {
62
62
  };
63
63
  if (t === 'string') {
64
64
  base.size = attr.size ?? 255;
65
- base.encrypt = !!(attr.encrypted ?? attr.encrypt);
65
+ base.encrypt = !!(attr.encrypt);
66
66
  }
67
67
  if (t === 'integer' || t === 'float' || t === 'double') {
68
68
  const min = toNumber(attr.min);
@@ -220,7 +220,7 @@ function compareColumnProperties(oldColumn, newAttribute, columnType) {
220
220
  if (prop === 'default')
221
221
  return na.xdefault;
222
222
  if (prop === 'encrypt')
223
- return na.encrypted ?? na.encrypt;
223
+ return na.encrypt;
224
224
  return na[prop];
225
225
  };
226
226
  const getOldVal = (prop) => {
@@ -5,6 +5,7 @@ export declare class InteractiveCLI {
5
5
  constructor(currentDir: string);
6
6
  run(): Promise<void>;
7
7
  private initControllerIfNeeded;
8
+ private manageBuckets;
8
9
  private selectDatabases;
9
10
  private selectCollections;
10
11
  /**
@@ -19,6 +19,7 @@ import { findYamlConfig } from "./config/yamlConfig.js";
19
19
  import { configCommands } from "./cli/commands/configCommands.js";
20
20
  import { databaseCommands } from "./cli/commands/databaseCommands.js";
21
21
  import { functionCommands } from "./cli/commands/functionCommands.js";
22
+ import { storageCommands } from "./cli/commands/storageCommands.js";
22
23
  import { transferCommands } from "./cli/commands/transferCommands.js";
23
24
  import { schemaCommands } from "./cli/commands/schemaCommands.js";
24
25
  var CHOICES;
@@ -44,6 +45,7 @@ var CHOICES;
44
45
  CHOICES["IMPORT_DATA"] = "\uD83D\uDCE5 Import data";
45
46
  CHOICES["RELOAD_CONFIG"] = "\uD83D\uDD04 Reload configuration files";
46
47
  CHOICES["UPDATE_FUNCTION_SPEC"] = "\u2699\uFE0F Update function specifications";
48
+ CHOICES["MANAGE_BUCKETS"] = "\uD83E\uDEA3 Manage storage buckets";
47
49
  CHOICES["EXIT"] = "\uD83D\uDC4B Exit";
48
50
  })(CHOICES || (CHOICES = {}));
49
51
  export class InteractiveCLI {
@@ -152,6 +154,9 @@ export class InteractiveCLI {
152
154
  await this.initControllerIfNeeded();
153
155
  await functionCommands.updateFunctionSpec(this);
154
156
  break;
157
+ case CHOICES.MANAGE_BUCKETS:
158
+ await this.manageBuckets();
159
+ break;
155
160
  case CHOICES.EXIT:
156
161
  MessageFormatter.success("Goodbye!");
157
162
  process.exit(0);
@@ -187,6 +192,31 @@ export class InteractiveCLI {
187
192
  // If no directConfig provided, keep existing controller
188
193
  }
189
194
  }
195
+ async manageBuckets() {
196
+ await this.initControllerIfNeeded();
197
+ while (true) {
198
+ const { action } = await inquirer.prompt([
199
+ {
200
+ type: 'list',
201
+ name: 'action',
202
+ message: chalk.blue('Bucket management'),
203
+ choices: [
204
+ { name: 'Create bucket', value: 'create' },
205
+ { name: 'Delete buckets', value: 'delete' },
206
+ { name: 'Back', value: 'back' },
207
+ ],
208
+ },
209
+ ]);
210
+ if (action === 'back')
211
+ break;
212
+ if (action === 'create') {
213
+ await storageCommands.createBucket(this);
214
+ }
215
+ else if (action === 'delete') {
216
+ await storageCommands.deleteBuckets(this);
217
+ }
218
+ }
219
+ }
190
220
  async selectDatabases(databases, message, multiSelect = true) {
191
221
  await this.initControllerIfNeeded();
192
222
  const configDatabases = this.getLocalDatabases();
package/dist/main.js CHANGED
@@ -407,6 +407,23 @@ const argv = yargs(hideBin(process.argv))
407
407
  type: "string",
408
408
  description: "Output directory for generated constants files (default: config-folder/constants)",
409
409
  default: "auto",
410
+ })
411
+ .option("constantsInclude", {
412
+ type: "string",
413
+ description: "Comma-separated categories to include: databases,collections,buckets,functions",
414
+ })
415
+ .option("generateSchemas", {
416
+ type: "boolean",
417
+ description: "Generate schemas/models without interactive prompts",
418
+ })
419
+ .option("schemaFormat", {
420
+ type: "string",
421
+ choices: ["zod", "json", "pydantic", "both", "all"],
422
+ description: "Schema format: zod, json, pydantic, both (zod+json), or all",
423
+ })
424
+ .option("schemaOutDir", {
425
+ type: "string",
426
+ description: "Output directory for generated schemas (absolute path respected)",
410
427
  })
411
428
  .option("migrateCollectionsToTables", {
412
429
  alias: ["migrate-collections"],
@@ -468,7 +468,7 @@ export class AppwriteToX {
468
468
  await generator.updateConfig(configWithApiContext, false);
469
469
  }
470
470
  MessageFormatter.info("Generating Zod schemas from synced collections...", { prefix: "Migration" });
471
- generator.generateSchemas();
471
+ await generator.generateSchemas();
472
472
  MessageFormatter.success("Sync-from-Appwrite process completed successfully", { prefix: "Migration" });
473
473
  }
474
474
  catch (error) {
@@ -551,8 +551,8 @@ export function generateTableSchema() {
551
551
  }
552
552
  // Add column definition (similar to attribute but with table terminology)
553
553
  tableSchema.$defs.column = JSON.parse(JSON.stringify(tableSchema.$defs.attribute));
554
- // Add encrypted property (table-specific feature)
555
- tableSchema.$defs.column.properties.encrypted = {
554
+ // Add encrypt property (table-specific feature)
555
+ tableSchema.$defs.column.properties.encrypt = {
556
556
  "type": "boolean",
557
557
  "description": "Whether the column should be encrypted",
558
558
  "default": false
@@ -259,6 +259,12 @@ export function createYamlValidationSchema(yamlSchemaFolder, useTables) {
259
259
  "default": false,
260
260
  "description": `Whether the ${useTables ? 'column' : 'attribute'} is an array`
261
261
  },
262
+ // Encryption flag for string types
263
+ "encrypt": {
264
+ "type": "boolean",
265
+ "default": false,
266
+ "description": `Enable encryption for string ${useTables ? 'columns' : 'attributes'}`
267
+ },
262
268
  ...(useTables ? {
263
269
  "unique": {
264
270
  "type": "boolean",
@@ -14,7 +14,7 @@ export function mapToCreateAttributeParams(attr, base) {
14
14
  const required = !!attr.required;
15
15
  const array = !!attr.array;
16
16
  const xdefault = attr.xdefault;
17
- const encrypt = attr.encrypted ?? attr.encrypt;
17
+ const encrypt = attr.encrypt;
18
18
  // Numeric helpers
19
19
  const rawMin = ensureNumber(attr.min);
20
20
  const rawMax = ensureNumber(attr.max);
@@ -160,7 +160,7 @@ export function mapToUpdateAttributeParams(attr, base) {
160
160
  setIfDefined("array", attr.array);
161
161
  // encrypt only applies to string types
162
162
  if (type === "string")
163
- setIfDefined("encrypt", attr.encrypted ?? attr.encrypt);
163
+ setIfDefined("encrypt", attr.encrypt);
164
164
  // Numeric normalization
165
165
  const toNum = (n) => (n === null || n === undefined ? undefined : (Number(n)));
166
166
  let min = toNum(attr.min);
@@ -177,7 +177,9 @@ export class JsonSchemaGenerator {
177
177
  return;
178
178
  }
179
179
  // Create JSON schemas directory using provided outputDirectory
180
- const jsonSchemasPath = path.join(this.appwriteFolderPath, outputDirectory);
180
+ const jsonSchemasPath = path.isAbsolute(outputDirectory)
181
+ ? outputDirectory
182
+ : path.join(this.appwriteFolderPath, outputDirectory);
181
183
  if (!fs.existsSync(jsonSchemasPath)) {
182
184
  fs.mkdirSync(jsonSchemasPath, { recursive: true });
183
185
  }
@@ -0,0 +1,17 @@
1
+ import type { AppwriteConfig } from 'appwrite-utils';
2
+ export declare class PydanticModelGenerator {
3
+ private config;
4
+ private appwriteFolderPath;
5
+ constructor(config: AppwriteConfig, appwriteFolderPath: string);
6
+ generatePydanticModels(options: {
7
+ baseOutputDirectory: string;
8
+ verbose?: boolean;
9
+ }): void;
10
+ private writeBase;
11
+ private generateModel;
12
+ private composeHeader;
13
+ private defaultInitializer;
14
+ private mapAttributeToPythonType;
15
+ private toSnake;
16
+ private toPascal;
17
+ }