appwrite-utils-cli 1.0.6 → 1.0.8
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 +64 -0
- package/dist/config/yamlConfig.js +0 -9
- package/dist/interactiveCLI.d.ts +1 -0
- package/dist/interactiveCLI.js +185 -21
- package/dist/migrations/comprehensiveTransfer.d.ts +66 -0
- package/dist/migrations/comprehensiveTransfer.js +366 -0
- package/dist/migrations/yaml/YamlImportConfigLoader.d.ts +4 -4
- package/dist/utils/loadConfigs.js +5 -5
- package/package.json +1 -1
- package/src/config/yamlConfig.ts +0 -9
- package/src/interactiveCLI.ts +196 -21
- package/src/migrations/comprehensiveTransfer.ts +505 -0
- package/src/utils/loadConfigs.ts +6 -5
@@ -0,0 +1,366 @@
|
|
1
|
+
import { converterFunctions, tryAwaitWithRetry } from "appwrite-utils";
|
2
|
+
import { Client, Databases, Storage, Users, Functions, Query, } from "node-appwrite";
|
3
|
+
import { InputFile } from "node-appwrite/file";
|
4
|
+
import { MessageFormatter } from "../shared/messageFormatter.js";
|
5
|
+
import { ProgressManager } from "../shared/progressManager.js";
|
6
|
+
import { getClient } from "../utils/getClientFromConfig.js";
|
7
|
+
import { transferDatabaseLocalToLocal, transferDatabaseLocalToRemote, transferStorageLocalToLocal, transferStorageLocalToRemote, transferUsersLocalToRemote } from "./transfer.js";
|
8
|
+
import { deployLocalFunction } from "../functions/deployments.js";
|
9
|
+
import { listFunctions, downloadLatestFunctionDeployment } from "../functions/methods.js";
|
10
|
+
import pLimit from "p-limit";
|
11
|
+
import chalk from "chalk";
|
12
|
+
import { join } from "node:path";
|
13
|
+
import fs from "node:fs";
|
14
|
+
export class ComprehensiveTransfer {
|
15
|
+
options;
|
16
|
+
sourceClient;
|
17
|
+
targetClient;
|
18
|
+
sourceUsers;
|
19
|
+
targetUsers;
|
20
|
+
sourceDatabases;
|
21
|
+
targetDatabases;
|
22
|
+
sourceStorage;
|
23
|
+
targetStorage;
|
24
|
+
sourceFunctions;
|
25
|
+
targetFunctions;
|
26
|
+
limit;
|
27
|
+
userLimit;
|
28
|
+
fileLimit;
|
29
|
+
results;
|
30
|
+
startTime;
|
31
|
+
tempDir;
|
32
|
+
constructor(options) {
|
33
|
+
this.options = options;
|
34
|
+
this.sourceClient = getClient(options.sourceEndpoint, options.sourceProject, options.sourceKey);
|
35
|
+
this.targetClient = getClient(options.targetEndpoint, options.targetProject, options.targetKey);
|
36
|
+
this.sourceUsers = new Users(this.sourceClient);
|
37
|
+
this.targetUsers = new Users(this.targetClient);
|
38
|
+
this.sourceDatabases = new Databases(this.sourceClient);
|
39
|
+
this.targetDatabases = new Databases(this.targetClient);
|
40
|
+
this.sourceStorage = new Storage(this.sourceClient);
|
41
|
+
this.targetStorage = new Storage(this.targetClient);
|
42
|
+
this.sourceFunctions = new Functions(this.sourceClient);
|
43
|
+
this.targetFunctions = new Functions(this.targetClient);
|
44
|
+
const baseLimit = options.concurrencyLimit || 10;
|
45
|
+
this.limit = pLimit(baseLimit);
|
46
|
+
// Different rate limits for different operations to prevent API throttling
|
47
|
+
// Users: Half speed (more sensitive operations)
|
48
|
+
// Files: Quarter speed (most bandwidth intensive)
|
49
|
+
this.userLimit = pLimit(Math.max(1, Math.floor(baseLimit / 2)));
|
50
|
+
this.fileLimit = pLimit(Math.max(1, Math.floor(baseLimit / 4)));
|
51
|
+
this.results = {
|
52
|
+
users: { transferred: 0, skipped: 0, failed: 0 },
|
53
|
+
databases: { transferred: 0, skipped: 0, failed: 0 },
|
54
|
+
buckets: { transferred: 0, skipped: 0, failed: 0 },
|
55
|
+
functions: { transferred: 0, skipped: 0, failed: 0 },
|
56
|
+
totalTime: 0,
|
57
|
+
};
|
58
|
+
this.startTime = Date.now();
|
59
|
+
this.tempDir = join(process.cwd(), ".appwrite-transfer-temp");
|
60
|
+
}
|
61
|
+
async execute() {
|
62
|
+
try {
|
63
|
+
MessageFormatter.info("Starting comprehensive transfer", { prefix: "Transfer" });
|
64
|
+
if (this.options.dryRun) {
|
65
|
+
MessageFormatter.info("DRY RUN MODE - No actual changes will be made", { prefix: "Transfer" });
|
66
|
+
}
|
67
|
+
// Show rate limiting configuration
|
68
|
+
const baseLimit = this.options.concurrencyLimit || 10;
|
69
|
+
const userLimit = Math.max(1, Math.floor(baseLimit / 2));
|
70
|
+
const fileLimit = Math.max(1, Math.floor(baseLimit / 4));
|
71
|
+
MessageFormatter.info(`Rate limits: General=${baseLimit}, Users=${userLimit}, Files=${fileLimit}`, { prefix: "Transfer" });
|
72
|
+
// Ensure temp directory exists
|
73
|
+
if (!fs.existsSync(this.tempDir)) {
|
74
|
+
fs.mkdirSync(this.tempDir, { recursive: true });
|
75
|
+
}
|
76
|
+
// Execute transfers in the correct order
|
77
|
+
if (this.options.transferUsers !== false) {
|
78
|
+
await this.transferAllUsers();
|
79
|
+
}
|
80
|
+
if (this.options.transferDatabases !== false) {
|
81
|
+
await this.transferAllDatabases();
|
82
|
+
}
|
83
|
+
if (this.options.transferBuckets !== false) {
|
84
|
+
await this.transferAllBuckets();
|
85
|
+
}
|
86
|
+
if (this.options.transferFunctions !== false) {
|
87
|
+
await this.transferAllFunctions();
|
88
|
+
}
|
89
|
+
this.results.totalTime = Date.now() - this.startTime;
|
90
|
+
this.printSummary();
|
91
|
+
return this.results;
|
92
|
+
}
|
93
|
+
catch (error) {
|
94
|
+
MessageFormatter.error("Comprehensive transfer failed", error instanceof Error ? error : new Error(String(error)), { prefix: "Transfer" });
|
95
|
+
throw error;
|
96
|
+
}
|
97
|
+
finally {
|
98
|
+
// Clean up temp directory
|
99
|
+
if (fs.existsSync(this.tempDir)) {
|
100
|
+
fs.rmSync(this.tempDir, { recursive: true, force: true });
|
101
|
+
}
|
102
|
+
}
|
103
|
+
}
|
104
|
+
async transferAllUsers() {
|
105
|
+
MessageFormatter.info("Starting user transfer phase", { prefix: "Transfer" });
|
106
|
+
if (this.options.dryRun) {
|
107
|
+
const usersList = await this.sourceUsers.list([Query.limit(1)]);
|
108
|
+
MessageFormatter.info(`DRY RUN: Would transfer ${usersList.total} users`, { prefix: "Transfer" });
|
109
|
+
return;
|
110
|
+
}
|
111
|
+
try {
|
112
|
+
// Use the existing user transfer function
|
113
|
+
// Note: The rate limiting is handled at the API level, not per-user
|
114
|
+
// since user operations are already sequential in the existing implementation
|
115
|
+
await transferUsersLocalToRemote(this.sourceUsers, this.options.targetEndpoint, this.options.targetProject, this.options.targetKey);
|
116
|
+
// Get actual count for results
|
117
|
+
const usersList = await this.sourceUsers.list([Query.limit(1)]);
|
118
|
+
this.results.users.transferred = usersList.total;
|
119
|
+
MessageFormatter.success(`User transfer completed`, { prefix: "Transfer" });
|
120
|
+
}
|
121
|
+
catch (error) {
|
122
|
+
MessageFormatter.error("User transfer failed", error instanceof Error ? error : new Error(String(error)), { prefix: "Transfer" });
|
123
|
+
this.results.users.failed = 1;
|
124
|
+
}
|
125
|
+
}
|
126
|
+
async transferAllDatabases() {
|
127
|
+
MessageFormatter.info("Starting database transfer phase", { prefix: "Transfer" });
|
128
|
+
try {
|
129
|
+
const sourceDatabases = await this.sourceDatabases.list();
|
130
|
+
const targetDatabases = await this.targetDatabases.list();
|
131
|
+
if (this.options.dryRun) {
|
132
|
+
MessageFormatter.info(`DRY RUN: Would transfer ${sourceDatabases.databases.length} databases`, { prefix: "Transfer" });
|
133
|
+
return;
|
134
|
+
}
|
135
|
+
const transferTasks = sourceDatabases.databases.map(db => this.limit(async () => {
|
136
|
+
try {
|
137
|
+
// Check if database exists in target
|
138
|
+
const existingDb = targetDatabases.databases.find(tdb => tdb.$id === db.$id);
|
139
|
+
if (!existingDb) {
|
140
|
+
// Create database in target
|
141
|
+
await this.targetDatabases.create(db.$id, db.name, db.enabled);
|
142
|
+
MessageFormatter.success(`Created database: ${db.name}`, { prefix: "Transfer" });
|
143
|
+
}
|
144
|
+
// Transfer database content
|
145
|
+
await transferDatabaseLocalToRemote(this.sourceDatabases, this.options.targetEndpoint, this.options.targetProject, this.options.targetKey, db.$id, db.$id);
|
146
|
+
this.results.databases.transferred++;
|
147
|
+
MessageFormatter.success(`Database ${db.name} transferred successfully`, { prefix: "Transfer" });
|
148
|
+
}
|
149
|
+
catch (error) {
|
150
|
+
MessageFormatter.error(`Database ${db.name} transfer failed`, error instanceof Error ? error : new Error(String(error)), { prefix: "Transfer" });
|
151
|
+
this.results.databases.failed++;
|
152
|
+
}
|
153
|
+
}));
|
154
|
+
await Promise.all(transferTasks);
|
155
|
+
MessageFormatter.success("Database transfer phase completed", { prefix: "Transfer" });
|
156
|
+
}
|
157
|
+
catch (error) {
|
158
|
+
MessageFormatter.error("Database transfer phase failed", error instanceof Error ? error : new Error(String(error)), { prefix: "Transfer" });
|
159
|
+
}
|
160
|
+
}
|
161
|
+
async transferAllBuckets() {
|
162
|
+
MessageFormatter.info("Starting bucket transfer phase", { prefix: "Transfer" });
|
163
|
+
try {
|
164
|
+
const sourceBuckets = await this.sourceStorage.listBuckets();
|
165
|
+
const targetBuckets = await this.targetStorage.listBuckets();
|
166
|
+
if (this.options.dryRun) {
|
167
|
+
let totalFiles = 0;
|
168
|
+
for (const bucket of sourceBuckets.buckets) {
|
169
|
+
const files = await this.sourceStorage.listFiles(bucket.$id, [Query.limit(1)]);
|
170
|
+
totalFiles += files.total;
|
171
|
+
}
|
172
|
+
MessageFormatter.info(`DRY RUN: Would transfer ${sourceBuckets.buckets.length} buckets with ${totalFiles} files`, { prefix: "Transfer" });
|
173
|
+
return;
|
174
|
+
}
|
175
|
+
const transferTasks = sourceBuckets.buckets.map(bucket => this.limit(async () => {
|
176
|
+
try {
|
177
|
+
// Check if bucket exists in target
|
178
|
+
const existingBucket = targetBuckets.buckets.find(tb => tb.$id === bucket.$id);
|
179
|
+
if (!existingBucket) {
|
180
|
+
// Create bucket in target
|
181
|
+
await this.targetStorage.createBucket(bucket.$id, bucket.name, bucket.$permissions, bucket.fileSecurity, bucket.enabled, bucket.maximumFileSize, bucket.allowedFileExtensions, bucket.compression, bucket.encryption, bucket.antivirus);
|
182
|
+
MessageFormatter.success(`Created bucket: ${bucket.name}`, { prefix: "Transfer" });
|
183
|
+
}
|
184
|
+
// Transfer bucket files with enhanced validation
|
185
|
+
await this.transferBucketFiles(bucket.$id, bucket.$id);
|
186
|
+
this.results.buckets.transferred++;
|
187
|
+
MessageFormatter.success(`Bucket ${bucket.name} transferred successfully`, { prefix: "Transfer" });
|
188
|
+
}
|
189
|
+
catch (error) {
|
190
|
+
MessageFormatter.error(`Bucket ${bucket.name} transfer failed`, error instanceof Error ? error : new Error(String(error)), { prefix: "Transfer" });
|
191
|
+
this.results.buckets.failed++;
|
192
|
+
}
|
193
|
+
}));
|
194
|
+
await Promise.all(transferTasks);
|
195
|
+
MessageFormatter.success("Bucket transfer phase completed", { prefix: "Transfer" });
|
196
|
+
}
|
197
|
+
catch (error) {
|
198
|
+
MessageFormatter.error("Bucket transfer phase failed", error instanceof Error ? error : new Error(String(error)), { prefix: "Transfer" });
|
199
|
+
}
|
200
|
+
}
|
201
|
+
async transferBucketFiles(sourceBucketId, targetBucketId) {
|
202
|
+
let lastFileId;
|
203
|
+
let transferredFiles = 0;
|
204
|
+
while (true) {
|
205
|
+
const queries = [Query.limit(50)]; // Smaller batch size for better rate limiting
|
206
|
+
if (lastFileId) {
|
207
|
+
queries.push(Query.cursorAfter(lastFileId));
|
208
|
+
}
|
209
|
+
const files = await this.sourceStorage.listFiles(sourceBucketId, queries);
|
210
|
+
if (files.files.length === 0)
|
211
|
+
break;
|
212
|
+
// Process files with rate limiting
|
213
|
+
const fileTasks = files.files.map(file => this.fileLimit(async () => {
|
214
|
+
try {
|
215
|
+
// Check if file already exists
|
216
|
+
try {
|
217
|
+
await this.targetStorage.getFile(targetBucketId, file.$id);
|
218
|
+
MessageFormatter.info(`File ${file.name} already exists, skipping`, { prefix: "Transfer" });
|
219
|
+
return;
|
220
|
+
}
|
221
|
+
catch (error) {
|
222
|
+
// File doesn't exist, proceed with transfer
|
223
|
+
}
|
224
|
+
// Download file with validation
|
225
|
+
const fileData = await this.validateAndDownloadFile(sourceBucketId, file.$id);
|
226
|
+
if (!fileData) {
|
227
|
+
MessageFormatter.warning(`File ${file.name} failed validation, skipping`, { prefix: "Transfer" });
|
228
|
+
return;
|
229
|
+
}
|
230
|
+
// Upload file to target
|
231
|
+
const fileToCreate = InputFile.fromBuffer(new Uint8Array(fileData), file.name);
|
232
|
+
await this.targetStorage.createFile(targetBucketId, file.$id, fileToCreate, file.$permissions);
|
233
|
+
transferredFiles++;
|
234
|
+
MessageFormatter.success(`Transferred file: ${file.name}`, { prefix: "Transfer" });
|
235
|
+
}
|
236
|
+
catch (error) {
|
237
|
+
MessageFormatter.error(`Failed to transfer file ${file.name}`, error instanceof Error ? error : new Error(String(error)), { prefix: "Transfer" });
|
238
|
+
}
|
239
|
+
}));
|
240
|
+
await Promise.all(fileTasks);
|
241
|
+
if (files.files.length < 50)
|
242
|
+
break;
|
243
|
+
lastFileId = files.files[files.files.length - 1].$id;
|
244
|
+
}
|
245
|
+
MessageFormatter.info(`Transferred ${transferredFiles} files from bucket ${sourceBucketId}`, { prefix: "Transfer" });
|
246
|
+
}
|
247
|
+
async validateAndDownloadFile(bucketId, fileId) {
|
248
|
+
let attempts = 3;
|
249
|
+
while (attempts > 0) {
|
250
|
+
try {
|
251
|
+
const fileData = await this.sourceStorage.getFileDownload(bucketId, fileId);
|
252
|
+
// Basic validation - ensure file is not empty and not too large
|
253
|
+
if (fileData.byteLength === 0) {
|
254
|
+
MessageFormatter.warning(`File ${fileId} is empty`, { prefix: "Transfer" });
|
255
|
+
return null;
|
256
|
+
}
|
257
|
+
if (fileData.byteLength > 50 * 1024 * 1024) { // 50MB limit
|
258
|
+
MessageFormatter.warning(`File ${fileId} is too large (${fileData.byteLength} bytes)`, { prefix: "Transfer" });
|
259
|
+
return null;
|
260
|
+
}
|
261
|
+
return fileData;
|
262
|
+
}
|
263
|
+
catch (error) {
|
264
|
+
attempts--;
|
265
|
+
MessageFormatter.warning(`Error downloading file ${fileId}, attempts left: ${attempts}`, { prefix: "Transfer" });
|
266
|
+
if (attempts === 0) {
|
267
|
+
MessageFormatter.error(`Failed to download file ${fileId} after all attempts`, error instanceof Error ? error : new Error(String(error)), { prefix: "Transfer" });
|
268
|
+
return null;
|
269
|
+
}
|
270
|
+
// Wait before retry
|
271
|
+
await new Promise(resolve => setTimeout(resolve, 1000 * (4 - attempts)));
|
272
|
+
}
|
273
|
+
}
|
274
|
+
return null;
|
275
|
+
}
|
276
|
+
async transferAllFunctions() {
|
277
|
+
MessageFormatter.info("Starting function transfer phase", { prefix: "Transfer" });
|
278
|
+
try {
|
279
|
+
const sourceFunctions = await listFunctions(this.sourceClient, [Query.limit(1000)]);
|
280
|
+
const targetFunctions = await listFunctions(this.targetClient, [Query.limit(1000)]);
|
281
|
+
if (this.options.dryRun) {
|
282
|
+
MessageFormatter.info(`DRY RUN: Would transfer ${sourceFunctions.functions.length} functions`, { prefix: "Transfer" });
|
283
|
+
return;
|
284
|
+
}
|
285
|
+
const transferTasks = sourceFunctions.functions.map(func => this.limit(async () => {
|
286
|
+
try {
|
287
|
+
// Check if function exists in target
|
288
|
+
const existingFunc = targetFunctions.functions.find(tf => tf.$id === func.$id);
|
289
|
+
if (existingFunc) {
|
290
|
+
MessageFormatter.info(`Function ${func.name} already exists, skipping creation`, { prefix: "Transfer" });
|
291
|
+
this.results.functions.skipped++;
|
292
|
+
return;
|
293
|
+
}
|
294
|
+
// Download function from source
|
295
|
+
const functionPath = await this.downloadFunction(func);
|
296
|
+
if (!functionPath) {
|
297
|
+
MessageFormatter.error(`Failed to download function ${func.name}`, undefined, { prefix: "Transfer" });
|
298
|
+
this.results.functions.failed++;
|
299
|
+
return;
|
300
|
+
}
|
301
|
+
// Deploy function to target
|
302
|
+
const functionConfig = {
|
303
|
+
$id: func.$id,
|
304
|
+
name: func.name,
|
305
|
+
runtime: func.runtime,
|
306
|
+
execute: func.execute,
|
307
|
+
events: func.events,
|
308
|
+
enabled: func.enabled,
|
309
|
+
logging: func.logging,
|
310
|
+
entrypoint: func.entrypoint,
|
311
|
+
commands: func.commands,
|
312
|
+
scopes: func.scopes,
|
313
|
+
timeout: func.timeout,
|
314
|
+
schedule: func.schedule,
|
315
|
+
installationId: func.installationId,
|
316
|
+
providerRepositoryId: func.providerRepositoryId,
|
317
|
+
providerBranch: func.providerBranch,
|
318
|
+
providerSilentMode: func.providerSilentMode,
|
319
|
+
providerRootDirectory: func.providerRootDirectory,
|
320
|
+
specification: func.specification,
|
321
|
+
dirPath: functionPath,
|
322
|
+
};
|
323
|
+
await deployLocalFunction(this.targetClient, func.name, functionConfig);
|
324
|
+
this.results.functions.transferred++;
|
325
|
+
MessageFormatter.success(`Function ${func.name} transferred successfully`, { prefix: "Transfer" });
|
326
|
+
}
|
327
|
+
catch (error) {
|
328
|
+
MessageFormatter.error(`Function ${func.name} transfer failed`, error instanceof Error ? error : new Error(String(error)), { prefix: "Transfer" });
|
329
|
+
this.results.functions.failed++;
|
330
|
+
}
|
331
|
+
}));
|
332
|
+
await Promise.all(transferTasks);
|
333
|
+
MessageFormatter.success("Function transfer phase completed", { prefix: "Transfer" });
|
334
|
+
}
|
335
|
+
catch (error) {
|
336
|
+
MessageFormatter.error("Function transfer phase failed", error instanceof Error ? error : new Error(String(error)), { prefix: "Transfer" });
|
337
|
+
}
|
338
|
+
}
|
339
|
+
async downloadFunction(func) {
|
340
|
+
try {
|
341
|
+
const { path } = await downloadLatestFunctionDeployment(this.sourceClient, func.$id, this.tempDir);
|
342
|
+
return path;
|
343
|
+
}
|
344
|
+
catch (error) {
|
345
|
+
MessageFormatter.error(`Failed to download function ${func.name}`, error instanceof Error ? error : new Error(String(error)), { prefix: "Transfer" });
|
346
|
+
return null;
|
347
|
+
}
|
348
|
+
}
|
349
|
+
printSummary() {
|
350
|
+
const duration = Math.round((Date.now() - this.startTime) / 1000);
|
351
|
+
MessageFormatter.info("=== COMPREHENSIVE TRANSFER SUMMARY ===", { prefix: "Transfer" });
|
352
|
+
MessageFormatter.info(`Total Time: ${duration}s`, { prefix: "Transfer" });
|
353
|
+
MessageFormatter.info(`Users: ${this.results.users.transferred} transferred, ${this.results.users.skipped} skipped, ${this.results.users.failed} failed`, { prefix: "Transfer" });
|
354
|
+
MessageFormatter.info(`Databases: ${this.results.databases.transferred} transferred, ${this.results.databases.skipped} skipped, ${this.results.databases.failed} failed`, { prefix: "Transfer" });
|
355
|
+
MessageFormatter.info(`Buckets: ${this.results.buckets.transferred} transferred, ${this.results.buckets.skipped} skipped, ${this.results.buckets.failed} failed`, { prefix: "Transfer" });
|
356
|
+
MessageFormatter.info(`Functions: ${this.results.functions.transferred} transferred, ${this.results.functions.skipped} skipped, ${this.results.functions.failed} failed`, { prefix: "Transfer" });
|
357
|
+
const totalTransferred = this.results.users.transferred + this.results.databases.transferred + this.results.buckets.transferred + this.results.functions.transferred;
|
358
|
+
const totalFailed = this.results.users.failed + this.results.databases.failed + this.results.buckets.failed + this.results.functions.failed;
|
359
|
+
if (totalFailed === 0) {
|
360
|
+
MessageFormatter.success(`All ${totalTransferred} items transferred successfully!`, { prefix: "Transfer" });
|
361
|
+
}
|
362
|
+
else {
|
363
|
+
MessageFormatter.warning(`${totalTransferred} items transferred, ${totalFailed} failed`, { prefix: "Transfer" });
|
364
|
+
}
|
365
|
+
}
|
366
|
+
}
|
@@ -195,8 +195,8 @@ export declare const YamlImportConfigSchema: z.ZodObject<{
|
|
195
195
|
}>>;
|
196
196
|
}, "strip", z.ZodTypeAny, {
|
197
197
|
batchSize: number;
|
198
|
-
skipValidation: boolean;
|
199
198
|
dryRun: boolean;
|
199
|
+
skipValidation: boolean;
|
200
200
|
continueOnError: boolean;
|
201
201
|
updateMapping?: {
|
202
202
|
targetField: string;
|
@@ -204,8 +204,8 @@ export declare const YamlImportConfigSchema: z.ZodObject<{
|
|
204
204
|
} | undefined;
|
205
205
|
}, {
|
206
206
|
batchSize?: number | undefined;
|
207
|
-
skipValidation?: boolean | undefined;
|
208
207
|
dryRun?: boolean | undefined;
|
208
|
+
skipValidation?: boolean | undefined;
|
209
209
|
continueOnError?: boolean | undefined;
|
210
210
|
updateMapping?: {
|
211
211
|
targetField: string;
|
@@ -215,8 +215,8 @@ export declare const YamlImportConfigSchema: z.ZodObject<{
|
|
215
215
|
}, "strip", z.ZodTypeAny, {
|
216
216
|
options: {
|
217
217
|
batchSize: number;
|
218
|
-
skipValidation: boolean;
|
219
218
|
dryRun: boolean;
|
219
|
+
skipValidation: boolean;
|
220
220
|
continueOnError: boolean;
|
221
221
|
updateMapping?: {
|
222
222
|
targetField: string;
|
@@ -304,8 +304,8 @@ export declare const YamlImportConfigSchema: z.ZodObject<{
|
|
304
304
|
};
|
305
305
|
options?: {
|
306
306
|
batchSize?: number | undefined;
|
307
|
-
skipValidation?: boolean | undefined;
|
308
307
|
dryRun?: boolean | undefined;
|
308
|
+
skipValidation?: boolean | undefined;
|
309
309
|
continueOnError?: boolean | undefined;
|
310
310
|
updateMapping?: {
|
311
311
|
targetField: string;
|
@@ -7,6 +7,7 @@ import chalk from "chalk";
|
|
7
7
|
import { findYamlConfig, loadYamlConfig } from "../config/yamlConfig.js";
|
8
8
|
import yaml from "js-yaml";
|
9
9
|
import { z } from "zod";
|
10
|
+
import { MessageFormatter } from "../shared/messageFormatter.js";
|
10
11
|
/**
|
11
12
|
* Recursively searches for configuration files starting from the given directory.
|
12
13
|
* Priority: 1) YAML configs in .appwrite directories, 2) appwriteConfig.ts files in subdirectories
|
@@ -79,7 +80,6 @@ const findAppwriteConfigTS = (dir, depth = 0) => {
|
|
79
80
|
// First check current directory for appwriteConfig.ts
|
80
81
|
for (const entry of entries) {
|
81
82
|
if (entry.isFile() && entry.name === "appwriteConfig.ts") {
|
82
|
-
console.log(`Found appwriteConfig.ts at: ${path.join(dir, entry.name)}`);
|
83
83
|
return path.join(dir, entry.name);
|
84
84
|
}
|
85
85
|
}
|
@@ -94,7 +94,6 @@ const findAppwriteConfigTS = (dir, depth = 0) => {
|
|
94
94
|
}
|
95
95
|
catch (error) {
|
96
96
|
// Ignore directory access errors
|
97
|
-
console.log(`Error accessing directory ${dir}:`, error);
|
98
97
|
}
|
99
98
|
return null;
|
100
99
|
};
|
@@ -134,7 +133,6 @@ export const loadConfigWithPath = async (configDir) => {
|
|
134
133
|
if (fs.existsSync(configPath)) {
|
135
134
|
const unregister = register(); // Register tsx enhancement
|
136
135
|
try {
|
137
|
-
console.log(`Loading TypeScript config from: ${configPath}`);
|
138
136
|
const configUrl = pathToFileURL(configPath).href;
|
139
137
|
const configModule = (await import(configUrl));
|
140
138
|
config = configModule.default?.default || configModule.default || configModule;
|
@@ -214,7 +212,6 @@ export const loadConfig = async (configDir) => {
|
|
214
212
|
// First try to find and load YAML config
|
215
213
|
const yamlConfigPath = findYamlConfig(configDir);
|
216
214
|
if (yamlConfigPath) {
|
217
|
-
console.log(`Loading YAML config from: ${yamlConfigPath}`);
|
218
215
|
config = await loadYamlConfig(yamlConfigPath);
|
219
216
|
actualConfigPath = yamlConfigPath;
|
220
217
|
}
|
@@ -225,7 +222,6 @@ export const loadConfig = async (configDir) => {
|
|
225
222
|
if (fs.existsSync(configPath)) {
|
226
223
|
const unregister = register(); // Register tsx enhancement
|
227
224
|
try {
|
228
|
-
console.log(`Loading TypeScript config from: ${configPath}`);
|
229
225
|
const configUrl = pathToFileURL(configPath).href;
|
230
226
|
const configModule = (await import(configUrl));
|
231
227
|
config = configModule.default?.default || configModule.default || configModule;
|
@@ -300,6 +296,10 @@ export const loadConfig = async (configDir) => {
|
|
300
296
|
else {
|
301
297
|
config.collections = config.collections || [];
|
302
298
|
}
|
299
|
+
// Log successful config loading
|
300
|
+
if (actualConfigPath) {
|
301
|
+
MessageFormatter.success(`Loaded config from: ${actualConfigPath}`, { prefix: "Config" });
|
302
|
+
}
|
303
303
|
return config;
|
304
304
|
};
|
305
305
|
export const findFunctionsDir = (dir, depth = 0) => {
|
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.8",
|
5
5
|
"main": "src/main.ts",
|
6
6
|
"type": "module",
|
7
7
|
"repository": {
|
package/src/config/yamlConfig.ts
CHANGED
@@ -256,18 +256,14 @@ export const findYamlConfig = (startDir: string): string | null => {
|
|
256
256
|
path.join(startDir, "appwrite.yml"),
|
257
257
|
];
|
258
258
|
|
259
|
-
console.log(`DEBUG: Checking YAML paths in ${startDir}:`);
|
260
259
|
for (const configPath of possiblePaths) {
|
261
|
-
console.log(` - ${configPath}: ${fs.existsSync(configPath)}`);
|
262
260
|
if (fs.existsSync(configPath)) {
|
263
261
|
return configPath;
|
264
262
|
}
|
265
263
|
}
|
266
264
|
|
267
265
|
// Recursively search subdirectories for .appwrite folders
|
268
|
-
console.log(`DEBUG: Starting recursive search from ${startDir}`);
|
269
266
|
const yamlConfigInSubdirs = findYamlConfigRecursive(startDir);
|
270
|
-
console.log(`DEBUG: Recursive search result: ${yamlConfigInSubdirs}`);
|
271
267
|
if (yamlConfigInSubdirs) {
|
272
268
|
return yamlConfigInSubdirs;
|
273
269
|
}
|
@@ -336,27 +332,22 @@ const shouldIgnoreDirectory = (dirName: string): boolean => {
|
|
336
332
|
const findYamlConfigRecursive = (dir: string, depth: number = 0): string | null => {
|
337
333
|
// Limit search depth to prevent infinite recursion
|
338
334
|
if (depth > 5) {
|
339
|
-
console.log(`DEBUG: Stopping search at depth ${depth} in ${dir}`);
|
340
335
|
return null;
|
341
336
|
}
|
342
337
|
|
343
338
|
if (shouldIgnoreDirectory(path.basename(dir))) {
|
344
|
-
console.log(`DEBUG: Ignoring directory ${dir}`);
|
345
339
|
return null;
|
346
340
|
}
|
347
341
|
|
348
342
|
try {
|
349
343
|
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
350
|
-
console.log(`DEBUG: Searching directory ${dir} at depth ${depth}, found ${entries.length} entries`);
|
351
344
|
|
352
345
|
for (const entry of entries) {
|
353
346
|
if (entry.isDirectory() && !shouldIgnoreDirectory(entry.name)) {
|
354
347
|
const fullPath = path.join(dir, entry.name);
|
355
|
-
console.log(`DEBUG: Checking subdirectory: ${fullPath}`);
|
356
348
|
|
357
349
|
// Check if this is an .appwrite directory
|
358
350
|
if (entry.name === ".appwrite") {
|
359
|
-
console.log(`DEBUG: Found .appwrite directory at ${fullPath}`);
|
360
351
|
const configPaths = [
|
361
352
|
path.join(fullPath, "appwriteConfig.yaml"),
|
362
353
|
path.join(fullPath, "appwriteConfig.yml"),
|