appwrite-utils-cli 0.0.12 → 0.0.14

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.
@@ -2,6 +2,7 @@ import { Databases, Storage, InputFile, Query, ID, Client, } from "node-appwrite
2
2
  import path from "path";
3
3
  import fs from "fs";
4
4
  import os from "os";
5
+ import { logger } from "./logging.js";
5
6
  const getDatabaseFromConfig = (config) => {
6
7
  if (!config.appwriteClient) {
7
8
  config.appwriteClient = new Client()
@@ -307,6 +308,7 @@ export const afterImportActions = {
307
308
  }
308
309
  }
309
310
  catch (error) {
311
+ logger.error(`Error creating file and updating field, params were:\ndbId: ${dbId}, collId: ${collId}, docId: ${docId}, fieldName: ${fieldName}, filePath: ${filePath}, fileName: ${fileName}\n\nError: ${error}`);
310
312
  console.error("Error creating file and updating field: ", error);
311
313
  console.log(`Params were: dbId: ${dbId}, collId: ${collId}, docId: ${docId}, fieldName: ${fieldName}, filePath: ${filePath}, fileName: ${fileName}`);
312
314
  }
@@ -119,8 +119,8 @@ export class ImportController {
119
119
  continue;
120
120
  console.log(`Processing update definitions for collection ID: ${collection.$id}`);
121
121
  await this.processBatch(db, collection, importDef, dataToImport);
122
- await setAllPendingAfterImportActionsToReady(this.database, db.$id, collection.$id);
123
122
  }
123
+ await setAllPendingAfterImportActionsToReady(this.database, db.$id, collection.$id);
124
124
  }
