testdriverai 4.2.10 → 4.2.11

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/agent.js CHANGED
@@ -49,6 +49,8 @@ const session = require("./lib/session.js");
49
49
  const notify = require("./lib/notify.js");
50
50
  const { emitter, events } = require("./lib/events.js");
51
51
 
52
+ const logger = log.logger;
53
+
52
54
  let lastPrompt = "";
53
55
  let terminalApp = "";
54
56
  let commandHistory = [];
@@ -84,7 +86,7 @@ let getArgs = () => {
84
86
  args[file] == "--help" ||
85
87
  args[file] == "-h"
86
88
  ) {
87
- console.log("Command: testdriverai [init, run, edit] [yaml filepath]");
89
+ logger.info("Command: testdriverai [init, run, edit] [yaml filepath]");
88
90
  process.exit(0);
89
91
  }
90
92
 
@@ -123,13 +125,13 @@ let a = getArgs();
123
125
  const thisFile = a.file;
124
126
  const thisCommand = a.command;
125
127
 
126
- log.log("info", chalk.green(`Howdy! I'm TestDriver v${package.version}`));
127
- log.log("info", chalk.dim(`Working on ${thisFile}`));
128
- console.log("");
129
- log.log("info", chalk.yellow(`This is beta software!`));
130
- log.log("info", `Join our Discord for help`);
131
- log.log("info", `https://discord.com/invite/cWDFW8DzPm`);
132
- console.log("");
128
+ logger.info(chalk.green(`Howdy! I'm TestDriver v${package.version}`));
129
+ logger.info(chalk.dim(`Working on ${thisFile}`));
130
+ logger.info("");
131
+ logger.info(chalk.yellow(`This is beta software!`));
132
+ logger.info(`Join our Discord for help`);
133
+ logger.info(`https://discord.com/invite/cWDFW8DzPm`);
134
+ logger.info("");
133
135
 
134
136
  // individual run ID for this session
135
137
  // let runID = new Date().getTime();
@@ -159,7 +161,7 @@ function fileCompleter(line) {
159
161
 
160
162
  return [matches.length ? matches : files, partial];
161
163
  } catch (e) {
162
- console.log(e);
164
+ logger.info("%s", e);
163
165
  return [[], partial];
164
166
  }
165
167
  }
@@ -219,8 +221,7 @@ const exit = async (failed = true, shouldSave = false) => {
219
221
  };
220
222
 
221
223
  const dieOnFatal = async (error) => {
222
- console.log("");
223
- log.log("info", chalk.red("Fatal Error") + `\n${error.message}`);
224
+ logger.error(chalk.red("Fatal Error") + `\n${error.message}`);
224
225
  await summarize(error.message);
225
226
  return await exit(true);
226
227
  };
