@suffolklitlab/alkiln 5.13.2 → 5.13.4

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 (39) hide show
  1. package/.github/workflows/github_server.yml +7 -7
  2. package/.github/workflows/playground.yml +1 -1
  3. package/CHANGELOG.md +32 -1
  4. package/action.yml +70 -28
  5. package/action_for_github_server/action.yml +61 -29
  6. package/docs/4_environments.md +243 -0
  7. package/docs/artifacts_folder_name.md +135 -0
  8. package/docs/constrained_random_tests.md +347 -0
  9. package/docs/decisions/006_which_log_level_to_pick_2024_09_5.md +70 -0
  10. package/docs/decisions/007_getting_and_storing_logs.md +179 -0
  11. package/docs/decisions/move_runtime_config_to_sources_folder.md +3 -0
  12. package/docs/decisions/reports_and_logs_2024_08_11.md +161 -0
  13. package/docs/kinds_of_errors.md +26 -0
  14. package/docs/prioritize_constrained_random_tests.md +101 -0
  15. package/docs/seprate_file_generation_GitHub_action.md +11 -0
  16. package/lib/docassemble/docassemble_api_interface.js +77 -51
  17. package/lib/docassemble/server_install.js +32 -9
  18. package/lib/docassemble/setup.js +40 -25
  19. package/lib/docassemble/takedown.js +33 -10
  20. package/lib/parse.js +36 -0
  21. package/lib/run_cucumber.js +115 -36
  22. package/lib/scope.js +69 -77
  23. package/lib/steps.js +70 -42
  24. package/lib/utils/Log.js +982 -0
  25. package/lib/utils/files.js +18 -11
  26. package/lib/utils/get_sources_paths.js +162 -0
  27. package/lib/utils/parse.js +1 -0
  28. package/lib/utils/proccess_command_line_args.js +267 -0
  29. package/lib/utils/reports.js +64 -17
  30. package/lib/utils/session_vars.js +34 -21
  31. package/lib/utils/time.js +44 -0
  32. package/package.json +1 -1
  33. package/tests/log_codes/check_codes.sh +2 -1
  34. package/tests/log_codes/log_code_expected_instances.txt +33 -9
  35. package/tests/unit_tests/getAllFields.test.js +5 -0
  36. package/tests/unit_tests/getMatchingRows.test.js +5 -0
  37. package/tests/unit_tests/log.test.js +581 -0
  38. package/tests/unit_tests/normalizeTable.test.js +5 -0
  39. package/lib/utils/log.js +0 -96
@@ -1,26 +1,33 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- // TODO: Rename to playground_install?
4
-
5
3
  const child_process = require(`child_process`);
6
4
 
7
- const log = require(`../utils/log`);
5
+ const Log = require(`../utils/Log`);
8
6
  const session_vars = require(`../utils/session_vars` );
7
+ const files = require(`../utils/files` );
8
+ const time = require(`../utils/time` );
9
+
10
+ const context = `setup`;
9
11
 
10
12
  const setup = async () => {
11
- /** In the docassemble testing account identified by the
12
- * DOCASSEMBLE_DEVELOPER_API_KEY secret, create a new Project,
13
- * pull code into that project, and, if needed, wait for the
14
- * server to reload.
15
- */
13
+ /** This is for a package being installed in an author's account (Playground)
14
+ * on their docassemble server. Using the author's
15
+ * DOCASSEMBLE_DEVELOPER_API_KEY secret, create a new Project, pull code
16
+ * into that project, and, if needed, wait for the server to reload.
17
+ */
16
18
 
17
19
  // Make and save project name
18
20
  let project_name = session_vars.get_project_base_name().replace(/[^A-Za-z0-9]/g, '') + `${Date.now()}`;
19
21
  session_vars.save_project_name( project_name );
20
22
 
21
- log.info({ type: `setup`, code: `ALK0046`, pre: `Starting to upload this package to `
23
+ const starting_msg = `Starting to upload this package to `
22
24
  + `a new Project called ${project_name} in the Playground `
23
- + `of your server's testing account. It may take a couple of minutes.` });
25
+ + `of your server's testing account. It may take a couple of minutes.`;
26
+ log.info({ code: `ALK0046`, context, },
27
+ starting_msg
28
+ );
29
+
30
+ // TODO: 1. Start listening to the normal console here (for debug logging)
24
31
 
25
32
  // dainstall . --apiurl https://site.org --apikey $API_KEY --playground --project=name
26
33
  // I think this won't run on windows? Why do I think this? Where on the
@@ -34,29 +41,37 @@ const setup = async () => {
34
41
  `--playground`, `--project=${ project_name }`
35
42
  ],
36
43
  // Prints messages from the process to the console while it's running
44
+ // result.stderr, result.stdout, etc. will be `null`
37
45
  { stdio: 'inherit', }
38
46
  );
