@suffolklitlab/alkiln 5.13.4 → 5.15.0

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.
Files changed (44) hide show
  1. package/.github/workflows/github_server.yml +1 -1
  2. package/.github/workflows/playground.yml +2 -1
  3. package/.github/workflows/unit_tests.yml +1 -1
  4. package/CHANGELOG.md +40 -0
  5. package/action.yml +26 -10
  6. package/action_for_github_server/action.yml +31 -27
  7. package/docs/4_environments.md +2 -2
  8. package/docs/artifacts_folder_name.md +121 -71
  9. package/docs/constrained_random_tests.md +220 -289
  10. package/docs/decisions/move_runtime_config_to_sources_folder.md +3 -1
  11. package/docs/prioritize_constrained_random_tests.md +35 -76
  12. package/lib/docassemble/server_install.js +0 -2
  13. package/lib/docassemble/setup.js +1 -3
  14. package/lib/docassemble/takedown.js +2 -2
  15. package/lib/globals.js +10 -0
  16. package/lib/run_cucumber.js +40 -53
  17. package/lib/scope.js +44 -1
  18. package/lib/setup/TestGenerator.js +1757 -0
  19. package/lib/setup/artifacts.js +20 -0
  20. package/lib/setup/generate.js +42 -0
  21. package/lib/setup/get_Gherkin_AST.js +26 -0
  22. package/lib/setup/validate.js +89 -0
  23. package/lib/steps.js +101 -42
  24. package/lib/utils/Log.js +317 -165
  25. package/lib/utils/_alkiln-constrained_random_tests_old.js +1776 -0
  26. package/lib/utils/get_error_stacks.js +16 -0
  27. package/lib/utils/reports.js +44 -22
  28. package/lib/utils/session_vars.js +77 -57
  29. package/lib/utils/set_sources_paths.js +98 -0
  30. package/package.json +18 -7
  31. package/tests/log_codes/check_codes.sh +17 -13
  32. package/tests/log_codes/log_code_expected_instances.txt +58 -0
  33. package/tests/unit_tests/constrained_random_answers/invalid.fixtures.js +178 -0
  34. package/tests/unit_tests/constrained_random_answers/invalid.test.js +251 -0
  35. package/tests/unit_tests/constrained_random_answers/valid.fixtures.js +957 -0
  36. package/tests/unit_tests/constrained_random_answers/valid.test.js +563 -0
  37. package/tests/unit_tests/example_of_chai_test_lifecycle.js +78 -0
  38. package/tests/unit_tests/log.test.js +17 -41
  39. package/docs/seprate_file_generation_GitHub_action.md +0 -11
  40. package/lib/parse.js +0 -36
  41. package/lib/utils/get_sources_paths.js +0 -162
  42. package/lib/utils/parse.js +0 -1
  43. package/lib/utils/proccess_command_line_args.js +0 -267
  44. /package/docs/{expected_inernal_results.md → expected_internal_results.md} +0 -0
package/lib/utils/Log.js CHANGED
@@ -4,15 +4,59 @@ const session_vars = require( `./session_vars` );
4
4
  const time = require(`./time`);
5
5
  const files = require(`./files`);
6
6
 
7
- // ? 🌈 💡 🔎 🤕 🐛
8
- // Alternatives: 🩹 ❤️‍🩹 🚑 🖊️
7
+ // ? 🌈 💡 🔎 🤕 🐛 🖊️
8
+ // Alternatives: 🩹 ❤️‍🩹 🚑
9
9
 
