appwrite-utils-cli 1.1.2 ā 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.
- package/dist/collections/attributes.js +47 -11
- package/dist/collections/indexes.js +26 -34
- package/package.json +1 -1
- package/src/collections/attributes.ts +50 -11
- package/src/collections/indexes.ts +38 -59
@@ -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++;
|
@@ -63,35 +63,22 @@ 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
|
*/
|
91
69
|
export const createOrUpdateIndexWithStatusCheck = async (dbId, db, collectionId, collection, index, retryCount = 0, maxRetries = 5) => {
|
92
70
|
console.log(chalk.blue(`Creating/updating index '${index.key}' (attempt ${retryCount + 1}/${maxRetries + 1})`));
|
93
71
|
try {
|
94
|
-
// 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
|
95
82
|
await createOrUpdateIndex(dbId, db, collectionId, index);
|
96
83
|
// Now wait for the index to become available
|
97
84
|
const success = await waitForIndexAvailable(db, dbId, collectionId, index.key, 60000, // 1 minute timeout
|
@@ -99,23 +86,28 @@ export const createOrUpdateIndexWithStatusCheck = async (dbId, db, collectionId,
|
|
99
86
|
if (success) {
|
100
87
|
return true;
|
101
88
|
}
|
102
|
-
// If not successful and we have retries left,
|
89
|
+
// If not successful and we have retries left, just retry the index creation
|
103
90
|
if (retryCount < maxRetries) {
|
104
|
-
console.log(chalk.yellow(`Index '${index.key}' failed/stuck, retrying...`));
|
105
|
-
//
|
106
|
-
|
107
|
-
//
|
108
|
-
|
109
|
-
if (newCollection) {
|
110
|
-
// Retry with the new collection
|
111
|
-
return await createOrUpdateIndexWithStatusCheck(dbId, db, newCollection.$id, newCollection, index, retryCount + 1, maxRetries);
|
112
|
-
}
|
91
|
+
console.log(chalk.yellow(`Index '${index.key}' failed/stuck, retrying (${retryCount + 1}/${maxRetries})...`));
|
92
|
+
// Wait a bit before retry
|
93
|
+
await new Promise(resolve => setTimeout(resolve, 2000 * (retryCount + 1)));
|
94
|
+
// Retry the index creation
|
95
|
+
return await createOrUpdateIndexWithStatusCheck(dbId, db, collectionId, collection, index, retryCount + 1, maxRetries);
|
113
96
|
}
|
114
97
|
console.log(chalk.red(`ā Failed to create index '${index.key}' after ${maxRetries + 1} attempts`));
|
115
98
|
return false;
|
116
99
|
}
|
117
100
|
catch (error) {
|
118
|
-
|
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
|
+
}
|
119
111
|
if (retryCount < maxRetries) {
|
120
112
|
console.log(chalk.yellow(`Retrying index '${index.key}' due to error...`));
|
121
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
|
|
@@ -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
|
@@ -152,7 +114,19 @@ export const createOrUpdateIndexWithStatusCheck = async (
|
|
152
114
|
console.log(chalk.blue(`Creating/updating index '${index.key}' (attempt ${retryCount + 1}/${maxRetries + 1})`));
|
153
115
|
|
154
116
|
try {
|
155
|
-
// 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
|
156
130
|
await createOrUpdateIndex(dbId, db, collectionId, index);
|
157
131
|
|
158
132
|
// Now wait for the index to become available
|
@@ -170,35 +144,40 @@ export const createOrUpdateIndexWithStatusCheck = async (
|
|
170
144
|
return true;
|
171
145
|
}
|
172
146
|
|
173
|
-
// If not successful and we have retries left,
|
147
|
+
// If not successful and we have retries left, just retry the index creation
|
174
148
|
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);
|
149
|
+
console.log(chalk.yellow(`Index '${index.key}' failed/stuck, retrying (${retryCount + 1}/${maxRetries})...`));
|
179
150
|
|
180
|
-
//
|
181
|
-
|
151
|
+
// Wait a bit before retry
|
152
|
+
await new Promise(resolve => setTimeout(resolve, 2000 * (retryCount + 1)));
|
182
153
|
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
187
|
-
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
);
|
194
|
-
}
|
154
|
+
// Retry the index creation
|
155
|
+
return await createOrUpdateIndexWithStatusCheck(
|
156
|
+
dbId,
|
157
|
+
db,
|
158
|
+
collectionId,
|
159
|
+
collection,
|
160
|
+
index,
|
161
|
+
retryCount + 1,
|
162
|
+
maxRetries
|
163
|
+
);
|
195
164
|
}
|
196
165
|
|
197
166
|
console.log(chalk.red(`ā Failed to create index '${index.key}' after ${maxRetries + 1} attempts`));
|
198
167
|
return false;
|
199
168
|
|
200
169
|
} catch (error) {
|
201
|
-
|
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
|
+
}
|
202
181
|
|
203
182
|
if (retryCount < maxRetries) {
|
204
183
|
console.log(chalk.yellow(`Retrying index '${index.key}' due to error...`));
|