39
47
 
40
- // 403 is invalid API key, but I'm not sure how to output
41
- // the response to find out the error code
48
+ // TODO: 2. Stop listening to the normal console here
42
49
 
50
+ // 403 is invalid API key, but I'm not sure how to output
51
+ // the response to find out the error code. stderr is `null`.
43
52
  if ( result.status !== 0 ) {
44
- log.error({
45
- type: `setup`,
46
- code: `ALK0047`,
47
- pre: `Status code ${ result.status }. Could not dainstall this package on your server. See above for more details.`
48
- });
49
- throw(result.stderr);
53
+ const install_err_msg = `Status code ${ result.status }. Could not `
54
+ + `dainstall this package on your server. See above for more details.`;
55
+ log.throw({ code: `ALK0047`, context,
56
+ before: `☝️ ☝️ ☝️ Error data ☝️ ☝️ ☝️\n`,
57
+ error: `Error while installing project` },
58
+ install_err_msg
59
+ );
50
60
  }
51
61
 
52
- log.success({
53
- type: `setup`,
54
- code: `ALK0048`,
55
- pre: `ALKiln created a Project in your Playground named "${ project_name }"!`
56
- });
62
+ log.success({ code: `ALK0048`, context, },
63
+ `ALKiln created a Project in your Playground named "${ project_name }"!`
64
+ );
57
65
  }; // Ends setup();
58
66
 
59
-
60
- // Before everything, make sure all the required env vars exist
67
+ // 1. session_vars - make sure all the required env vars exist
61
68
  session_vars.validateEnvironment();
69
+
70
+ // 2. Prepare logs
71
+ // Local devs: This will be separate from your test paths
72
+ // unless you pass a `path` argument into the cucumber run command
73
+ const argv = require(`minimist`)(process.argv.slice(2));
74
+ const log = new Log({ path: argv.path, context, });
75
+
76
+ // 3. run
62
77
  setup();
@@ -1,26 +1,49 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  const da_i = require('./docassemble_api_interface');
4
- const log = require('../utils/log');
5
- const session_vars = require('../utils/session_vars' );
4
+ const session_vars = require('../utils/session_vars');
5
+ const Log = require('../utils/Log');
6
+
7
+ const context = `takedown`;
6
8
 
7
9
  const takedown = async () => {
8
- /* In the docassemble testing account identified by the
9
- * DOCASSEMBLE_DEVELOPER_API_KEY secret, delete the
10
- * Project created for this test.
11
- * For local development, clean up project name file. */
12
- log.info({ type: `takedown`, code: `ALK0177`, pre: `Trying to remove the docassemble Project from the testing account.` });
10
+ /** In the docassemble testing account identified by the
11
+ * DOCASSEMBLE_DEVELOPER_API_KEY secret, delete the Project created for this
12
+ * test. For local development, clean up Project name in the runtime_config
13
+ * file.
14
+ * */
15
+ log.info({ code: `ALK0177`, context, },
16
+ `Trying to remove the docassemble Project from the testing account.`
17
+ );
13
18
  // Explicit try-catch prevents "Unhandled promise rejection error"
14
19
  // avoids showing confusing node errors to users that they shouldn't have to fix
15
20
  try {
16
21
  await da_i.delete_project();
17
22
  session_vars.delete_project_name();
18
- log.success({ type: `takedown`, code: `ALK0178`, pre: `Test takedown has finished successfully!` });
23
+ log.info({ code: `ALK0178`, context, },
24
+ `Test takedown finished successfully!`
25
+ );
19
26
  } catch (error) {
20
- log.error({type: `takedown`, code: `ALK0179`, pre: `Could not delete project`, data: JSON.stringify(error, null, 4)})
21
- // we don't re-throw the exception, so make sure to exit with 1 to let GitHub Actions know the script failed
27
+ log.throw({ code: `ALK0179`, context, error: error },
28
+ `Unable to delete project`,
29
+ );
30
+ // we don't re-throw the exception, so make sure to exit
31
+ // with 1 to let GitHub Actions know the script failed
22
32
  process.exitCode = 1;
23
33
  }
34
+
35
+ // TODO: ? Build the final setup, run, & takedown verbose log
36
+ // file in here. Add to it in the GitHub action ?
24
37
  };
