appwrite-utils-cli 0.10.66 → 0.10.67
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 +2 -0
- package/dist/main.js +7 -1
- package/dist/migrations/transfer.d.ts +6 -1
- package/dist/migrations/transfer.js +53 -1
- package/dist/utilsController.d.ts +1 -0
- package/dist/utilsController.js +16 -2
- package/package.json +1 -1
- package/src/main.ts +8 -1
- package/src/migrations/transfer.ts +93 -0
- package/src/utilsController.ts +32 -1
package/README.md
CHANGED
@@ -72,6 +72,7 @@ Available options:
|
|
72
72
|
- `--projectId`: Set the Appwrite project ID
|
73
73
|
- `--apiKey`: Set the Appwrite API key
|
74
74
|
- `--transfer`: Transfer data between databases or collections
|
75
|
+
- `--transfer-users`: Transfer users between instances (will not overwrite)
|
75
76
|
- `--fromDbId`: Set the source database ID for transfer
|
76
77
|
- `--toDbId`: Set the destination database ID for transfer
|
77
78
|
- `--fromCollectionId`: Set the source collection ID for transfer
|
@@ -147,6 +148,7 @@ This updated CLI ensures that developers have robust tools at their fingertips t
|
|
147
148
|
|
148
149
|
## Changelog
|
149
150
|
|
151
|
+
- 0.10.67: Added `--transfer-users` boolean flag to also transfer users between projects
|
150
152
|
- 0.10.66: Fixed `ignore` always being an empty array, if not set, so it properly ignores the defaults
|
151
153
|
- 0.10.65: Fixed the stupid local functions not caring about the ignore string, and added `__pycache__` and `.venv` to default ignores
|
152
154
|
- 0.10.64: Fixed `Deploy Function(s)` not ignoring things properly
|
package/dist/main.js
CHANGED
@@ -37,6 +37,10 @@ const argv = yargs(hideBin(process.argv))
|
|
37
37
|
.option("wipeCollections", {
|
38
38
|
type: "boolean",
|
39
39
|
description: "Wipe collections, uses collectionIds option to get the collections to wipe",
|
40
|
+
})
|
41
|
+
.option("transferUsers", {
|
42
|
+
type: "boolean",
|
43
|
+
description: "Transfer users between projects",
|
40
44
|
})
|
41
45
|
.option("generate", {
|
42
46
|
type: "boolean",
|
@@ -171,6 +175,7 @@ async function main() {
|
|
171
175
|
importData: parsedArgv.import,
|
172
176
|
shouldWriteFile: parsedArgv.writeData,
|
173
177
|
wipeCollections: parsedArgv.wipeCollections,
|
178
|
+
transferUsers: parsedArgv.transferUsers,
|
174
179
|
};
|
175
180
|
if (parsedArgv.updateFunctionSpec) {
|
176
181
|
if (!parsedArgv.functionId || !parsedArgv.specification) {
|
@@ -309,7 +314,7 @@ async function main() {
|
|
309
314
|
}
|
310
315
|
}
|
311
316
|
// Validate that at least one transfer type is specified
|
312
|
-
if (!fromDb && !sourceBucket) {
|
317
|
+
if (!fromDb && !sourceBucket && !options.transferUsers) {
|
313
318
|
throw new Error("No source database or bucket specified for transfer");
|
314
319
|
}
|
315
320
|
const transferOptions = {
|
@@ -321,6 +326,7 @@ async function main() {
|
|
321
326
|
transferKey: parsedArgv.remoteApiKey,
|
322
327
|
sourceBucket: sourceBucket,
|
323
328
|
targetBucket: targetBucket,
|
329
|
+
transferUsers: options.transferUsers,
|
324
330
|
};
|
325
331
|
await controller.transferData(transferOptions);
|
326
332
|
}
|
@@ -1,4 +1,4 @@
|
|
1
|
-
import { Databases, Storage, type Models } from "node-appwrite";
|
1
|
+
import { Databases, Storage, Users, type Models } from "node-appwrite";
|
2
2
|
export interface TransferOptions {
|
3
3
|
fromDb: Models.Database | undefined;
|
4
4
|
targetDb: Models.Database | undefined;
|
@@ -9,6 +9,7 @@ export interface TransferOptions {
|
|
9
9
|
transferKey?: string;
|
10
10
|
sourceBucket?: Models.Bucket;
|
11
11
|
targetBucket?: Models.Bucket;
|
12
|
+
transferUsers?: boolean;
|
12
13
|
}
|
13
14
|
export declare const transferStorageLocalToLocal: (storage: Storage, fromBucketId: string, toBucketId: string) => Promise<void>;
|
14
15
|
export declare const transferStorageLocalToRemote: (localStorage: Storage, endpoint: string, projectId: string, apiKey: string, fromBucketId: string, toBucketId: string) => Promise<void>;
|
@@ -28,3 +29,7 @@ export declare const transferDocumentsBetweenDbsLocalToRemote: (localDb: Databas
|
|
28
29
|
*/
|
29
30
|
export declare const transferDatabaseLocalToLocal: (localDb: Databases, fromDbId: string, targetDbId: string) => Promise<void>;
|
30
31
|
export declare const transferDatabaseLocalToRemote: (localDb: Databases, endpoint: string, projectId: string, apiKey: string, fromDbId: string, toDbId: string) => Promise<void>;
|
32
|
+
export declare const transferUsersLocalToRemote: (localUsers: Users, endpoint: string, projectId: string, apiKey: string, options?: {
|
33
|
+
limit?: number;
|
34
|
+
offset?: number;
|
35
|
+
}) => Promise<void>;
|
@@ -1,5 +1,5 @@
|
|
1
1
|
import { tryAwaitWithRetry } from "appwrite-utils";
|
2
|
-
import { Client, Databases, IndexType, Query, Storage, } from "node-appwrite";
|
2
|
+
import { Client, Databases, IndexType, Query, Storage, Users, } from "node-appwrite";
|
3
3
|
import { InputFile } from "node-appwrite/file";
|
4
4
|
import { getAppwriteClient } from "../utils/helperFunctions.js";
|
5
5
|
import { createOrUpdateAttribute, createUpdateCollectionAttributes, } from "../collections/attributes.js";
|
@@ -7,6 +7,7 @@ import { parseAttribute } from "appwrite-utils";
|
|
7
7
|
import chalk from "chalk";
|
8
8
|
import { fetchAllCollections } from "../collections/methods.js";
|
9
9
|
import { createOrUpdateIndex, createOrUpdateIndexes, } from "../collections/indexes.js";
|
10
|
+
import { getClient } from "src/utils/getClientFromConfig.js";
|
10
11
|
export const transferStorageLocalToLocal = async (storage, fromBucketId, toBucketId) => {
|
11
12
|
console.log(`Transferring files from ${fromBucketId} to ${toBucketId}`);
|
12
13
|
let lastFileId;
|
@@ -377,3 +378,54 @@ export const transferDatabaseLocalToRemote = async (localDb, endpoint, projectId
|
|
377
378
|
}
|
378
379
|
}
|
379
380
|
};
|
381
|
+
export const transferUsersLocalToRemote = async (localUsers, endpoint, projectId, apiKey, options = {}) => {
|
382
|
+
console.log(chalk.blue("Starting user transfer to remote instance..."));
|
383
|
+
const client = getClient(endpoint, apiKey, projectId);
|
384
|
+
const remoteUsers = new Users(client);
|
385
|
+
let totalTransferred = 0;
|
386
|
+
let lastId;
|
387
|
+
while (true) {
|
388
|
+
const queries = [Query.limit(100)];
|
389
|
+
if (lastId) {
|
390
|
+
queries.push(Query.cursorAfter(lastId));
|
391
|
+
}
|
392
|
+
const usersList = await tryAwaitWithRetry(async () => localUsers.list(queries));
|
393
|
+
if (usersList.users.length === 0) {
|
394
|
+
break;
|
395
|
+
}
|
396
|
+
for (const user of usersList.users) {
|
397
|
+
try {
|
398
|
+
// Check if user already exists in remote
|
399
|
+
try {
|
400
|
+
await tryAwaitWithRetry(async () => remoteUsers.get(user.$id));
|
401
|
+
console.log(chalk.yellow(`User ${user.$id} already exists, skipping...`));
|
402
|
+
continue;
|
403
|
+
}
|
404
|
+
catch (error) {
|
405
|
+
// User doesn't exist, proceed with creation
|
406
|
+
}
|
407
|
+
await tryAwaitWithRetry(async () => remoteUsers.create(user.$id, user.email, user.phone, // phone - optional
|
408
|
+
user.password, // password - cannot transfer hashed passwords
|
409
|
+
user.name));
|
410
|
+
// Update user preferences and status
|
411
|
+
await tryAwaitWithRetry(async () => remoteUsers.updatePrefs(user.$id, user.prefs));
|
412
|
+
if (!user.emailVerification) {
|
413
|
+
await tryAwaitWithRetry(async () => remoteUsers.updateEmailVerification(user.$id, false));
|
414
|
+
}
|
415
|
+
if (user.status === false) {
|
416
|
+
await tryAwaitWithRetry(async () => remoteUsers.updateStatus(user.$id, false));
|
417
|
+
}
|
418
|
+
totalTransferred++;
|
419
|
+
console.log(chalk.green(`Transferred user ${user.$id}`));
|
420
|
+
}
|
421
|
+
catch (error) {
|
422
|
+
console.error(chalk.red(`Failed to transfer user ${user.$id}:`), error);
|
423
|
+
}
|
424
|
+
}
|
425
|
+
if (usersList.users.length < 100) {
|
426
|
+
break;
|
427
|
+
}
|
428
|
+
lastId = usersList.users[usersList.users.length - 1].$id;
|
429
|
+
}
|
430
|
+
console.log(chalk.green(`Successfully transferred ${totalTransferred} users`));
|
431
|
+
};
|
package/dist/utilsController.js
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
-
import { Client, Databases, Query, Storage } from "node-appwrite";
|
1
|
+
import { Client, Databases, Query, Storage, Users, } from "node-appwrite";
|
2
2
|
import {} from "appwrite-utils";
|
3
3
|
import { loadConfig, findAppwriteConfig, findFunctionsDir, } from "./utils/loadConfigs.js";
|
4
4
|
import { UsersController } from "./migrations/users.js";
|
@@ -11,7 +11,7 @@ import { backupDatabase, ensureDatabaseConfigBucketsExist, initOrGetBackupStorag
|
|
11
11
|
import path from "path";
|
12
12
|
import { converterFunctions, validationRules, } from "appwrite-utils";
|
13
13
|
import { afterImportActions } from "./migrations/afterImportActions.js";
|
14
|
-
import { transferDatabaseLocalToLocal, transferDatabaseLocalToRemote, transferStorageLocalToLocal, transferStorageLocalToRemote, } from "./migrations/transfer.js";
|
14
|
+
import { transferDatabaseLocalToLocal, transferDatabaseLocalToRemote, transferStorageLocalToLocal, transferStorageLocalToRemote, transferUsersLocalToRemote, } from "./migrations/transfer.js";
|
15
15
|
import { getClient } from "./utils/getClientFromConfig.js";
|
16
16
|
import { fetchAllDatabases } from "./migrations/databases.js";
|
17
17
|
import { listFunctions, updateFunctionSpecifications, } from "./functions/methods.js";
|
@@ -321,6 +321,20 @@ export class UtilsController {
|
|
321
321
|
await transferDatabaseLocalToLocal(sourceClient, fromDb.$id, targetDb.$id);
|
322
322
|
}
|
323
323
|
}
|
324
|
+
if (options.transferUsers) {
|
325
|
+
if (!options.isRemote) {
|
326
|
+
console.log(chalk.yellow("User transfer is only supported for remote transfers. Skipping..."));
|
327
|
+
}
|
328
|
+
else if (!this.appwriteServer) {
|
329
|
+
throw new Error("Appwrite server not initialized");
|
330
|
+
}
|
331
|
+
else {
|
332
|
+
console.log(chalk.blue("Starting user transfer..."));
|
333
|
+
const localUsers = new Users(this.appwriteServer);
|
334
|
+
await transferUsersLocalToRemote(localUsers, options.transferEndpoint, options.transferProject, options.transferKey);
|
335
|
+
console.log(chalk.green("User transfer completed"));
|
336
|
+
}
|
337
|
+
}
|
324
338
|
// Handle storage transfer
|
325
339
|
if (this.storage && (options.sourceBucket || options.fromDb)) {
|
326
340
|
const sourceBucketId = options.sourceBucket?.$id ||
|
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.10.
|
4
|
+
"version": "0.10.67",
|
5
5
|
"main": "src/main.ts",
|
6
6
|
"type": "module",
|
7
7
|
"repository": {
|
package/src/main.ts
CHANGED
@@ -31,6 +31,7 @@ interface CliOptions {
|
|
31
31
|
projectId?: string;
|
32
32
|
apiKey?: string;
|
33
33
|
transfer?: boolean;
|
34
|
+
transferUsers?: boolean;
|
34
35
|
fromDbId?: string;
|
35
36
|
toDbId?: string;
|
36
37
|
fromCollectionId?: string;
|
@@ -77,6 +78,10 @@ const argv = yargs(hideBin(process.argv))
|
|
77
78
|
description:
|
78
79
|
"Wipe collections, uses collectionIds option to get the collections to wipe",
|
79
80
|
})
|
81
|
+
.option("transferUsers", {
|
82
|
+
type: "boolean",
|
83
|
+
description: "Transfer users between projects",
|
84
|
+
})
|
80
85
|
.option("generate", {
|
81
86
|
type: "boolean",
|
82
87
|
description: "Generate TypeScript schemas from database schemas",
|
@@ -216,6 +221,7 @@ async function main() {
|
|
216
221
|
importData: parsedArgv.import,
|
217
222
|
shouldWriteFile: parsedArgv.writeData,
|
218
223
|
wipeCollections: parsedArgv.wipeCollections,
|
224
|
+
transferUsers: parsedArgv.transferUsers,
|
219
225
|
};
|
220
226
|
|
221
227
|
if (parsedArgv.updateFunctionSpec) {
|
@@ -415,7 +421,7 @@ async function main() {
|
|
415
421
|
}
|
416
422
|
|
417
423
|
// Validate that at least one transfer type is specified
|
418
|
-
if (!fromDb && !sourceBucket) {
|
424
|
+
if (!fromDb && !sourceBucket && !options.transferUsers) {
|
419
425
|
throw new Error("No source database or bucket specified for transfer");
|
420
426
|
}
|
421
427
|
|
@@ -428,6 +434,7 @@ async function main() {
|
|
428
434
|
transferKey: parsedArgv.remoteApiKey,
|
429
435
|
sourceBucket: sourceBucket,
|
430
436
|
targetBucket: targetBucket,
|
437
|
+
transferUsers: options.transferUsers,
|
431
438
|
};
|
432
439
|
|
433
440
|
await controller.transferData(transferOptions);
|
@@ -5,6 +5,7 @@ import {
|
|
5
5
|
IndexType,
|
6
6
|
Query,
|
7
7
|
Storage,
|
8
|
+
Users,
|
8
9
|
type Models,
|
9
10
|
} from "node-appwrite";
|
10
11
|
import { InputFile } from "node-appwrite/file";
|
@@ -20,6 +21,7 @@ import {
|
|
20
21
|
createOrUpdateIndex,
|
21
22
|
createOrUpdateIndexes,
|
22
23
|
} from "../collections/indexes.js";
|
24
|
+
import { getClient } from "src/utils/getClientFromConfig.js";
|
23
25
|
|
24
26
|
export interface TransferOptions {
|
25
27
|
fromDb: Models.Database | undefined;
|
@@ -31,6 +33,7 @@ export interface TransferOptions {
|
|
31
33
|
transferKey?: string;
|
32
34
|
sourceBucket?: Models.Bucket;
|
33
35
|
targetBucket?: Models.Bucket;
|
36
|
+
transferUsers?: boolean;
|
34
37
|
}
|
35
38
|
|
36
39
|
export const transferStorageLocalToLocal = async (
|
@@ -766,3 +769,93 @@ export const transferDatabaseLocalToRemote = async (
|
|
766
769
|
}
|
767
770
|
}
|
768
771
|
};
|
772
|
+
|
773
|
+
export const transferUsersLocalToRemote = async (
|
774
|
+
localUsers: Users,
|
775
|
+
endpoint: string,
|
776
|
+
projectId: string,
|
777
|
+
apiKey: string,
|
778
|
+
options: {
|
779
|
+
limit?: number;
|
780
|
+
offset?: number;
|
781
|
+
} = {}
|
782
|
+
) => {
|
783
|
+
console.log(chalk.blue("Starting user transfer to remote instance..."));
|
784
|
+
|
785
|
+
const client = getClient(endpoint, apiKey, projectId);
|
786
|
+
const remoteUsers = new Users(client);
|
787
|
+
|
788
|
+
let totalTransferred = 0;
|
789
|
+
let lastId: string | undefined;
|
790
|
+
|
791
|
+
while (true) {
|
792
|
+
const queries = [Query.limit(100)];
|
793
|
+
if (lastId) {
|
794
|
+
queries.push(Query.cursorAfter(lastId));
|
795
|
+
}
|
796
|
+
|
797
|
+
const usersList = await tryAwaitWithRetry(async () =>
|
798
|
+
localUsers.list(queries)
|
799
|
+
);
|
800
|
+
|
801
|
+
if (usersList.users.length === 0) {
|
802
|
+
break;
|
803
|
+
}
|
804
|
+
|
805
|
+
for (const user of usersList.users) {
|
806
|
+
try {
|
807
|
+
// Check if user already exists in remote
|
808
|
+
try {
|
809
|
+
await tryAwaitWithRetry(async () => remoteUsers.get(user.$id));
|
810
|
+
console.log(
|
811
|
+
chalk.yellow(`User ${user.$id} already exists, skipping...`)
|
812
|
+
);
|
813
|
+
continue;
|
814
|
+
} catch (error: any) {
|
815
|
+
// User doesn't exist, proceed with creation
|
816
|
+
}
|
817
|
+
|
818
|
+
await tryAwaitWithRetry(async () =>
|
819
|
+
remoteUsers.create(
|
820
|
+
user.$id,
|
821
|
+
user.email,
|
822
|
+
user.phone, // phone - optional
|
823
|
+
user.password, // password - cannot transfer hashed passwords
|
824
|
+
user.name
|
825
|
+
)
|
826
|
+
);
|
827
|
+
|
828
|
+
// Update user preferences and status
|
829
|
+
await tryAwaitWithRetry(async () =>
|
830
|
+
remoteUsers.updatePrefs(user.$id, user.prefs)
|
831
|
+
);
|
832
|
+
|
833
|
+
if (!user.emailVerification) {
|
834
|
+
await tryAwaitWithRetry(async () =>
|
835
|
+
remoteUsers.updateEmailVerification(user.$id, false)
|
836
|
+
);
|
837
|
+
}
|
838
|
+
|
839
|
+
if (user.status === false) {
|
840
|
+
await tryAwaitWithRetry(async () =>
|
841
|
+
remoteUsers.updateStatus(user.$id, false)
|
842
|
+
);
|
843
|
+
}
|
844
|
+
|
845
|
+
totalTransferred++;
|
846
|
+
console.log(chalk.green(`Transferred user ${user.$id}`));
|
847
|
+
} catch (error) {
|
848
|
+
console.error(chalk.red(`Failed to transfer user ${user.$id}:`), error);
|
849
|
+
}
|
850
|
+
}
|
851
|
+
|
852
|
+
if (usersList.users.length < 100) {
|
853
|
+
break;
|
854
|
+
}
|
855
|
+
lastId = usersList.users[usersList.users.length - 1].$id;
|
856
|
+
}
|
857
|
+
|
858
|
+
console.log(
|
859
|
+
chalk.green(`Successfully transferred ${totalTransferred} users`)
|
860
|
+
);
|
861
|
+
};
|
package/src/utilsController.ts
CHANGED
@@ -1,4 +1,11 @@
|
|
1
|
-
import {
|
1
|
+
import {
|
2
|
+
Client,
|
3
|
+
Databases,
|
4
|
+
Query,
|
5
|
+
Storage,
|
6
|
+
Users,
|
7
|
+
type Models,
|
8
|
+
} from "node-appwrite";
|
2
9
|
import {
|
3
10
|
type AppwriteConfig,
|
4
11
|
type AppwriteFunction,
|
@@ -46,6 +53,7 @@ import {
|
|
46
53
|
transferDatabaseLocalToRemote,
|
47
54
|
transferStorageLocalToLocal,
|
48
55
|
transferStorageLocalToRemote,
|
56
|
+
transferUsersLocalToRemote,
|
49
57
|
type TransferOptions,
|
50
58
|
} from "./migrations/transfer.js";
|
51
59
|
import { getClient } from "./utils/getClientFromConfig.js";
|
@@ -66,6 +74,7 @@ export interface SetupOptions {
|
|
66
74
|
wipeCollections?: boolean;
|
67
75
|
wipeDocumentStorage?: boolean;
|
68
76
|
wipeUsers?: boolean;
|
77
|
+
transferUsers?: boolean;
|
69
78
|
generateSchemas?: boolean;
|
70
79
|
importData?: boolean;
|
71
80
|
checkDuplicates?: boolean;
|
@@ -486,6 +495,28 @@ export class UtilsController {
|
|
486
495
|
}
|
487
496
|
}
|
488
497
|
|
498
|
+
if (options.transferUsers) {
|
499
|
+
if (!options.isRemote) {
|
500
|
+
console.log(
|
501
|
+
chalk.yellow(
|
502
|
+
"User transfer is only supported for remote transfers. Skipping..."
|
503
|
+
)
|
504
|
+
);
|
505
|
+
} else if (!this.appwriteServer) {
|
506
|
+
throw new Error("Appwrite server not initialized");
|
507
|
+
} else {
|
508
|
+
console.log(chalk.blue("Starting user transfer..."));
|
509
|
+
const localUsers = new Users(this.appwriteServer);
|
510
|
+
await transferUsersLocalToRemote(
|
511
|
+
localUsers,
|
512
|
+
options.transferEndpoint!,
|
513
|
+
options.transferProject!,
|
514
|
+
options.transferKey!
|
515
|
+
);
|
516
|
+
console.log(chalk.green("User transfer completed"));
|
517
|
+
}
|
518
|
+
}
|
519
|
+
|
489
520
|
// Handle storage transfer
|
490
521
|
if (this.storage && (options.sourceBucket || options.fromDb)) {
|
491
522
|
const sourceBucketId =
|