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.
- 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/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
|
|
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 ??
|
|
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
|
-
//
|
|
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
|
|
118
|
-
|
|
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: "
|
|
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: "
|
|
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
|
-
|
|
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,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.
|
|
270
|
-
encrypt: attribute.
|
|
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.
|
|
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
|
-
|
|
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.
|
|
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", "
|
|
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
|
-
"
|
|
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.
|
|
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.
|
|
223
|
+
return na.encrypt;
|
|
224
224
|
return na[prop];
|
|
225
225
|
};
|
|
226
226
|
const getOldVal = (prop) => {
|
package/dist/interactiveCLI.d.ts
CHANGED
package/dist/interactiveCLI.js
CHANGED
|
@@ -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
|
|
555
|
-
tableSchema.$defs.column.properties.
|
|
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
|
package/dist/setupCommands.js
CHANGED
|
@@ -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.
|
|
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.
|
|
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.
|
|
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
|
+
}
|