25
38
 
39
+ // 1. session_vars - make sure all the required env vars exist
40
+ session_vars.validateEnvironment();
41
+
42
+ // 2. Prepare logs
43
+ // Local devs: This will be separate from your test artifacts paths
44
+ // unless you pass a `path` argument into the `cucumber` run command
45
+ const argv = require(`minimist`)( process.argv.slice(2) );
46
+ const log = new Log({ path: argv.path, context, });
47
+
48
+ // 3. run
26
49
  takedown();
package/lib/parse.js ADDED
@@ -0,0 +1,36 @@
1
+ #!/usr/bin/env node
2
+
3
+ let log = `log`;
4
+ let args = get_command_line_args();
5
+ let sources_paths = get_sources_paths( args );
6
+ let feature_files_paths = get_feature_files( sources_paths );
7
+
8
+ let all_errors = [];
9
+ // Just see if any error when Gherkin parses them
10
+ for ( let file_path of feature_files_paths ) {
11
+ try {
12
+ Gherkin.parse( file_path );
13
+ } catch ( error ) {
14
+ all_errors.push( error );
15
+ }
16
+ }
17
+ // let generated_files_paths = constrained_random_answer_tests.generate();
18
+ // // Just see if any error when Gherkin parses them
19
+ // for ( let generated_path of generated_files_paths ) {
20
+ // try {
21
+ // Gherkin.parse( generated_path );
22
+ // } catch ( error ) {
23
+ // all_errors.push( error );
24
+ // }
25
+ // }
26
+
27
+ let generator_errors = constrained_random_answer_tests.generate();
28
+ all_errors = [ ...all_errors, ...generator_errors ];
29
+
30
+ if ( all_errors.length > 0 ) {
31
+ // Use the Log class in here
32
+ for ( let one_error of all_errors ) {
33
+ console.log( `An error:`, one_error );
34
+ }
35
+ throw new Error( `ALKiln ran into an error when parsing the test files. See above for more details` );
36
+ }
@@ -1,9 +1,10 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  const fs = require('fs');
4
+ const path = require(`path`);
4
5
  const cucumber_api = require('@cucumber/cucumber/api');
5
6
 
6
- const log = require('./utils/log'); // These always log in the console
7
+ const Log = require('./utils/Log');
7
8
  const session_vars = require('./utils/session_vars');
8
9
 
9
10
  const SUMMARY_FILENAME = `cucumber-report.txt`;
@@ -53,11 +54,14 @@ function build_config( command_args ) {
53
54
  }; // Ends build_config()
54
55
 
55
56
 
