@suffolklitlab/alkiln 5.14.0 → 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 (35) hide show
  1. package/.github/workflows/github_server.yml +1 -1
  2. package/.github/workflows/playground.yml +2 -1
  3. package/CHANGELOG.md +29 -0
  4. package/action.yml +26 -10
  5. package/action_for_github_server/action.yml +31 -27
  6. package/docs/4_environments.md +2 -2
  7. package/docs/artifacts_folder_name.md +121 -71
  8. package/lib/docassemble/server_install.js +0 -2
  9. package/lib/docassemble/setup.js +1 -3
  10. package/lib/docassemble/takedown.js +2 -2
  11. package/lib/globals.js +10 -0
  12. package/lib/run_cucumber.js +40 -53
  13. package/lib/scope.js +32 -0
  14. package/lib/setup/TestGenerator.js +1757 -0
  15. package/lib/setup/artifacts.js +20 -0
  16. package/lib/setup/generate.js +42 -0
  17. package/lib/setup/get_Gherkin_AST.js +26 -0
  18. package/lib/setup/validate.js +89 -0
  19. package/lib/steps.js +53 -19
  20. package/lib/utils/Log.js +302 -149
  21. package/lib/utils/get_error_stacks.js +16 -0
  22. package/lib/utils/reports.js +43 -23
  23. package/lib/utils/session_vars.js +77 -57
  24. package/lib/utils/set_sources_paths.js +98 -0
  25. package/package.json +18 -7
  26. package/tests/log_codes/check_codes.sh +17 -13
  27. package/tests/log_codes/log_code_expected_instances.txt +58 -0
  28. package/tests/unit_tests/constrained_random_answers/invalid.fixtures.js +178 -0
  29. package/tests/unit_tests/constrained_random_answers/invalid.test.js +251 -0
  30. package/tests/unit_tests/constrained_random_answers/valid.fixtures.js +957 -0
  31. package/tests/unit_tests/constrained_random_answers/valid.test.js +563 -0
  32. package/tests/unit_tests/log.test.js +17 -41
  33. package/_alkiln_meta/gherkin_parsed_example.json +0 -1546
  34. package/_alkiln_meta/shift_logs.js +0 -75
  35. /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 || [];
246
366
 
247
- let formatted_str_to_return = this.debug({ level, before, icon, code, context, error, do_throw }, ...logs );
367
+ if ( do_throw || error !== undefined ) {
368
+ error = this._get_Error_safely({ maybe_error: error });
369
+ }
370
+
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 });
@@ -555,48 +688,29 @@ class Log {
555
688
  } // ends outer try
556
689
  }
557
690
 
