appwrite-utils-cli 1.1.1 → 1.1.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.
@@ -164,16 +164,38 @@ export const createOrUpdateAttributeWithStatusCheck = async (db, dbId, collectio
164
164
  if (success) {
165
165
  return true;
166
166
  }
167
- // If not successful and we have retries left, delete collection and try again
167
+ // If not successful and we have retries left, delete specific attribute and try again
168
168
  if (retryCount < maxRetries) {
169
- console.log(chalk.yellow(`Attribute '${attribute.key}' failed/stuck, retrying...`));
170
- // Get fresh collection data
171
- const freshCollection = await db.getCollection(dbId, collection.$id);
172
- // Delete and recreate collection
173
- const newCollection = await deleteAndRecreateCollection(db, dbId, freshCollection, retryCount + 1);
174
- if (newCollection) {
175
- // Retry with the new collection
176
- return await createOrUpdateAttributeWithStatusCheck(db, dbId, newCollection, attribute, retryCount + 1, maxRetries);
169
+ console.log(chalk.yellow(`Attribute '${attribute.key}' failed/stuck, deleting and retrying...`));
170
+ // Try to delete the specific stuck attribute instead of the entire collection
171
+ try {
172
+ await db.deleteAttribute(dbId, collection.$id, attribute.key);
173
+ console.log(chalk.yellow(`Deleted stuck attribute '${attribute.key}', will retry creation`));
174
+ // Wait a bit before retry
175
+ await delay(3000);
176
+ // Get fresh collection data
177
+ const freshCollection = await db.getCollection(dbId, collection.$id);
178
+ // Retry with the same collection (attribute should be gone now)
179
+ return await createOrUpdateAttributeWithStatusCheck(db, dbId, freshCollection, attribute, retryCount + 1, maxRetries);
180
+ }
181
+ catch (deleteError) {
182
+ console.log(chalk.red(`Failed to delete stuck attribute '${attribute.key}': ${deleteError}`));
183
+ // If attribute deletion fails, only then try collection recreation as last resort
184
+ if (retryCount >= maxRetries - 1) {
185
+ console.log(chalk.yellow(`Last resort: Recreating collection for attribute '${attribute.key}'`));
186
+ // Get fresh collection data
187
+ const freshCollection = await db.getCollection(dbId, collection.$id);
188
+ // Delete and recreate collection
189
+ const newCollection = await deleteAndRecreateCollection(db, dbId, freshCollection, retryCount + 1);
190
+ if (newCollection) {
191
+ // Retry with the new collection
192
+ return await createOrUpdateAttributeWithStatusCheck(db, dbId, newCollection, attribute, retryCount + 1, maxRetries);
193
+ }
194
+ }
195
+ else {
196
+ // Continue to next retry without collection recreation
197
+ return await createOrUpdateAttributeWithStatusCheck(db, dbId, collection, attribute, retryCount + 1, maxRetries);
198
+ }
177
199
  }
178
200
  }
179
201
  console.log(chalk.red(`❌ Failed to create attribute '${attribute.key}' after ${maxRetries + 1} attempts`));
@@ -437,32 +459,58 @@ export const createUpdateCollectionAttributesWithStatusCheck = async (db, dbId,
437
459
  await delay(500); // Longer delay for deletions
438
460
  }
439
461
  }
440
- // Create attributes ONE BY ONE with proper status checking
462
+ // Create attributes ONE BY ONE with proper status checking and persistent retry logic
441
463
  console.log(chalk.blue(`Creating ${attributes.length} attributes sequentially with status monitoring...`));
442
464
  let currentCollection = collection;
443
- const failedAttributes = [];
444
- for (const attribute of attributes) {
445
- console.log(chalk.blue(`\n--- Processing attribute: ${attribute.key} ---`));
446
- const success = await createOrUpdateAttributeWithStatusCheck(db, dbId, currentCollection, attribute);
447
- if (success) {
448
- console.log(chalk.green(`✅ Successfully created attribute: ${attribute.key}`));
449
- // Get updated collection data for next iteration
465
+ let attributesToProcess = [...attributes];
466
+ let overallRetryCount = 0;
467
+ const maxOverallRetries = 3;
468
+ while (attributesToProcess.length > 0 && overallRetryCount < maxOverallRetries) {
469
+ const remainingAttributes = [...attributesToProcess];
470
+ attributesToProcess = []; // Reset for next iteration
471
+ console.log(chalk.blue(`\n=== Attempt ${overallRetryCount + 1}/${maxOverallRetries} - Processing ${remainingAttributes.length} attributes ===`));
472
+ for (const attribute of remainingAttributes) {
473
+ console.log(chalk.blue(`\n--- Processing attribute: ${attribute.key} ---`));
474
+ const success = await createOrUpdateAttributeWithStatusCheck(db, dbId, currentCollection, attribute);
475
+ if (success) {
476
+ console.log(chalk.green(`✅ Successfully created attribute: ${attribute.key}`));
477
+ // Get updated collection data for next iteration
478
+ try {
479
+ currentCollection = await db.getCollection(dbId, collection.$id);
480
+ }
481
+ catch (error) {
482
+ console.log(chalk.yellow(`Warning: Could not refresh collection data: ${error}`));
483
+ }
484
+ // Add delay between successful attributes
485
+ await delay(1000);
486
+ }
487
+ else {
488
+ console.log(chalk.red(`❌ Failed to create attribute: ${attribute.key}, will retry in next round`));
489
+ attributesToProcess.push(attribute); // Add back to retry list
490
+ }
491
+ }
492
+ if (attributesToProcess.length === 0) {
493
+ console.log(chalk.green(`\n✅ Successfully created all ${attributes.length} attributes for collection: ${collection.name}`));
494
+ return true;
495
+ }
496
+ overallRetryCount++;
497
+ if (overallRetryCount < maxOverallRetries) {
498
+ console.log(chalk.yellow(`\n⏳ Waiting 5 seconds before retrying ${attributesToProcess.length} failed attributes...`));
499
+ await delay(5000);
500
+ // Refresh collection data before retry
450
501
  try {
451
502
  currentCollection = await db.getCollection(dbId, collection.$id);
503
+ console.log(chalk.blue(`Refreshed collection data for retry`));
452
504
  }
453
505
  catch (error) {
454
- console.log(chalk.yellow(`Warning: Could not refresh collection data: ${error}`));
506
+ console.log(chalk.yellow(`Warning: Could not refresh collection data for retry: ${error}`));
455
507
  }
456
- // Add delay between successful attributes
457
- await delay(1000);
458
- }
459
- else {
460
- console.log(chalk.red(`❌ Failed to create attribute: ${attribute.key}`));
461
- failedAttributes.push(attribute.key);
462
508
  }
463
509
  }
464
- if (failedAttributes.length > 0) {
465
- console.log(chalk.red(`\n❌ Failed to create ${failedAttributes.length} attributes: ${failedAttributes.join(', ')}`));
510
+ // If we get here, some attributes still failed after all retries
511
+ if (attributesToProcess.length > 0) {
512
+ console.log(chalk.red(`\n❌ Failed to create ${attributesToProcess.length} attributes after ${maxOverallRetries} attempts: ${attributesToProcess.map(a => a.key).join(', ')}`));
513
+ console.log(chalk.red(`This may indicate a fundamental issue with the attribute definitions or Appwrite instance`));
466
514
  return false;
467
515
  }
468
516
  console.log(chalk.green(`\n✅ Successfully created all ${attributes.length} attributes for collection: ${collection.name}`));
@@ -63,28 +63,6 @@ retryCount = 0, maxRetries = 5) => {
63
63
  }
64
64
  return false;
65
65
  };
66
- /**
67
- * Delete collection and recreate for index retry (reused from attributes.ts)
68
- */
69
- const deleteAndRecreateCollectionForIndex = async (db, dbId, collection, retryCount) => {
70
- try {
71
- console.log(chalk.yellow(`🗑️ Deleting collection '${collection.name}' for index retry ${retryCount}`));
72
- // Delete the collection
73
- await db.deleteCollection(dbId, collection.$id);
74
- console.log(chalk.yellow(`Deleted collection '${collection.name}'`));
75
- // Wait a bit before recreating
76
- await delay(2000);
77
- // Recreate the collection
78
- console.log(chalk.blue(`🔄 Recreating collection '${collection.name}'`));
79
- const newCollection = await db.createCollection(dbId, collection.$id, collection.name, collection.$permissions, collection.documentSecurity, collection.enabled);
80
- console.log(chalk.green(`✅ Recreated collection '${collection.name}'`));
81
- return newCollection;
82
- }
83
- catch (error) {
84
- console.log(chalk.red(`Failed to delete/recreate collection '${collection.name}': ${error}`));
85
- return null;
86
- }
87
- };
88
66
  /**
89
67
  * Enhanced index creation with proper status monitoring and retry logic
90
68
  */
@@ -99,17 +77,13 @@ export const createOrUpdateIndexWithStatusCheck = async (dbId, db, collectionId,
99
77
  if (success) {
100
78
  return true;
101
79
  }
102
- // If not successful and we have retries left, delete collection and try again
80
+ // If not successful and we have retries left, just retry the index creation
103
81
  if (retryCount < maxRetries) {
104
- console.log(chalk.yellow(`Index '${index.key}' failed/stuck, retrying...`));
105
- // Get fresh collection data
106
- const freshCollection = await db.getCollection(dbId, collectionId);
107
- // Delete and recreate collection
108
- const newCollection = await deleteAndRecreateCollectionForIndex(db, dbId, freshCollection, retryCount + 1);
109
- if (newCollection) {
110
- // Retry with the new collection
111
- return await createOrUpdateIndexWithStatusCheck(dbId, db, newCollection.$id, newCollection, index, retryCount + 1, maxRetries);
112
- }
82
+ console.log(chalk.yellow(`Index '${index.key}' failed/stuck, retrying (${retryCount + 1}/${maxRetries})...`));
83
+ // Wait a bit before retry
84
+ await new Promise(resolve => setTimeout(resolve, 2000 * (retryCount + 1)));
85
+ // Retry the index creation
86
+ return await createOrUpdateIndexWithStatusCheck(dbId, db, collectionId, collection, index, retryCount + 1, maxRetries);
113
87
  }
114
88
  console.log(chalk.red(`❌ Failed to create index '${index.key}' after ${maxRetries + 1} attempts`));
115
89
  return false;
@@ -130,22 +104,40 @@ export const createOrUpdateIndexWithStatusCheck = async (dbId, db, collectionId,
130
104
  */
131
105
  export const createOrUpdateIndexesWithStatusCheck = async (dbId, db, collectionId, collection, indexes) => {
132
106
  console.log(chalk.blue(`Creating/updating ${indexes.length} indexes with status monitoring...`));
133
- const failedIndexes = [];
134
- for (const index of indexes) {
135
- console.log(chalk.blue(`\n--- Processing index: ${index.key} ---`));
136
- const success = await createOrUpdateIndexWithStatusCheck(dbId, db, collectionId, collection, index);
137
- if (success) {
138
- console.log(chalk.green(`✅ Successfully created index: ${index.key}`));
139
- // Add delay between successful indexes
140
- await delay(1000);
107
+ let indexesToProcess = [...indexes];
108
+ let overallRetryCount = 0;
109
+ const maxOverallRetries = 3;
110
+ while (indexesToProcess.length > 0 && overallRetryCount < maxOverallRetries) {
111
+ const remainingIndexes = [...indexesToProcess];
112
+ indexesToProcess = []; // Reset for next iteration
113
+ console.log(chalk.blue(`\n=== Attempt ${overallRetryCount + 1}/${maxOverallRetries} - Processing ${remainingIndexes.length} indexes ===`));
114
+ for (const index of remainingIndexes) {
115
+ console.log(chalk.blue(`\n--- Processing index: ${index.key} ---`));
116
+ const success = await createOrUpdateIndexWithStatusCheck(dbId, db, collectionId, collection, index);
117
+ if (success) {
118
+ console.log(chalk.green(`✅ Successfully created index: ${index.key}`));
119
+ // Add delay between successful indexes
120
+ await delay(1000);
121
+ }
122
+ else {
123
+ console.log(chalk.red(`❌ Failed to create index: ${index.key}, will retry in next round`));
124
+ indexesToProcess.push(index); // Add back to retry list
125
+ }
126
+ }
127
+ if (indexesToProcess.length === 0) {
128
+ console.log(chalk.green(`\n✅ Successfully created all ${indexes.length} indexes`));
129
+ return true;
141
130
  }
142
- else {
143
- console.log(chalk.red(`❌ Failed to create index: ${index.key}`));
144
- failedIndexes.push(index.key);
131
+ overallRetryCount++;
132
+ if (overallRetryCount < maxOverallRetries) {
133
+ console.log(chalk.yellow(`\n⏳ Waiting 5 seconds before retrying ${indexesToProcess.length} failed indexes...`));
134
+ await delay(5000);
145
135
  }
146
136
  }
147
- if (failedIndexes.length > 0) {
148
- console.log(chalk.red(`\n❌ Failed to create ${failedIndexes.length} indexes: ${failedIndexes.join(', ')}`));
137
+ // If we get here, some indexes still failed after all retries
138
+ if (indexesToProcess.length > 0) {
139
+ console.log(chalk.red(`\n❌ Failed to create ${indexesToProcess.length} indexes after ${maxOverallRetries} attempts: ${indexesToProcess.map(i => i.key).join(', ')}`));
140
+ console.log(chalk.red(`This may indicate a fundamental issue with the index definitions or Appwrite instance`));
149
141
  return false;
150
142
  }
151
143
  console.log(chalk.green(`\n✅ Successfully created all ${indexes.length} indexes`));
@@ -1516,7 +1516,9 @@ export class InteractiveCLI {
1516
1516
  async comprehensiveTransfer() {
1517
1517
  MessageFormatter.info("Starting comprehensive transfer configuration...", { prefix: "Transfer" });
1518
1518
  try {
1519
- // Initialize controller to optionally load config if available (supports both YAML and TypeScript configs)\n await this.initControllerIfNeeded();\n \n // Check if user has an appwrite config for easier setup
1519
+ // Initialize controller to optionally load config if available (supports both YAML and TypeScript configs)
1520
+ await this.initControllerIfNeeded();
1521
+ // Check if user has an appwrite config for easier setup
1520
1522
  const hasAppwriteConfig = this.controller?.config?.appwriteEndpoint &&
1521
1523
  this.controller?.config?.appwriteProject &&
1522
1524
  this.controller?.config?.appwriteKey;
@@ -213,7 +213,9 @@ export class ComprehensiveTransfer {
213
213
  const attributesSuccess = await this.createCollectionAttributesWithStatusCheck(this.targetDatabases, dbId, targetCollection, attributesToCreate);
214
214
  if (!attributesSuccess) {
215
215
  MessageFormatter.error(`Failed to create some attributes for collection ${collection.name}`, undefined, { prefix: "Transfer" });
216
- // Continue with the transfer even if some attributes failed
216
+ MessageFormatter.error(`Skipping index creation and document transfer for collection ${collection.name} due to attribute failures`, undefined, { prefix: "Transfer" });
217
+ // Skip indexes and document transfer if attributes failed
218
+ continue;
217
219
  }
218
220
  else {
219
221
  MessageFormatter.success(`All attributes created successfully for collection ${collection.name}`, { prefix: "Transfer" });
@@ -223,7 +225,7 @@ export class ComprehensiveTransfer {
223
225
  const indexesSuccess = await this.createCollectionIndexesWithStatusCheck(dbId, this.targetDatabases, targetCollection.$id, targetCollection, collection.indexes);
224
226
  if (!indexesSuccess) {
225
227
  MessageFormatter.error(`Failed to create some indexes for collection ${collection.name}`, undefined, { prefix: "Transfer" });
226
- // Continue with the transfer even if some indexes failed
228
+ MessageFormatter.warning(`Proceeding with document transfer despite index failures for collection ${collection.name}`, { prefix: "Transfer" });
227
229
  }
228
230
  else {
229
231
  MessageFormatter.success(`All indexes created successfully for collection ${collection.name}`, { prefix: "Transfer" });
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.1.1",
4
+ "version": "1.1.3",
5
5
  "main": "src/main.ts",
6
6
  "type": "module",
7
7
  "repository": {
@@ -263,26 +263,65 @@ export const createOrUpdateAttributeWithStatusCheck = async (
263
263
  return true;
264
264
  }
265
265
 
266
- // If not successful and we have retries left, delete collection and try again
266
+ // If not successful and we have retries left, delete specific attribute and try again
267
267
  if (retryCount < maxRetries) {
268
- console.log(chalk.yellow(`Attribute '${attribute.key}' failed/stuck, retrying...`));
268
+ console.log(chalk.yellow(`Attribute '${attribute.key}' failed/stuck, deleting and retrying...`));
269
269
 
270
- // Get fresh collection data
271
- const freshCollection = await db.getCollection(dbId, collection.$id);
272
-
273
- // Delete and recreate collection
274
- const newCollection = await deleteAndRecreateCollection(db, dbId, freshCollection, retryCount + 1);
275
-
276
- if (newCollection) {
277
- // Retry with the new collection
270
+ // Try to delete the specific stuck attribute instead of the entire collection
271
+ try {
272
+ await db.deleteAttribute(dbId, collection.$id, attribute.key);
273
+ console.log(chalk.yellow(`Deleted stuck attribute '${attribute.key}', will retry creation`));
274
+
275
+ // Wait a bit before retry
276
+ await delay(3000);
277
+
278
+ // Get fresh collection data
279
+ const freshCollection = await db.getCollection(dbId, collection.$id);
280
+
281
+ // Retry with the same collection (attribute should be gone now)
278
282
  return await createOrUpdateAttributeWithStatusCheck(
279
283
  db,
280
284
  dbId,
281
- newCollection,
285
+ freshCollection,
282
286
  attribute,
283
287
  retryCount + 1,
284
288
  maxRetries
285
289
  );
290
+ } catch (deleteError) {
291
+ console.log(chalk.red(`Failed to delete stuck attribute '${attribute.key}': ${deleteError}`));
292
+
293
+ // If attribute deletion fails, only then try collection recreation as last resort
294
+ if (retryCount >= maxRetries - 1) {
295
+ console.log(chalk.yellow(`Last resort: Recreating collection for attribute '${attribute.key}'`));
296
+
297
+ // Get fresh collection data
298
+ const freshCollection = await db.getCollection(dbId, collection.$id);
299
+
300
+ // Delete and recreate collection
301
+ const newCollection = await deleteAndRecreateCollection(db, dbId, freshCollection, retryCount + 1);
302
+
303
+ if (newCollection) {
304
+ // Retry with the new collection
305
+ return await createOrUpdateAttributeWithStatusCheck(
306
+ db,
307
+ dbId,
308
+ newCollection,
309
+ attribute,
310
+ retryCount + 1,
311
+ maxRetries
312
+ );
313
+ }
314
+ } else {
315
+ // Continue to next retry without collection recreation
316
+ return await createOrUpdateAttributeWithStatusCheck(
317
+ db,
318
+ dbId,
319
+ collection,
320
+ attribute,
321
+ retryCount + 1,
322
+ maxRetries
323
+ );
324
+ }
286
325
  }
287
326
  }
288
327
 
@@ -810,42 +849,73 @@ export const createUpdateCollectionAttributesWithStatusCheck = async (
810
849
  }
811
850
  }
812
851
 
813
- // Create attributes ONE BY ONE with proper status checking
852
+ // Create attributes ONE BY ONE with proper status checking and persistent retry logic
814
853
  console.log(chalk.blue(`Creating ${attributes.length} attributes sequentially with status monitoring...`));
815
854
 
816
855
  let currentCollection = collection;
817
- const failedAttributes: string[] = [];
856
+ let attributesToProcess = [...attributes];
857
+ let overallRetryCount = 0;
858
+ const maxOverallRetries = 3;
818
859
 
819
- for (const attribute of attributes) {
820
- console.log(chalk.blue(`\n--- Processing attribute: ${attribute.key} ---`));
860
+ while (attributesToProcess.length > 0 && overallRetryCount < maxOverallRetries) {
861
+ const remainingAttributes = [...attributesToProcess];
862
+ attributesToProcess = []; // Reset for next iteration
821
863
 
822
- const success = await createOrUpdateAttributeWithStatusCheck(
823
- db,
824
- dbId,
825
- currentCollection,
826
- attribute
827
- );
864
+ console.log(chalk.blue(`\n=== Attempt ${overallRetryCount + 1}/${maxOverallRetries} - Processing ${remainingAttributes.length} attributes ===`));
828
865
 
829
- if (success) {
830
- console.log(chalk.green(`✅ Successfully created attribute: ${attribute.key}`));
866
+ for (const attribute of remainingAttributes) {
867
+ console.log(chalk.blue(`\n--- Processing attribute: ${attribute.key} ---`));
868
+
869
+ const success = await createOrUpdateAttributeWithStatusCheck(
870
+ db,
871
+ dbId,
872
+ currentCollection,
873
+ attribute
874
+ );
875
+
876
+ if (success) {
877
+ console.log(chalk.green(`✅ Successfully created attribute: ${attribute.key}`));
878
+
879
+ // Get updated collection data for next iteration
880
+ try {
881
+ currentCollection = await db.getCollection(dbId, collection.$id);
882
+ } catch (error) {
883
+ console.log(chalk.yellow(`Warning: Could not refresh collection data: ${error}`));
884
+ }
885
+
886
+ // Add delay between successful attributes
887
+ await delay(1000);
888
+ } else {
889
+ console.log(chalk.red(`❌ Failed to create attribute: ${attribute.key}, will retry in next round`));
890
+ attributesToProcess.push(attribute); // Add back to retry list
891
+ }
892
+ }
893
+
894
+ if (attributesToProcess.length === 0) {
895
+ console.log(chalk.green(`\n✅ Successfully created all ${attributes.length} attributes for collection: ${collection.name}`));
896
+ return true;
897
+ }
898
+
899
+ overallRetryCount++;
900
+
901
+ if (overallRetryCount < maxOverallRetries) {
902
+ console.log(chalk.yellow(`\n⏳ Waiting 5 seconds before retrying ${attributesToProcess.length} failed attributes...`));
903
+ await delay(5000);
831
904
 
832
- // Get updated collection data for next iteration
905
+ // Refresh collection data before retry
833
906
  try {
834
907
  currentCollection = await db.getCollection(dbId, collection.$id);
908
+ console.log(chalk.blue(`Refreshed collection data for retry`));
835
909
  } catch (error) {
836
- console.log(chalk.yellow(`Warning: Could not refresh collection data: ${error}`));
910
+ console.log(chalk.yellow(`Warning: Could not refresh collection data for retry: ${error}`));
837
911
  }
838
-
839
- // Add delay between successful attributes
840
- await delay(1000);
841
- } else {
842
- console.log(chalk.red(`❌ Failed to create attribute: ${attribute.key}`));
843
- failedAttributes.push(attribute.key);
844
912
  }
845
913
  }
846
914
 
847
- if (failedAttributes.length > 0) {
848
- console.log(chalk.red(`\n❌ Failed to create ${failedAttributes.length} attributes: ${failedAttributes.join(', ')}`));
915
+ // If we get here, some attributes still failed after all retries
916
+ if (attributesToProcess.length > 0) {
917
+ console.log(chalk.red(`\n❌ Failed to create ${attributesToProcess.length} attributes after ${maxOverallRetries} attempts: ${attributesToProcess.map(a => a.key).join(', ')}`));
918
+ console.log(chalk.red(`This may indicate a fundamental issue with the attribute definitions or Appwrite instance`));
849
919
  return false;
850
920
  }
851
921
 
@@ -98,44 +98,6 @@ const waitForIndexAvailable = async (
98
98
  return false;
99
99
  };
100
100
 
101
- /**
102
- * Delete collection and recreate for index retry (reused from attributes.ts)
103
- */
104
- const deleteAndRecreateCollectionForIndex = async (
105
- db: Databases,
106
- dbId: string,
107
- collection: Models.Collection,
108
- retryCount: number
109
- ): Promise<Models.Collection | null> => {
110
- try {
111
- console.log(chalk.yellow(`🗑️ Deleting collection '${collection.name}' for index retry ${retryCount}`));
112
-
113
- // Delete the collection
114
- await db.deleteCollection(dbId, collection.$id);
115
- console.log(chalk.yellow(`Deleted collection '${collection.name}'`));
116
-
117
- // Wait a bit before recreating
118
- await delay(2000);
119
-
120
- // Recreate the collection
121
- console.log(chalk.blue(`🔄 Recreating collection '${collection.name}'`));
122
- const newCollection = await db.createCollection(
123
- dbId,
124
- collection.$id,
125
- collection.name,
126
- collection.$permissions,
127
- collection.documentSecurity,
128
- collection.enabled
129
- );
130
-
131
- console.log(chalk.green(`✅ Recreated collection '${collection.name}'`));
132
- return newCollection;
133
-
134
- } catch (error) {
135
- console.log(chalk.red(`Failed to delete/recreate collection '${collection.name}': ${error}`));
136
- return null;
137
- }
138
- };
139
101
 
140
102
  /**
141
103
  * Enhanced index creation with proper status monitoring and retry logic
@@ -170,28 +132,23 @@ export const createOrUpdateIndexWithStatusCheck = async (
170
132
  return true;
171
133
  }
172
134
 
173
- // If not successful and we have retries left, delete collection and try again
135
+ // If not successful and we have retries left, just retry the index creation
174
136
  if (retryCount < maxRetries) {
175
- console.log(chalk.yellow(`Index '${index.key}' failed/stuck, retrying...`));
176
-
177
- // Get fresh collection data
178
- const freshCollection = await db.getCollection(dbId, collectionId);
137
+ console.log(chalk.yellow(`Index '${index.key}' failed/stuck, retrying (${retryCount + 1}/${maxRetries})...`));
179
138
 
180
- // Delete and recreate collection
181
- const newCollection = await deleteAndRecreateCollectionForIndex(db, dbId, freshCollection, retryCount + 1);
139
+ // Wait a bit before retry
140
+ await new Promise(resolve => setTimeout(resolve, 2000 * (retryCount + 1)));
182
141
 
183
- if (newCollection) {
184
- // Retry with the new collection
185
- return await createOrUpdateIndexWithStatusCheck(
186
- dbId,
187
- db,
188
- newCollection.$id,
189
- newCollection,
190
- index,
191
- retryCount + 1,
192
- maxRetries
193
- );
194
- }
142
+ // Retry the index creation
143
+ return await createOrUpdateIndexWithStatusCheck(
144
+ dbId,
145
+ db,
146
+ collectionId,
147
+ collection,
148
+ index,
149
+ retryCount + 1,
150
+ maxRetries
151
+ );
195
152
  }
196
153
 
197
154
  console.log(chalk.red(`❌ Failed to create index '${index.key}' after ${maxRetries + 1} attempts`));
@@ -233,32 +190,55 @@ export const createOrUpdateIndexesWithStatusCheck = async (
233
190
  ): Promise<boolean> => {
234
191
  console.log(chalk.blue(`Creating/updating ${indexes.length} indexes with status monitoring...`));
235
192
 
236
- const failedIndexes: string[] = [];
193
+ let indexesToProcess = [...indexes];
194
+ let overallRetryCount = 0;
195
+ const maxOverallRetries = 3;
237
196
 
238
- for (const index of indexes) {
239
- console.log(chalk.blue(`\n--- Processing index: ${index.key} ---`));
197
+ while (indexesToProcess.length > 0 && overallRetryCount < maxOverallRetries) {
198
+ const remainingIndexes = [...indexesToProcess];
199
+ indexesToProcess = []; // Reset for next iteration
240
200
 
241
- const success = await createOrUpdateIndexWithStatusCheck(
242
- dbId,
243
- db,
244
- collectionId,
245
- collection,
246
- index
247
- );
201
+ console.log(chalk.blue(`\n=== Attempt ${overallRetryCount + 1}/${maxOverallRetries} - Processing ${remainingIndexes.length} indexes ===`));
248
202
 
249
- if (success) {
250
- console.log(chalk.green(`✅ Successfully created index: ${index.key}`));
203
+ for (const index of remainingIndexes) {
204
+ console.log(chalk.blue(`\n--- Processing index: ${index.key} ---`));
251
205
 
252
- // Add delay between successful indexes
253
- await delay(1000);
254
- } else {
255
- console.log(chalk.red(`❌ Failed to create index: ${index.key}`));
256
- failedIndexes.push(index.key);
206
+ const success = await createOrUpdateIndexWithStatusCheck(
207
+ dbId,
208
+ db,
209
+ collectionId,
210
+ collection,
211
+ index
212
+ );
213
+
214
+ if (success) {
215
+ console.log(chalk.green(`✅ Successfully created index: ${index.key}`));
216
+
217
+ // Add delay between successful indexes
218
+ await delay(1000);
219
+ } else {
220
+ console.log(chalk.red(`❌ Failed to create index: ${index.key}, will retry in next round`));
221
+ indexesToProcess.push(index); // Add back to retry list
222
+ }
223
+ }
224
+
225
+ if (indexesToProcess.length === 0) {
226
+ console.log(chalk.green(`\n✅ Successfully created all ${indexes.length} indexes`));
227
+ return true;
228
+ }
229
+
230
+ overallRetryCount++;
231
+
232
+ if (overallRetryCount < maxOverallRetries) {
233
+ console.log(chalk.yellow(`\n⏳ Waiting 5 seconds before retrying ${indexesToProcess.length} failed indexes...`));
234
+ await delay(5000);
257
235
  }
258
236
  }
259
237
 
260
- if (failedIndexes.length > 0) {
261
- console.log(chalk.red(`\n❌ Failed to create ${failedIndexes.length} indexes: ${failedIndexes.join(', ')}`));
238
+ // If we get here, some indexes still failed after all retries
239
+ if (indexesToProcess.length > 0) {
240
+ console.log(chalk.red(`\n❌ Failed to create ${indexesToProcess.length} indexes after ${maxOverallRetries} attempts: ${indexesToProcess.map(i => i.key).join(', ')}`));
241
+ console.log(chalk.red(`This may indicate a fundamental issue with the index definitions or Appwrite instance`));
262
242
  return false;
263
243
  }
264
244
 
@@ -2052,7 +2052,10 @@ export class InteractiveCLI {
2052
2052
  MessageFormatter.info("Starting comprehensive transfer configuration...", { prefix: "Transfer" });
2053
2053
 
2054
2054
  try {
2055
- // Initialize controller to optionally load config if available (supports both YAML and TypeScript configs)\n await this.initControllerIfNeeded();\n \n // Check if user has an appwrite config for easier setup
2055
+ // Initialize controller to optionally load config if available (supports both YAML and TypeScript configs)
2056
+ await this.initControllerIfNeeded();
2057
+
2058
+ // Check if user has an appwrite config for easier setup
2056
2059
  const hasAppwriteConfig = this.controller?.config?.appwriteEndpoint &&
2057
2060
  this.controller?.config?.appwriteProject &&
2058
2061
  this.controller?.config?.appwriteKey;
@@ -331,7 +331,9 @@ export class ComprehensiveTransfer {
331
331
 
332
332
  if (!attributesSuccess) {
333
333
  MessageFormatter.error(`Failed to create some attributes for collection ${collection.name}`, undefined, { prefix: "Transfer" });
334
- // Continue with the transfer even if some attributes failed
334
+ MessageFormatter.error(`Skipping index creation and document transfer for collection ${collection.name} due to attribute failures`, undefined, { prefix: "Transfer" });
335
+ // Skip indexes and document transfer if attributes failed
336
+ continue;
335
337
  } else {
336
338
  MessageFormatter.success(`All attributes created successfully for collection ${collection.name}`, { prefix: "Transfer" });
337
339
  }
@@ -349,7 +351,7 @@ export class ComprehensiveTransfer {
349
351
 
350
352
  if (!indexesSuccess) {
351
353
  MessageFormatter.error(`Failed to create some indexes for collection ${collection.name}`, undefined, { prefix: "Transfer" });
352
- // Continue with the transfer even if some indexes failed
354
+ MessageFormatter.warning(`Proceeding with document transfer despite index failures for collection ${collection.name}`, { prefix: "Transfer" });
353
355
  } else {
354
356
  MessageFormatter.success(`All indexes created successfully for collection ${collection.name}`, { prefix: "Transfer" });
355
357
  }