appwrite-utils-cli 1.0.1 → 1.0.3
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/README.md +5 -0
- package/dist/interactiveCLI.js +8 -5
- package/dist/utils/configMigration.js +177 -71
- package/dist/utils/loadConfigs.js +3 -1
- package/package.json +1 -1
- package/src/interactiveCLI.ts +8 -5
- package/src/utils/configMigration.ts +223 -86
- package/src/utils/loadConfigs.ts +3 -1
package/README.md
CHANGED
@@ -375,6 +375,11 @@ This updated CLI ensures that developers have robust tools at their fingertips t
|
|
375
375
|
- **Better error messages**: Clear validation and error reporting
|
376
376
|
- **Type safety**: Full TypeScript support for all new features
|
377
377
|
|
378
|
+
### Changelog
|
379
|
+
|
380
|
+
- 1.0.3: Fixed appwriteConfig detection for `--it` so it detects when you can migrate your config
|
381
|
+
- 1.0.2: Fixed migrations, sorry about that!
|
382
|
+
|
378
383
|
**Migration Note**: While fully backward compatible, we recommend migrating to YAML configuration for the best experience. Use `--setup` to generate new YAML configurations.
|
379
384
|
|
380
385
|
- 0.10.86: Fixed `selectCollections` not always filtering by `databaseId`
|
package/dist/interactiveCLI.js
CHANGED
@@ -1393,11 +1393,14 @@ export class InteractiveCLI {
|
|
1393
1393
|
}
|
1394
1394
|
// Then check for TypeScript config
|
1395
1395
|
const configPath = findAppwriteConfig(this.currentDir);
|
1396
|
-
if (configPath
|
1397
|
-
|
1398
|
-
|
1399
|
-
|
1400
|
-
|
1396
|
+
if (configPath) {
|
1397
|
+
const tsConfigPath = join(configPath, 'appwriteConfig.ts');
|
1398
|
+
if (fs.existsSync(tsConfigPath)) {
|
1399
|
+
this.isUsingTypeScriptConfig = true;
|
1400
|
+
MessageFormatter.info("TypeScript configuration detected", { prefix: "Config" });
|
1401
|
+
MessageFormatter.info("Consider migrating to YAML for better organization", { prefix: "Config" });
|
1402
|
+
return;
|
1403
|
+
}
|
1401
1404
|
}
|
1402
1405
|
// No config found
|
1403
1406
|
this.isUsingTypeScriptConfig = false;
|
@@ -47,7 +47,7 @@ async function findAppwriteConfigFiles(dir) {
|
|
47
47
|
}
|
48
48
|
async function migrateConfigFile(configFilePath, workingDir) {
|
49
49
|
const configDir = path.dirname(configFilePath);
|
50
|
-
const appwriteDir = path.join(configDir, '.appwrite');
|
50
|
+
const appwriteDir = path.join(path.dirname(configDir), '.appwrite');
|
51
51
|
MessageFormatter.info(`Migrating ${path.relative(workingDir, configFilePath)}`, { prefix: "Migration" });
|
52
52
|
// Check if .appwrite directory already exists
|
53
53
|
if (existsSync(appwriteDir)) {
|
@@ -57,9 +57,8 @@ async function migrateConfigFile(configFilePath, workingDir) {
|
|
57
57
|
return;
|
58
58
|
}
|
59
59
|
}
|
60
|
-
//
|
61
|
-
const
|
62
|
-
const config = await parseTypeScriptConfig(configContent);
|
60
|
+
// Load and parse the TypeScript config
|
61
|
+
const config = await parseTypeScriptConfig(configFilePath);
|
63
62
|
// Create .appwrite directory
|
64
63
|
await fs.mkdir(appwriteDir, { recursive: true });
|
65
64
|
// Convert config to YAML and save
|
@@ -70,87 +69,194 @@ async function migrateConfigFile(configFilePath, workingDir) {
|
|
70
69
|
noRefs: true
|
71
70
|
});
|
72
71
|
await fs.writeFile(path.join(appwriteDir, 'appwriteConfig.yaml'), yamlContent);
|
73
|
-
//
|
74
|
-
const
|
75
|
-
for (const
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
await fs.
|
80
|
-
MessageFormatter.info(`
|
72
|
+
// Copy all directories except collections and schemas (we handle collections separately, skip schemas entirely)
|
73
|
+
const entries = await fs.readdir(configDir, { withFileTypes: true });
|
74
|
+
for (const entry of entries) {
|
75
|
+
if (entry.isDirectory() && entry.name !== 'collections' && entry.name !== 'schemas') {
|
76
|
+
const sourcePath = path.join(configDir, entry.name);
|
77
|
+
const targetPath = path.join(appwriteDir, entry.name);
|
78
|
+
await fs.cp(sourcePath, targetPath, { recursive: true });
|
79
|
+
MessageFormatter.info(`Copied ${entry.name}/ to .appwrite/${entry.name}/`, { prefix: "Migration" });
|
81
80
|
}
|
82
81
|
}
|
83
|
-
//
|
84
|
-
const
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
82
|
+
// Convert TypeScript collections to YAML collections
|
83
|
+
const collectionsPath = path.join(configDir, 'collections');
|
84
|
+
if (existsSync(collectionsPath)) {
|
85
|
+
const targetCollectionsPath = path.join(appwriteDir, 'collections');
|
86
|
+
await fs.mkdir(targetCollectionsPath, { recursive: true });
|
87
|
+
const collectionFiles = await fs.readdir(collectionsPath);
|
88
|
+
for (const file of collectionFiles) {
|
89
|
+
if (file.endsWith('.ts')) {
|
90
|
+
await convertCollectionToYaml(path.join(collectionsPath, file), targetCollectionsPath);
|
91
|
+
}
|
92
|
+
}
|
93
|
+
MessageFormatter.info(`Converted TypeScript collections to YAML in .appwrite/collections/`, { prefix: "Migration" });
|
92
94
|
}
|
95
|
+
// Keep original config file in place (no backup needed since we're not deleting it)
|
93
96
|
MessageFormatter.success(`Migration completed for ${path.relative(workingDir, configFilePath)}`, { prefix: "Migration" });
|
94
97
|
}
|
95
|
-
async function parseTypeScriptConfig(
|
96
|
-
// This is a simplified parser - in a real implementation, you might want to use a proper TypeScript parser
|
97
|
-
// For now, we'll use a regex-based approach to extract the config object
|
98
|
+
async function parseTypeScriptConfig(configFilePath) {
|
98
99
|
try {
|
99
|
-
//
|
100
|
-
const
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
|
108
|
-
|
100
|
+
// Use tsx to import the TypeScript config file directly
|
101
|
+
const { register } = await import("tsx/esm/api");
|
102
|
+
const { pathToFileURL } = await import("node:url");
|
103
|
+
const unregister = register();
|
104
|
+
try {
|
105
|
+
const configUrl = pathToFileURL(configFilePath).href;
|
106
|
+
const configModule = await import(configUrl);
|
107
|
+
const config = configModule.default?.default || configModule.default || configModule;
|
108
|
+
if (!config) {
|
109
|
+
throw new Error("Failed to load config from TypeScript file");
|
110
|
+
}
|
111
|
+
return config;
|
112
|
+
}
|
113
|
+
finally {
|
114
|
+
unregister();
|
109
115
|
}
|
110
|
-
// Convert to JSON-like format and parse
|
111
|
-
let configStr = configMatch[1];
|
112
|
-
// Replace TypeScript-specific syntax
|
113
|
-
configStr = configStr
|
114
|
-
.replace(/(\w+):/g, '"$1":') // Quote property names
|
115
|
-
.replace(/'/g, '"') // Convert single quotes to double quotes
|
116
|
-
.replace(/,(\s*[}\]])/g, '$1'); // Remove trailing commas
|
117
|
-
const config = JSON.parse(configStr);
|
118
|
-
return config;
|
119
116
|
}
|
120
117
|
catch (error) {
|
121
|
-
MessageFormatter.error("Could not
|
122
|
-
throw new Error('Failed to
|
118
|
+
MessageFormatter.error("Could not load TypeScript config", error instanceof Error ? error : new Error(String(error)), { prefix: "Migration" });
|
119
|
+
throw new Error('Failed to load TypeScript configuration file. Please ensure it exports a valid config object.');
|
123
120
|
}
|
124
121
|
}
|
125
122
|
function convertToYAMLConfig(config) {
|
126
|
-
// Convert the config to YAML
|
123
|
+
// Convert the config to the nested YAML structure
|
127
124
|
const yamlConfig = {
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
132
|
-
|
125
|
+
appwrite: {
|
126
|
+
endpoint: config.appwriteEndpoint,
|
127
|
+
project: config.appwriteProject,
|
128
|
+
key: config.appwriteKey
|
129
|
+
},
|
130
|
+
logging: {
|
131
|
+
enabled: config.logging?.enabled ?? false,
|
132
|
+
level: config.logging?.level ?? "info",
|
133
|
+
console: config.logging?.console ?? false,
|
134
|
+
logDirectory: "./logs"
|
135
|
+
},
|
136
|
+
backups: {
|
137
|
+
enabled: config.enableBackups ?? false,
|
138
|
+
interval: config.backupInterval ?? 3600,
|
139
|
+
retention: config.backupRetention ?? 30,
|
140
|
+
cleanup: config.enableBackupCleanup ?? false
|
141
|
+
},
|
142
|
+
data: {
|
143
|
+
enableMockData: config.enableMockData ?? false,
|
144
|
+
documentBucketId: config.documentBucketId ?? "documents",
|
145
|
+
usersCollectionName: config.usersCollectionName ?? "Users",
|
146
|
+
importDirectory: "importData"
|
147
|
+
},
|
148
|
+
schemas: {
|
149
|
+
outputDirectory: "schemas",
|
150
|
+
yamlSchemaDirectory: ".yaml_schemas"
|
151
|
+
},
|
152
|
+
migrations: {
|
153
|
+
enabled: true
|
154
|
+
},
|
155
|
+
databases: (config.databases || []).map(db => ({
|
156
|
+
id: db.$id,
|
157
|
+
name: db.name,
|
158
|
+
collections: [] // Collections will be handled separately
|
159
|
+
})),
|
160
|
+
buckets: (config.buckets || []).map(bucket => ({
|
161
|
+
id: bucket.$id,
|
162
|
+
name: bucket.name,
|
163
|
+
permissions: bucket.$permissions?.map((p) => ({
|
164
|
+
permission: p.permission,
|
165
|
+
target: p.target
|
166
|
+
})) || [],
|
167
|
+
fileSecurity: bucket.fileSecurity ?? false,
|
168
|
+
enabled: bucket.enabled ?? true,
|
169
|
+
maximumFileSize: bucket.maximumFileSize ?? 30000000,
|
170
|
+
allowedFileExtensions: bucket.allowedFileExtensions || [],
|
171
|
+
compression: bucket.compression || "gzip",
|
172
|
+
encryption: bucket.encryption ?? false,
|
173
|
+
antivirus: bucket.antivirus ?? false
|
174
|
+
})),
|
175
|
+
functions: (config.functions || []).map((func) => ({
|
176
|
+
id: func.$id,
|
177
|
+
name: func.name,
|
178
|
+
runtime: func.runtime,
|
179
|
+
execute: func.execute || [],
|
180
|
+
events: func.events || [],
|
181
|
+
schedule: func.schedule || "",
|
182
|
+
timeout: func.timeout ?? 15,
|
183
|
+
enabled: func.enabled ?? true,
|
184
|
+
logging: func.logging ?? false,
|
185
|
+
entrypoint: func.entrypoint || "src/main.js",
|
186
|
+
commands: func.commands || "",
|
187
|
+
scopes: func.scopes || [],
|
188
|
+
specification: func.specification || "s-1vcpu-512mb"
|
189
|
+
}))
|
133
190
|
};
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
|
152
|
-
|
191
|
+
return yamlConfig;
|
192
|
+
}
|
193
|
+
async function convertCollectionToYaml(tsFilePath, targetDir) {
|
194
|
+
try {
|
195
|
+
// Load the TypeScript collection using tsx
|
196
|
+
const { register } = await import("tsx/esm/api");
|
197
|
+
const { pathToFileURL } = await import("node:url");
|
198
|
+
const unregister = register();
|
199
|
+
try {
|
200
|
+
const configUrl = pathToFileURL(tsFilePath).href;
|
201
|
+
const collectionModule = await import(configUrl);
|
202
|
+
const collection = collectionModule.default?.default || collectionModule.default || collectionModule;
|
203
|
+
if (!collection) {
|
204
|
+
throw new Error("Failed to load collection from TypeScript file");
|
205
|
+
}
|
206
|
+
// Convert collection to YAML format
|
207
|
+
const yamlCollection = {
|
208
|
+
name: collection.name,
|
209
|
+
id: collection.$id,
|
210
|
+
documentSecurity: collection.documentSecurity ?? false,
|
211
|
+
enabled: collection.enabled ?? true,
|
212
|
+
permissions: (collection.permissions || collection.$permissions || []).map((p) => ({
|
213
|
+
permission: p.permission,
|
214
|
+
target: p.target
|
215
|
+
})),
|
216
|
+
attributes: (collection.attributes || []).map((attr) => ({
|
217
|
+
key: attr.key,
|
218
|
+
type: attr.type,
|
219
|
+
size: attr.size,
|
220
|
+
required: attr.required ?? false,
|
221
|
+
array: attr.array,
|
222
|
+
default: attr.xdefault || attr.default,
|
223
|
+
description: attr.description,
|
224
|
+
min: attr.min,
|
225
|
+
max: attr.max,
|
226
|
+
elements: attr.elements,
|
227
|
+
relatedCollection: attr.relatedCollection,
|
228
|
+
relationType: attr.relationType,
|
229
|
+
twoWay: attr.twoWay,
|
230
|
+
twoWayKey: attr.twoWayKey,
|
231
|
+
onDelete: attr.onDelete,
|
232
|
+
side: attr.side
|
233
|
+
})),
|
234
|
+
indexes: (collection.indexes || []).map((idx) => ({
|
235
|
+
key: idx.key,
|
236
|
+
type: idx.type,
|
237
|
+
attributes: idx.attributes,
|
238
|
+
orders: idx.orders
|
239
|
+
})),
|
240
|
+
importDefs: collection.importDefs || []
|
241
|
+
};
|
242
|
+
// Remove undefined values
|
243
|
+
const cleanYamlCollection = JSON.parse(JSON.stringify(yamlCollection, (key, value) => value === undefined ? undefined : value));
|
244
|
+
// Write YAML file
|
245
|
+
const fileName = path.basename(tsFilePath, '.ts') + '.yaml';
|
246
|
+
const targetPath = path.join(targetDir, fileName);
|
247
|
+
const yamlContent = yaml.dump(cleanYamlCollection, {
|
248
|
+
indent: 2,
|
249
|
+
lineWidth: 120,
|
250
|
+
noRefs: true
|
251
|
+
});
|
252
|
+
await fs.writeFile(targetPath, yamlContent);
|
253
|
+
MessageFormatter.info(`Converted ${path.basename(tsFilePath)} to ${fileName}`, { prefix: "Migration" });
|
254
|
+
}
|
255
|
+
finally {
|
256
|
+
unregister();
|
153
257
|
}
|
154
258
|
}
|
155
|
-
|
259
|
+
catch (error) {
|
260
|
+
MessageFormatter.error(`Failed to convert collection ${path.basename(tsFilePath)}`, error instanceof Error ? error : new Error(String(error)), { prefix: "Migration" });
|
261
|
+
}
|
156
262
|
}
|
@@ -68,7 +68,7 @@ const shouldIgnoreDirectory = (dirName) => {
|
|
68
68
|
};
|
69
69
|
const findAppwriteConfigTS = (dir, depth = 0) => {
|
70
70
|
// Limit search depth to prevent infinite recursion
|
71
|
-
if (depth >
|
71
|
+
if (depth > 10) {
|
72
72
|
return null;
|
73
73
|
}
|
74
74
|
if (shouldIgnoreDirectory(path.basename(dir))) {
|
@@ -79,6 +79,7 @@ const findAppwriteConfigTS = (dir, depth = 0) => {
|
|
79
79
|
// First check current directory for appwriteConfig.ts
|
80
80
|
for (const entry of entries) {
|
81
81
|
if (entry.isFile() && entry.name === "appwriteConfig.ts") {
|
82
|
+
console.log(`Found appwriteConfig.ts at: ${path.join(dir, entry.name)}`);
|
82
83
|
return path.join(dir, entry.name);
|
83
84
|
}
|
84
85
|
}
|
@@ -93,6 +94,7 @@ const findAppwriteConfigTS = (dir, depth = 0) => {
|
|
93
94
|
}
|
94
95
|
catch (error) {
|
95
96
|
// Ignore directory access errors
|
97
|
+
console.log(`Error accessing directory ${dir}:`, error);
|
96
98
|
}
|
97
99
|
return null;
|
98
100
|
};
|
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.0.
|
4
|
+
"version": "1.0.3",
|
5
5
|
"main": "src/main.ts",
|
6
6
|
"type": "module",
|
7
7
|
"repository": {
|
package/src/interactiveCLI.ts
CHANGED
@@ -1914,11 +1914,14 @@ export class InteractiveCLI {
|
|
1914
1914
|
|
1915
1915
|
// Then check for TypeScript config
|
1916
1916
|
const configPath = findAppwriteConfig(this.currentDir);
|
1917
|
-
if (configPath
|
1918
|
-
|
1919
|
-
|
1920
|
-
|
1921
|
-
|
1917
|
+
if (configPath) {
|
1918
|
+
const tsConfigPath = join(configPath, 'appwriteConfig.ts');
|
1919
|
+
if (fs.existsSync(tsConfigPath)) {
|
1920
|
+
this.isUsingTypeScriptConfig = true;
|
1921
|
+
MessageFormatter.info("TypeScript configuration detected", { prefix: "Config" });
|
1922
|
+
MessageFormatter.info("Consider migrating to YAML for better organization", { prefix: "Config" });
|
1923
|
+
return;
|
1924
|
+
}
|
1922
1925
|
}
|
1923
1926
|
|
1924
1927
|
// No config found
|
@@ -22,19 +22,39 @@ interface AppwriteConfigTS {
|
|
22
22
|
}
|
23
23
|
|
24
24
|
interface AppwriteConfigYAML {
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
25
|
+
appwrite: {
|
26
|
+
endpoint: string;
|
27
|
+
project: string;
|
28
|
+
key: string;
|
29
|
+
};
|
30
|
+
logging: {
|
31
|
+
enabled: boolean;
|
32
|
+
level: string;
|
33
|
+
console: boolean;
|
34
|
+
logDirectory: string;
|
35
|
+
};
|
36
|
+
backups: {
|
37
|
+
enabled: boolean;
|
38
|
+
interval: number;
|
39
|
+
retention: number;
|
40
|
+
cleanup: boolean;
|
41
|
+
};
|
42
|
+
data: {
|
43
|
+
enableMockData: boolean;
|
44
|
+
documentBucketId: string;
|
45
|
+
usersCollectionName: string;
|
46
|
+
importDirectory: string;
|
47
|
+
};
|
48
|
+
schemas: {
|
49
|
+
outputDirectory: string;
|
50
|
+
yamlSchemaDirectory: string;
|
51
|
+
};
|
52
|
+
migrations: {
|
53
|
+
enabled: boolean;
|
54
|
+
};
|
55
|
+
databases: Array<{ id: string; name: string; collections?: string[] }>;
|
36
56
|
buckets: Array<any>;
|
37
|
-
|
57
|
+
functions: Array<any>;
|
38
58
|
}
|
39
59
|
|
40
60
|
export async function migrateConfig(workingDir: string): Promise<void> {
|
@@ -87,7 +107,7 @@ async function findAppwriteConfigFiles(dir: string): Promise<string[]> {
|
|
87
107
|
|
88
108
|
async function migrateConfigFile(configFilePath: string, workingDir: string): Promise<void> {
|
89
109
|
const configDir = path.dirname(configFilePath);
|
90
|
-
const appwriteDir = path.join(configDir, '.appwrite');
|
110
|
+
const appwriteDir = path.join(path.dirname(configDir), '.appwrite');
|
91
111
|
|
92
112
|
MessageFormatter.info(`Migrating ${path.relative(workingDir, configFilePath)}`, { prefix: "Migration" });
|
93
113
|
|
@@ -102,9 +122,8 @@ async function migrateConfigFile(configFilePath: string, workingDir: string): Pr
|
|
102
122
|
}
|
103
123
|
}
|
104
124
|
|
105
|
-
//
|
106
|
-
const
|
107
|
-
const config = await parseTypeScriptConfig(configContent);
|
125
|
+
// Load and parse the TypeScript config
|
126
|
+
const config = await parseTypeScriptConfig(configFilePath);
|
108
127
|
|
109
128
|
// Create .appwrite directory
|
110
129
|
await fs.mkdir(appwriteDir, { recursive: true });
|
@@ -118,95 +137,213 @@ async function migrateConfigFile(configFilePath: string, workingDir: string): Pr
|
|
118
137
|
});
|
119
138
|
await fs.writeFile(path.join(appwriteDir, 'appwriteConfig.yaml'), yamlContent);
|
120
139
|
|
121
|
-
//
|
122
|
-
const
|
123
|
-
for (const
|
124
|
-
|
125
|
-
|
126
|
-
|
127
|
-
|
128
|
-
await fs.
|
129
|
-
MessageFormatter.info(`
|
140
|
+
// Copy all directories except collections and schemas (we handle collections separately, skip schemas entirely)
|
141
|
+
const entries = await fs.readdir(configDir, { withFileTypes: true });
|
142
|
+
for (const entry of entries) {
|
143
|
+
if (entry.isDirectory() && entry.name !== 'collections' && entry.name !== 'schemas') {
|
144
|
+
const sourcePath = path.join(configDir, entry.name);
|
145
|
+
const targetPath = path.join(appwriteDir, entry.name);
|
146
|
+
|
147
|
+
await fs.cp(sourcePath, targetPath, { recursive: true });
|
148
|
+
MessageFormatter.info(`Copied ${entry.name}/ to .appwrite/${entry.name}/`, { prefix: "Migration" });
|
130
149
|
}
|
131
150
|
}
|
132
151
|
|
133
|
-
//
|
134
|
-
const
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
152
|
+
// Convert TypeScript collections to YAML collections
|
153
|
+
const collectionsPath = path.join(configDir, 'collections');
|
154
|
+
if (existsSync(collectionsPath)) {
|
155
|
+
const targetCollectionsPath = path.join(appwriteDir, 'collections');
|
156
|
+
await fs.mkdir(targetCollectionsPath, { recursive: true });
|
157
|
+
|
158
|
+
const collectionFiles = await fs.readdir(collectionsPath);
|
159
|
+
for (const file of collectionFiles) {
|
160
|
+
if (file.endsWith('.ts')) {
|
161
|
+
await convertCollectionToYaml(path.join(collectionsPath, file), targetCollectionsPath);
|
162
|
+
}
|
163
|
+
}
|
164
|
+
MessageFormatter.info(`Converted TypeScript collections to YAML in .appwrite/collections/`, { prefix: "Migration" });
|
145
165
|
}
|
146
166
|
|
167
|
+
// Keep original config file in place (no backup needed since we're not deleting it)
|
168
|
+
|
147
169
|
MessageFormatter.success(`Migration completed for ${path.relative(workingDir, configFilePath)}`, { prefix: "Migration" });
|
148
170
|
}
|
149
171
|
|
150
|
-
async function parseTypeScriptConfig(
|
151
|
-
// This is a simplified parser - in a real implementation, you might want to use a proper TypeScript parser
|
152
|
-
// For now, we'll use a regex-based approach to extract the config object
|
153
|
-
|
172
|
+
async function parseTypeScriptConfig(configFilePath: string): Promise<AppwriteConfigTS> {
|
154
173
|
try {
|
155
|
-
//
|
156
|
-
const
|
157
|
-
|
158
|
-
.replace(/\/\/.*$/gm, '') // Remove line comments
|
159
|
-
.replace(/^import.*$/gm, '') // Remove imports
|
160
|
-
.replace(/^export.*$/gm, ''); // Remove exports
|
174
|
+
// Use tsx to import the TypeScript config file directly
|
175
|
+
const { register } = await import("tsx/esm/api");
|
176
|
+
const { pathToFileURL } = await import("node:url");
|
161
177
|
|
162
|
-
|
163
|
-
const configMatch = cleanContent.match(/const\s+\w+\s*:\s*\w+\s*=\s*({[\s\S]*?});/);
|
164
|
-
if (!configMatch) {
|
165
|
-
throw new Error('Could not find config object in TypeScript file');
|
166
|
-
}
|
167
|
-
|
168
|
-
// Convert to JSON-like format and parse
|
169
|
-
let configStr = configMatch[1];
|
178
|
+
const unregister = register();
|
170
179
|
|
171
|
-
|
172
|
-
|
173
|
-
|
174
|
-
|
175
|
-
|
176
|
-
|
177
|
-
|
178
|
-
|
180
|
+
try {
|
181
|
+
const configUrl = pathToFileURL(configFilePath).href;
|
182
|
+
const configModule = await import(configUrl);
|
183
|
+
const config = configModule.default?.default || configModule.default || configModule;
|
184
|
+
|
185
|
+
if (!config) {
|
186
|
+
throw new Error("Failed to load config from TypeScript file");
|
187
|
+
}
|
188
|
+
|
189
|
+
return config as AppwriteConfigTS;
|
190
|
+
} finally {
|
191
|
+
unregister();
|
192
|
+
}
|
179
193
|
} catch (error) {
|
180
|
-
MessageFormatter.error("Could not
|
181
|
-
throw new Error('Failed to
|
194
|
+
MessageFormatter.error("Could not load TypeScript config", error instanceof Error ? error : new Error(String(error)), { prefix: "Migration" });
|
195
|
+
throw new Error('Failed to load TypeScript configuration file. Please ensure it exports a valid config object.');
|
182
196
|
}
|
183
197
|
}
|
184
198
|
|
185
199
|
function convertToYAMLConfig(config: AppwriteConfigTS): AppwriteConfigYAML {
|
186
|
-
// Convert the config to YAML
|
200
|
+
// Convert the config to the nested YAML structure
|
187
201
|
const yamlConfig: AppwriteConfigYAML = {
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
202
|
+
appwrite: {
|
203
|
+
endpoint: config.appwriteEndpoint,
|
204
|
+
project: config.appwriteProject,
|
205
|
+
key: config.appwriteKey
|
206
|
+
},
|
207
|
+
logging: {
|
208
|
+
enabled: config.logging?.enabled ?? false,
|
209
|
+
level: config.logging?.level ?? "info",
|
210
|
+
console: config.logging?.console ?? false,
|
211
|
+
logDirectory: "./logs"
|
212
|
+
},
|
213
|
+
backups: {
|
214
|
+
enabled: config.enableBackups ?? false,
|
215
|
+
interval: config.backupInterval ?? 3600,
|
216
|
+
retention: config.backupRetention ?? 30,
|
217
|
+
cleanup: config.enableBackupCleanup ?? false
|
218
|
+
},
|
219
|
+
data: {
|
220
|
+
enableMockData: config.enableMockData ?? false,
|
221
|
+
documentBucketId: config.documentBucketId ?? "documents",
|
222
|
+
usersCollectionName: config.usersCollectionName ?? "Users",
|
223
|
+
importDirectory: "importData"
|
224
|
+
},
|
225
|
+
schemas: {
|
226
|
+
outputDirectory: "schemas",
|
227
|
+
yamlSchemaDirectory: ".yaml_schemas"
|
228
|
+
},
|
229
|
+
migrations: {
|
230
|
+
enabled: true
|
231
|
+
},
|
232
|
+
databases: (config.databases || []).map(db => ({
|
233
|
+
id: db.$id,
|
234
|
+
name: db.name,
|
235
|
+
collections: [] // Collections will be handled separately
|
236
|
+
})),
|
237
|
+
buckets: (config.buckets || []).map(bucket => ({
|
238
|
+
id: bucket.$id,
|
239
|
+
name: bucket.name,
|
240
|
+
permissions: bucket.$permissions?.map((p: any) => ({
|
241
|
+
permission: p.permission,
|
242
|
+
target: p.target
|
243
|
+
})) || [],
|
244
|
+
fileSecurity: bucket.fileSecurity ?? false,
|
245
|
+
enabled: bucket.enabled ?? true,
|
246
|
+
maximumFileSize: bucket.maximumFileSize ?? 30000000,
|
247
|
+
allowedFileExtensions: bucket.allowedFileExtensions || [],
|
248
|
+
compression: bucket.compression || "gzip",
|
249
|
+
encryption: bucket.encryption ?? false,
|
250
|
+
antivirus: bucket.antivirus ?? false
|
251
|
+
})),
|
252
|
+
functions: (config.functions || []).map((func: any) => ({
|
253
|
+
id: func.$id,
|
254
|
+
name: func.name,
|
255
|
+
runtime: func.runtime,
|
256
|
+
execute: func.execute || [],
|
257
|
+
events: func.events || [],
|
258
|
+
schedule: func.schedule || "",
|
259
|
+
timeout: func.timeout ?? 15,
|
260
|
+
enabled: func.enabled ?? true,
|
261
|
+
logging: func.logging ?? false,
|
262
|
+
entrypoint: func.entrypoint || "src/main.js",
|
263
|
+
commands: func.commands || "",
|
264
|
+
scopes: func.scopes || [],
|
265
|
+
specification: func.specification || "s-1vcpu-512mb"
|
266
|
+
}))
|
193
267
|
};
|
194
268
|
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
269
|
+
return yamlConfig;
|
270
|
+
}
|
271
|
+
|
272
|
+
async function convertCollectionToYaml(tsFilePath: string, targetDir: string): Promise<void> {
|
273
|
+
try {
|
274
|
+
// Load the TypeScript collection using tsx
|
275
|
+
const { register } = await import("tsx/esm/api");
|
276
|
+
const { pathToFileURL } = await import("node:url");
|
277
|
+
|
278
|
+
const unregister = register();
|
279
|
+
|
280
|
+
try {
|
281
|
+
const configUrl = pathToFileURL(tsFilePath).href;
|
282
|
+
const collectionModule = await import(configUrl);
|
283
|
+
const collection = collectionModule.default?.default || collectionModule.default || collectionModule;
|
284
|
+
|
285
|
+
if (!collection) {
|
286
|
+
throw new Error("Failed to load collection from TypeScript file");
|
287
|
+
}
|
288
|
+
|
289
|
+
// Convert collection to YAML format
|
290
|
+
const yamlCollection = {
|
291
|
+
name: collection.name,
|
292
|
+
id: collection.$id,
|
293
|
+
documentSecurity: collection.documentSecurity ?? false,
|
294
|
+
enabled: collection.enabled ?? true,
|
295
|
+
permissions: (collection.permissions || collection.$permissions || []).map((p: any) => ({
|
296
|
+
permission: p.permission,
|
297
|
+
target: p.target
|
298
|
+
})),
|
299
|
+
attributes: (collection.attributes || []).map((attr: any) => ({
|
300
|
+
key: attr.key,
|
301
|
+
type: attr.type,
|
302
|
+
size: attr.size,
|
303
|
+
required: attr.required ?? false,
|
304
|
+
array: attr.array,
|
305
|
+
default: attr.xdefault || attr.default,
|
306
|
+
description: attr.description,
|
307
|
+
min: attr.min,
|
308
|
+
max: attr.max,
|
309
|
+
elements: attr.elements,
|
310
|
+
relatedCollection: attr.relatedCollection,
|
311
|
+
relationType: attr.relationType,
|
312
|
+
twoWay: attr.twoWay,
|
313
|
+
twoWayKey: attr.twoWayKey,
|
314
|
+
onDelete: attr.onDelete,
|
315
|
+
side: attr.side
|
316
|
+
})),
|
317
|
+
indexes: (collection.indexes || []).map((idx: any) => ({
|
318
|
+
key: idx.key,
|
319
|
+
type: idx.type,
|
320
|
+
attributes: idx.attributes,
|
321
|
+
orders: idx.orders
|
322
|
+
})),
|
323
|
+
importDefs: collection.importDefs || []
|
324
|
+
};
|
325
|
+
|
326
|
+
// Remove undefined values
|
327
|
+
const cleanYamlCollection = JSON.parse(JSON.stringify(yamlCollection, (key, value) =>
|
328
|
+
value === undefined ? undefined : value
|
329
|
+
));
|
330
|
+
|
331
|
+
// Write YAML file
|
332
|
+
const fileName = path.basename(tsFilePath, '.ts') + '.yaml';
|
333
|
+
const targetPath = path.join(targetDir, fileName);
|
334
|
+
const yamlContent = yaml.dump(cleanYamlCollection, {
|
335
|
+
indent: 2,
|
336
|
+
lineWidth: 120,
|
337
|
+
noRefs: true
|
338
|
+
});
|
339
|
+
|
340
|
+
await fs.writeFile(targetPath, yamlContent);
|
341
|
+
MessageFormatter.info(`Converted ${path.basename(tsFilePath)} to ${fileName}`, { prefix: "Migration" });
|
342
|
+
|
343
|
+
} finally {
|
344
|
+
unregister();
|
208
345
|
}
|
346
|
+
} catch (error) {
|
347
|
+
MessageFormatter.error(`Failed to convert collection ${path.basename(tsFilePath)}`, error instanceof Error ? error : new Error(String(error)), { prefix: "Migration" });
|
209
348
|
}
|
210
|
-
|
211
|
-
return yamlConfig;
|
212
349
|
}
|
package/src/utils/loadConfigs.ts
CHANGED
@@ -74,7 +74,7 @@ const shouldIgnoreDirectory = (dirName: string): boolean => {
|
|
74
74
|
|
75
75
|
const findAppwriteConfigTS = (dir: string, depth: number = 0): string | null => {
|
76
76
|
// Limit search depth to prevent infinite recursion
|
77
|
-
if (depth >
|
77
|
+
if (depth > 10) {
|
78
78
|
return null;
|
79
79
|
}
|
80
80
|
|
@@ -88,6 +88,7 @@ const findAppwriteConfigTS = (dir: string, depth: number = 0): string | null =>
|
|
88
88
|
// First check current directory for appwriteConfig.ts
|
89
89
|
for (const entry of entries) {
|
90
90
|
if (entry.isFile() && entry.name === "appwriteConfig.ts") {
|
91
|
+
console.log(`Found appwriteConfig.ts at: ${path.join(dir, entry.name)}`);
|
91
92
|
return path.join(dir, entry.name);
|
92
93
|
}
|
93
94
|
}
|
@@ -101,6 +102,7 @@ const findAppwriteConfigTS = (dir: string, depth: number = 0): string | null =>
|
|
101
102
|
}
|
102
103
|
} catch (error) {
|
103
104
|
// Ignore directory access errors
|
105
|
+
console.log(`Error accessing directory ${dir}:`, error);
|
104
106
|
}
|
105
107
|
|
106
108
|
return null;
|