hdoc-tools 0.9.17 → 0.9.19

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -41,13 +41,15 @@ If the -v switch is provided, then more verbose output is output, which includes
41
41
 
42
42
  Performs a local build of the book, validates the links and static content are present and correct and outputs as a ZIP file.
43
43
 
44
- If the -v switch is provided, then more verbose output is provided, which includes a list of all HTML files created and checked for embedded links and static content.
44
+ If the -v switch is provided, then a more verbose output is provided, which includes a list of all HTML files created and checked for embedded links and static content.
45
45
 
46
- ### build
46
+ Use the --set-version argument to set the version number of the built book.
47
+
48
+ ### validate
47
49
 
48
50
  Performs a minimum local build of the book, then validates the links and static content are present and correct.
49
51
 
50
- If the -v switch is provided, then more verbose output is provided, which includes a list of all HTML files created and checked for embedded links and static content.
52
+ If the -v switch is provided, then a more verbose output is provided, which includes a list of all HTML files created and checked for embedded links and static content.
51
53
 
52
54
  Use the --set-version argument to set the version number of the built book.
53
55
 
package/hdoc-build-pdf.js CHANGED
@@ -7,7 +7,6 @@
7
7
  fs = require('fs-extra'),
8
8
  mime = require('mime-types'),
9
9
  path = require('path'),
10
- puppeteer = require('puppeteer'),
11
10
  hdoc = require(path.join(__dirname, 'hdoc-module.js'));
12
11
 
13
12
  const dree_options = {
@@ -99,7 +98,7 @@
99
98
  return html_source;
100
99
  };
101
100
 
102
- exports.generate_pdf = async function (pdf_template_path, pdf_template_content, book_config, html_source, target_file, verbose = false) {
101
+ exports.generate_pdf = async function (browser, pdf_template_path, pdf_template_content, book_config, html_source, target_file, verbose = false) {
103
102
  if (verbose) console.log(`Generating PDF: ${target_file}`);
104
103
  let pdf_size = 0;
105
104
  // Cache footer
@@ -120,9 +119,6 @@
120
119
 
121
120
  html_source = pdf_template_content.replace('{{book_title}}', book_config.title).replace('{{document_content}}', html_source).replace('{{hb_logo}}', hb_logo);
122
121
 
123
- // Create a browser instance
124
- const browser = await puppeteer.launch();
125
-
126
122
  // Create a new page
127
123
  const page = await browser.newPage();
128
124
 
@@ -166,9 +162,7 @@
166
162
  } catch (err) {
167
163
  console.log(`Error generating PDF ${target_file} - ${err}`);
168
164
  }
169
-
170
- // Close the browser instance
171
- await browser.close();
165
+ await page.close();
172
166
  return pdf_size;
173
167
  };
174
168
  })();
package/hdoc-build.js CHANGED
@@ -6,6 +6,7 @@
6
6
  fs = require('fs-extra'),
7
7
  mdfm = require('markdown-it-front-matter'),
8
8
  path = require('path'),
9
+ puppeteer = require('puppeteer'),
9
10
  URL = require("url").URL,
10
11
  hdoc_validate = require(path.join(__dirname, 'hdoc-validate.js')),
11
12
  hdoc = require(path.join(__dirname, 'hdoc-module.js')),
@@ -26,8 +27,9 @@
26
27
  regex_version = /^[0-9]{1,3}[.][0-9]{1,3}[.][0-9]{1,6}$/;
27
28
 
28
29
  let bc = {}, // Breadcrumbs map
29
- book_read_time = 0,
30
30
  built_file_hashes = [],
31
+ book_read_time = 0,
32
+ browser = {},
31
33
  conversion_attempted = 0,
32
34
  conversion_success = 0,
33
35
  conversion_failed = 0,
@@ -238,7 +240,7 @@
238
240
 
239
241
  // Generate PDF file from HTML
240
242
  const pdf_file_path = file_path.path.replace(path.extname(file_path.path), '.pdf');
241
- pdf_size = await hdoc_build_pdf.generate_pdf(pdf_template_path, pdf_template, hdocbook_config, pdf_txt, pdf_file_path, verbose);
243
+ pdf_size = await hdoc_build_pdf.generate_pdf(browser, pdf_template_path, pdf_template, hdocbook_config, pdf_txt, pdf_file_path, verbose);
242
244
  }