125
125
  async loadData(importDef) {
126
126
  const filePath = path.resolve(this.appwriteFolderPath, importDef.filePath);
@@ -158,7 +158,6 @@ export class ImportController {
158
158
  let associatedDoc;
159
159
  if (isMembersCollection &&
160
160
  (finalItem.hasOwnProperty("email") || item.hasOwnProperty("phone"))) {
161
- console.log("Found members collection, creating user...");
162
161
  const usersController = new UsersController(this.config, this.database);
163
162
  const userToCreate = AuthUserCreateSchema.safeParse({
164
163
  ...finalItem,
@@ -172,7 +171,6 @@ export class ImportController {
172
171
  createIdToUse = user.$id;
173
172
  context.docId = createIdToUse;
174
173
  context = { ...context, ...user };
175
- console.log("Created user, deleting keys in finalItem that exist in user...");
176
174
  const associatedDocFound = await this.database.listDocuments(db.$id, context.collId, [Query.equal("$id", createIdToUse)]);
177
175
  if (associatedDocFound.documents.length > 0) {
178
176
  associatedDoc = associatedDocFound.documents[0];
@@ -185,7 +183,6 @@ export class ImportController {
185
183
  deletedKeys.push(key);
186
184
  }
187
185
  });
188
- console.log(`Set createIdToUse to ${createIdToUse}. Deleted keys: ${deletedKeys.join(", ")}.`);
189
186
  }
190
187
  else if (isMembersCollection) {
191
188
  logger.error(`Skipping user & contact creation for ${item} due to lack of email...`);
@@ -201,15 +198,13 @@ export class ImportController {
201
198
  !associatedDoc) {
202
199
  const createdContext = await this.handleCreate(context, finalItem, updateDefs, createIdToUse);
203
200
  context = { ...context, ...createdContext };
204
- logger.info(`Handled create for ${context.docId}}`);
205
201
  }
206
202
  else {
207
203
  const updatedContext = await this.handleUpdate(context, finalItem, importDef);
208
204
  context = { ...context, ...updatedContext };
209
- logger.info(`Handled update for ${context.docId}`);
210
205
  }
211
206
  const afterImportActionContext = structuredClone(context);
212
- const attributeMappingsWithActions = this.getAttributeMappingsWithActions(importDef.attributeMappings, context, finalItem);
207
+ const attributeMappingsWithActions = this.getAttributeMappingsWithActions(importDef.attributeMappings, afterImportActionContext, finalItem);
213
208
  if (attributeMappingsWithActions.some((m) => m.postImportActions)) {
214
209
  logger.info(`Pushing to post-import actions queue for ${context.docId}`);
215
210
  const afterImportOperationContext = ContextObject.parse({
@@ -238,9 +233,6 @@ export class ImportController {
238
233
  async handleCreate(context, finalItem, updateDefs, id) {
239
234
  const existing = await documentExists(this.database, context.dbId, context.collId, finalItem);
240
235
  if (!existing) {
241
- if (id) {
242
- console.log(`Creating document with provided ID (member): ${id}`);
243
- }
244
236
  const createdDoc = await this.database.createDocument(context.dbId, context.collId, id || ID.unique(), finalItem);
245
237
  context.docId = createdDoc.$id;
246
238
  context.createdDoc = createdDoc;
@@ -253,7 +245,6 @@ export class ImportController {
253
245
  }
254
246
  });
255
247
  }
256
- console.log(`Created document ID: ${createdDoc.$id}`);
257
248
  return context;
258
249
  }
259
250
  else {
@@ -354,13 +345,19 @@ export class ImportController {
354
345
  for (const batch of resultsData) {
355
346
  const actionOperation = ContextObject.parse(JSON.parse(batch.data));
356
347
  const { context, finalItem, attributeMappings } = actionOperation;
348
+ if (finalItem.$id && !context.docId) {
349
+ context.docId =
350
+ finalItem.$id || context.createdDoc.$id || context.$id || undefined;
351
+ logger.info(`Setting docId to ${finalItem.$id} because docId not found in context, batch ${batch.$id}, context is ${JSON.stringify(context)}`);
352
+ }
357
353
  try {
358
354
  await this.importDataActions.executeAfterImportActions(finalItem, attributeMappings, context);
359
355
  // Mark batch as processed
360
356
  await this.database.deleteDocument("migrations", "batches", batch.$id);
361
357
  }
362
358
  catch (error) {
363
- logger.error(`Failed to execute batch ${batch.$id}:`, error);
359
+ logger.error(`Failed to execute batch ${batch.$id}:`, error, "Context is :", context);
360
+ await this.database.deleteDocument("migrations", "batches", batch.$id);
364
361
  }
365
362
  }
366
363
  // After processing all batches, update the operation status
@@ -58,7 +58,6 @@ export class ImportDataActions {
58
58
  const convertedItem = convertObjectBySchema(item, conversionSchema);
59
59
  // Merge the converted item back into the original item object
60
60
  Object.assign(item, convertedItem);
61
- console.log("Converted item:", item);
62
61
  return item;
63
62
  }
64
63
  /**
@@ -73,7 +72,6 @@ export class ImportDataActions {
73
72
  if (!validationActions ||
74
73
  !Array.isArray(validationActions) ||
75
74
  !validationActions.length) {
76
- console.warn("No validation actions defined for the item, assuming true");
77
75
  return true; // Assume items without validation actions as valid.
78
76
  }
79
77
  for (const ruleDef of validationActions) {
@@ -137,7 +135,7 @@ export class ImportDataActions {
137
135
  await actionMethod(this.config, ...resolvedParams);
138
136
  }
139
137
  catch (error) {
140
- logger.error(`Error executing action '${actionName}':`, error);
138
+ logger.error(`Error executing action '${actionName}' with context:`, context, error);
141
139
  throw new Error(`Execution failed for action '${actionName}': ${error.message}`);
142
140
  }
143
141
  }
@@ -1,7 +1,7 @@
1
1
  import winston from "winston";
2
2
  export const logger = winston.createLogger({
3
3
  level: "info",
4
- format: winston.format.prettyPrint(),
4
+ format: winston.format.json({ space: 2 }),
5
5
  defaultMeta: { service: "appwrite-utils-cli" },
6
6
  transports: [
7
7
  //
@@ -9,6 +9,7 @@ export const logger = winston.createLogger({
9
9
  // - Write all logs with importance level of `info` or less to `combined.log`
10
10
  //
11
11
  new winston.transports.File({ filename: "error.log", level: "error" }),
12
+ new winston.transports.File({ filename: "warn.log", level: "warn" }),
12
13
  new winston.transports.File({ filename: "combined.log" }),
13
14
  ],
14
15
  });
@@ -40,7 +40,6 @@ export class UsersController {
40
40
  }
41
41
  }
42
42
  async createUserAndReturn(item) {
43
- console.log("Creating user with item", item);
44
43
  // Attempt to find an existing user by email or phone.
45
44
  let foundUsers = [];
46
45
  if (item.email) {
@@ -59,13 +58,11 @@ export class UsersController {
59
58
  }
60
59
  let userToReturn = foundUsers[0] || undefined;
61
60
  if (!userToReturn) {
62
- console.log("Creating user cause not found");
63
61
  userToReturn = await this.users.create(item.userId || ID.unique(), item.email || undefined, item.phone && item.phone.length < 15 && item.phone.startsWith("+")
64
62
  ? item.phone
65
63
  : undefined, item.password?.toLowerCase() || `changeMe${item.email}`.toLowerCase(), item.name || undefined);
66
64
  }
67
65
  else {
68
- console.log("Updating user cause found");
69
66
  // Update user details as necessary, ensuring email uniqueness if attempting an update.
70
67
  if (item.email &&
71
68
  item.email !== userToReturn.email &&
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.12",
4
+ "version": "0.0.14",
5
5
  "main": "src/main.ts",
6
6
  "type": "module",
7
7
  "repository": {
@@ -11,6 +11,7 @@ import type { AppwriteConfig } from "./schema.js";
11
11
  import path from "path";
12
12
  import fs from "fs";
13
13
  import os from "os";
14
+ import { logger } from "./logging.js";
14
15
 
15
16
  const getDatabaseFromConfig = (config: AppwriteConfig) => {
16
17
  if (!config.appwriteClient) {
@@ -481,6 +482,9 @@ export const afterImportActions = {
481
482
  console.log("Created file from path: ", file.$id);
482
483
  }
483
484
  } catch (error) {
485
+ logger.error(
486
+ `Error creating file and updating field, params were:\ndbId: ${dbId}, collId: ${collId}, docId: ${docId}, fieldName: ${fieldName}, filePath: ${filePath}, fileName: ${fileName}\n\nError: ${error}`
487
+ );
484
488
  console.error("Error creating file and updating field: ", error);
485
489
  console.log(
486
490
  `Params were: dbId: ${dbId}, collId: ${collId}, docId: ${docId}, fieldName: ${fieldName}, filePath: ${filePath}, fileName: ${fileName}`
@@ -199,12 +199,12 @@ export class ImportController {
199
199
  `Processing update definitions for collection ID: ${collection.$id}`
200
200
  );
201
201
  await this.processBatch(db, collection, importDef, dataToImport);
202
- await setAllPendingAfterImportActionsToReady(
203
- this.database,
204
- db.$id,
205
- collection.$id
206
- );
207
202
  }
203
+ await setAllPendingAfterImportActionsToReady(
204
+ this.database,
205
+ db.$id,
206
+ collection.$id
207
+ );
208
208
  }
209
209
 
210
210
  async loadData(importDef: ImportDef): Promise<any[]> {
@@ -269,7 +269,6 @@ export class ImportController {
269
269
  isMembersCollection &&
270
270
  (finalItem.hasOwnProperty("email") || item.hasOwnProperty("phone"))
271
271
  ) {
272
- console.log("Found members collection, creating user...");
273
272
  const usersController = new UsersController(
274
273
  this.config,
275
274
  this.database
@@ -288,9 +287,6 @@ export class ImportController {
288
287
  createIdToUse = user.$id;
289
288
  context.docId = createIdToUse;
290
289
  context = { ...context, ...user };
291
- console.log(
292
- "Created user, deleting keys in finalItem that exist in user..."
293
- );
294
290
  const associatedDocFound = await this.database.listDocuments(
295
291
  db.$id,
296
292
  context.collId,
@@ -307,11 +303,6 @@ export class ImportController {
307
303
  deletedKeys.push(key);
308
304
  }
309
305
  });
310
- console.log(
311
- `Set createIdToUse to ${createIdToUse}. Deleted keys: ${deletedKeys.join(
312
- ", "
313
- )}.`
314
- );
315
306
  } else if (isMembersCollection) {
316
307
  logger.error(
317
308
  `Skipping user & contact creation for ${item} due to lack of email...`
@@ -341,7 +332,6 @@ export class ImportController {
341
332
  createIdToUse
342
333
  );
343
334
  context = { ...context, ...createdContext };
344
- logger.info(`Handled create for ${context.docId}}`);
345
335
  } else {
346
336
  const updatedContext = await this.handleUpdate(
347
337
  context,
@@ -349,13 +339,12 @@ export class ImportController {
349
339
  importDef
350
340
  );
351
341
  context = { ...context, ...updatedContext };
352
- logger.info(`Handled update for ${context.docId}`);
353
342
  }
354
343
  const afterImportActionContext = structuredClone(context);
355
344
  const attributeMappingsWithActions =
356
345
  this.getAttributeMappingsWithActions(
357
346
  importDef.attributeMappings,
358
- context,
347
+ afterImportActionContext,
359
348
  finalItem
360
349
  );
361
350
  if (attributeMappingsWithActions.some((m) => m.postImportActions)) {
@@ -404,9 +393,6 @@ export class ImportController {
404
393
  finalItem
405
394
  );
406
395
  if (!existing) {
407
- if (id) {
408
- console.log(`Creating document with provided ID (member): ${id}`);
409
- }
410
396
  const createdDoc = await this.database.createDocument(
411
397
  context.dbId,
412
398
  context.collId,
@@ -428,8 +414,6 @@ export class ImportController {
428
414
  }
429
415
  });
430
416
  }
431
-
432
- console.log(`Created document ID: ${createdDoc.$id}`);
433
417
  return context;
434
418
  } else {
435
419
  console.log("Document already exists, skipping creation.");
@@ -563,6 +547,17 @@ export class ImportController {
563
547
  for (const batch of resultsData) {
564
548
  const actionOperation = ContextObject.parse(JSON.parse(batch.data));
565
549
  const { context, finalItem, attributeMappings } = actionOperation;
550
+ if (finalItem.$id && !context.docId) {
551
+ context.docId =
552
+ finalItem.$id || context.createdDoc.$id || context.$id || undefined;
553
+ logger.info(
554
+ `Setting docId to ${
555
+ finalItem.$id
556
+ } because docId not found in context, batch ${
557
+ batch.$id
558
+ }, context is ${JSON.stringify(context)}`
559
+ );
560
+ }
566
561
  try {
567
562
  await this.importDataActions.executeAfterImportActions(
568
563
  finalItem,
@@ -576,7 +571,17 @@ export class ImportController {
576
571
  batch.$id
577
572
  );
578
573
  } catch (error) {
579
- logger.error(`Failed to execute batch ${batch.$id}:`, error);
574
+ logger.error(
575
+ `Failed to execute batch ${batch.$id}:`,
576
+ error,
577
+ "Context is :",
578
+ context
579
+ );
580
+ await this.database.deleteDocument(
581
+ "migrations",
582
+ "batches",
583
+ batch.$id
584
+ );
580
585
  }
581
586
  }
582
587
 
@@ -92,7 +92,6 @@ export class ImportDataActions {
92
92
  const convertedItem = convertObjectBySchema(item, conversionSchema);
93
93
  // Merge the converted item back into the original item object
94
94
  Object.assign(item, convertedItem);
95
- console.log("Converted item:", item);
96
95
  return item;
97
96
  }
98
97
 
@@ -114,9 +113,6 @@ export class ImportDataActions {
114
113
  !Array.isArray(validationActions) ||
115
114
  !validationActions.length
116
115
  ) {
117
- console.warn(
118
- "No validation actions defined for the item, assuming true"
119
- );
120
116
  return true; // Assume items without validation actions as valid.
121
117
  }
122
118
  for (const ruleDef of validationActions) {
@@ -223,7 +219,11 @@ export class ImportDataActions {
223
219
  );
224
220
  await (actionMethod as any)(this.config, ...resolvedParams);
225
221
  } catch (error: any) {
226
- logger.error(`Error executing action '${actionName}':`, error);
222
+ logger.error(
223
+ `Error executing action '${actionName}' with context:`,
224
+ context,
225
+ error
226
+ );
227
227
  throw new Error(
228
228
  `Execution failed for action '${actionName}': ${error.message}`
229
229
  );
@@ -2,7 +2,7 @@ import winston from "winston";
2
2
 
3
3
  export const logger = winston.createLogger({
4
4
  level: "info",
5
- format: winston.format.prettyPrint(),
5
+ format: winston.format.json({ space: 2 }),
6
6
  defaultMeta: { service: "appwrite-utils-cli" },
7
7
  transports: [
8
8
  //
@@ -10,6 +10,7 @@ export const logger = winston.createLogger({
10
10
  // - Write all logs with importance level of `info` or less to `combined.log`
11
11
  //
12
12
  new winston.transports.File({ filename: "error.log", level: "error" }),
13
+ new winston.transports.File({ filename: "warn.log", level: "warn" }),
13
14
  new winston.transports.File({ filename: "combined.log" }),
14
15
  ],
15
16
  });
@@ -49,8 +49,6 @@ export class UsersController {
49
49
  }
50
50
 
51
51
  async createUserAndReturn(item: AuthUserCreate) {
52
- console.log("Creating user with item", item);
53
-
54
52
  // Attempt to find an existing user by email or phone.
55
53
  let foundUsers: Models.User<Models.Preferences>[] = [];
56
54
  if (item.email) {
@@ -71,7 +69,6 @@ export class UsersController {
71
69
  let userToReturn = foundUsers[0] || undefined;
72
70
 
73
71
  if (!userToReturn) {
74
- console.log("Creating user cause not found");
75
72
  userToReturn = await this.users.create(
76
73
  item.userId || ID.unique(),
77
74
  item.email || undefined,
@@ -82,7 +79,6 @@ export class UsersController {
82
79
  item.name || undefined
83
80
  );
84
81
  } else {
85
- console.log("Updating user cause found");
86
82
  // Update user details as necessary, ensuring email uniqueness if attempting an update.
87
83
  if (
88
84
  item.email &&