appwrite-utils-cli 1.1.3 → 1.1.4

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.
@@ -459,17 +459,53 @@ export const createUpdateCollectionAttributesWithStatusCheck = async (db, dbId,
459
459
  await delay(500); // Longer delay for deletions
460
460
  }
461
461
  }
462
- // Create attributes ONE BY ONE with proper status checking and persistent retry logic
463
- console.log(chalk.blue(`Creating ${attributes.length} attributes sequentially with status monitoring...`));
462
+ // First, get fresh collection data and determine which attributes actually need processing
463
+ console.log(chalk.blue(`Analyzing ${attributes.length} attributes to determine which need processing...`));
464
464
  let currentCollection = collection;
465
- let attributesToProcess = [...attributes];
465
+ try {
466
+ currentCollection = await db.getCollection(dbId, collection.$id);
467
+ }
468
+ catch (error) {
469
+ console.log(chalk.yellow(`Warning: Could not refresh collection data: ${error}`));
470
+ }
471
+ const existingAttributesMap = new Map();
472
+ try {
473
+ // @ts-expect-error
474
+ const parsedAttributes = currentCollection.attributes.map((attr) => parseAttribute(attr));
475
+ parsedAttributes.forEach(attr => existingAttributesMap.set(attr.key, attr));
476
+ }
477
+ catch (error) {
478
+ console.log(chalk.yellow(`Warning: Could not parse existing attributes: ${error}`));
479
+ }
480
+ // Filter to only attributes that need processing (new or changed)
481
+ const attributesToProcess = attributes.filter(attribute => {
482
+ const existing = existingAttributesMap.get(attribute.key);
483
+ if (!existing) {
484
+ console.log(chalk.blue(`āž• New attribute: ${attribute.key}`));
485
+ return true;
486
+ }
487
+ const needsUpdate = !attributesSame(existing, attribute);
488
+ if (needsUpdate) {
489
+ console.log(chalk.blue(`šŸ”„ Changed attribute: ${attribute.key}`));
490
+ }
491
+ else {
492
+ console.log(chalk.gray(`āœ… Unchanged attribute: ${attribute.key} (skipping)`));
493
+ }
494
+ return needsUpdate;
495
+ });
496
+ if (attributesToProcess.length === 0) {
497
+ console.log(chalk.green(`āœ… All ${attributes.length} attributes are already up to date for collection: ${collection.name}`));
498
+ return true;
499
+ }
500
+ console.log(chalk.blue(`Creating ${attributesToProcess.length} attributes sequentially with status monitoring...`));
501
+ let remainingAttributes = [...attributesToProcess];
466
502
  let overallRetryCount = 0;
