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
|
-
//
|
463
|
-
console.log(chalk.blue(`
|
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
|
-
|
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 (
|
469
|
-
const
|
470
|
-
|
471
|
-
console.log(chalk.blue(`\n=== Attempt ${overallRetryCount + 1}/${maxOverallRetries} - Processing ${
|
472
|
-
for (const attribute of
|
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
|
-
|
525
|
+
remainingAttributes.push(attribute); // Add back to retry list
|
490
526
|
}
|
491
527
|
}
|
492
|
-
if (
|
493
|
-
console.log(chalk.green(`\nā
Successfully created all ${
|
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,
|
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
|
-
|
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.
|
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
|
-
//
|
853
|
-
console.log(chalk.blue(`
|
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
|
-
|
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 (
|
861
|
-
const
|
862
|
-
|
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 ${
|
903
|
+
console.log(chalk.blue(`\n=== Attempt ${overallRetryCount + 1}/${maxOverallRetries} - Processing ${attributesToProcessThisRound.length} attributes ===`));
|
865
904
|
|
866
|
-
for (const attribute of
|
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
|
-
|
929
|
+
remainingAttributes.push(attribute); // Add back to retry list
|
891
930
|
}
|
892
931
|
}
|
893
932
|
|
894
|
-
if (
|
895
|
-
console.log(chalk.green(`\nā
Successfully created all ${
|
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,
|
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
|
-
|
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...`));
|