@suffolklitlab/alkiln 5.6.0 → 5.7.0-feature-tap1

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.
@@ -12,7 +12,7 @@ const time = require('../utils/time');
12
12
  // do it before trying to load a page.
13
13
 
14
14
  // TODO: How can we test busy server stuff properly? Especially down
15
- // to specific functions, like `.pull()` which run in a very small
15
+ // to specific functions, which can run in a very small
16
16
  // window.
17
17
 
18
18
  /** Notes:
@@ -171,287 +171,10 @@ da_i.get_dev_id = async function ( dev_id_options ) {
171
171
  return dev_id;
172
172
  }; // Ends da_i.get_dev_id();
173
173
 
174
-
175
- // node -e 'require("./lib/docassemble/docassemble_api_interface.js").validate_api_key()'
176
- da_i.validate_api_key = async function ( api_key_options ) {
177
- /** Make sure the api key is the right one for this server.
178
- * See https://docassemble.org/docs/api.html#user. */
179
-
180
- log.info({ pre: `Checking the testing account's API key.` });
181
-
182
- // Default timeouts
183
- api_key_options = api_key_options || { timeout: DEFAULT_MAX_REQUEST_MS };
184
-
185
- try {
186
-
187
- // There's only one possible success result
188
- // As long as this succeeds, we're good
189
- await _da_REST.get_dev_id( api_key_options.timeout );
190
- log.info({ pre: `Your testing account's API key is perfectly valid on this server.` });
191
- return true;
192
-
193
- } catch ( error ) {
194
- // There's only one possible sever response error
195
- if ( error.response && error.response.status === 403 ) {
196
- // Throw in here, or at the caller?
197
- log.error({
198
- pre: `The API key in the GitHub secret DOCASSEMBLE_DEVELOPER_API_KEY `
199
- + `failed on this server with "Access Denied". Are you sure your `
200
- + `DOCASSEMBLE_DEVELOPER_API_KEY secret is set to the API key for `
201
- + `the docassemble account that runs the tests on this server? `
202
- + `Are you sure your SERVER_URL GitHub secret is set to the server `
203
- + `address on which this testing account exists?`
204
- + `To update your GitHub secrets, either set this repository's `
205
- + `secrets at ${ session_vars.get_repo_url() }/settings/secrets `
206
- + `or ask your GitHub organization admin to set the `
207
- + `organization's secrets.`
208
- });
209
- } // ends 403 message
210
- throw error;
211
- } // ends try to get dev_id
212
- }; // Ends da_i.validate_api_key()
213
-
214
-
215
- // node -e 'require("./lib/docassemble/docassemble_api_interface.js").loop_to_create_project()'
216
- da_i.loop_to_create_project = async function( creation_options ) {
217
- /** Create a docassemble Project on the docassemble server's account.
218
- * Loop through potential project names until we find one that
219
- * isn't already taken and then use that name to create the Project. */
220
-
221
- // Defaults
222
- creation_options = creation_options || {};
223
- let timeout = creation_options.timeout || DEFAULT_MAX_REQUEST_MS;
224
- let max_tries = creation_options.max_tries || 15;
225
- let wait_seconds = creation_options.wait_seconds || 5;
226
-
227
- let project_name = null;
228
- // Unlikely, but 2 devs could run locally and create the same Project name
229
- let num_tries = 0;
230
-
231
- // Try to create a project. If the project name is already taken, increment and loop again.
232
- while ( num_tries <= max_tries ) {
233
- num_tries++;
234
- try {
235
-
236
- // Try next name that could be unique. Must remove all non-valid characters
237
- // TODO: This sanitization is already done in session_vars, so remove it from here
238
- project_name = session_vars.get_project_base_name().replace(/[^A-Za-z0-9]/g, '') + `${Date.now()}`;
239
- log.info({ pre: `Trying to create a docassemble Project with the name ${ project_name }` });
240
- let response = await _da_REST.create_project( project_name, timeout );
241
- log.info({ pre: `The docassemble server is ready. Created a Project named ${ project_name }` });
242
- // If succeeded in creating project, return the name that worked
243
- return project_name;
244
-
245
- } catch ( error ) {
246
- // TODO: Make a `normalize_ajax_responses_and_errors()`?
247
- let response = error;
248
-
249
- if ( response && response.data ) {
250
- if ( response.data.includes( `Invalid project` ) ) {
251
- if ( num_tries < max_tries ) {
252
- // A non-unique project name. Try the next project name.
253
- continue;
254
-
255
- } else {
256
- // On the last loop, failed because there were too many project
257
- // names already taken
258
- log.error({
259
- pre: `There were already ${ max_tries } projects with the base `
260
- + `name ${ session_vars.get_project_base_name() }. If you need to allow `
261
- + `more, file an issue at https://github.com/SuffolkLITLab/ALKiln/issues.`
262
- });
263
- throw( error );
264
- }
265
-
266
- // ends "Invalid project" error
267
- } else {
268
- // Other response errors should be thrown with link to docs
269
- log.error({
270
- type: `docassemble ${ response.status } "${ response.data }"`,
271
- pre: `See https://docassemble.org/docs/api.html#playground_post_project.`
272
- });
273
- throw ( error );
274
- }
275
- } else { // for if error.response
276
-
277
- // If it's a timeout error, server is busy. Try again after waiting.
278
- // Why are we using `max_tries` for timeout error as well?
279
- if ( is_timeout_error( error ) && num_tries < max_tries ) {
280
- log.info({ pre: `The docassemble server was busy when we tried to `
281
- + `create the Project. Attempt ${ num_tries } of `
282
- + `${ max_tries }. Will try again in ${ wait_seconds } seconds.` });
283
- num_tries--; // Don't count this try
284
- await time.waitForTimeout( wait_seconds * 1000 );
285
- continue;
286
-
287
- // A non-timeout, non-server error should just be thrown
288
- } else {
289
- log.error({ pre: `Ran into unexpected error when trying to create the Project.` });
290
- throw error;
291
-
292
- } // ends type of non-response error
293
- } // ends type of response error
294
- } // ends try
295
- } // ends while
296
- }; // Ends da_i.loop_to_create_project()
297
-
298
-
299
- // node -e 'require("./lib/docassemble/docassemble_api_interface.js").pull()'
300
- da_i.pull = async function ( pull_options ) {
301
- /** Pull github code into an existing Project. */
302
-
303
- log.info({ pre: `Trying to pull code from the "${ session_vars.get_branch_name() }" branch.` });
304
-
305
- // Default timeouts
306
- let { timeout } = pull_options || { timeout: DEFAULT_MAX_REQUEST_MS };
307
-
308
- try {
309
-
310
- let response = await _da_REST.pull( timeout );
311
-
312
- // If server didn't need to restart
313
- if ( response && response.status === 204 ) {
314
- log.info({ pre: `Your Docassemble server finished pulling the package's code.` });
315
- return null;
316
- }
317
-
318
- // If server needed to restart because of the pulled code
319
- if ( response && response.status === 200 ) {
320
- log.debug({ pre: `Pull task id: ${ response.data.task_id }` });
321
- return response.data.task_id;
322
- }
323
-
324
- } catch ( error ) {
325
- // Pulling should not error
326
- log.error({ pre: `Pulling into the docassemble Project ran into an unexpected error.` });
327
- log.debug({ pre: `See https://docassemble.org/docs/api.html#playground_pull.` });
328
- throw error;
329
- }
330
- }; // Ends da_i.pull()
331
-
332
- // node -e 'require("./lib/docassemble/docassemble_api_interface.js").has_task_finished()'
333
- da_i.has_task_finished = async function ( api_endpoint, task_id, options ) {
334
- /** Check task status if it exists. If not, return true as task did
335
- * not require a server restart. See da_i.pull().
336
- * See https://docassemble.org/docs/api.html#restart_status. */
337
-
338
- // Defaults
339
- let { timeout } = options || { timeout: DEFAULT_MAX_REQUEST_MS };
340
-
341
- try {
342
-
343
- // If server did need to restart, check if it's done.
344
- let response = await _da_REST.has_task_finished( api_endpoint, task_id, timeout );
345
- log.debug({ pre: `Task status: ${ response.status }. Docassemble status text: ${ response.data.status }. Task id: ${ task_id }` });
346
-
347
- if ( response.data.status === 'completed' ) {
348
- return true;
349
-
350
- } else if ( response.data.status === 'working' ) {
351
- log.info({ pre: `The docassemble server is uploading the package.` });
352
- return false;
353
-
354
- } else if ( response.data.status === 'unknown' ) {
355
- // Server is working, but status 'unknown'? Bad design, but this
356
- // does get returned when install packages, so return true
357
- log.info({
358
- pre: `\`task_id\` "${ task_id }" has expired. If it's been `
359
- + `less than 1 hour since the task started, the task is `
360
- + `complete. See https://docassemble.org/docs/api.html#restart_status.`,
361
- data: response.data,
362
- });
363
- return true;
364
- } // ends status type
365
-
366
- } catch ( error ) {
367
- // Timeout means the server is busy.
368
- if ( is_timeout_error( error ) ) {
369
- log.info({ pre: `The docassemble server is still uploading the package.` });
370
- return false;
371
- }
372
-
373
- log.error({ pre: `Tried to check that the package was uploaded, but ran into an unexpected error.` });
374
- throw error;
375
- } // ends try to check for server done restarting
376
-
377
- }; // Ends da_i.has_task_finished()
378
-
379
- da_i.wait_for_playground_to_restart = async ( task_id, options ) => {
380
- return await da_i.wait_for_server_to_restart( "/api/restart_status", task_id, options );
381
- };
382
-
383
- da_i.wait_for_package_to_restart = async ( task_id, options ) => {
384
- return await da_i.wait_for_server_to_restart( "/api/package_update_status", task_id, options );
385
- };
386
-
387
- da_i.wait_for_server_to_restart = async ( api_endpoint, task_id, options ) => {
388
- /** Wait for the server to finish restarting in case a module needs to load.
389
- * This is a polling function because things may be returned
390
- * that aren't equivalent to the server being done with a task. */
391
- log.info({ pre: `Docassemble is pulling this package. The task_id is `
392
- + `"${ task_id }". A task_id is usually created for a package `
393
- + `with a module. It can take a few minutes to finish uploading.` });
394
-
395
- // Base default
396
- options = options || {};
397
- // Default timeout
398
- let timeout = options.timeout || DEFAULT_MAX_REQUEST_MS;
399
- // Other defaults
400
- let default_wait_between_loops = 4 * 1000;
401
- let default_wait_for_response = 1 * 1000;
402
- let default_max_loops = timeout/( default_wait_between_loops + default_wait_for_response );
403
- // Actual values
404
- let wait_between_loops = options.wait_between_loops || default_wait_between_loops;
405
- let wait_for_response = options.wait_for_response || default_wait_for_response;
406
- let max_loops = options.max_loops || default_max_loops;
407
-
408
- // Flags for stopping the `while` loop
409
- let num_tries = 0;
410
- let done_restarting = false;
411
-
412
- // Why loop instead of one long timeout? Because it might not just hang.
413
- // We might get a return value that's just `waiting`, meaning the
414
- // task is not done.
415
- while ( !done_restarting && num_tries <= max_loops ) {
416
- num_tries++;
417
- // Wait only a short time for the task to finish, not the full default timeout
418
- done_restarting = await da_i.has_task_finished( api_endpoint, task_id, { timeout: wait_for_response } );
419
- if ( !done_restarting ) {
420
- if ( num_tries < max_loops ) {
421
- // Show a slightly different message after a number of tries to reassure
422
- // users we're still paying attention
423
- let msg_start = `Not yet complete`;
424
- if ( num_tries > 3 ) { msg_start = `Still not complete`; }
425
-
426
- // When the server is busy, the task timeout causes this loop to actually
427
- // take 5 seconds to complete, but this only waits for 4. I'm not sure how
428
- // to make that clear
429
- log.info({ pre: `${ msg_start }. Check ${ num_tries } of ${ max_loops }. `
430
- + `Will check again in ${( wait_between_loops + wait_for_response )/1000 } seconds.`
431
- });
432
- await time.waitForTimeout( wait_between_loops );
433
-
434
- } else {
435
- // This is our last round, let's wrap it up
436
- let server_msg = `ALKiln setup: It seems like the docassemble server `
437
- + `didn't finish uploading the code after ${ timeout/1000 } `
438
- + `seconds. Some things you can try: check your server is up, try `
439
- + `re-running the test, and try pulling the package manually `
440
- + `into the playground.`;
441
- // Add link to docs once the docs are more comprehensive
442
- throw( server_msg );
443
- } // ends if ran out of tries
444
- } // ends if done_restarting
445
- } // ends while not done restarting
446
-
447
- log.info({ pre: `The docassemble server has uploaded your package.` });
448
- }; // Ends da_i.wait_for_server_to_restart()
449
-
450
-
451
- // Delete the Project
452
174
  // node -e 'require("./lib/docassemble/docassemble_api_interface.js").delete_project( "create1" )'