56
- (async function main() {
57
+ async function main() {
57
58
  /** Create the cucumber config, run the tests, create and
58
59
  * manipluate the output. */
60
+
59
61
  const environment = { cwd: process.cwd() };
60
- log.info({ type: `run`, code: `ALK0049`, pre: `environment`, data: environment });
62
+ log.info({ code: `ALK0049`, context: `pre_run environment` },
63
+ environment
64
+ );
61
65
 
62
66
  // We'll use the user info to construct the possible paths to /sources
63
67
  let id = session_vars.get_user_id();
@@ -65,8 +69,9 @@ function build_config( command_args ) {
65
69
  let project = session_vars.get_user_project_name();
66
70
 
67
71
  // process.argv is a list of strings of commands and args.
68
- // If using `npm run cucumber`, use `--` before `--sources=./foo`
69
- let argv = require(`minimist`)( process.argv.slice(2), {
72
+ // If using `npm run cucumber` (as opposed to the bin commands),
73
+ // use `--` before `--sources=./foo`
74
+ const argv = require(`minimist`)( process.argv.slice(2), {
70
75
  default: {
71
76
  sources: [
72
77
  `./docassemble/*/data/sources`, // In GitHub folder
@@ -85,35 +90,52 @@ function build_config( command_args ) {
85
90
  let one_dir_exists = session_vars.set_sources_paths( sources_list );
86
91
  if ( !one_dir_exists ) {
87
92
  // If we don't throw, it'll be more confusing when no tests run
88
- let msg = `ALKiln can't find your "sources" folder(s). See above for details.`;
89
- let code = `ALK0050`
90
- log.error({ type: `run`, code: code, pre: msg });
91
- throw new ReferenceError(`🤕 ${ code } run ERROR: ${ msg }`);
93
+ log.throw({ code: `ALK0050`, context: `pre_run`,
94
+ error: new ReferenceError(
95
+ `ALKiln can't find your "sources" folder(s). See above for details.`
96
+ ),
97
+ });
92
98
  }
93
99
 
94
100
  const { runConfiguration } = await cucumber_api.loadConfiguration({
95
101
  provided: build_config( argv ),
96
102
  }, environment);
97
-
98
- log.info({ type: `run`, code: `ALK0051`, pre: `runConfiguration`, data: runConfiguration });
99
-
100
- var path = require(`path`);
101
- var codeDir = path.dirname(require.main.filename);
102
- log.info({ type: `run`, code: `ALK0052`, pre: `working directory`, data: codeDir });
103
+ log.info({ code: `ALK0051`, context: `pre_run config`, },
104
+ runConfiguration
105
+ );
106
+
107
+ const codeDir = path.dirname(require.main.filename);
108
+ log.info({ code: `ALK0052`, context: `pre_run directory`, },
109
+ codeDir, `\n\n`
110
+ );
103
111
  const support = await cucumber_api.loadSupport({ support: {
104
112
  requireModules: [ `${ codeDir }/index.js` ],
105
113
  requirePaths: [ `${ codeDir }/index.js` ],
106
114
  importPaths: [],
107
115
  }, sources: {paths: []}}, environment);
108
116
 
109
- log.info({ type: `run`, code: `ALK0053`, pre: `working directory`, data: codeDir });
110
- log.info({ type: `plain`, pre: `\n\n` });
111
-
117
+ // ====================
118
+ /** Run */
119
+ // ====================
112
120
  const { success } = await cucumber_api.runCucumber({...runConfiguration, support});
121
+ // ====================
122
+
123
+ /** Write to files.
124
+ *
125
+ * Note: Don't add to the purely report-focused files. Those are files where
126
+ * authors can focus on the info we give them, which they often miss in
127
+ * the GitHub action.
128
+ */
129
+
113
130
  if ( success ) {
114
- log.success({ type: `run`, code: `ALK0054`, pre: `Tests succeeded!` });
131
+ log.success({ code: `ALK0054`, context: `post_run`, },
132
+ `Tests succeeded!`
133
+ );
115
134
  } else {
116
- log.error({ type: `run`, code: `ALK0055`, pre: `The test run ran into something unexpected. Look above for more details.` });
135
+ // Don't want to throw error here, so don't pass error object
136
+ log.warn({ code: `ALK0055`, context: `post_run`, },
137
+ `The tests ran into something unexpected. Look above for more details.`
138
+ );
117
139
  }
118
140
  // Custom exit code. May overlap with other systems.
119
141
  // Best info I've found via stack overflow: https://tldp.org/LDP/abs/html/exitcodes.html
@@ -123,10 +145,12 @@ function build_config( command_args ) {
123
145
  // This is hardcoded information from `provide_config` above.
124
146
  // This code isn't used for anything other than detecting and removing color codes.
125
147
  let summary_file_name = SUMMARY_FILENAME;
126
- fs.readFile(summary_file_name, 'utf8', (err, text) => {
127
- if (err) {
148
+ fs.readFile(summary_file_name, 'utf8', (read_error, text) => {
149
+ if ( read_error ) {
128
150
  // Something else will probably show an error that's more relevant
129
- log.info({ type: `run`, code: `ALK0056`, pre: `Failed to read cucumber report`, data: err });
151
+ log.warn({ code: `ALK0056`, context: `post_run error`, },
152
+ `Failed to read cucumber report`, read_error
153
+ );
130
154
  return;
131
155
  }
132
156
 
@@ -134,20 +158,75 @@ function build_config( command_args ) {
134
158
  // https://gist.github.com/fnky/458719343aabd01cfb17a3a4f7296797#colors--graphics-mode
135
159
  text = text.replace(/\x1b\[[0-9][0-9]?[0-9]?m/g, '');
136
160
 
137
- // Put it in the correct file in the correct folder
138
- let artifacts_path = session_vars.get_artifacts_path_name();
139
- try {
140
- fs.appendFileSync( `${ artifacts_path }/${ log.debug_log_file }`, `\n\n${text}`);
141
- } catch ( error ) {
142
- log.info({ type: `run`, code: `ALK0182`, pre: `Something probably went wrong earlier. Look above for more details, but here is more information just in case: ALKiln was unable to write to ${ artifacts_path }/${ log.debug_log_file }.`, data: error });
143
- }
144
161
  try {
145
- // Add to a unexpected results file when tests themselves fail
162
+ // Add to the debug and non-passed test output files
146
163
  if ( exitCode === 50 ) {
147
- fs.appendFileSync( `${ artifacts_path }/${ log.unexpected_filename }`, `\n\n${ text }` );
164
+ // Save only non-passing test output to cut out cruft.
165
+ // Our reports save only non-passing tests to this file.
166
+ log.debug({ icon: `🔎`, code: `ALK0216`, context: `post_run`, level: `warn` },
167
+ `\n\n`, text
168
+ );
169
+ } else {
170
+ log.debug({ code: `ALK0225`, context: `post_run`, },
171
+ `\n\n`, text
172
+ );
148
173
  }
149
- } catch ( error ) {
150
- log.info({ type: `run`, code: `ALK0181`, pre: `Something probably went wrong earlier. Look above for more details, but here is more information just in case: ALKiln was unable to write to ${ artifacts_path }/${ log.unexpected_filename }.`, data: error });
151
- }
174
+
175
+ /** Include debug info at the bottom of the unexpected results file if
176
+ * needed. Avoid creating the file if its not necessary. */
177
+ let temp_debug_unexpected_content = ``;
178
+ try {
179
+ temp_debug_unexpected_content = fs.readFileSync(`${ log.path }/${ log.temp_unexpected_debug_log_filename }`);
180
+ } catch ( ignore_err ) { /* Missing debug info is ok */ }
181
+
182
+ if ( temp_debug_unexpected_content !== `` ) {
183
+ // Add spacing before the debug content if file already has content
184
+ try {
185
+ fs.readFileSync(`${ log.path }/${ log.unexpected_filename }`); // Does it have contents?
186
+ fs.appendFileSync(`${ log.path }/${ log.unexpected_filename }`, `\n\n\n\n\n` );
187
+ } catch ( ignore_err ) { /* Missing unexpected file is ok */ }
188
+
189
+ // Add the heading for debug content
190
+ fs.appendFileSync( `${ log.path }/${ log.unexpected_filename }`,
191
+ `≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡
192
+ 🐛 Full DEBUG info (some repeated content) 🐛
193
+ ≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡≡\n\n`
194
+ );
195
+ // Add the debug content itself
196
+ fs.appendFileSync(`${ log.path }/${ log.unexpected_filename }`, temp_debug_unexpected_content );
197
+ }
198
+
199
+ } catch ( writing_cucumber_error ) {
200
+ let code = `ALK0182`;
201
+ let context = `post_run error`
202
+ let msg = `Something probably went wrong earlier. Look above for more details. Skipped writing to ${ log.path }/${ log.debug_log_filename }.`;
203
+ try {
204
+ log.warn({ code, context, }, msg, writing_cucumber_error );
205
+ } catch ( all_writing_error ) {
206
+ console.warn( `🔎 ${ code } ${ context } WARNING ${ Date.now() }: ${ msg }` );
207
+ console.warn( writing_cucumber_error );
208
+ console.warn( all_writing_error );
209
+ }
210
+
211
+ } // ends try writing to debug files
212
+
213
+ return;
152
214
  });
153
- })();
215
+ }
216
+
217
+
218
+ // 1. session_vars - make sure all the required env vars exist
219
+ session_vars.validateEnvironment();
220
+
221
+ // 2. Prepare logs
222
+ // process.argv is a list of strings of commands and args.
223
+ // If using `npm run cucumber` (as opposed to the bin commands),
224
+ // use `--` before `--sources=./foo`
225
+ const argv = require(`minimist`)( process.argv.slice(2) );
226
+ // The artifacts folder might be getting created for the first
227
+ // time right here. The tests themselves should avoid creating
228
+ // their own file.
229
+ const log = new Log({ path: argv.path, context: `run` });
230
+
231
+ // 3. run
232
+ main();