10
10
  /**
11
11
  * Future issues:
12
- * - [ ] Save logs from GitHub actions as well? Users can download them themselves, but this
13
- * might not be clear to them
14
- * - [ ] Capture cucumber ProgressFormatter logs instead of making them ourselves?
12
+ * - [ ] Save logs from GitHub actions as well? Users can download them
13
+ * themselves, but this might not be clear to them.
14
+ * - [ ] Capture cucumber ProgressFormatter logs instead of making them
15
+ * ourselves?
15
16
  * - [ ] keeping report separate for now (can just be an issue to integrate)
17
+ * - [ ] Discuss: instead of logging above an error, manipulate the error
18
+ * message to prepend the accompanying log code, etc (all except the `before`
19
+ * prop). Before this, do UX research to see if the current format is as
20
+ * confusing to authors as it seems from this end.
21
+ * - [ ] Discuss: Should `.error()` return an Error? See discussion:
22
+ * https://github.com/SuffolkLITLab/ALKiln/discussions/988
23
+ * */
24
+
25
+ /**
26
+ * Quick examples:
27
+ *
28
+ log.debug({ code: `ALK0%%%`, context: `unspecified context`,},
29
+ `The message`,
30
+ some_val
31
+ );
32
+
33
+ log.success({ code: `ALK0%%%`, context: `unspecified context`,},
34
+ `The message`,
35
+ some_val
36
+ );
37
+ log.info({ code: `ALK0%%%`, context: `unspecified context`,},
38
+ `The message`,
39
+ some_val
40
+ );
41
+ log.warn({ code: `ALK0%%%`, context: `unspecified context`,},
42
+ `The message`,
43
+ some_val
44
+ );
45
+ log.error({ code: `ALK0%%%`, context: `unspecified context`,},
46
+ `The message`,
47
+ some_val
48
+ );
49
+ log.throw({ code: `ALK0%%%`, context: `unspecified context`,
50
+ error: new Error(
51
+ `Error message`
52
+ ),}
53
+ );
54
+ log.throw({ code: `ALK0%%%`, context: `unspecified context`,
55
+ error: new Error(
56
+ `Error message`
57
+ ),},
58
+ `Additional message`
59
+ );
16
60
  * */
17
61
 
