appwrite-utils-cli 0.9.91 → 0.9.92
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 +1 -0
- package/dist/collections/attributes.js +20 -2
- package/dist/interactiveCLI.js +9 -2
- package/package.json +1 -1
- package/src/collections/attributes.ts +23 -2
- package/src/interactiveCLI.ts +25 -18
package/README.md
CHANGED
@@ -125,6 +125,7 @@ This updated CLI ensures that developers have robust tools at their fingertips t
|
|
125
125
|
|
126
126
|
## Changelog
|
127
127
|
|
128
|
+
- 0.9.92: Fixed `createOrUpdateAttributes` so it deletes attributes that don't exist in local config when you are running `syncDb`. Also updated the database and collection selection, so it won't try and fetch the collections and databases that don't exist (ones you picked from local config) and error
|
128
129
|
- 0.9.91: Fixed another webpack error, screw you react (but you're supported now so I guess not-screw-you)
|
129
130
|
- 0.9.90: Fixed Webpack errors (why tf does webpack add an extra `default`...???)
|
130
131
|
- 0.9.80: Fixed collections not being unique between local and remote
|
@@ -232,10 +232,28 @@ export const createOrUpdateAttribute = async (db, dbId, collection, attribute) =
|
|
232
232
|
};
|
233
233
|
export const createUpdateCollectionAttributes = async (db, dbId, collection, attributes) => {
|
234
234
|
console.log(chalk.green(`Creating/Updating attributes for collection: ${collection.name}`));
|
235
|
+
// @ts-expect-error
|
236
|
+
const existingAttributes = collection.attributes.map((attr) => parseAttribute(attr)) || [];
|
237
|
+
const attributesToRemove = existingAttributes.filter((attr) => !attributes.some((a) => a.key === attr.key));
|
238
|
+
const indexesToRemove = collection.indexes.filter((index) => attributesToRemove.some((attr) => index.attributes.includes(attr.key)));
|
239
|
+
if (attributesToRemove.length > 0) {
|
240
|
+
if (indexesToRemove.length > 0) {
|
241
|
+
console.log(chalk.red(`Removing indexes as they rely on an attribute that is being removed: ${indexesToRemove.map((index) => index.key).join(", ")}`));
|
242
|
+
for (const index of indexesToRemove) {
|
243
|
+
await tryAwaitWithRetry(async () => await db.deleteIndex(dbId, collection.$id, index.key));
|
244
|
+
await delay(100);
|
245
|
+
}
|
246
|
+
}
|
247
|
+
for (const attr of attributesToRemove) {
|
248
|
+
console.log(chalk.red(`Removing attribute: ${attr.key} as it is no longer in the collection`));
|
249
|
+
await tryAwaitWithRetry(async () => await db.deleteAttribute(dbId, collection.$id, attr.key));
|
250
|
+
await delay(50);
|
251
|
+
}
|
252
|
+
}
|
235
253
|
const batchSize = 3;
|
236
254
|
for (let i = 0; i < attributes.length; i += batchSize) {
|
237
255
|
const batch = attributes.slice(i, i + batchSize);
|
238
|
-
const attributePromises = batch.map((attribute) => createOrUpdateAttribute(db, dbId, collection, attribute));
|
256
|
+
const attributePromises = batch.map((attribute) => tryAwaitWithRetry(async () => await createOrUpdateAttribute(db, dbId, collection, attribute)));
|
239
257
|
const results = await Promise.allSettled(attributePromises);
|
240
258
|
results.forEach((result) => {
|
241
259
|
if (result.status === "rejected") {
|
@@ -243,7 +261,7 @@ export const createUpdateCollectionAttributes = async (db, dbId, collection, att
|
|
243
261
|
}
|
244
262
|
});
|
245
263
|
// Add delay after each batch
|
246
|
-
await delay(
|
264
|
+
await delay(200);
|
247
265
|
}
|
248
266
|
console.log(`Finished creating/updating attributes for collection: ${collection.name}`);
|
249
267
|
};
|
package/dist/interactiveCLI.js
CHANGED
@@ -4,7 +4,7 @@ import { createEmptyCollection, setupDirsFiles } from "./utils/setupFiles.js";
|
|
4
4
|
import { fetchAllDatabases } from "./databases/methods.js";
|
5
5
|
import { fetchAllCollections } from "./collections/methods.js";
|
6
6
|
import { listBuckets, createBucket } from "./storage/methods.js";
|
7
|
-
import { Databases, Storage, Client, Compression, } from "node-appwrite";
|
7
|
+
import { Databases, Storage, Client, Compression, Query, } from "node-appwrite";
|
8
8
|
import { getClient } from "./utils/getClientFromConfig.js";
|
9
9
|
import { parseAttribute, PermissionToAppwritePermission } from "appwrite-utils";
|
10
10
|
import { ulid } from "ulidx";
|
@@ -130,7 +130,14 @@ export class InteractiveCLI {
|
|
130
130
|
}
|
131
131
|
async selectCollections(database, databasesClient, message, multiSelect = true) {
|
132
132
|
await this.initControllerIfNeeded();
|
133
|
-
const
|
133
|
+
const dbExists = await databasesClient.list([Query.equal("name", database.name)]);
|
134
|
+
let collections = [];
|
135
|
+
if (dbExists.total === 0) {
|
136
|
+
console.log(chalk.red(`Database "${database.name}" does not exist, using only local collection options`));
|
137
|
+
}
|
138
|
+
else {
|
139
|
+
collections = await fetchAllCollections(database.$id, databasesClient);
|
140
|
+
}
|
134
141
|
const configCollections = this.getLocalCollections();
|
135
142
|
const collectionNames = collections.map((c) => c.name).concat(configCollections.map((c) => c.name));
|
136
143
|
const allCollectionNamesUnique = Array.from(new Set(collectionNames));
|
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": "0.9.
|
4
|
+
"version": "0.9.92",
|
5
5
|
"main": "src/main.ts",
|
6
6
|
"type": "module",
|
7
7
|
"repository": {
|
@@ -471,11 +471,32 @@ export const createUpdateCollectionAttributes = async (
|
|
471
471
|
chalk.green(`Creating/Updating attributes for collection: ${collection.name}`)
|
472
472
|
);
|
473
473
|
|
474
|
+
// @ts-expect-error
|
475
|
+
const existingAttributes: Attribute[] = collection.attributes.map((attr) => parseAttribute(attr)) || [];
|
476
|
+
|
477
|
+
const attributesToRemove = existingAttributes.filter((attr) => !attributes.some((a) => a.key === attr.key));
|
478
|
+
const indexesToRemove = collection.indexes.filter((index) => attributesToRemove.some((attr) => index.attributes.includes(attr.key)));
|
479
|
+
|
480
|
+
if (attributesToRemove.length > 0) {
|
481
|
+
if (indexesToRemove.length > 0) {
|
482
|
+
console.log(chalk.red(`Removing indexes as they rely on an attribute that is being removed: ${indexesToRemove.map((index) => index.key).join(", ")}`));
|
483
|
+
for (const index of indexesToRemove) {
|
484
|
+
await tryAwaitWithRetry(async () => await db.deleteIndex(dbId, collection.$id, index.key));
|
485
|
+
await delay(100);
|
486
|
+
}
|
487
|
+
}
|
488
|
+
for (const attr of attributesToRemove) {
|
489
|
+
console.log(chalk.red(`Removing attribute: ${attr.key} as it is no longer in the collection`));
|
490
|
+
await tryAwaitWithRetry(async () => await db.deleteAttribute(dbId, collection.$id, attr.key));
|
491
|
+
await delay(50);
|
492
|
+
}
|
493
|
+
}
|
494
|
+
|
474
495
|
const batchSize = 3;
|
475
496
|
for (let i = 0; i < attributes.length; i += batchSize) {
|
476
497
|
const batch = attributes.slice(i, i + batchSize);
|
477
498
|
const attributePromises = batch.map((attribute) =>
|
478
|
-
createOrUpdateAttribute(db, dbId, collection, attribute)
|
499
|
+
tryAwaitWithRetry(async () => await createOrUpdateAttribute(db, dbId, collection, attribute))
|
479
500
|
);
|
480
501
|
|
481
502
|
const results = await Promise.allSettled(attributePromises);
|
@@ -486,7 +507,7 @@ export const createUpdateCollectionAttributes = async (
|
|
486
507
|
});
|
487
508
|
|
488
509
|
// Add delay after each batch
|
489
|
-
await delay(
|
510
|
+
await delay(200);
|
490
511
|
}
|
491
512
|
console.log(
|
492
513
|
`Finished creating/updating attributes for collection: ${collection.name}`
|
package/src/interactiveCLI.ts
CHANGED
@@ -10,6 +10,7 @@ import {
|
|
10
10
|
Client,
|
11
11
|
type Models,
|
12
12
|
Compression,
|
13
|
+
Query,
|
13
14
|
} from "node-appwrite";
|
14
15
|
import { getClient } from "./utils/getClientFromConfig.js";
|
15
16
|
import type { TransferOptions } from "./migrations/transfer.js";
|
@@ -37,7 +38,7 @@ enum CHOICES {
|
|
37
38
|
export class InteractiveCLI {
|
38
39
|
private controller: UtilsController | undefined;
|
39
40
|
|
40
|
-
constructor(private currentDir: string) {}
|
41
|
+
constructor(private currentDir: string) { }
|
41
42
|
|
42
43
|
async run(): Promise<void> {
|
43
44
|
console.log(chalk.green("Welcome to Appwrite Utils CLI Tool by Zach Handley"));
|
@@ -129,12 +130,12 @@ export class InteractiveCLI {
|
|
129
130
|
}
|
130
131
|
return acc;
|
131
132
|
}, [] as Models.Database[]);
|
132
|
-
|
133
|
+
|
133
134
|
const choices = allDatabases
|
134
135
|
.sort((a, b) => a.name.localeCompare(b.name))
|
135
136
|
.map((db) => ({ name: db.name, value: db }))
|
136
137
|
.filter((db) => db.name.toLowerCase() !== "migrations");
|
137
|
-
|
138
|
+
|
138
139
|
const { selectedDatabases } = await inquirer.prompt([
|
139
140
|
{
|
140
141
|
type: multiSelect ? "checkbox" : "list",
|
@@ -145,7 +146,7 @@ export class InteractiveCLI {
|
|
145
146
|
pageSize: 10,
|
146
147
|
},
|
147
148
|
]);
|
148
|
-
|
149
|
+
|
149
150
|
return selectedDatabases;
|
150
151
|
}
|
151
152
|
|
@@ -153,13 +154,19 @@ export class InteractiveCLI {
|
|
153
154
|
database: Models.Database,
|
154
155
|
databasesClient: Databases,
|
155
156
|
message: string,
|
156
|
-
multiSelect = true
|
157
|
+
multiSelect = true
|
157
158
|
): Promise<Models.Collection[]> {
|
158
159
|
await this.initControllerIfNeeded();
|
159
|
-
const
|
160
|
-
|
161
|
-
|
162
|
-
|
160
|
+
const dbExists = await databasesClient.list([Query.equal("name", database.name)]);
|
161
|
+
let collections: Models.Collection[] = [];
|
162
|
+
if (dbExists.total === 0) {
|
163
|
+
console.log(chalk.red(`Database "${database.name}" does not exist, using only local collection options`));
|
164
|
+
} else {
|
165
|
+
collections = await fetchAllCollections(
|
166
|
+
database.$id,
|
167
|
+
databasesClient
|
168
|
+
);
|
169
|
+
}
|
163
170
|
const configCollections = this.getLocalCollections();
|
164
171
|
const collectionNames = collections.map((c) => c.name).concat(configCollections.map((c) => c.name));
|
165
172
|
const allCollectionNamesUnique = Array.from(new Set(collectionNames));
|
@@ -690,10 +697,10 @@ export class InteractiveCLI {
|
|
690
697
|
let targetDatabases: Models.Database[];
|
691
698
|
let remoteOptions:
|
692
699
|
| {
|
693
|
-
|
694
|
-
|
695
|
-
|
696
|
-
|
700
|
+
transferEndpoint: string;
|
701
|
+
transferProject: string;
|
702
|
+
transferKey: string;
|
703
|
+
}
|
697
704
|
| undefined;
|
698
705
|
|
699
706
|
if (isRemote) {
|
@@ -771,12 +778,12 @@ export class InteractiveCLI {
|
|
771
778
|
const sourceStorage = new Storage(this.controller!.appwriteServer!);
|
772
779
|
const targetStorage = isRemote
|
773
780
|
? new Storage(
|
774
|
-
|
775
|
-
|
776
|
-
|
777
|
-
|
778
|
-
)
|
781
|
+
getClient(
|
782
|
+
remoteOptions!.transferEndpoint,
|
783
|
+
remoteOptions!.transferProject,
|
784
|
+
remoteOptions!.transferKey
|
779
785
|
)
|
786
|
+
)
|
780
787
|
: sourceStorage;
|
781
788
|
|
782
789
|
const sourceBuckets = await listBuckets(sourceStorage);
|