@@ -238,19 +239,17 @@ const haveAIResolveError = async (error, markdown, depth = 0, undo = true) => {
238
239
  let safeKey = JSON.stringify(eMessage);
239
240
  errorCounts[safeKey] = errorCounts[safeKey] ? errorCounts[safeKey] + 1 : 1;
240
241
 
241
- log.log("debug", eMessage);
242
+ logger.error(eMessage);
242
243
 
243
- if (process.env["DEV"]) {
244
- console.log(error, eMessage);
245
- console.log(error.stack);
246
- }
244
+ logger.debug("%j", error);
245
+ logger.debug("%s", error.stack);
247
246
 
248
247
  log.prettyMarkdown(eMessage);
249
248
 
250
249
  // if we get the same error 3 times in `run` mode, we exit
251
250
  if (errorCounts[safeKey] > errorLimit - 1) {
252
- console.log(chalk.red("Error loop detected. Exiting."));
253
- console.log(eMessage);
251
+ logger.info(chalk.red("Error loop detected. Exiting."));
252
+ logger.info("%s", eMessage);
254
253
  await summarize(eMessage);
255
254
  return await exit(true);
256
255
  }
@@ -268,8 +267,8 @@ const haveAIResolveError = async (error, markdown, depth = 0, undo = true) => {
268
267
 
269
268
  speak("thinking...");
270
269
  notify("thinking...");
271
- log.log("info", chalk.dim("thinking..."), true);
272
- log.log("info", "");
270
+ logger.info(chalk.dim("thinking..."), true);
271
+ logger.info("");
273
272
 
274
273
  const mdStream = log.createMarkdownStreamLogger();
275
274
 
@@ -300,14 +299,14 @@ const check = async () => {
300
299
  checkCount++;
301
300
 
302
301
  if (checkCount >= checkLimit) {
303
- log.log("info", chalk.red("Exploratory loop detected. Exiting."));
302
+ logger.info(chalk.red("Exploratory loop detected. Exiting."));
304
303
  await summarize("Check loop detected.");
305
304
  return await exit(true);
306
305
  }
307
306
 
308
- log.log("info", "");
309
- log.log("info", chalk.dim("checking..."), "testdriver");
310
- log.log("info", "");
307
+ logger.info("");
308
+ logger.info(chalk.dim("checking..."), "testdriver");
309
+ logger.info("");
311
310
 
312
311
  let thisScreenshot = await system.captureScreenBase64(1, false, true);
313
312
  let images = [lastScreenshot, thisScreenshot];
@@ -342,7 +341,7 @@ const check = async () => {
342
341
  const runCommand = async (command, depth) => {
343
342
  let yml = await yaml.dump(command);
344
343
 
345
- log.log("debug", `running command: \n\n${yml}`);
344
+ logger.debug(`running command: \n\n${yml}`);
346
345
 
347
346
  try {
348
347
  let response;
@@ -378,6 +377,7 @@ let csv = [["command,time"]];
378
377
 
379
378
  const executeCommands = async (commands, depth, pushToHistory = false) => {
380
379
  if (commands?.length) {
380
+
381
381
  for (const command of commands) {
382
382
  if (pushToHistory) {
383
383
  executionHistory[executionHistory.length - 1]?.commands.push(command);
@@ -386,7 +386,7 @@ const executeCommands = async (commands, depth, pushToHistory = false) => {
386
386
  await runCommand(command, depth);
387
387
 
388
388
  let timeToComplete = (new Date().getTime() - lastCommand) / 1000;
389
- // console.log(timeToComplete, 'seconds')
389
+ // logger.info(timeToComplete, 'seconds')
390
390
 
391
391
  csv.push([command.command, timeToComplete]);
392
392
  lastCommand = new Date().getTime();
@@ -399,7 +399,7 @@ const executeCommands = async (commands, depth, pushToHistory = false) => {
399
399
  const executeCodeBlocks = async (codeblocks, depth, pushToHistory = false) => {
400
400
  depth = depth + 1;
401
401
 
402
- log.log("debug", { message: "execute code blocks", depth });
402
+ logger.debug("%j", { message: "execute code blocks", depth });
403
403
 
404
404
  for (const codeblock of codeblocks) {
405
405
  let commands;
@@ -424,13 +424,13 @@ const executeCodeBlocks = async (codeblocks, depth, pushToHistory = false) => {
424
424
  const aiExecute = async (message, validateAndLoop = false) => {
425
425
  executionHistory.push({ prompt: lastPrompt, commands: [] });
426
426
 
427
- log.log("debug", "kicking off exploratory loop");
427
+ logger.debug("kicking off exploratory loop");
428
428
 
429
429
  // kick everything off
430
430
  await actOnMarkdown(message, 0, true);
431
431
 
432
432
  if (validateAndLoop) {
433
- log.log("debug", "exploratory loop resolved, check your work");
433
+ logger.debug("exploratory loop resolved, check your work");
434
434
 
435
435
  let response = await check();
436
436
 
@@ -441,13 +441,13 @@ const aiExecute = async (message, validateAndLoop = false) => {
441
441
  return await haveAIResolveError(error, response, 0);
442
442
  }
443
443
 
444
- log.log("debug", `found ${checkCodeblocks.length} codeblocks`);
444
+ logger.debug(`found ${checkCodeblocks.length} codeblocks`);
445
445
 
446
446
  if (checkCodeblocks.length > 0) {
447
- log.log("debug", "check thinks more needs to be done");
447
+ logger.debug("check thinks more needs to be done");
448
448
  return await aiExecute(response, validateAndLoop);
449
449
  } else {
450
- log.log("debug", "seems complete, returning");
450
+ logger.debug("seems complete, returning");
451
451
  return response;
452
452
  }
453
453
  }
@@ -469,8 +469,8 @@ const assert = async (expect) => {
469
469
 
470
470
  speak("thinking...");
471
471
  notify("thinking...");
472
- log.log("info", chalk.dim("thinking..."), true);
473
- log.log("info", "");
472
+ logger.info(chalk.dim("thinking..."), true);
473
+ logger.info("");
474
474
 
475
475
  let response = `\`\`\`yml
476
476
  commands:
@@ -489,14 +489,14 @@ const humanInput = async (currentTask, validateAndLoop = false) => {
489
489
  lastPrompt = currentTask;
490
490
  checkCount = 0;
491
491
 
492
- log.log("debug", "humanInput called");
492
+ logger.debug("humanInput called");
493
493
 
494
494
  tasks.push(currentTask);
495
495
 
496
496
  speak("thinking...");
497
497
  notify("thinking...");
498
- log.log("info", chalk.dim("thinking..."), true);
499
- log.log("info", "");
498
+ logger.info(chalk.dim("thinking..."), true);
499
+ logger.info("");
500
500
 
501
501
  lastScreenshot = await system.captureScreenBase64();
502
502
 
@@ -519,19 +519,19 @@ const humanInput = async (currentTask, validateAndLoop = false) => {
519
519
 
520
520
  await aiExecute(message.data, validateAndLoop);
521
521
 
522
- log.log("debug", "showing prompt from humanInput response check");
522
+ logger.debug("showing prompt from humanInput response check");
523
523
 
524
524
  await save({ silent: true });
525
525
  };
526
526
 
527
527
  const generate = async (type, count) => {
528
- log.log("debug", "generate called", type);
528
+ logger.debug("generate called, %s", type);
529
529
 
530
530
  speak("thinking...");
531
531
  notify("thinking...");
532
532
 
533
- log.log("info", chalk.dim("thinking..."), true);
534
- log.log("info", "");
533
+ logger.info(chalk.dim("thinking..."), true);
534
+ logger.info("");
535
535
 
536
536
  let image = await system.captureScreenBase64();
537
537
  const mdStream = log.createMarkdownStreamLogger();
@@ -583,7 +583,7 @@ const generate = async (type, count) => {
583
583
  };
584
584
 
585
585
  const popFromHistory = async (fullStep) => {
586
- log.log("info", chalk.dim("undoing..."), true);
586
+ logger.info(chalk.dim("undoing..."), true);
587
587
 
588
588
  if (executionHistory.length) {
589
589
  if (fullStep) {
@@ -620,7 +620,7 @@ ${yml}
620
620
 
621
621
  // this function is responsible for starting the recursive process of executing codeblocks
622
622
  const actOnMarkdown = async (content, depth, pushToHistory = false) => {
623
- log.log("debug", {
623
+ logger.debug("%j", {
624
624
  message: "actOnMarkdown called",
625
625
  depth,
626
626
  });
@@ -692,7 +692,7 @@ const firstPrompt = async () => {
692
692
 
693
693
  analytics.track("input", { input });
694
694
 
695
- console.log(""); // adds a nice break between submissions
695
+ logger.info(""); // adds a nice break between submissions
696
696
 
697
697
  let commands = input.split(" ");
698
698
 
@@ -732,8 +732,8 @@ const firstPrompt = async () => {
732
732
 
733
733
  if (!object?.steps) {
734
734
  analytics.track("load invalid yaml");
735
- log.log("error", "Invalid YAML. No steps found.");
736
- console.log("Invalid YAML: " + thisFile);
735
+ logger.error("Invalid YAML. No steps found.");
736
+ logger.info("Invalid YAML: " + thisFile);
737
737
  return await exit(true);
738
738
  }
739
739
 
@@ -748,7 +748,7 @@ const firstPrompt = async () => {
748
748
  ${yml}
749
749
  \`\`\``;
750
750
 
751
- log.log("info", `Loaded test script ${thisFile}\n`);
751
+ logger.info(`Loaded test script ${thisFile}\n`);
752
752
 
753
753
  log.prettyMarkdown(`
754
754
 
@@ -770,7 +770,7 @@ let setTerminalWindowTransparency = async (hide) => {
770
770
  .end();
771
771
  } catch (e) {
772
772
  // Suppress error
773
- console.error("Caught exception:", e);
773
+ logger.error("Caught exception: %s", e);
774
774
  }
775
775
  } else {
776
776
  try {
@@ -780,7 +780,7 @@ let setTerminalWindowTransparency = async (hide) => {
780
780
  .end();
781
781
  } catch (e) {
782
782
  // Suppress error
783
- console.error("Caught exception:", e);
783
+ logger.error("Caught exception:", e);
784
784
  }
785
785
  }
786
786
 
@@ -800,23 +800,23 @@ let setTerminalWindowTransparency = async (hide) => {
800
800
  }
801
801
  } catch (e) {
802
802
  // Suppress error
803
- console.error("Caught exception:", e);
803
+ logger.error("Caught exception: %s", e);
804
804
  }
805
805
  };
806
806
 
807
807
  // this function is responsible for summarizing the test script that has already executed
808
- // it is what is saved to the `/tmp/oiResult.log.log` file and output to the action as a summary
808
+ // it is what is saved to the `/tmp/oiResult.log` file and output to the action as a summary
809
809
  let summarize = async (error = null) => {
810
810
  analytics.track("summarize");
811
811
 
812
- log.log("info", "");
812
+ logger.info("");
813
813
 
814
- log.log("info", chalk.dim("reviewing test..."), true);
814
+ logger.info(chalk.dim("reviewing test..."), true);
815
815
 
816
816
  // let text = prompts.summarize(tasks, error);
817
817
  let image = await system.captureScreenBase64();
818
818
 
819
- log.log("info", chalk.dim("summarizing..."), true);
819
+ logger.info(chalk.dim("summarizing..."), true);
820
820
 
821
821
  const mdStream = log.createMarkdownStreamLogger();
822
822
  let reply = await sdk.req(
@@ -834,11 +834,11 @@ let summarize = async (error = null) => {
834
834
  );
835
835
  mdStream.end();
836
836
 
837
- let resultFile = "/tmp/oiResult.log.log";
837
+ let resultFile = "/tmp/oiResult.log";
838
838
  if (process.platform === "win32") {
839
- resultFile = "/Windows/Temp/oiResult.log.log";
839
+ resultFile = "/Windows/Temp/oiResult.log";
840
840
  }
841
- // write reply to /tmp/oiResult.log.log
841
+ // write reply to /tmp/oiResult.log
842
842
  fs.writeFileSync(resultFile, reply.data);
843
843
  };
844
844
 
@@ -848,21 +848,21 @@ let save = async ({ filepath = thisFile, silent = false } = {}) => {
848
848
  analytics.track("save", { silent });
849
849
 
850
850
  if (!silent) {
851
- log.log("info", chalk.dim("saving..."), true);
852
- console.log("");
851
+ logger.info(chalk.dim("saving..."), true);
852
+ logger.info("");
853
853
  }
854
854
 
855
855
  if (!executionHistory.length) {
856
856
  return;
857
857
  }
858
858
 
859
- // write reply to /tmp/oiResult.log.log
859
+ // write reply to /tmp/oiResult.log
860
860
  let regression = await generator.dumpToYML(executionHistory);
861
861
  try {
862
862
  fs.writeFileSync(filepath, regression);
863
863
  } catch (e) {
864
- log.log("error", e.message);
865
- console.log(e);
864
+ logger.error(e.message);
865
+ logger.error("%s", e);
866
866
  }
867
867
 
868
868
  if (!silent) {
@@ -872,11 +872,11 @@ let save = async ({ filepath = thisFile, silent = false } = {}) => {
872
872
  ${regression}
873
873
  \`\`\``);
874
874
 
875
- // console.log(csv.join('\n'))
875
+ // logger.info(csv.join('\n'))
876
876
 
877
877
  const fileName = filepath.split("/").pop();
878
878
  if (!silent) {
879
- log.log("info", chalk.dim(`saved as ${fileName}`));
879
+ logger.info(chalk.dim(`saved as ${fileName}`));
880
880
  }
881
881
  }
882
882
  };
@@ -891,7 +891,7 @@ let run = async (file, shouldSave = false, shouldExit = true) => {
891
891
  setTerminalWindowTransparency(true);
892
892
  emitter.emit(events.interactive, false);
893
893
 
894
- log.log("info", chalk.cyan(`running ${file}...`));
894
+ logger.info(chalk.cyan(`running ${file}...`));
895
895
 
896
896
  executionHistory = [];
897
897
  let yml;
@@ -900,10 +900,9 @@ let run = async (file, shouldSave = false, shouldExit = true) => {
900
900
  try {
901
901
  yml = fs.readFileSync(file, "utf-8");
902
902
  } catch (e) {
903
- console.log(e);
904
- log.log("error", "File not found. Please try again.");
905
- console.log(`File not found: ${file}`);
906
- console.log(`Current directory: ${process.cwd()}`);
903
+ logger.error(e);
904
+ logger.error(`File not found: ${file}`);
905
+ logger.error(`Current directory: ${process.cwd()}`);
907
906
 
908
907
  await summarize("File not found");
909
908
  await exit(true);
@@ -922,9 +921,8 @@ let run = async (file, shouldSave = false, shouldExit = true) => {
922
921
  try {
923
922
  ymlObj = await yaml.load(yml);
924
923
  } catch (e) {
925
- console.log(e);
926
- log.log("error", "Invalid YAML. Please try again.");
927
- console.log(`Invalid YAML: ${file}`);
924
+ logger.error("%s", e);
925
+ logger.error(`Invalid YAML: ${file}`);
928
926
 
929
927
  await summarize("Invalid YAML");
930
928
  await exit(true);
@@ -933,8 +931,7 @@ let run = async (file, shouldSave = false, shouldExit = true) => {
933
931
  if (ymlObj.version) {
934
932
  let valid = isValidVersion(ymlObj.version);
935
933
  if (!valid) {
936
- log.log("error", "Version mismatch. Please try again.");
937
- console.log(
934
+ logger.error(
938
935
  `Version mismatch: ${file}. Trying to run a test with v${ymlObj.version} test when this package is v${package.version}.`,
939
936
  );
940
937
 
@@ -946,8 +943,8 @@ let run = async (file, shouldSave = false, shouldExit = true) => {
946
943
  executionHistory = [];
947
944
 
948
945
  for (const step of ymlObj.steps) {
949
- log.log("info", ``, null);
950
- log.log("info", chalk.yellow(`${step.prompt || "no prompt"}`), null);
946
+ logger.info(``, null);
947
+ logger.info(chalk.yellow(`${step.prompt || "no prompt"}`), null);
951
948
 
952
949
  executionHistory.push({
953
950
  prompt: step.prompt,
@@ -958,8 +955,8 @@ let run = async (file, shouldSave = false, shouldExit = true) => {
958
955
  ${yaml.dump(step)}
959
956
  \`\`\``;
960
957
 
961
- log.log("debug", markdown);
962
- log.log("debug", "load calling actOnMarkdown");
958
+ logger.debug(markdown);
959
+ logger.debug("load calling actOnMarkdown");
963
960
 
964
961
  lastPrompt = step.prompt;
965
962
  await actOnMarkdown(markdown, 0, true);
@@ -995,7 +992,7 @@ const setTerminalApp = async (win) => {
995
992
  const iffy = async (condition, then, otherwise, depth) => {
996
993
  analytics.track("if", { condition });
997
994
 
998
- log.log("info", generator.jsonToManual({ command: "if", condition }));
995
+ logger.info(generator.jsonToManual({ command: "if", condition }));
999
996
 
1000
997
  let response = await commands.assert(condition);
1001
998
 
@@ -1011,11 +1008,11 @@ const iffy = async (condition, then, otherwise, depth) => {
1011
1008
  const embed = async (file, depth) => {
1012
1009
  analytics.track("embed", { file });
1013
1010
 
1014
- log.log("info", generator.jsonToManual({ command: "embed", file }));
1011
+ logger.info(generator.jsonToManual({ command: "embed", file }));
1015
1012
 
1016
1013
  depth = depth + 1;
1017
1014
 
1018
- log.log("info", `${file} (start)`);
1015
+ logger.info(`${file} (start)`);
1019
1016
 
1020
1017
  // get the current wowrking directory where this file is being executed
1021
1018
  let cwd = process.cwd();
@@ -1041,17 +1038,17 @@ const embed = async (file, depth) => {
1041
1038
  await executeCommands(step.commands, depth);
1042
1039
  }
1043
1040
 
1044
- log.log("info", `${file} (end)`);
1041
+ logger.info(`${file} (end)`);
1045
1042
  };
1046
1043
 
1047
1044
  const start = async () => {
1048
- // console.log(await system.getPrimaryDisplay());
1045
+ // logger.info(await system.getPrimaryDisplay());
1049
1046
 
1050
1047
  // @todo add-auth
1051
1048
  // if (!process.env.DASHCAM_API_KEY) {
1052
- // log.log('info', chalk.red(`You must supply an API key`), 'system')
1053
- // log.log('info', `Supply your API key with the \`DASHCAM_API_KEY\` environment variable.`, 'system');
1054
- // log.log('info', 'You can get a key in the Dashcam Discord server: https://discord.com/invite/cWDFW8DzPm', 'system')
1049
+ // log('info', chalk.red(`You must supply an API key`), 'system')
1050
+ // log('info', `Supply your API key with the \`DASHCAM_API_KEY\` environment variable.`, 'system');
1051
+ // log('info', 'You can get a key in the Dashcam Discord server: https://discord.com/invite/cWDFW8DzPm', 'system')
1055
1052
  // process.exit();
1056
1053
  // }
1057
1054
 
@@ -1062,13 +1059,11 @@ const start = async () => {
1062
1059
  process.platform === "darwin" &&
1063
1060
  !macScreenPerms.hasScreenCapturePermission()
1064
1061
  ) {
1065
- log.log("info", chalk.red("Screen capture permissions not enabled."));
1066
- log.log(
1067
- "info",
1062
+ logger.info(chalk.red("Screen capture permissions not enabled."));
1063
+ logger.info(
1068
1064
  "You must enable screen capture permissions for the application calling `testdriverai`.",
1069
1065
  );
1070
- log.log(
1071
- "info",
1066
+ logger.info(
1072
1067
  "Read More: https://docs.testdriver.ai/faq/screen-recording-permissions-mac-only",
1073
1068
  );
1074
1069
  analytics.track("noMacPermissions");
@@ -1078,14 +1073,14 @@ const start = async () => {
1078
1073
  if (thisCommand !== "run") {
1079
1074
  speak("Howdy! I am TestDriver version " + package.version);
1080
1075
 
1081
- console.log(
1076
+ logger.info(
1082
1077
  chalk.red("Warning!") +
1083
1078
  chalk.dim(" TestDriver sends screenshots of the desktop to our API."),
1084
1079
  );
1085
- console.log(
1080
+ logger.info(
1086
1081
  chalk.dim("https://docs.testdriver.ai/security-and-privacy/agent"),
1087
1082
  );
1088
- console.log("");
1083
+ logger.info("");
1089
1084
  }
1090
1085
 
1091
1086
  analytics.track("command", { command: thisCommand, file: thisFile });
@@ -1103,14 +1098,14 @@ const start = async () => {
1103
1098
 
1104
1099
  process.on("uncaughtException", async (err) => {
1105
1100
  analytics.track("uncaughtException", { err });
1106
- console.error("Uncaught Exception:", err);
1101
+ logger.error("Uncaught Exception: %s", err);
1107
1102
  // You might want to exit the process after handling the error
1108
1103
  await exit(true);
1109
1104
  });
1110
1105
 
1111
1106
  process.on("unhandledRejection", async (reason, promise) => {
1112
1107
  analytics.track("unhandledRejection", { reason, promise });
1113
- console.error("Unhandled Rejection at:", promise, "reason:", reason);
1108
+ logger.error("Unhandled Rejection at: %s, reason: %s", promise, reason);
1114
1109
  // Optionally, you might want to exit the process
1115
1110
  await exit(true);
1116
1111
  });
package/index.js CHANGED
@@ -2,6 +2,7 @@
2
2
  const config = require("./lib/config.js");
3
3
  const system = require("./lib/system.js");
4
4
  const { emitter, events } = require("./lib/events.js");
5
+ const { logger } = require("./lib/logger.js");
5
6
 
6
7
  (async () => {
7
8
 
@@ -40,7 +41,7 @@ const { emitter, events } = require("./lib/events.js");
40
41
  agent.start();
41
42
  })
42
43
  .catch((err) => {
43
- console.error(err);
44
+ logger.error("%s", err);
44
45
  process.exit(1);
45
46
  });
46
47
  }