18
62
  class Log {
@@ -103,23 +147,96 @@ class Log {
103
147
  * const log = new Log({ path: "_alkiln-123", context: "setup" });
104
148
  * */
105
149
 
106
- // GitHub actions will pass in a path that may already exist
107
- if ( path ) { files.make_artifacts_folder( path ); }
108
- // Other processes, like ALKilnInThePlayground, don't have a path
109
- else { path = files.make_artifacts_folder(); }
150
+ // These records help internal tests check for correct behavior
151
+ this.internal_log_records = [];
110
152
 
111
- this.path = path;
153
+ let dir = path; // eventually, convert to this.dir instead of this.path
154
+
155
+ let is_new_folder = this._is_new_folder({ dir }).is_new;
156
+ this.path = this._make_or_get_folder({ dir }).dir;
157
+ // Discuss: Should making/saving the artifacts folder be hidden in here? If
158
+ // not, where should it be? `make_artifacts_folder` maybe...
112
159
  session_vars.save_artifacts_path_name( this.path );
113
160
 
161
+ // Make sure `log` is in the logged context
162
+ if ( context ) { context = context + ` log`; }
163
+ else { context = `log`; }
164
+ let files_msg = `ALKiln will save test results files at "${ this.path }"`;
165
+ if ( is_new_folder ) {
166
+ this.info({ code: `ALK0214`, context, }, files_msg );
167
+ } else {
168
+ this.debug({ code: `ALK0277`, context, }, files_msg );
169
+ }
170
+
114
171
  // Has this `Log` ever failed to append to the debug file?
115
172
  this.fs_append_to_debug_file_failed = false;
116
173
 
117
- // Add `log` to the context and tell the author what the path is
118
- if ( context ) { context = context + ` log`; }
119
- else { context = `log`; }
120
- this.info({ code: `ALK0214`, context, },
121
- `Saving files to "${ this.path }"`
122
- );
174
+ }
175
+
176
+ clear_internal_log_records() {
177
+ this.internal_log_records = [];
178
+ }
179
+
180
+ _is_new_folder({ dir }) {
181
+ /** Check if the artifacts folder is a new folder.
182
+ *
183
+ * @param {object} obj - Named arguments
184
+ * @param {undefined | str} obj.dir - A folder path or undefined
185
+ *
186
+ * @returns {{ is_new: bool, errors: [] | [Error] }} - Whether the folder
187
+ * previously existed (if yes, true) and a list of any Errors that
188
+ * happened
189
+ * */
190
+ let is_new = true;
191
+ try {
192
+
193
+ if ( dir ) {
194
+ if ( fs.existsSync( dir )) { is_new = false; }
195
+ } else if ( session_vars.get_artifacts_path_name() ) {
196
+ is_new = false;
197
+ }
198
+ return { is_new, errors: [] };
199
+
200
+ } catch ( folder_check_error ) {
201
+ return { is_new, errors: [ folder_check_error ]}
202
+ }
203
+ }
204
+
205
+ _make_or_get_folder({ dir }) {
206
+ /** Make a new folder for artifacts or get one that already exists.
207
+ *
208
+ * The chosen directory will be, in order of precedence, the `dir` value,
209
+ * an existing folder, or a completely new folder.
210
+ *
211
+ * @param {object} obj - Named arguments
212
+ * @param {undefined | str} obj.dir - Optional. Desired path to the
213
+ * artifacts folder.
214
+ *
215
+ * @returns { {dir: str, errors: [Error]} } Object with the given directory
216
+ * name and a list of 0 or more Error instances.
217
+ * */
218
+ try {
219
+
220
+ // GitHub actions pass in a dir, but other processes, like
221
+ // ALKilnInThePlayground, don't have a path
222
+ if ( !dir ) {
223
+ if ( session_vars.get_artifacts_path_name() ) {
224
+ dir = session_vars.get_artifacts_path_name();
225
+ } else {
226
+ dir = files.make_artifacts_folder();
227
+ }
228
+ }
229
+
230
+ if ( !fs.existsSync( dir )) {
231
+ files.make_artifacts_folder( dir );
232
+ }
233
+
234
+ return { dir, errors: [] };
235
+
236
+ } catch ( error_making_dir ) {
237
+ return { dir, errors: [ error_making_dir ]};
238
+ }
239
+
123
240
  }
124
241
 
125
242
  _console( console_opts = {}, ...logs ) {
@@ -128,7 +245,8 @@ class Log {
128
245
  * takedown, run_cucumber (before and after running the tests), and API calls
129
246
  * that interact with the server at those times. It writes to the console at
130
247
  * different levels and throws errors. If it's not throwing a given error, it
131
- * returns the combined metadata and the logs as a formatted string.
248
+ * returns the combined metadata and the logs as a formatted string. Only
249
+ * internal code should call this method.
132
250
  *
133
251
  * The `_console` method will:
134
252
  * 1. Save the data to files with `log.debug()`
@@ -143,9 +261,12 @@ class Log {
143
261
  * ┌───────────────────────────────────────┐
144
262
  * │ ._console() │
145
263
  * ├───────────────────────────────────────┤
146
- * .throw() ──┐ │ .debug() (all) (formatted logs str) │
147
- * │ │ get metadata string (all) │
148
- * │ │ │
264
+ * all below ─┐ │ │
265
+ * ├────► .debug() (formatted logs str) │
266
+ * └────► get metadata string (all) │
267
+ * then... │ │
268
+ * │ │
269
+ * .throw() ──┐ │ │
149
270
  * ├────► get safe Error │
150
271
  * ├────► console.error() with extra info │
151
272
  * └────► throw Error │
@@ -153,15 +274,17 @@ class Log {
153
274
  * │ │ │
154
275
  * .info() ──╘════► console.info() logs ════════════╗ │
155
276
  * │ ║ │
156
- * .warn() ───────► console.warn() logs─────┐ ║ │
157
- * │ ▼ ║ │
277
+ * .warn() ───────► console.warn() logs───────┐ ║ │
278
+ * │ │ ║ │
279
+ * .error() ───────► console.error() logs───┐ │ ║ │
280
+ * │ ▼ ▼ ║ │
158
281
  * │ return formatted logs string ◄══╝ │
159
282
  * └───────────────────────────────────────┘
160
283
  *
161
284
  * @param { Object } console_opts - Required. Metadata and functional flags
162
- * and data
163
- * @param { success|info|warn|error } console_opts.level - Required. The log
164
- * level. Functional and metadata. Defined by the internal caller.
285
+ * and data.
286
+ * @param { "success"|"info"|"warn"|"error" } console_opts.level - Required.
287
+ * The log level. Functional and metadata. Defined by the internal caller.
165
288
  * @param { string } console_opts.before - Optional. Text to add before the
166
289
  * metadata of the log. Metadata. Default "".
167
290
  * @param { string } console_opts.icon - Required. Unicode char put at the
@@ -172,18 +295,14 @@ class Log {
172
295
  * @param { string } console_opts.context - Optional. Info about the context
173
296
  * where the log was called. Metadata. Examples: "setup", "run",
174
297
  * "takedown". Default "". Defined by the original caller.
175
- * @param { Error|any } console_opts.error - Optional. Only include if
176
- * `do_throw` is true. Otherwise, `error` must be `undefined`. The error
177
- * or data to throw if `do_throw` is `true`. Before that throw, `_console`
178
- * will write the before, metadata, and logs. Try to use an actual
179
- * instance of Error. Otherwise, the stack trace will include this
180
- * function, which is confusing. Avoid that. Default `undefined`. Defined
181
- * by the original caller.
298
+ * @param { Error|any } console_opts.error - Optional. The error or data to
299
+ * eventually throw if `do_throw` is `true`, like with `.throw()`.
300
+ * Otherwise, just another log. It will keep its original stack trace.
301
+ * Default is `undefined`. Defined by the original caller.
182
302
  * @param { boolean } console_opts.do_throw - Optional. If `true` caller wants
183
- * to throw an error error. Default `false`. Defined by the original
184
- * caller.
303
+ * to throw an error. Default `false`. Defined by the internal caller.
185
304
  * @param { ...any } logs - Optional. The logs to write to the console. Joined
186
- * by " " or "\n" depending on the type of the log. Default `undefined`.
305
+ * by " " or "\n" depending on the type of the log. Default becomes `[]`.
187
306
  * Defined by the original caller.
188
307
  *
189
308
  * @returns { string } Formatted metadata, logs, and, if given, the error.
@@ -243,44 +362,46 @@ class Log {
243
362
  error, // Sometimes undefined
244
363
  do_throw = false, // Sometimes undefined
245
364
  } = console_opts;
365
+ logs = logs || [];
366
+
367
+ if ( do_throw || error !== undefined ) {
368
+ error = this._get_Error_safely({ maybe_error: error });
369
+ }
246
370
 
247
- let formatted_str_to_return = this.debug({ level, before, icon, code, context, error, do_throw }, ...logs );
371
+ let formatted_str_to_return = this.debug({
372
+ level, before, icon, code, context, error, do_throw,
373
+ }, ...logs );
248
374
 
249
375
  let metadata_str = this._format_metadata({ level, icon, code, context });
250
376
 
251
377
  // Throw whatever we can throw
252
378
  if ( do_throw ) {
253
- error = this._get_Error_safely({ maybe_error: error });
254
- // WARNING: If someone catches the error we throw, the console logs that
255
- // get logged here before catching will be confusing.
379
+ // WARNING: If someone catches the error we throw, these strings will get
380
+ // logged. The final resulting logs may become confusing.
256
381
  try {
257
- logs = logs || [];
258
- console.error( `${before}${metadata_str}:`, ...logs );
382
+ console.error( `\n\n\n${before}${metadata_str}:`, ...logs );
259
383
  } catch ( pre_throw_error ) {
260
384
  this.debug({
261
385
  icon: `🖊️`, code: `ALK0215`, context: `internal log`, level: `note`,
262
386
  }, `Skipped logging metadata before throwing the error`, pre_throw_error );
263
387
  }
264
388
 
265
- if ( error instanceof Error ) {
266
- throw error;
267
- } else {
268
- // non-error messages will get in here if do_throw is `true` Ensure the
269
- // call stack gets created at this point.
270
- // Discuss: Should we include the metadata in this error since we don't
271
- // need to preserve a stack trace? That's inconsistent, though.
272
- throw new Error( error );
273
- }
389
+ // Readers will see this text in the console, so give a clarifying name
390
+ let error_from_alkiln = error;
391
+ // ALKiln threw an error when preparing, cleaning up, or running the tests
392
+ throw error_from_alkiln;
274
393
  }
275
394
 
276
395
  // Avoid using custom-formatted logs and error here.
277
396
  // Let the console show them as it wants.
278
397
  try {
279
- // log at the given level if console has that method (`log` by default).
280
- // If a non-standard level was passed in, like `success`, use `info` by
281
- // default.
398
+
399
+ let combined = this._combined_logs_and_error({ logs, error, do_throw });
400
+ // log at the given level if console has that method (`log` by default
401
+ // from ._console). If a non-standard level, like `success`, was passed in
402
+ // use `info` by default.
282
403
  if ( !console[ level ]) { level = `info`; }
283
- console[ level ]( `${before}${metadata_str}:`, ...logs );
404
+ console[ level ]( `${before}${metadata_str}:`, ...combined );
284
405
 
285
406
  } catch ( console_log_error ) {
286
407
  // Fail silently
@@ -298,15 +419,28 @@ class Log {
298
419
  * Returns a formatted string version of the log with its metadata. Avoids
299
420
  * throwing internal errors while giving the max info possible. Avoids
300
421
  * writing to the visible console. Every log should come through here.
422
+ * External code can call this method.
301
423
  *
302
424
  * Tries to make saved logs look as similar as possible to console logs.
303
425
  *
304
- * `.debug()` has the same signature as `._console()`, but saves to files
305
- * instead of writing to the console.
306
- *
307
- * @params - Similar to `._console()`, except we expect to use `.debug()`
308
- * directly as well as internally, while we expect to only use
309
- * `._console()` internally.
426
+ * @param { Object } console_opts - Required. Metadata and functional flags
427
+ * and data.
428
+ * @param { "success"|"info"|"warn"|"error" } console_opts.level - Required.
429
+ * The log level. Functional and metadata. Defined by the internal
430
+ * caller.
431
+ * @param { string } console_opts.before - Optional. Text to add before the
432
+ * metadata of the log. Metadata. Default "".
433
+ * @param { string } console_opts.icon - Required. Unicode char put at the
434
+ * start of metadata. Makes the level more easy to see quickly. Defined
435
+ * by the internal caller.
436
+ * @param { string } console_opts.code - Required. Unique consecutive log id
437
+ * with the format ALK####. Defined by the original caller.
438
+ * @param { string } console_opts.context - Optional. Info about the context
439
+ * where the log was called. Metadata. Examples: "setup", "run",
440
+ * "takedown". Default "". Defined by the original caller.
441
+ * @param { ...any } logs - Optional. The logs to write to the console.
442
+ * Joined by " " or "\n" depending on the type of the log. Default
443
+ * becomes `[]`. Defined by the original caller.
310
444
  *
311
445
  * @returns { string } Same as `._console()`
312
446
  *
@@ -333,9 +467,12 @@ class Log {
333
467
  icon = `🐛`,
334
468
  code = `ALK000d`,
335
469
  context = ``,
336
- error, // Sometimes undefined
470
+ error,
337
471
  do_throw = false,
338
472
  } = debug_opts;
473
+ logs = logs || [];
474
+
475
+ this.internal_log_records.push({ opts: debug_opts, logs });
339
476
 
340
477
  // "warning" level and icon for for internal devs we use to avoid disrupting
341
478
  // the info authors are looking for
@@ -351,17 +488,13 @@ class Log {
351
488
  let logging_errors_msgs = [];
352
489
  try {
353
490
 
354
- // Use `with_error` to include the error in the debug and unexpected
355
- // results logs
356
- let with_error = this._combine_logs_and_possible_error({
357
- logs, maybe_error: error, do_throw,
358
- });
359
-
360
- let stringified = this._stringify_logs( ...with_error );
491
+ let combined = this._combined_logs_and_error({ logs, error, do_throw });
361
492
 
493
+ let stringified = this._stringify_logs( ...combined );
362
494
  // Question: Does `formatted` double log the error in the console?
363
495
  // Answer: `._console` doesn't use `formatted`, just returns it.
364
496
  formatted = `${ before }${ metadata_str }: ${ stringified }`;
497
+
365
498
  } catch ( formatted_log_error ) {
366
499
  formatted = `🖊️ ALK0212 internal debug NOTE [${ Date.now() }]: Skipped formatting debug logs.`;
367
500
  this._save_to_debug_safely({ unexpected: true, text: formatted });
@@ -491,31 +624,28 @@ class Log {
491
624
  } // Ends Log._stringify_logs()
492
625
 
493
626
  _stringify_inline({ prev_line_type, inline_log }) {
494
- /** Formats the start of an inline log based on the previous log and returns
495
- * that combined string.
627
+ /** Formats the log to add a space or a new line separator. May end up with
628
+ * a leading space for now.
496
629
  *
497
630
  * @param { "inline"|"block"|null } prev_line_type - Required. The type of
498
631
  * the previous log.
499
632
  * @param { null|non-obj } inline_log - Required. The log.
500
633
  *
501
- * @returns { string } The formatted log.
634
+ * @returns { string } The formatted line.
502
635
  *
503
636
  * @example
504
637
  * // Backticks are to make the surrounding white space more clear
505
638
  * log._stringify_inline({ prev_line_type: null, inline_log: "apple" });
506
- * // `apple`
639
+ * // "apple "
507
640
  * log._stringify_inline({ prev_line_type: 'inline', inline_log: "apple" });
508
- * // ` apple`
641
+ * // "apple "
509
642
  * log._stringify_inline({ prev_line_type: 'block', inline_log: "apple" });
510
- * // `
511
- * // apple`
643
+ * // "
644
+ * // apple"
512
645
  * */
513
- // An inline log's string starts with nothing if it's the first string...
514
646
  let start = ``;
515
- // ...starts after a space if its next to an inline string...
516
- if ( prev_line_type === `inline` ) { start = ` `; }
517
- // ...or starts on a new line if it's after a block of text, like an error
518
- // or object.
647
+ if ( prev_line_type === `inline` && !inline_log.startsWith(`\n`) ) { start = ` `; }
648
+ // This is actually the end of the block-type log
519
649
  else if ( prev_line_type === `block` ) { start = `\n`; }
520
650
  return `${ start }${ inline_log }`;
521
651
  }
@@ -525,6 +655,8 @@ class Log {
525
655
  * a "block"-type separator at the start. Can handle circular references.
526
656
  * Catch ANY error.
527
657
  *
658
+ * @param { "inline"|"block"|null } prev_line_type - Required. The type of
659
+ * the previous log.
528
660
  * @param { Object } obj_log - Required. The object to format.
529
661
  *
530
662
  * @returns { string } The obj as a string plus a separator
@@ -536,15 +668,15 @@ class Log {
536
668
  * // { fruit: 'bananas' }
537
669
  * */
538
670
  let start = ``;
539
- if ( prev_line_type === `inline` || prev_line_type === `block` ) {
540
- start = `\n`;
541
- }
671
+ // Avoid adding a new line to the start of the first line
672
+ let is_first_line = prev_line_type === null;
673
+ if ( !is_first_line ) { start = `\n`; }
542
674
 
543
675
  try {
544
676
  // Try to stringify the object.
545
677
  return `${ start }${ this._inspect({ value: obj_log }) }`;
546
678
 
547
- // Discuss: record these errors?
679
+ // Discuss: record the outer error?
548
680
  } catch ( outer_error ) {
549
681
  // If that fails, see if it has its own way of stringifying
550
682
  try {
@@ -556,48 +688,29 @@ class Log {
556
688
  } // ends outer try
557
689
  }
558
690
 
559
- _combine_logs_and_possible_error({ logs=[], do_throw=false, maybe_error }) {
560
- /** Returns a list of combined values to be logged. If we need to include
561
- * an error, ensure it's an error. Catch ANY internal errors.
562
- *
563
- * @param { array } logs - Required. List of logs.
564
- * @param { boolean } do_throw - Optional. Default is `false`. If `true`
565
- * caller wants to throw an error.
566
- * @param { Error|undefined } maybe_error - Optional. Default is
567
- * `undefined`. A value that might be. turned into an Error and is
568
- * definitely _safe_ to turn into an Error. For example, no circular
569
- * references.
570
- *
571
- * @returns { array } List of all values that should be logged.
572
- *
573
- * @example
574
- * let with_error = log._combine_logs_and_possible_error({
575
- * [ "a", "b" ], do_throw: true, maybe_error: "A string."
576
- * });
577
- * // [ "a", "b", <An error with its stack and the message "A string."> ]
578
- * @example
579
- * let with_error = log._combine_logs_and_possible_error({
580
- * [ "a", "b" ], do_throw: false, maybe_error: new Error("An error.")
581
- * });
582
- * // [ "a", "b" ]
691
+ _combined_logs_and_error({ logs=[], error, do_throw=false }) {
692
+ /** Return a list of the given logs combined with the given error, if it
693
+ * exists, ensuring the error is an Error.
694
+ *
695
+ * @param { ...any } logs - Optional. The logs to write to the console.
696
+ * Joined by " " or "\n" depending on the type of the log. Default
697
+ * becomes `[]`. Defined by the original caller.
698
+ * @param { Error|any } console_opts.error - Optional. Any. Default is
699
+ * `undefined`. Defined by the original caller.
700
+ * @param { boolean } console_opts.do_throw - Optional. If `true` caller wants
701
+ * to throw an error. Default `false`. Defined by the internal caller.
702
+ *
703
+ * @return [ any ] - List of logs, potentially with an added error
704
+ *
583
705
  * */
584
- let combined = [ ...logs ];
585
- try {
586
- // If we're supposed to throw, make sure the error gets an Error value.
587
- if ( do_throw ) {
588
- combined.push( this._get_Error_safely({ maybe_error }) );
589
-
590
- // Otherwise error should be `undefined`. If not, we get a warning.
591
- } else if ( maybe_error !== undefined ) {
592
- this._save_to_debug_safely({ unexpected: true, text: `🖊️ ALK0223 internal debug NOTE [${ Date.now() }]: Only logs that throw should give a value to \`error\`. Skipping the value.` });
593
- }
594
- } catch ( combining_error_error ) {
595
- this._save_to_debug_safely({ unexpected: true, text: `🖊️ ALK0226 internal debug NOTE [${ Date.now() }]: Skipped combining the logs.` });
596
- this._save_to_debug_safely({ unexpected: true, text: combining_error_error.stack });
706
+ if ( do_throw || error !== undefined ) {
707
+ error = this._get_Error_safely({ maybe_error: error });
597
708
  }
709
+ let logs_maybe_with_error = [ ...logs ];
710
+ if ( error !== undefined ) { logs_maybe_with_error.push( error ); }
598
711
 
599
- return combined;
600
- } // Ends _combine_logs_and_possible_error()
712
+ return logs_maybe_with_error;
713
+ }
601
714
 
602
715
  _get_Error_safely({ maybe_error }) {
603
716
  /** Given anything, return an Error. Avoid throwing any errors.
@@ -605,17 +718,28 @@ class Log {
605
718
  * @param { Object } opts
606
719
  * @param { * } opts.maybe_error - Optional. Can be anything.
607
720
  *
608
- * @returns { Error }
721
+ * @returns { Error|undefined }
609
722
  * */
610
- if ( maybe_error instanceof Error ) {
611
- return maybe_error;
612
- } else {
613
- if ( this._json_does_error( maybe_error )) {
614
- maybe_error = this._get_inspected_val_safely( maybe_error );
723
+ try {
724
+
725
+ if ( maybe_error instanceof Error ) {
726
+ return maybe_error;
727
+ } else if ( maybe_error ) {
728
+
729
+ if ( this._json_does_error( maybe_error )) {
730
+ maybe_error = this._get_inspected_val_safely( maybe_error );
731
+ }
732
+
733
+ let error_from_alkiln = maybe_error; // clearer name
734
+
735
+ // The stack trace `new Error()` creates here will be less helpful, but
736
+ // it's all we can do if the caller hasn't already created the error.
737
+ return new Error( error_from_alkiln );
738
+
615
739
  }
616
- // The stack trace will be less helpful, but it's all we can do if the
617
- // caller hasn't already created the error.
618
- return new Error( maybe_error );
740
+ } catch ( get_safe_error_error ) {
741
+ this._save_to_debug_safely({ unexpected: true, text: `🖊️ ALK0226 internal debug NOTE [${ Date.now() }]: Skipped combining the logs.` });
742
+ this._save_to_debug_safely({ unexpected: true, text: get_safe_error_error.stack });
619
743
  }
620
744
  }
621
745
 
@@ -681,7 +805,7 @@ class Log {
681
805
  * @param logs { ...string } - Optional. Default is `undefined`. Explanation
682
806
  * of what happened.
683
807
  *
684
- * @returns { string } Formatted metadata, logs, and, if given, the error.
808
+ * @returns { string } Formatted metadata and logs.
685
809
  *
686
810
  * @example
687
811
  * log.warn({
@@ -693,13 +817,11 @@ class Log {
693
817
  * */
694
818
  // Discuss: do we want to log some successes as "💡 ... SUCCESS: " for
695
819
  // smaller successful operations along the way?
696
- let {
697
- code = `ALK000s`, before = ``, context = ``
698
- } = success_opts;
699
820
  return this._console({
700
- level: `success`, icon: `🌈`,
701
- before, code, context,
702
- }, ...logs);
821
+ code: `ALK000s`,
822
+ ...success_opts,
823
+ level: `success`, icon: `🌈`, do_throw: false,
824
+ }, ...logs );
703
825
  }
704
826
 
705
827
  info( info_opts = {}, ...logs ) {
@@ -717,7 +839,7 @@ class Log {
717
839
  * @param logs { ...string } - Optional. Default is `undefined`. Explanation
718
840
  * of what happened.
719
841
  *
720
- * @returns { string } Formatted metadata, logs, and, if given, the error.
842
+ * @returns { string } Formatted metadata and logs.
721
843
  *
722
844
  * @example
723
845
  * log.warn({
@@ -727,13 +849,11 @@ class Log {
727
849
  * }, "demo log");
728
850
  * // ~~~💡 ALK00d4 demo INFO [2024-08-16 00:30:39UTC]: demo log
729
851
  * */
730
- let {
731
- code = `ALK000i`, before = ``, context = ``,
732
- } = info_opts;
733
852
  return this._console({
734
- level: `info`, icon: `💡`,
735
- before, code, context,
736
- }, ...logs);
853
+ code: `ALK000i`,
854
+ ...info_opts,
855
+ level: `info`, icon: `💡`, do_throw: false,
856
+ }, ...logs );
737
857
  }
738
858
 
739
859
  warn( warning_opts = {}, ...logs ) {
@@ -751,7 +871,7 @@ class Log {
751
871
  * @param logs { ...string } - Optional. Default is `undefined`. Explanation
752
872
  * of what happened.
753
873
  *
754
- * @returns { string } Formatted metadata, logs, and, if given, the error.
874
+ * @returns { string } Formatted metadata and logs.
755
875
  *
756
876
  * @example
757
877
  * log.warn({
@@ -761,13 +881,47 @@ class Log {
761
881
  * }, "demo log");
762
882
  * // ~~~🔎 ALK00d5 demo WARNING [2024-08-16 00:30:39UTC]: demo log
763
883
  * */
764
- let {
765
- code = `ALK000w`, before = ``, context = ``
766
- } = warning_opts;
767
884
  return this._console({
768
- level: `warn`, icon: `🔎`,
769
- before, code, context,
770
- }, ...logs);
885
+ code: `ALK000w`,
886
+ ...warning_opts,
887
+ level: `warn`, icon: `🔎`, do_throw: false,
888
+ }, ...logs );
889
+ }
890
+
891
+ error( error_opts = {}, ...logs ) {
892
+ /** Log errors (messages with the error icon) to the console using
893
+ * additional metadata and the given logs. Use it in ALKlin test suite
894
+ * wrapper functionality, like setup and takedown.
895
+ *
896
+ * @param { Object } error_opts
897
+ * @param { string } error_opts.code - Required. Unique log id of the
898
+ * format "ALK####", where "#" is a digit.
899
+ * @param { string } error_opts.before - Optional. Default is `""`.
900
+ * Decoration to log before the metadata.
901
+ * @param { string } error_opts.context - Optional. Default is `""`. Info
902
+ * about the origin of the logs.
903
+ * @param { ...string } logs - Optional. Default is `undefined`. Explanation
904
+ * of what happened.
905
+ * @param { Error|any } error_opts.error - Default is `undefined`.
906
+ * `.error()` treats this like an item in `logs` since `.error()`
907
+ * doesn't throw.
908
+ *
909
+ * @returns { string } Formatted metadata, logs, and, if given, the error.
910
+ *
911
+ * @example
912
+ * log.warn({
913
+ * before: "~~~",
914
+ * code: "ALK00d5",
915
+ * context: "demo",
916
+ * error:
917
+ * }, "demo log");
918
+ * // ~~~🤕 ALK00d5 demo WARNING [2024-08-16 00:30:39UTC]: demo log
919
+ * */
920
+ return this._console({
921
+ code: `ALK000e`,
922
+ ...error_opts,
923
+ level: `error`, icon: `🤕`, do_throw: false,
924
+ }, ...logs );
771
925
  }
772
926
 
773
927
  throw( throw_opts = {}, ...logs ){
@@ -807,14 +961,11 @@ class Log {
807
961
  * // Uncaught ReferenceError: undefined_value is not defined
808
962
  * // <the stack trace>
809
963
  * */
810
- let {
811
- code = `ALK000t`, before = ``, context = ``, error = `ALKiln default error`,
812
- } = throw_opts;
813
-
814
964
  this._console({
815
- level: `error`, icon: `🤕`, do_throw: true,
816
- before, code, context, error,
817
- }, ...logs);
965
+ code: `ALK000t`, error: new Error(`ALKiln default error`),
966
+ ...throw_opts,
967
+ do_throw: true, level: `error`, icon: `🤕`,
968
+ }, ...logs );
818
969
  }
819
970
 
820
971
  _save_to_debug_safely( save_debug_opts = {} ) {
@@ -962,18 +1113,19 @@ class Log {
962
1113
  }
963
1114
 
964
1115
  clear() {
965
- /** Empty all files possible without deleting them.
1116
+ /** Empty all files possible without deleting them. Never throw. This is
1117
+ * nice to have, but not crucial.
966
1118
  *
967
1119
  * @returns { Log } Self.
968
1120
  * */
969
1121
  try { fs.writeFileSync( `${ this.path }/${ this.debug_log_filename }`, `` ); }
970
- catch (error) { /* Fail without throwing. Nice to have, not crucial. */ }
1122
+ catch (error) {}
971
1123
  try { fs.writeFileSync( `${ this.path }/${ this.report_log_filename }`, `` ); }
972
- catch (error) { /* Fail without throwing. Nice to have, not crucial. */ }
1124
+ catch (error) {}
973
1125
  try { fs.writeFileSync( `${ this.path }/${ this.report_filename }`, `` ); }
974
- catch (error) { /* Fail without throwing. Nice to have, not crucial. */ }
1126
+ catch (error) {}
975
1127
  try { fs.writeFileSync( `${ this.path }/${ this.unexpected_filename }`, `` ); }
976
- catch (error) { /* Fail without throwing. Nice to have, not crucial. */ }
1128
+ catch (error) {}
977
1129
  return this;
978
1130
  }
979
1131