appwrite-utils-cli 0.10.65 → 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 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,8 @@ 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
152
+ - 0.10.66: Fixed `ignore` always being an empty array, if not set, so it properly ignores the defaults
150
153
  - 0.10.65: Fixed the stupid local functions not caring about the ignore string, and added `__pycache__` and `.venv` to default ignores
151
154
  - 0.10.64: Fixed `Deploy Function(s)` not ignoring things properly
152
155
  - 0.10.63: My `collectLocalFunctions` function was failing to add the `scopes` and a few others to the function, accidentally, fixed now
@@ -514,7 +514,7 @@ export class InteractiveCLI {
514
514
  events: f.events || [],
515
515
  schedule: f.schedule || "",
516
516
  timeout: f.timeout || 15,
517
- ignore: f.ignore || [],
517
+ ignore: f.ignore,
518
518
  enabled: f.enabled !== false,
519
519
  logging: f.logging !== false,
520
520
  entrypoint: f.entrypoint || "src/index.ts",
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
+ };
@@ -10,6 +10,7 @@ export interface SetupOptions {
10
10
  wipeCollections?: boolean;
11
11
  wipeDocumentStorage?: boolean;
12
12
  wipeUsers?: boolean;
13
+ transferUsers?: boolean;
13
14
  generateSchemas?: boolean;
14
15
  importData?: boolean;
15
16
  checkDuplicates?: boolean;
@@ -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.65",
4
+ "version": "0.10.67",
5
5
  "main": "src/main.ts",
6
6
  "type": "module",
7
7
  "repository": {
@@ -712,7 +712,7 @@ export class InteractiveCLI {
712
712
  events: f.events || [],
713
713
  schedule: f.schedule || "",
714
714
  timeout: f.timeout || 15,
715
- ignore: f.ignore || [],
715
+ ignore: f.ignore,
716
716
  enabled: f.enabled !== false,
717
717
  logging: f.logging !== false,
718
718
  entrypoint: f.entrypoint || "src/index.ts",
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
+ };
@@ -1,4 +1,11 @@
1
- import { Client, Databases, Query, Storage, type Models } from "node-appwrite";
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 =