558
- _combine_logs_and_possible_error({ logs=[], do_throw=false, maybe_error }) {
559
- /** Returns a list of combined values to be logged. If we need to include
560
- * an error, ensure it's an error. Catch ANY internal errors.
561
- *
562
- * @param { array } logs - Required. List of logs.
563
- * @param { boolean } do_throw - Optional. Default is `false`. If `true`
564
- * caller wants to throw an error.
565
- * @param { Error|undefined } maybe_error - Optional. Default is
566
- * `undefined`. A value that might be. turned into an Error and is
567
- * definitely _safe_ to turn into an Error. For example, no circular
568
- * references.
569
- *
570
- * @returns { array } List of all values that should be logged.
571
- *
572
- * @example
573
- * let with_error = log._combine_logs_and_possible_error({
574
- * [ "a", "b" ], do_throw: true, maybe_error: "A string."
575
- * });
576
- * // [ "a", "b", <An error with its stack and the message "A string."> ]
577
- * @example
578
- * let with_error = log._combine_logs_and_possible_error({
579
- * [ "a", "b" ], do_throw: false, maybe_error: new Error("An error.")
580
- * });
581
- * // [ "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
+ *
582
705
  * */
583
- let combined = [ ...logs ];
584
- try {
585
- // If we're supposed to throw, make sure the error gets an Error value.
586
- if ( do_throw ) {
587
- combined.push( this._get_Error_safely({ maybe_error }) );
588
-
589
- // Otherwise error should be `undefined`. If not, we get a warning.
590
- } else if ( maybe_error !== undefined ) {
591
- 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.` });
592
- }
593
- } catch ( combining_error_error ) {
594
- this._save_to_debug_safely({ unexpected: true, text: `🖊️ ALK0226 internal debug NOTE [${ Date.now() }]: Skipped combining the logs.` });
595
- 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 });
596
708
  }
709
+ let logs_maybe_with_error = [ ...logs ];
710
+ if ( error !== undefined ) { logs_maybe_with_error.push( error ); }
597
711
 
598
- return combined;
599
- } // Ends _combine_logs_and_possible_error()
712
+ return logs_maybe_with_error;
713
+ }
600
714
 
601
715
  _get_Error_safely({ maybe_error }) {
602
716
  /** Given anything, return an Error. Avoid throwing any errors.
@@ -604,17 +718,28 @@ class Log {
604
718
  * @param { Object } opts
605
719
  * @param { * } opts.maybe_error - Optional. Can be anything.
606
720
  *
607
- * @returns { Error }
721
+ * @returns { Error|undefined }
608
722
  * */
609
- if ( maybe_error instanceof Error ) {
610
- return maybe_error;
611
- } else {
612
- if ( this._json_does_error( maybe_error )) {
613
- 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
+
614
739
  }
615
- // The stack trace will be less helpful, but it's all we can do if the
616
- // caller hasn't already created the error.
617
- 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 });
618
743
  }
619
744
  }
620
745
 
@@ -680,7 +805,7 @@ class Log {
680
805
  * @param logs { ...string } - Optional. Default is `undefined`. Explanation
681
806
  * of what happened.
682
807
  *
683
- * @returns { string } Formatted metadata, logs, and, if given, the error.
808
+ * @returns { string } Formatted metadata and logs.
684
809
  *
685
810
  * @example
686
811
  * log.warn({
@@ -692,13 +817,11 @@ class Log {
692
817
  * */
693
818
  // Discuss: do we want to log some successes as "💡 ... SUCCESS: " for
694
819
  // smaller successful operations along the way?
695
- let {
696
- code = `ALK000s`, before = ``, context = ``
697
- } = success_opts;
698
820
  return this._console({
699
- level: `success`, icon: `🌈`,
700
- before, code, context,
701
- }, ...logs);
821
+ code: `ALK000s`,
822
+ ...success_opts,
823
+ level: `success`, icon: `🌈`, do_throw: false,
824
+ }, ...logs );
702
825
  }
703
826
 
704
827
  info( info_opts = {}, ...logs ) {
@@ -716,7 +839,7 @@ class Log {
716
839
  * @param logs { ...string } - Optional. Default is `undefined`. Explanation
717
840
  * of what happened.
718
841
  *
719
- * @returns { string } Formatted metadata, logs, and, if given, the error.
842
+ * @returns { string } Formatted metadata and logs.
720
843
  *
721
844
  * @example
722
845
  * log.warn({
@@ -726,13 +849,11 @@ class Log {
726
849
  * }, "demo log");
727
850
  * // ~~~💡 ALK00d4 demo INFO [2024-08-16 00:30:39UTC]: demo log
728
851
  * */
729
- let {
730
- code = `ALK000i`, before = ``, context = ``,
731
- } = info_opts;
732
852
  return this._console({
733
- level: `info`, icon: `💡`,
734
- before, code, context,
735
- }, ...logs);
853
+ code: `ALK000i`,
854
+ ...info_opts,
855
+ level: `info`, icon: `💡`, do_throw: false,
856
+ }, ...logs );
736
857
  }
737
858
 
738
859
  warn( warning_opts = {}, ...logs ) {
@@ -750,7 +871,7 @@ class Log {
750
871
  * @param logs { ...string } - Optional. Default is `undefined`. Explanation
751
872
  * of what happened.
752
873
  *
753
- * @returns { string } Formatted metadata, logs, and, if given, the error.
874
+ * @returns { string } Formatted metadata and logs.
754
875
  *
755
876
  * @example
756
877
  * log.warn({
@@ -760,13 +881,47 @@ class Log {
760
881
  * }, "demo log");
761
882
  * // ~~~🔎 ALK00d5 demo WARNING [2024-08-16 00:30:39UTC]: demo log
762
883
  * */
763
- let {
764
- code = `ALK000w`, before = ``, context = ``
765
- } = warning_opts;
766
884
  return this._console({
767
- level: `warn`, icon: `🔎`,
768
- before, code, context,
769
- }, ...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 );
770
925
  }
771
926
 
772
927
  throw( throw_opts = {}, ...logs ){
@@ -806,14 +961,11 @@ class Log {
806
961
  * // Uncaught ReferenceError: undefined_value is not defined
807
962
  * // <the stack trace>
808
963
  * */
809
- let {
810
- code = `ALK000t`, before = ``, context = ``, error = `ALKiln default error`,
811
- } = throw_opts;
812
-
813
964
  this._console({
814
- level: `error`, icon: `🤕`, do_throw: true,
815
- before, code, context, error,
816
- }, ...logs);
965
+ code: `ALK000t`, error: new Error(`ALKiln default error`),
966
+ ...throw_opts,
967
+ do_throw: true, level: `error`, icon: `🤕`,
968
+ }, ...logs );
817
969
  }
818
970
 
819
971
  _save_to_debug_safely( save_debug_opts = {} ) {
@@ -961,18 +1113,19 @@ class Log {
961
1113
  }
962
1114
 
963
1115
  clear() {
964
- /** 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.
965
1118
  *
966
1119
  * @returns { Log } Self.
967
1120
  * */
968
1121
  try { fs.writeFileSync( `${ this.path }/${ this.debug_log_filename }`, `` ); }
969
- catch (error) { /* Fail without throwing. Nice to have, not crucial. */ }
1122
+ catch (error) {}
970
1123
  try { fs.writeFileSync( `${ this.path }/${ this.report_log_filename }`, `` ); }
971
- catch (error) { /* Fail without throwing. Nice to have, not crucial. */ }
1124
+ catch (error) {}
972
1125
  try { fs.writeFileSync( `${ this.path }/${ this.report_filename }`, `` ); }
973
- catch (error) { /* Fail without throwing. Nice to have, not crucial. */ }
1126
+ catch (error) {}
974
1127
  try { fs.writeFileSync( `${ this.path }/${ this.unexpected_filename }`, `` ); }
975
- catch (error) { /* Fail without throwing. Nice to have, not crucial. */ }
1128
+ catch (error) {}
976
1129
  return this;
977
1130
  }
978
1131
 
@@ -0,0 +1,16 @@
1
+ module.exports = function get_error_stacks({ errors }) {
2
+ /** Given a list of Error instances, return a list of their stacks
3
+ *
4
+ * Discuss: Move this into `Log`. Potentially print each stack with the
5
+ * same icon as the message. `{to_stack: [Errors]}.
6
+ *
7
+ * @param {object} obj - Named arguments
8
+ * @param {[Error]} obj.errors - A list of Error instances
9
+ *
10
+ * @returns {[Error.stack]} - A list of Error instances' `.stack` properties
11
+ * */
12
+ let stacks = errors.map(function( error ) {
13
+ return error.stack;
14
+ });
15
+ return stacks;
16
+ };