453
175
  da_i.delete_project = async function ( delete_options ) {
454
- /** Delete a project on the user's da server. */
176
+ /** Delete a project on the user's da server. Do we need to watch
177
+ * for a timeout? */
455
178
 
456
179
  // Defaults
457
180
  let { timeout } = delete_options || { timeout: DEFAULT_MAX_REQUEST_MS };
@@ -7,6 +7,7 @@ const fg = require('fast-glob');
7
7
  const server_install = async () => {
8
8
  session_vars.save_install_method(`server`);
9
9
 
10
+ // Get path to folders inside repo main "docassemble" folder
10
11
  let current_dir = process.cwd();
11
12
  let path = `${ current_dir }/docassemble/*`;
12
13
  let dirs = fg.sync(
@@ -19,6 +20,7 @@ const server_install = async () => {
19
20
 
20
21
  let folders = dirs.map(function (dir) { return dir.split('/').pop(); })
21
22
 
23
+ // Get names of all folders in repo "docassemble" folder
22
24
  let chosen_folder = folders[0];
23
25
  if ( folders.length === 0 ) {
24
26
  let msg = `There are 0 folders at ${path}. Tests won't run.`;
@@ -1,50 +1,60 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- const da_i = require('./docassemble_api_interface');
4
- const log = require('../utils/log');
5
- const session_vars = require('../utils/session_vars' );
6
- const files = require('../utils/files' );
3
+ // TODO: Rename to playground_install?
7
4
 
5
+ const child_process = require(`child_process`);
6
+
7
+ const log = require(`../utils/log`);
8
+ const session_vars = require(`../utils/session_vars` );
8
9
 
9
10
  const setup = async () => {
10
- /* In the docassemble testing account identified by the
11
+ /** In the docassemble testing account identified by the
11
12
  * DOCASSEMBLE_DEVELOPER_API_KEY secret, create a new Project,
12
13
  * pull code into that project, and, if needed, wait for the
13
- * server to restart. */
14
- log.info({ type: `setup`, pre: `Starting to upload this package to `
15
- + `a new Project in the server's testing account.` });
16
-
17
- // Check that API key is valid. Will error if it is invalid.
18
- // Should the error be thrown here?
19
- await da_i.validate_api_key();
20
-
21
- // Create a project on the da server's account with a unique name then
22
- // store it locally for later deleting the project.
23
- let project_name = null;
24
- // Explicit try-catch prevents "Unhandled promise rejection error"
25
- // avoids showing confusing node errors to users that they shouldn't have to fix
26
- try {
27
- project_name = await da_i.loop_to_create_project();
28
- log.info({type: `setup`, pre: `Created a Project named ${ project_name }`});
29
- } catch (error) {
30
- log.error({type: `setup`, pre: `an unexpected error occurred while trying to create a docassemble project in the user's account`, data: JSON.stringify(error, null, 4)})
31
- // we don't re-throw the exception, so make sure to exit with 1 to let Github Actions know the script failed
32
- process.exitCode = 1;
33
- return;
34
- }
35
- // Once the name is created, save that name to a local file.
14
+ * server to reload.
15
+ */
16
+
17
+ // Make and save project name
18
+ let project_name = session_vars.get_project_base_name().replace(/[^A-Za-z0-9]/g, '') + `${Date.now()}`;
36
19
  session_vars.save_project_name( project_name );
37
20
 
38
- // Pull code and wait for server restart to finish if needed
39
- let task_id = await da_i.pull();
40
- // Task id will be null if server did not need to restart
41
- if ( task_id ) {
42
- await da_i.wait_for_playground_to_restart( task_id );
21
+ log.info({ type: `setup`, pre: `Starting to upload this package to `
22
+ + `a new Project called ${project_name} in the Playground `
23
+ + `of your server's testing account. It may take a couple of minutes.` });
24
+
25
+ // dainstall . --apiurl https://site.org --apikey $API_KEY --playground --project=name
26
+ // I think this won't run on windows? Why do I think this? Where on the
27
+ // internet did I find this. Some command here or in dainstall doesn't
28
+ // work on windows.
29
+ let result = child_process.spawnSync(
30
+ `dainstall`,
31
+ [
32
+ `.`, `--apiurl`, session_vars.get_da_server_url(),
33
+ `--apikey`, session_vars.get_dev_api_key(), // validates api key
34
+ `--playground`, `--project=${ project_name }`
35
+ ],
36
+ // Prints messages from the process to the console while it's running
37
+ { stdio: 'inherit', }
38
+ );
39
+
40
+ // 403 is invalid API key, but I'm not sure how to output
41
+ // the response to find out the error code
42
+
43
+ if ( result.status !== 0 ) {
44
+ log.error({
45
+ type: `setup`,
46
+ pre: `Status code ${ result.status }. Could not dainstall this package on your server. See above for more details.`
47
+ });
48
+ throw(result.stderr);
43
49
  }
44
50
 
45
- // Finish
46
- log.info({ type: `setup`, pre: `Success! Test setup has finished successfully.` });
51
+ log.info({
52
+ type: `setup`,
53
+ pre: `Success! ALKiln created a Project in your Playground named "${ project_name }"`
54
+ });
47
55
  }; // Ends setup();
48
56
 
49
57
 
58
+ // Before everything, make sure all the required env vars exist
59
+ session_vars.validateEnvironment();
50
60
  setup();