243
245
 
244
246
  html_txt = `${fm_header_content}\n${doc_header}\n${html_txt}`;
@@ -448,7 +450,7 @@
448
450
 
449
451
  // Generate PDF file from HTML
450
452
  const pdf_file_path = file_path.path.replace(path.extname(file_path.path), '.pdf');
451
- pdf_size = await hdoc_build_pdf.generate_pdf(pdf_template_path, pdf_template, hdocbook_config, pdf_txt, pdf_file_path, verbose);
453
+ pdf_size = await hdoc_build_pdf.generate_pdf(browser, pdf_template_path, pdf_template, hdocbook_config, pdf_txt, pdf_file_path, verbose);
452
454
  }
453
455
 
454
456
  html_txt = `${fm_header}\n${doc_header}\n${html_txt}`;
@@ -572,6 +574,7 @@
572
574
  }
573
575
  verbose = verbose_output;
574
576
 
577
+ const start_time = Date.now();
575
578
  // GERRY: The purpose of this function is to create a zip file containing the hdocbook content,
576
579
  // * Create a _work folder
577
580
  // * copy the hdocbook content to the work folder
@@ -688,6 +691,11 @@
688
691
  // Get a list of MD files in work_path
689
692
  dree.scan(work_path, dreeOptions, build_file_callback);
690
693
 
694
+ if (pdf_enable) {
695
+ // Create a Chromium browser instance to generate PDFs with
696
+ browser = await puppeteer.launch({headless: 'new'});
697
+ }
698
+
691
699
  // Work through MD files and convert to HTML
692
700
  for (let i = 0; i < md_files.length; i++) {
693
701
  await transform_markdown_and_save_html(md_files[i]);
@@ -698,6 +706,11 @@
698
706
  await transform_static_html(static_html_files[i]);
699
707
  }
700
708
 
709
+ if (pdf_enable) {
710
+ // Close the Chromium browser instance
711
+ await browser.close();
712
+ }
713
+
701
714
  // Output to console
702
715
  console.log(` MD files found: ${conversion_attempted}`);
703
716
  console.log(`Successfully converted to HTML: ${conversion_success}`);
@@ -710,6 +723,8 @@
710
723
  // Validate content
711
724
  const validation_success = await hdoc_validate.run(work_path, doc_id, verbose, hdocbook_config, hdocbook_project, bc);
712
725
  if (!validation_success) {
726
+ const end_time = Date.now();
727
+ console.log(`\nTime Taken: ${get_duration(start_time, end_time)}\n`);
713
728
  process.exit(1);
714
729
  }
715
730
 
@@ -722,9 +737,10 @@
722
737
  console.log(`Error deleting ${md_files_delete[i]}: ${e}`);
723
738
  }
724
739
  }
725
-
740
+
726
741
  // Add book read timing to the hdocbook.json
727
742
  hdocbook_config.readingTime = Math.ceil(book_read_time + ((book_read_time / 100) * 10));
743
+ hdocbook_config.navigation.items = hdoc.strip_drafts(hdocbook_config.navigation.items);
728
744
  try {
729
745
  fs.writeFileSync(work_hdocbook_path, JSON.stringify(hdocbook_config, null, 2));
730
746
  console.log('\nhdocbook.json update success:', work_hdocbook_path);
@@ -761,5 +777,21 @@
761
777
  } else {
762
778
  console.log('\nValidation Complete\n');
763
779
  }
780
+ const end_time = Date.now();
781
+ console.log(`Time Taken: ${get_duration(start_time, end_time)}\n`);
764
782
  };
783
+
784
+ const get_duration = function(start, end) {
785
+ const total_time = new Date(end - start).toISOString().slice(11,19);
786
+ const duration_arr = total_time.split(':');
787
+ let duration = '';
788
+ if (parseInt(duration_arr[0], 10) > 0) {
789
+ duration += parseInt(duration_arr[0], 10) + 'h ';
790
+ }
791
+ if (duration !== '' || parseInt(duration_arr[1], 10)) {
792
+ duration += parseInt(duration_arr[1], 10) + 'm ';
793
+ }
794
+ duration += parseInt(duration_arr[2], 10) + 's';
795
+ return duration;
796
+ }
765
797
  })();
