appwrite-utils-cli 0.0.251 → 0.0.253
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/main.js +6 -0
- package/dist/migrations/dataLoader.d.ts +3 -9
- package/dist/migrations/dataLoader.js +43 -48
- package/dist/migrations/importController.js +1 -1
- package/dist/migrations/importDataActions.js +0 -1
- package/dist/utilsController.d.ts +1 -0
- package/package.json +1 -1
- package/src/main.ts +6 -0
- package/src/migrations/dataLoader.ts +51 -48
- package/src/migrations/importController.ts +2 -1
- package/src/migrations/importDataActions.ts +0 -4
- package/src/utilsController.ts +1 -0
package/dist/main.js
CHANGED
@@ -13,6 +13,7 @@ async function main() {
|
|
13
13
|
let generateSchemas = false;
|
14
14
|
let importData = false;
|
15
15
|
let wipeDocuments = false;
|
16
|
+
let shouldWriteFile = false;
|
16
17
|
if (args.includes("--prod")) {
|
17
18
|
runProd = true;
|
18
19
|
}
|
@@ -40,6 +41,9 @@ async function main() {
|
|
40
41
|
if (args.includes("--wipe-users") || args.includes("--wipeUsers")) {
|
41
42
|
wipeUsers = true;
|
42
43
|
}
|
44
|
+
if (args.includes("--write-data") || args.includes("--writeData")) {
|
45
|
+
shouldWriteFile = true;
|
46
|
+
}
|
43
47
|
if (args.includes("--init")) {
|
44
48
|
await controller.run({
|
45
49
|
runProd: runProd,
|
@@ -53,6 +57,7 @@ async function main() {
|
|
53
57
|
generateMockData: false,
|
54
58
|
importData: false,
|
55
59
|
checkDuplicates: false,
|
60
|
+
shouldWriteFile: shouldWriteFile,
|
56
61
|
});
|
57
62
|
}
|
58
63
|
else {
|
@@ -68,6 +73,7 @@ async function main() {
|
|
68
73
|
wipeUsers: wipeUsers,
|
69
74
|
importData: importData,
|
70
75
|
checkDuplicates: false,
|
76
|
+
shouldWriteFile: shouldWriteFile,
|
71
77
|
});
|
72
78
|
}
|
73
79
|
}
|
@@ -4,14 +4,6 @@ import { z } from "zod";
|
|
4
4
|
import { type Databases } from "node-appwrite";
|
5
5
|
export declare const CollectionImportDataSchema: z.ZodObject<{
|
6
6
|
collection: z.ZodOptional<z.ZodObject<Omit<{
|
7
|
-
/**
|
8
|
-
* Prepares user data by checking for duplicates based on email or phone, adding to a duplicate map if found,
|
9
|
-
* and then returning the transformed item without user-specific keys.
|
10
|
-
*
|
11
|
-
* @param item - The raw item to be processed.
|
12
|
-
* @param attributeMappings - The attribute mappings for the item.
|
13
|
-
* @returns The transformed item with user-specific keys removed.
|
14
|
-
*/
|
15
7
|
$id: z.ZodDefault<z.ZodOptional<z.ZodString>>;
|
16
8
|
$createdAt: z.ZodString;
|
17
9
|
$updatedAt: z.ZodString;
|
@@ -1567,7 +1559,8 @@ export declare class DataLoader {
|
|
1567
1559
|
private emailToUserIdMap;
|
1568
1560
|
private phoneToUserIdMap;
|
1569
1561
|
userExistsMap: Map<string, boolean>;
|
1570
|
-
|
1562
|
+
private shouldWriteFile;
|
1563
|
+
constructor(appwriteFolderPath: string, importDataActions: ImportDataActions, database: Databases, config: AppwriteConfig, shouldWriteFile?: boolean);
|
1571
1564
|
getCollectionKey(name: string): string;
|
1572
1565
|
loadData(importDef: ImportDef): Promise<any[]>;
|
1573
1566
|
checkMapValuesForId(newId: string, collectionName: string): string | false;
|
@@ -1586,6 +1579,7 @@ export declare class DataLoader {
|
|
1586
1579
|
getAllUsers(): Promise<import("node-appwrite").Models.User<import("node-appwrite").Models.Preferences>[]>;
|
1587
1580
|
start(dbId: string): Promise<void>;
|
1588
1581
|
updateReferencesInRelatedCollections(): Promise<void>;
|
1582
|
+
private writeMapsToJsonFile;
|
1589
1583
|
/**
|
1590
1584
|
* Prepares user data by checking for duplicates based on email or phone, adding to a duplicate map if found,
|
1591
1585
|
* and then returning the transformed item without user-specific keys.
|
@@ -45,13 +45,15 @@ export class DataLoader {
|
|
45
45
|
emailToUserIdMap = new Map();
|
46
46
|
phoneToUserIdMap = new Map();
|
47
47
|
userExistsMap = new Map();
|
48
|
+
shouldWriteFile = false;
|
48
49
|
// Constructor to initialize the DataLoader with necessary configurations
|
49
|
-
constructor(appwriteFolderPath, importDataActions, database, config) {
|
50
|
+
constructor(appwriteFolderPath, importDataActions, database, config, shouldWriteFile) {
|
50
51
|
this.appwriteFolderPath = appwriteFolderPath;
|
51
52
|
this.importDataActions = importDataActions;
|
52
53
|
this.database = database;
|
53
54
|
this.usersController = new UsersController(config, database);
|
54
55
|
this.config = config;
|
56
|
+
this.shouldWriteFile = shouldWriteFile || false;
|
55
57
|
}
|
56
58
|
// Helper method to generate a consistent key for collections
|
57
59
|
getCollectionKey(name) {
|
@@ -156,7 +158,14 @@ export class DataLoader {
|
|
156
158
|
async getAllUsers() {
|
157
159
|
const users = new UsersController(this.config, this.database);
|
158
160
|
const allUsers = await users.getAllUsers();
|
161
|
+
// Iterate over the users and setup our maps ahead of time for email and phone
|
159
162
|
for (const user of allUsers) {
|
163
|
+
if (user.email) {
|
164
|
+
this.emailToUserIdMap.set(user.email, user.$id);
|
165
|
+
}
|
166
|
+
if (user.phone) {
|
167
|
+
this.phoneToUserIdMap.set(user.phone, user.$id);
|
168
|
+
}
|
160
169
|
this.userExistsMap.set(user.$id, true);
|
161
170
|
}
|
162
171
|
return allUsers;
|
@@ -169,15 +178,6 @@ export class DataLoader {
|
|
169
178
|
await this.setupMaps(dbId);
|
170
179
|
const allUsers = await this.getAllUsers();
|
171
180
|
console.log(`Fetched ${allUsers.length} users`);
|
172
|
-
// Iterate over the users and setup our maps ahead of time for email and phone
|
173
|
-
for (const user of allUsers) {
|
174
|
-
if (user.email) {
|
175
|
-
this.emailToUserIdMap.set(user.email, user.$id);
|
176
|
-
}
|
177
|
-
if (user.phone) {
|
178
|
-
this.phoneToUserIdMap.set(user.phone, user.$id);
|
179
|
-
}
|
180
|
-
}
|
181
181
|
// Iterate over the configured databases to find the matching one
|
182
182
|
for (const db of this.config.databases) {
|
183
183
|
if (db.$id !== dbId) {
|
@@ -222,7 +222,9 @@ export class DataLoader {
|
|
222
222
|
console.log("---------------------------------");
|
223
223
|
console.log(`Data setup for database: ${dbId} completed`);
|
224
224
|
console.log("---------------------------------");
|
225
|
-
|
225
|
+
if (this.shouldWriteFile) {
|
226
|
+
this.writeMapsToJsonFile();
|
227
|
+
}
|
226
228
|
}
|
227
229
|
async updateReferencesInRelatedCollections() {
|
228
230
|
// Iterate over each collection configuration
|
@@ -350,43 +352,36 @@ export class DataLoader {
|
|
350
352
|
// }
|
351
353
|
// }
|
352
354
|
// }
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
364
|
-
|
365
|
-
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
|
370
|
-
|
371
|
-
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
// console.error("Error writing data to JSON file:", err);
|
384
|
-
// return;
|
385
|
-
// }
|
386
|
-
// console.log(`Data successfully written to ${outputFile}`);
|
387
|
-
// }
|
388
|
-
// );
|
389
|
-
// }
|
355
|
+
writeMapsToJsonFile() {
|
356
|
+
const outputDir = path.resolve(process.cwd());
|
357
|
+
const outputFile = path.join(outputDir, "dataLoaderOutput.json");
|
358
|
+
const dataToWrite = {
|
359
|
+
dataFromCollections: Array.from(this.importMap.entries()).map(([key, value]) => {
|
360
|
+
return {
|
361
|
+
collection: key,
|
362
|
+
data: value.data.map((item) => item.finalData),
|
363
|
+
};
|
364
|
+
}),
|
365
|
+
// Convert Maps to arrays of entries for serialization
|
366
|
+
mergedUserMap: Array.from(this.mergedUserMap.entries()),
|
367
|
+
// emailToUserIdMap: Array.from(this.emailToUserIdMap.entries()),
|
368
|
+
// phoneToUserIdMap: Array.from(this.phoneToUserIdMap.entries()),
|
369
|
+
};
|
370
|
+
// Use JSON.stringify with a replacer function to handle Maps
|
371
|
+
const replacer = (key, value) => {
|
372
|
+
if (value instanceof Map) {
|
373
|
+
return Array.from(value.entries());
|
374
|
+
}
|
375
|
+
return value;
|
376
|
+
};
|
377
|
+
fs.writeFile(outputFile, JSON.stringify(dataToWrite, replacer, 2), "utf8", (err) => {
|
378
|
+
if (err) {
|
379
|
+
console.error("Error writing data to JSON file:", err);
|
380
|
+
return;
|
381
|
+
}
|
382
|
+
console.log(`Data successfully written to ${outputFile}`);
|
383
|
+
});
|
384
|
+
}
|
390
385
|
/**
|
391
386
|
* Prepares user data by checking for duplicates based on email or phone, adding to a duplicate map if found,
|
392
387
|
* and then returning the transformed item without user-specific keys.
|
@@ -58,7 +58,7 @@ export class ImportController {
|
|
58
58
|
console.log(`Starting import data for database: ${db.name}`);
|
59
59
|
console.log(`---------------------------------`);
|
60
60
|
// await this.importCollections(db);
|
61
|
-
const dataLoader = new DataLoader(this.appwriteFolderPath, this.importDataActions, this.database, this.config);
|
61
|
+
const dataLoader = new DataLoader(this.appwriteFolderPath, this.importDataActions, this.database, this.config, this.setupOptions.shouldWriteFile);
|
62
62
|
await dataLoader.start(db.$id);
|
63
63
|
await this.importCollections(db, dataLoader);
|
64
64
|
await resolveAndUpdateRelationships(db.$id, this.database, this.config);
|
@@ -103,7 +103,6 @@ export class ImportDataActions {
|
|
103
103
|
for (const mapping of attributeMap) {
|
104
104
|
const { postImportActions } = mapping;
|
105
105
|
if (!postImportActions || !Array.isArray(postImportActions)) {
|
106
|
-
console.warn(`No post-import actions defined for attribute: ${mapping.targetKey}`, postImportActions);
|
107
106
|
continue; // Skip to the next attribute if no actions are defined
|
108
107
|
}
|
109
108
|
for (const actionDef of postImportActions) {
|
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.0.
|
4
|
+
"version": "0.0.253",
|
5
5
|
"main": "src/main.ts",
|
6
6
|
"type": "module",
|
7
7
|
"repository": {
|
package/src/main.ts
CHANGED
@@ -16,6 +16,7 @@ async function main() {
|
|
16
16
|
let generateSchemas = false;
|
17
17
|
let importData = false;
|
18
18
|
let wipeDocuments = false;
|
19
|
+
let shouldWriteFile = false;
|
19
20
|
if (args.includes("--prod")) {
|
20
21
|
runProd = true;
|
21
22
|
}
|
@@ -43,6 +44,9 @@ async function main() {
|
|
43
44
|
if (args.includes("--wipe-users") || args.includes("--wipeUsers")) {
|
44
45
|
wipeUsers = true;
|
45
46
|
}
|
47
|
+
if (args.includes("--write-data") || args.includes("--writeData")) {
|
48
|
+
shouldWriteFile = true;
|
49
|
+
}
|
46
50
|
if (args.includes("--init")) {
|
47
51
|
await controller.run({
|
48
52
|
runProd: runProd,
|
@@ -56,6 +60,7 @@ async function main() {
|
|
56
60
|
generateMockData: false,
|
57
61
|
importData: false,
|
58
62
|
checkDuplicates: false,
|
63
|
+
shouldWriteFile: shouldWriteFile,
|
59
64
|
});
|
60
65
|
} else {
|
61
66
|
await controller.run({
|
@@ -70,6 +75,7 @@ async function main() {
|
|
70
75
|
wipeUsers: wipeUsers,
|
71
76
|
importData: importData,
|
72
77
|
checkDuplicates: false,
|
78
|
+
shouldWriteFile: shouldWriteFile,
|
73
79
|
});
|
74
80
|
}
|
75
81
|
}
|
@@ -63,19 +63,22 @@ export class DataLoader {
|
|
63
63
|
private emailToUserIdMap = new Map<string, string>();
|
64
64
|
private phoneToUserIdMap = new Map<string, string>();
|
65
65
|
userExistsMap = new Map<string, boolean>();
|
66
|
+
private shouldWriteFile = false;
|
66
67
|
|
67
68
|
// Constructor to initialize the DataLoader with necessary configurations
|
68
69
|
constructor(
|
69
70
|
appwriteFolderPath: string,
|
70
71
|
importDataActions: ImportDataActions,
|
71
72
|
database: Databases,
|
72
|
-
config: AppwriteConfig
|
73
|
+
config: AppwriteConfig,
|
74
|
+
shouldWriteFile?: boolean
|
73
75
|
) {
|
74
76
|
this.appwriteFolderPath = appwriteFolderPath;
|
75
77
|
this.importDataActions = importDataActions;
|
76
78
|
this.database = database;
|
77
79
|
this.usersController = new UsersController(config, database);
|
78
80
|
this.config = config;
|
81
|
+
this.shouldWriteFile = shouldWriteFile || false;
|
79
82
|
}
|
80
83
|
|
81
84
|
// Helper method to generate a consistent key for collections
|
@@ -213,7 +216,14 @@ export class DataLoader {
|
|
213
216
|
async getAllUsers() {
|
214
217
|
const users = new UsersController(this.config, this.database);
|
215
218
|
const allUsers = await users.getAllUsers();
|
219
|
+
// Iterate over the users and setup our maps ahead of time for email and phone
|
216
220
|
for (const user of allUsers) {
|
221
|
+
if (user.email) {
|
222
|
+
this.emailToUserIdMap.set(user.email, user.$id);
|
223
|
+
}
|
224
|
+
if (user.phone) {
|
225
|
+
this.phoneToUserIdMap.set(user.phone, user.$id);
|
226
|
+
}
|
217
227
|
this.userExistsMap.set(user.$id, true);
|
218
228
|
}
|
219
229
|
return allUsers;
|
@@ -227,15 +237,6 @@ export class DataLoader {
|
|
227
237
|
await this.setupMaps(dbId);
|
228
238
|
const allUsers = await this.getAllUsers();
|
229
239
|
console.log(`Fetched ${allUsers.length} users`);
|
230
|
-
// Iterate over the users and setup our maps ahead of time for email and phone
|
231
|
-
for (const user of allUsers) {
|
232
|
-
if (user.email) {
|
233
|
-
this.emailToUserIdMap.set(user.email, user.$id);
|
234
|
-
}
|
235
|
-
if (user.phone) {
|
236
|
-
this.phoneToUserIdMap.set(user.phone, user.$id);
|
237
|
-
}
|
238
|
-
}
|
239
240
|
// Iterate over the configured databases to find the matching one
|
240
241
|
for (const db of this.config.databases) {
|
241
242
|
if (db.$id !== dbId) {
|
@@ -290,7 +291,9 @@ export class DataLoader {
|
|
290
291
|
console.log("---------------------------------");
|
291
292
|
console.log(`Data setup for database: ${dbId} completed`);
|
292
293
|
console.log("---------------------------------");
|
293
|
-
|
294
|
+
if (this.shouldWriteFile) {
|
295
|
+
this.writeMapsToJsonFile();
|
296
|
+
}
|
294
297
|
}
|
295
298
|
|
296
299
|
async updateReferencesInRelatedCollections() {
|
@@ -446,46 +449,46 @@ export class DataLoader {
|
|
446
449
|
// }
|
447
450
|
// }
|
448
451
|
|
449
|
-
|
450
|
-
|
451
|
-
|
452
|
+
private writeMapsToJsonFile() {
|
453
|
+
const outputDir = path.resolve(process.cwd());
|
454
|
+
const outputFile = path.join(outputDir, "dataLoaderOutput.json");
|
452
455
|
|
453
|
-
|
454
|
-
|
455
|
-
|
456
|
-
|
457
|
-
|
458
|
-
|
459
|
-
|
460
|
-
|
461
|
-
|
462
|
-
|
463
|
-
|
464
|
-
|
465
|
-
|
466
|
-
|
456
|
+
const dataToWrite = {
|
457
|
+
dataFromCollections: Array.from(this.importMap.entries()).map(
|
458
|
+
([key, value]) => {
|
459
|
+
return {
|
460
|
+
collection: key,
|
461
|
+
data: value.data.map((item: any) => item.finalData),
|
462
|
+
};
|
463
|
+
}
|
464
|
+
),
|
465
|
+
// Convert Maps to arrays of entries for serialization
|
466
|
+
mergedUserMap: Array.from(this.mergedUserMap.entries()),
|
467
|
+
// emailToUserIdMap: Array.from(this.emailToUserIdMap.entries()),
|
468
|
+
// phoneToUserIdMap: Array.from(this.phoneToUserIdMap.entries()),
|
469
|
+
};
|
467
470
|
|
468
|
-
|
469
|
-
|
470
|
-
|
471
|
-
|
472
|
-
|
473
|
-
|
474
|
-
|
471
|
+
// Use JSON.stringify with a replacer function to handle Maps
|
472
|
+
const replacer = (key: any, value: any) => {
|
473
|
+
if (value instanceof Map) {
|
474
|
+
return Array.from(value.entries());
|
475
|
+
}
|
476
|
+
return value;
|
477
|
+
};
|
475
478
|
|
476
|
-
|
477
|
-
|
478
|
-
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
483
|
-
|
484
|
-
|
485
|
-
|
486
|
-
|
487
|
-
|
488
|
-
|
479
|
+
fs.writeFile(
|
480
|
+
outputFile,
|
481
|
+
JSON.stringify(dataToWrite, replacer, 2),
|
482
|
+
"utf8",
|
483
|
+
(err) => {
|
484
|
+
if (err) {
|
485
|
+
console.error("Error writing data to JSON file:", err);
|
486
|
+
return;
|
487
|
+
}
|
488
|
+
console.log(`Data successfully written to ${outputFile}`);
|
489
|
+
}
|
490
|
+
);
|
491
|
+
}
|
489
492
|
|
490
493
|
/**
|
491
494
|
* Prepares user data by checking for duplicates based on email or phone, adding to a duplicate map if found,
|
@@ -107,7 +107,8 @@ export class ImportController {
|
|
107
107
|
this.appwriteFolderPath,
|
108
108
|
this.importDataActions,
|
109
109
|
this.database,
|
110
|
-
this.config
|
110
|
+
this.config,
|
111
|
+
this.setupOptions.shouldWriteFile
|
111
112
|
);
|
112
113
|
await dataLoader.start(db.$id);
|
113
114
|
await this.importCollections(db, dataLoader);
|
@@ -161,10 +161,6 @@ export class ImportDataActions {
|
|
161
161
|
for (const mapping of attributeMap) {
|
162
162
|
const { postImportActions } = mapping;
|
163
163
|
if (!postImportActions || !Array.isArray(postImportActions)) {
|
164
|
-
console.warn(
|
165
|
-
`No post-import actions defined for attribute: ${mapping.targetKey}`,
|
166
|
-
postImportActions
|
167
|
-
);
|
168
164
|
continue; // Skip to the next attribute if no actions are defined
|
169
165
|
}
|
170
166
|
for (const actionDef of postImportActions) {
|