467
503
  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) {
504
+ while (remainingAttributes.length > 0 && overallRetryCount < maxOverallRetries) {
505
+ const attributesToProcessThisRound = [...remainingAttributes];
506
+ remainingAttributes = []; // Reset for next iteration
507
+ console.log(chalk.blue(`\n=== Attempt ${overallRetryCount + 1}/${maxOverallRetries} - Processing ${attributesToProcessThisRound.length} attributes ===`));
508
+ for (const attribute of attributesToProcessThisRound) {
473
509
  console.log(chalk.blue(`\n--- Processing attribute: ${attribute.key} ---`));
474
510
  const success = await createOrUpdateAttributeWithStatusCheck(db, dbId, currentCollection, attribute);
475
511
  if (success) {
@@ -486,11 +522,11 @@ export const createUpdateCollectionAttributesWithStatusCheck = async (db, dbId,
486
522
  }
487
523
  else {
488
524
  console.log(chalk.red(`āŒ Failed to create attribute: ${attribute.key}, will retry in next round`));
489
- attributesToProcess.push(attribute); // Add back to retry list
525
+ remainingAttributes.push(attribute); // Add back to retry list
490
526
  }
491
527
  }
492
- if (attributesToProcess.length === 0) {
493
- console.log(chalk.green(`\nāœ… Successfully created all ${attributes.length} attributes for collection: ${collection.name}`));
528
+ if (remainingAttributes.length === 0) {
529
+ console.log(chalk.green(`\nāœ… Successfully created all ${attributesToProcess.length} attributes for collection: ${collection.name}`));
494
530
  return true;
495
531
  }
496
532
  overallRetryCount++;
@@ -69,7 +69,16 @@ retryCount = 0, maxRetries = 5) => {
69
69
  export const createOrUpdateIndexWithStatusCheck = async (dbId, db, collectionId, collection, index, retryCount = 0, maxRetries = 5) => {
70
70
  console.log(chalk.blue(`Creating/updating index '${index.key}' (attempt ${retryCount + 1}/${maxRetries + 1})`));
71
71
  try {
72
- // First, try to create/update the index using existing logic
72
+ // First, validate that all required attributes exist
73
+ const freshCollection = await db.getCollection(dbId, collectionId);
74
+ const existingAttributeKeys = freshCollection.attributes.map((attr) => attr.key);
75
+ const missingAttributes = index.attributes.filter(attr => !existingAttributeKeys.includes(attr));
76
+ if (missingAttributes.length > 0) {
77
+ console.log(chalk.red(`āŒ Index '${index.key}' cannot be created: missing attributes [${missingAttributes.join(', ')}]`));
78
+ console.log(chalk.red(`Available attributes: [${existingAttributeKeys.join(', ')}]`));
79
+ return false; // Don't retry if attributes are missing
80
+ }
81
+ // Try to create/update the index using existing logic
73
82
  await createOrUpdateIndex(dbId, db, collectionId, index);
74
83
  // Now wait for the index to become available
75
84
  const success = await waitForIndexAvailable(db, dbId, collectionId, index.key, 60000, // 1 minute timeout
@@ -89,7 +98,16 @@ export const createOrUpdateIndexWithStatusCheck = async (dbId, db, collectionId,
89
98
  return false;
90
99
  }
91
100
  catch (error) {
92
- console.log(chalk.red(`Error creating index '${index.key}': ${error}`));
101
+ const errorMessage = error instanceof Error ? error.message : String(error);
102
+ console.log(chalk.red(`Error creating index '${index.key}': ${errorMessage}`));
103
+ // Check if this is a permanent error that shouldn't be retried
104
+ if (errorMessage.includes('not found') ||
105
+ errorMessage.includes('missing') ||
106
+ errorMessage.includes('does not exist') ||
107
+ errorMessage.includes('attribute') && errorMessage.includes('not found')) {
108
+ console.log(chalk.red(`āŒ Index '${index.key}' has permanent error - not retrying`));
109
+ return false;
110
+ }
93
111
  if (retryCount < maxRetries) {
94
112
  console.log(chalk.yellow(`Retrying index '${index.key}' due to error...`));
95
113
  // Wait a bit before retry
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.3",
4
+ "version": "1.1.4",
5
5
  "main": "src/main.ts",
6
6
  "type": "module",
7
7
  "repository": {
@@ -849,21 +849,60 @@ export const createUpdateCollectionAttributesWithStatusCheck = async (
849
849
  }
850
850
  }
851
851
 
852
- // Create attributes ONE BY ONE with proper status checking and persistent retry logic
853
- console.log(chalk.blue(`Creating ${attributes.length} attributes sequentially with status monitoring...`));
852
+ // First, get fresh collection data and determine which attributes actually need processing
853
+ console.log(chalk.blue(`Analyzing ${attributes.length} attributes to determine which need processing...`));
854
854
 
855
855
  let currentCollection = collection;
856
- let attributesToProcess = [...attributes];
856
+ try {
857
+ currentCollection = await db.getCollection(dbId, collection.$id);
858
+ } catch (error) {
859
+ console.log(chalk.yellow(`Warning: Could not refresh collection data: ${error}`));
860
+ }
861
+
862
+ const existingAttributesMap = new Map<string, Attribute>();
863
+ try {
864
+ // @ts-expect-error
865
+ const parsedAttributes = currentCollection.attributes.map((attr) => parseAttribute(attr));
866
+ parsedAttributes.forEach(attr => existingAttributesMap.set(attr.key, attr));
867
+ } catch (error) {
868
+ console.log(chalk.yellow(`Warning: Could not parse existing attributes: ${error}`));
869
+ }
870
+
871
+ // Filter to only attributes that need processing (new or changed)
872
+ const attributesToProcess = attributes.filter(attribute => {
873
+ const existing = existingAttributesMap.get(attribute.key);
874
+ if (!existing) {
875
+ console.log(chalk.blue(`āž• New attribute: ${attribute.key}`));
876
+ return true;
877
+ }
878
+
879
+ const needsUpdate = !attributesSame(existing, attribute);
880
+ if (needsUpdate) {
881
+ console.log(chalk.blue(`šŸ”„ Changed attribute: ${attribute.key}`));
882
+ } else {
883
+ console.log(chalk.gray(`āœ… Unchanged attribute: ${attribute.key} (skipping)`));
884
+ }
885
+ return needsUpdate;
886
+ });
887
+
888
+ if (attributesToProcess.length === 0) {
889
+ console.log(chalk.green(`āœ… All ${attributes.length} attributes are already up to date for collection: ${collection.name}`));
890
+ return true;
891
+ }
892
+
893
+ console.log(chalk.blue(`Creating ${attributesToProcess.length} attributes sequentially with status monitoring...`));
894
+
895
+ let remainingAttributes = [...attributesToProcess];
857
896
  let overallRetryCount = 0;
858
897
  const maxOverallRetries = 3;
859
898
 
860
- while (attributesToProcess.length > 0 && overallRetryCount < maxOverallRetries) {
861
- const remainingAttributes = [...attributesToProcess];
862
- attributesToProcess = []; // Reset for next iteration
899
+ while (remainingAttributes.length > 0 && overallRetryCount < maxOverallRetries) {
900
+ const attributesToProcessThisRound = [...remainingAttributes];
901
+ remainingAttributes = []; // Reset for next iteration
863
902
 
864
- console.log(chalk.blue(`\n=== Attempt ${overallRetryCount + 1}/${maxOverallRetries} - Processing ${remainingAttributes.length} attributes ===`));
903
+ console.log(chalk.blue(`\n=== Attempt ${overallRetryCount + 1}/${maxOverallRetries} - Processing ${attributesToProcessThisRound.length} attributes ===`));
865
904
 
866
- for (const attribute of remainingAttributes) {
905
+ for (const attribute of attributesToProcessThisRound) {
867
906
  console.log(chalk.blue(`\n--- Processing attribute: ${attribute.key} ---`));
868
907
 
869
908
  const success = await createOrUpdateAttributeWithStatusCheck(
@@ -887,12 +926,12 @@ export const createUpdateCollectionAttributesWithStatusCheck = async (
887
926
  await delay(1000);
888
927
  } else {
889
928
  console.log(chalk.red(`āŒ Failed to create attribute: ${attribute.key}, will retry in next round`));
890
- attributesToProcess.push(attribute); // Add back to retry list
929
+ remainingAttributes.push(attribute); // Add back to retry list
891
930
  }
892
931
  }
893
932
 
894
- if (attributesToProcess.length === 0) {
895
- console.log(chalk.green(`\nāœ… Successfully created all ${attributes.length} attributes for collection: ${collection.name}`));
933
+ if (remainingAttributes.length === 0) {
934
+ console.log(chalk.green(`\nāœ… Successfully created all ${attributesToProcess.length} attributes for collection: ${collection.name}`));
896
935
  return true;
897
936
  }
898
937
 
@@ -114,7 +114,19 @@ export const createOrUpdateIndexWithStatusCheck = async (
114
114
  console.log(chalk.blue(`Creating/updating index '${index.key}' (attempt ${retryCount + 1}/${maxRetries + 1})`));
115
115
 
116
116
  try {
117
- // First, try to create/update the index using existing logic
117
+ // First, validate that all required attributes exist
118
+ const freshCollection = await db.getCollection(dbId, collectionId);
119
+ const existingAttributeKeys = freshCollection.attributes.map((attr: any) => attr.key);
120
+
121
+ const missingAttributes = index.attributes.filter(attr => !existingAttributeKeys.includes(attr));
122
+
123
+ if (missingAttributes.length > 0) {
124
+ console.log(chalk.red(`āŒ Index '${index.key}' cannot be created: missing attributes [${missingAttributes.join(', ')}]`));
125
+ console.log(chalk.red(`Available attributes: [${existingAttributeKeys.join(', ')}]`));
126
+ return false; // Don't retry if attributes are missing
127
+ }
128
+
129
+ // Try to create/update the index using existing logic
118
130
  await createOrUpdateIndex(dbId, db, collectionId, index);
119
131
 
120
132
  // Now wait for the index to become available
@@ -155,7 +167,17 @@ export const createOrUpdateIndexWithStatusCheck = async (
155
167
  return false;
156
168
 
157
169
  } catch (error) {
158
- console.log(chalk.red(`Error creating index '${index.key}': ${error}`));
170
+ const errorMessage = error instanceof Error ? error.message : String(error);
171
+ console.log(chalk.red(`Error creating index '${index.key}': ${errorMessage}`));
172
+
173
+ // Check if this is a permanent error that shouldn't be retried
174
+ if (errorMessage.includes('not found') ||
175
+ errorMessage.includes('missing') ||
176
+ errorMessage.includes('does not exist') ||
177
+ errorMessage.includes('attribute') && errorMessage.includes('not found')) {
178
+ console.log(chalk.red(`āŒ Index '${index.key}' has permanent error - not retrying`));
179
+ return false;
180
+ }
159
181
 
160
182
  if (retryCount < maxRetries) {
161
183
  console.log(chalk.yellow(`Retrying index '${index.key}' due to error...`));