package/hdoc-module.js CHANGED
@@ -290,7 +290,22 @@
290
290
  return response;
291
291
  };
292
292
 
293
-
293
+ exports.strip_drafts = function (nav_items) {
294
+ let return_nav = nav_items;
295
+ recurse_nav(return_nav);
296
+ return return_nav;
297
+ };
298
+ const recurse_nav = function(nav_items) {
299
+ for (const key in nav_items) {
300
+ if (nav_items[key].draft) {
301
+ nav_items.splice(key, 1);
302
+ recurse_nav(nav_items);
303
+ } else if (nav_items[key].items) {
304
+ recurse_nav(nav_items[key].items);
305
+ }
306
+ }
307
+ };
308
+
294
309
  exports.build_breadcrumbs = function (nav_items) {
295
310
  let bc = {};
296
311
  // Steve - need to rework this to use recursion, as it's hard-coded for a maximum of 4 levels deep right now
@@ -379,5 +394,4 @@
379
394
  }
380
395
  return bc;
381
396
  };
382
-
383
397
  })();
package/hdoc-validate.js CHANGED
@@ -1,3 +1,7 @@
1
+ const {
2
+ file
3
+ } = require('jszip');
4
+
1
5
  (function () {
2
6
  'use strict';
3
7
 
@@ -8,17 +12,18 @@
8
12
  https = require('https'),
9
13
  path = require('path'),
10
14
  hdoc = require(path.join(__dirname, 'hdoc-module.js')),
11
- translator = require('american-british-english-translator');
12
-
13
- const agent = new https.Agent({
14
- rejectUnauthorized: false
15
- }),
16
- spellcheck_options = {
17
- british: true,
18
- spelling: true
19
- },
20
- regex_nav_paths = /[a-z0-9-\/]+/g;
21
-
15
+ translator = require('american-british-english-translator'),
16
+ { trueCasePathSync } = require('true-case-path');
17
+
18
+ const agent = new https.Agent({
19
+ rejectUnauthorized: false
20
+ }),
21
+ spellcheck_options = {
22
+ british: true,
23
+ spelling: true
24
+ },
25
+ regex_nav_paths = /[a-z0-9-\/]+/g;
26
+
22
27
  let prod_families = {},
23
28
  prods_supported = [];
24
29
 
@@ -31,9 +36,11 @@
31
36
  exclude_links = {},
32
37
  exclude_spellcheck = {};
33
38
 
34
- const load_product_families = async function() {
39
+ const load_product_families = async function () {
35
40
  try {
36
- const prods = await axios.get('https://docs.hornbill.com/_books/products.json', { httpsAgent: agent });
41
+ const prods = await axios.get('https://docs.hornbill.com/_books/products.json', {
42
+ httpsAgent: agent
43
+ });
37
44
  if (prods.status === 200) {
38
45
  prod_families = prods.data;
39
46
  for (let i = 0; i < prod_families.products.length; i++) {
@@ -51,9 +58,9 @@
51
58
 
52
59
  const spellcheckContent = async function (sourceFile, excludes) {
53
60
  const text = fs.readFileSync(sourceFile.path, 'utf8');
54
- const source_path = sourceFile.relativePath.replace('.'+sourceFile.extension, '');
61
+ const source_path = sourceFile.relativePath.replace('.' + sourceFile.extension, '');
55
62
  const translate_output = translator.translate(text, spellcheck_options);
56
- if(Object.keys(translate_output).length){
63
+ if (Object.keys(translate_output).length) {
57
64
 
58
65
  for (const key in translate_output) {
59
66
  if (translate_output.hasOwnProperty(key)) {
@@ -70,11 +77,11 @@
70
77
  }
71
78
  }
72
79
  }
73
- }
80
+ }
74
81
  }
75
82
  };
76
83
 
77
- const checkNavigation = async function (doc_id, flat_nav) {
84
+ const checkNavigation = async function (source_path, flat_nav) {
78
85
  let nav_errors = [];
79
86
  for (const key in flat_nav) {
80
87
  if (flat_nav.hasOwnProperty(key)) {
@@ -82,15 +89,33 @@
82
89
  const invalid_chars = key.replace(regex_nav_paths, '');
83
90
  if (invalid_chars !== '') {
84
91
  nav_errors.push(`Navigation path [${key}] contains the following invalid characters: [${[...invalid_chars].join('] [')}]`);
85
- }
86
-
87
- //Validate path
92
+ }
93
+
94
+ // Validate path exists - key should be a html file at this point
95
+ let file_exists = true;
96
+ let file_name = path.join(source_path, key + '.html');
97
+ if (!fs.existsSync(file_name)) {
98
+ file_name = path.join(source_path, key + '.htm');
99
+ if (!fs.existsSync(file_name)) {
100
+ file_exists = false;
101
+ nav_errors.push(`Navigation path [${key}] file does not exist.`);
102
+ }
103
+ }
104
+
105
+ if (file_exists) {
106
+ const true_file = trueCasePathSync(file_name);
107
+ if (true_file !== file_name) {
108
+ nav_errors.push(`Navigation path [${key}] does not match filename case [${path.basename(true_file)}].`);
109
+ }
110
+ }
111
+
112
+ // Validate path
88
113
  const paths = key.split('/');
89
114
  for (let i = 0; i < paths.length; i++) {
90
115
  const path_words = paths[i].split('-');
91
116
  for (let j = 0; j < path_words.length; j++) {
92
117
  const translate_output = translator.translate(path_words[j], spellcheck_options);
93
- if(Object.keys(translate_output).length){
118
+ if (Object.keys(translate_output).length) {
94
119
  for (const spell_val in translate_output) {
95
120
  if (translate_output.hasOwnProperty(spell_val)) {
96
121
  for (const spelling in translate_output[spell_val][0]) {
@@ -108,7 +133,7 @@
108
133
  for (let i = 0; i < flat_nav[key].length; i++) {
109
134
  if (flat_nav[key][i].link === key) {
110
135
  const translate_output = translator.translate(flat_nav[key][i].text, spellcheck_options);
111
- if(Object.keys(translate_output).length){
136
+ if (Object.keys(translate_output).length) {
112
137
  for (const spell_val in translate_output) {
113
138
  if (translate_output.hasOwnProperty(spell_val)) {
114
139
  for (let j = 0; j < translate_output[spell_val].length; j++) {
@@ -163,7 +188,9 @@
163
188
  }
164
189
 
165
190
  try {
166
- await axios.get(links[i], { httpsAgent: agent });
191
+ await axios.get(links[i], {
192
+ httpsAgent: agent
193
+ });
167
194
  messages[htmlFile.relativePath].push(`Link is a valid external URL: ${links[i]}`);
168
195
  } catch (e) {
169
196
  // Handle errors
@@ -197,15 +224,17 @@
197
224
  const checkTags = async function (htmlFile) {
198
225
  const htmlBody = fs.readFileSync(htmlFile.path, 'utf8');
199
226
  const $ = cheerio.load(htmlBody);
200
-
227
+
201
228
  const h1_tags = $('h1').map(function () {
202
229
  return $(this);
203
230
  }).get();
231
+
204
232
  if (h1_tags.length && h1_tags.length > 1) {
233
+ //console.log("PATH: ", htmlFile.path); //hdoc-guide/hdocbook/markdown.html
205
234
  let error_msg = `${h1_tags.length} <h1> tags found in content: `;
206
235
  for (let i = 0; i < h1_tags.length; i++) {
207
- error_msg += h1_tags[i].text();
208
- if (i < h1_tags.length - 1) error_msg += '; ';
236
+ error_msg += h1_tags[i].text();
237
+ if (i < h1_tags.length - 1) error_msg += '; ';
209
238
  }
210
239
  errors[htmlFile.relativePath].push(error_msg);
211
240
  }
@@ -214,7 +243,7 @@
214
243
  const dreeOptions = {
215
244
  descendants: true,
216
245
  depth: 10,
217
- extensions: ['htm', 'html','md'],
246
+ extensions: ['htm', 'html', 'md'],
218
247
  hash: false,
219
248
  normalize: true,
220
249
  size: false,
@@ -324,7 +353,7 @@
324
353
  for (let i = 0; i < prod_families.products.length; i++) {
325
354
  if (prod_families.products[i].id === hdocbook_config.productFamily) {
326
355
  valid_product = true;
327
- }
356
+ }
328
357
  }
329
358
  if (!valid_product) {
330
359
  let val_prod_error = `Incorrect productFamily: ${hdocbook_config.productFamily}. Supported values:`;
@@ -332,18 +361,17 @@
332
361
  val_prod_error += `\n - ${prods_supported[i]}`
333
362
  }
334
363
  meta_errors.push(val_prod_error)
335
-
364
+
336
365
  }
337
366
 
338
367
  // Check navigation spellings
339
- const nav_errors = await checkNavigation(doc_id, nav_items);
368
+ const nav_errors = await checkNavigation(source_path, nav_items);
340
369
  if (nav_errors.length > 0) meta_errors.push(...nav_errors);
341
370
 
342
371
  if (meta_errors.length > 0) {
343
372
  console.log('\r\n-----------------------');
344
373
  console.log(' Validation Output ');
345
374
  console.log('-----------------------');
346
- console.log()
347
375
  console.log(`\r\n${meta_errors.length} Validation Errors Found\r\n`);
348
376
  for (let i = 0; i < meta_errors.length; i++) {
349
377
  console.log(`- ${meta_errors[i]}`);
@@ -353,12 +381,12 @@
353
381
 
354
382
  if (hdocbook_project.validation) {
355
383
  if (hdocbook_project.validation.exclude_links && hdocbook_project.validation.exclude_links instanceof Array) {
356
- hdocbook_project.validation.exclude_links.forEach(function(excl_link) {
357
- exclude_links[excl_link] = true;
358
- });
384
+ hdocbook_project.validation.exclude_links.forEach(function (excl_link) {
385
+ exclude_links[excl_link] = true;
386
+ });
359
387
  }
360
388
  if (hdocbook_project.validation.exclude_spellcheck && hdocbook_project.validation.exclude_spellcheck instanceof Array) {
361
- hdocbook_project.validation.exclude_spellcheck.forEach(function(excl_sc) {
389
+ hdocbook_project.validation.exclude_spellcheck.forEach(function (excl_sc) {
362
390
  exclude_spellcheck[excl_sc.document_path] = excl_sc.words;
363
391
  });
364
392
  }
@@ -437,21 +465,20 @@
437
465
  console.log('\r\n-----------------------');
438
466
  console.log(' Validation Output ');
439
467
  console.log('-----------------------');
440
-
441
- for (const key in errors) {
442
- if (errors.hasOwnProperty(key) && errors[key].length > 0) {
443
- console.log(`\r\n${errors[key].length} error(s) in ${key}`);
444
- for (let i = 0; i < errors[key].length; i++) {
445
- console.log(` - ${errors[key][i]}`);
446
- errorcount++;
447
- }
448
- }
449
- }
450
-
451
468
  if (errorcount > 0) {
452
469
  console.log(`\r\n${errorcount} Validation Errors Found\r\n`);
470
+ for (const key in errors) {
471
+ if (errors.hasOwnProperty(key) && errors[key].length > 0) {
472
+ console.log(`\r\n${errors[key].length} error(s) in ${key}`);
473
+ for (let i = 0; i < errors[key].length; i++) {
474
+ console.log(` - ${errors[key][i]}`);
475
+ errorcount++;
476
+ }
477
+ }
478
+ }
453
479
  return false;
454
480
  }
481
+
455
482
  console.log(`\r\nNo Validation Errors Found!\r\n`);
456
483
  return true;
457
484
  };
package/hdoc.js CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env node
2
2
  (async function () {
3
3
  'use strict';
4
4
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hdoc-tools",
3
- "version": "0.9.17",
3
+ "version": "0.9.19",
4
4
  "description": "Hornbill HDocBook Development Support Tool",
5
5
  "main": "hdoc.js",
6
6
  "bin": {
@@ -50,8 +50,9 @@
50
50
  "mime-types": "^2.1.35",
51
51
  "multer": "^1.4.5-lts.1",
52
52
  "prompt": "^1.3.0",
53
- "puppeteer": "^19.6.0",
53
+ "puppeteer": "^19.8.0",
54
54
  "stream": "0.0.2",
55
+ "true-case-path": "^2.2.1",
55
56
  "words-count": "^2.0.2",
56
57
  "zip-a-folder": "^1.1.5"
57
58
  }