hdoc-tools 0.31.0 → 0.32.1

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/hdoc-build-pdf.js CHANGED
@@ -1,219 +1,233 @@
1
- (() => {
2
- const axios = require("axios");
3
- const cheerio = require("cheerio");
4
- const fs = require("fs-extra");
5
- const mime = require("mime-types");
6
- const path = require("node:path");
7
- const hdoc = require(path.join(__dirname, "hdoc-module.js"));
8
-
9
- let hb_logo = "";
10
- let footer = "";
11
- let header = "";
12
-
13
- const get_footer = (template_path) => {
14
- let footer_content = null;
15
- try {
16
- footer_content = fs.readFileSync(
17
- path.join(template_path, "template-footer.html"),
18
- "utf8",
19
- );
20
- } catch (err) {
21
- console.error(`Error loading template: ${err}`);
22
- }
23
- return footer_content;
24
- };
25
-
26
- const get_header = (template_path) => {
27
- let header_content = null;
28
- try {
29
- header_content = fs.readFileSync(
30
- path.join(template_path, "template-header.html"),
31
- "utf8",
32
- );
33
- } catch (err) {
34
- console.error(`Error loading template: ${err}`);
35
- }
36
- return header_content;
37
- };
38
-
39
- exports.process_images = async (file_path, html_source, verbose) => {
40
- const book_work_root = file_path.path.replace(file_path.relativePath, "");
41
- if (verbose) console.log("Parsing img tags from HTML source");
42
-
43
- let processed_html_source = html_source;
44
- // Use cheerio to parse html
45
- const $ = cheerio.load(processed_html_source);
46
-
47
- // Get iFrames from HTML, to replace with a tags
48
- const iframes = [];
49
- const iframe_html = $("iframe")
50
- .map(function () {
51
- const response = {
52
- html: $.html(this),
53
- src: $(this).attr("src"),
54
- title: $(this).attr("title")
55
- ? $(this).attr("title")
56
- : "No Link Title Provided",
57
- };
58
- return response;
59
- })
60
- .get();
61
- iframes.push(...iframe_html);
62
- for (let i = 0; i < iframes.length; i++) {
63
- const link = `<p><a href="${iframes[i].src}">${iframes[i].title}</a></p>`;
64
- const regex = new RegExp(
65
- `<iframe.*src="${iframes[i].src.replace("/", "\\/")}".*</iframe>`,
66
- );
67
- processed_html_source = processed_html_source.replace(regex, link);
68
- }
69
-
70
- // Get image links from HTML, to embed into the pdf
71
- const imgs = [];
72
- const srcs = $("img")
73
- .map(function (i) {
74
- return $(this).attr("src");
75
- })
76
- .get();
77
- imgs.push(...srcs);
78
- for (let i = 0; i < imgs.length; i++) {
79
- if (!hdoc.valid_url(imgs[i])) {
80
- // Internal link
81
- const image_path = path.join(
82
- book_work_root,
83
- imgs[i].replace("_books/", ""),
84
- );
85
- try {
86
- const image_buffer = fs.readFileSync(image_path);
87
- const mime_type = mime.lookup(image_path);
88
- let image_b64 = image_buffer.toString("base64");
89
- image_b64 = `data:${mime_type};base64,${image_b64}`;
90
- processed_html_source = processed_html_source.replace(
91
- imgs[i],
92
- image_b64,
93
- );
94
- } catch (err) {
95
- console.error(
96
- "Error reading image from HTML source [",
97
- image_path,
98
- "] -",
99
- err,
100
- );
101
- return null;
102
- }
103
- } else {
104
- // External Link
105
- try {
106
- const file_response = await axios.get(imgs[i]);
107
- if (file_response.status === 200) {
108
- const image_buffer = file_response.data;
109
- const mime_type = mime.lookup(imgs[i]);
110
- let image_b64 = image_buffer.toString("base64");
111
- image_b64 = `data:${mime_type};base64,${image_b64}`;
112
- processed_html_source = processed_html_source.replace(
113
- imgs[i],
114
- image_b64,
115
- );
116
- } else {
117
- throw `Unexpected Status ${file_response.status}`;
118
- }
119
- } catch (err) {
120
- console.error(
121
- `Error downloading external source [${imgs[i]}] - ${err}`,
122
- );
123
- }
124
- }
125
- }
126
-
127
- return processed_html_source;
128
- };
129
-
130
- exports.generate_pdf = async (
131
- browser,
132
- pdf_template_path,
133
- pdf_template_content,
134
- book_config,
135
- html_source,
136
- target_file,
137
- css_templates,
138
- verbose = false,
139
- ) => {
140
- let pdf_size = 0;
141
- // Cache footer
142
- if (footer === "") footer = get_footer(pdf_template_path);
143
-
144
- // Read svg logo file into buffer, convert to B64 string
145
- if (hb_logo === "") {
146
- const hb_logo_path = path.join(
147
- pdf_template_path,
148
- "images",
149
- "hornbill-logo-full.svg",
150
- );
151
- try {
152
- const hb_logo_file_buffer = fs.readFileSync(hb_logo_path);
153
- hb_logo = hb_logo_file_buffer.toString("base64");
154
- hb_logo = `data:image/svg+xml;base64,${hb_logo}`;
155
- } catch (err) {
156
- console.error("Error reading logo from template:", err);
157
- return pdf_size;
158
- }
159
- }
160
-
161
- // Cache header
162
- if (header === "") {
163
- header = get_header(pdf_template_path)
164
- .replace("{{book_title}}", book_config.title)
165
- .replace("{{hb_logo}}", hb_logo);
166
- }
167
-
168
- const processed_html_source = pdf_template_content
169
- .replace("{{book_title}}", book_config.title)
170
- .replace("{{document_content}}", html_source);
171
-
172
- const page = await browser.newPage();
173
-
174
- // To reflect CSS used for screens instead of print
175
- await page.emulateMediaType("screen");
176
-
177
- // Set HTML content from HTML source
178
- await page.setContent(processed_html_source, {
179
- waitUntil: "domcontentloaded",
180
- });
181
- for (let i = 0; i < css_templates.length; i++) {
182
- try {
183
- await page.addStyleTag({
184
- content: css_templates[i],
185
- });
186
- } catch (e) {
187
- console.error(`Error applying template for [${target_file}]: ${e}`);
188
- }
189
- }
190
-
191
- try {
192
- const pdf_gen = await page.pdf({
193
- path: target_file,
194
- printBackground: true,
195
- format: "A4",
196
- displayHeaderFooter: true,
197
- headerTemplate: header,
198
- footerTemplate: footer,
199
- margin: {
200
- top: "90px",
201
- right: "30px",
202
- bottom: "60px",
203
- left: "30px",
204
- },
205
- timeout: 0,
206
- });
207
- const currdate = new Date();
208
- const datetime = currdate.toISOString();
209
- if (verbose)
210
- console.log(`[${datetime}] PDF generation success: ${target_file}`);
211
-
212
- pdf_size = pdf_gen.byteLength;
213
- } catch (err) {
214
- console.error(`Error generating PDF ${target_file} - ${err}`);
215
- }
216
- await page.close();
217
- return pdf_size;
218
- };
219
- })();
1
+ (() => {
2
+ const axios = require("axios");
3
+ const cheerio = require("cheerio");
4
+ const fs = require("fs-extra");
5
+ const mime = require("mime-types");
6
+ const path = require("node:path");
7
+ const hdoc = require(path.join(__dirname, "hdoc-module.js"));
8
+
9
+ let hb_logo = "";
10
+ let footer = "";
11
+ let header = "";
12
+
13
+ const get_footer = (template_path) => {
14
+ let footer_content = null;
15
+ try {
16
+ footer_content = fs.readFileSync(
17
+ path.join(template_path, "template-footer.html"),
18
+ "utf8",
19
+ );
20
+ } catch (err) {
21
+ console.error(`Error loading template: ${err}`);
22
+ }
23
+ return footer_content;
24
+ };
25
+
26
+ const get_header = (template_path) => {
27
+ let header_content = null;
28
+ try {
29
+ header_content = fs.readFileSync(
30
+ path.join(template_path, "template-header.html"),
31
+ "utf8",
32
+ );
33
+ } catch (err) {
34
+ console.error(`Error loading template: ${err}`);
35
+ }
36
+ return header_content;
37
+ };
38
+
39
+ exports.process_images = async (file_path, html_source, verbose) => {
40
+ const book_work_root = file_path.path.replace(file_path.relativePath, "");
41
+ if (verbose) console.log("Parsing img tags from HTML source");
42
+
43
+ let processed_html_source = html_source;
44
+ // Use cheerio to parse html
45
+ const $ = cheerio.load(processed_html_source);
46
+
47
+ // Get iFrames from HTML, to replace with a tags
48
+ const iframes = [];
49
+ const iframe_html = $("iframe")
50
+ .map(function () {
51
+ const response = {
52
+ html: $.html(this),
53
+ src: $(this).attr("src"),
54
+ title: $(this).attr("title")
55
+ ? $(this).attr("title")
56
+ : "No Link Title Provided",
57
+ };
58
+ return response;
59
+ })
60
+ .get();
61
+ iframes.push(...iframe_html);
62
+ for (let i = 0; i < iframes.length; i++) {
63
+ const link = `<p><a href="${iframes[i].src}">${iframes[i].title}</a></p>`;
64
+ const regex = new RegExp(
65
+ `<iframe.*src="${iframes[i].src.replace("/", "\\/")}".*</iframe>`,
66
+ );
67
+ processed_html_source = processed_html_source.replace(regex, link);
68
+ }
69
+
70
+ // Get image links from HTML, to embed into the pdf
71
+ const imgs = [];
72
+ const srcs = $("img")
73
+ .map(function (i) {
74
+ return $(this).attr("src");
75
+ })
76
+ .get();
77
+ imgs.push(...srcs);
78
+ for (let i = 0; i < imgs.length; i++) {
79
+ if (!hdoc.valid_url(imgs[i])) {
80
+ // Internal link
81
+ const image_path = path.join(
82
+ book_work_root,
83
+ imgs[i].replace("_books/", ""),
84
+ );
85
+ try {
86
+ const image_buffer = fs.readFileSync(image_path);
87
+ const mime_type = mime.lookup(image_path);
88
+ let image_b64 = image_buffer.toString("base64");
89
+ image_b64 = `data:${mime_type};base64,${image_b64}`;
90
+ processed_html_source = processed_html_source.replace(
91
+ imgs[i],
92
+ image_b64,
93
+ );
94
+ } catch (err) {
95
+ console.error(
96
+ "Error reading image from HTML source [",
97
+ image_path,
98
+ "] -",
99
+ err,
100
+ );
101
+ return null;
102
+ }
103
+ } else {
104
+ // External Link
105
+ try {
106
+ const file_response = await axios.get(imgs[i], {
107
+ responseType: 'arraybuffer'
108
+ });
109
+ if (file_response.status === 200) {
110
+ let image_b64 = imageEncode(file_response.data, file_response.headers['content-type']);
111
+
112
+
113
+ const regexQ = `<img\\s+[^>]*src=["']${imgs[i].replaceAll('/', '\\/').replaceAll('.', '\\.')}["'][^>]*>`;
114
+ const regex = new RegExp(regexQ);
115
+
116
+ const found_img_tag = processed_html_source.match(regex);
117
+ const new_img_tag = found_img_tag[0].replace(imgs[i], image_b64);
118
+
119
+ processed_html_source = processed_html_source.replace(
120
+ found_img_tag,
121
+ new_img_tag,
122
+ );
123
+ } else {
124
+ throw `Unexpected Status ${file_response.status}`;
125
+ }
126
+ } catch (err) {
127
+ console.error(
128
+ `Error downloading external source [${imgs[i]}] - ${err}`,
129
+ );
130
+ }
131
+ }
132
+ }
133
+
134
+ return processed_html_source;
135
+ };
136
+
137
+ const imageEncode = (arrayBuffer, mimeType) => {
138
+ let u8 = new Uint8Array(arrayBuffer)
139
+ let b64encoded = btoa([].reduce.call(new Uint8Array(arrayBuffer),function(p,c){return p+String.fromCharCode(c)},''))
140
+ let mimetype=`image/${mimeType}`
141
+ return "data:"+mimetype+";base64,"+b64encoded
142
+ }
143
+
144
+ exports.generate_pdf = async (
145
+ browser,
146
+ pdf_template_path,
147
+ pdf_template_content,
148
+ book_config,
149
+ html_source,
150
+ target_file,
151
+ css_templates,
152
+ verbose = false,
153
+ ) => {
154
+ let pdf_size = 0;
155
+ // Cache footer
156
+ if (footer === "") footer = get_footer(pdf_template_path);
157
+
158
+ // Read svg logo file into buffer, convert to B64 string
159
+ if (hb_logo === "") {
160
+ const hb_logo_path = path.join(
161
+ pdf_template_path,
162
+ "images",
163
+ "hornbill-logo-full.svg",
164
+ );
165
+ try {
166
+ const hb_logo_file_buffer = fs.readFileSync(hb_logo_path);
167
+ hb_logo = hb_logo_file_buffer.toString("base64");
168
+ hb_logo = `data:image/svg+xml;base64,${hb_logo}`;
169
+ } catch (err) {
170
+ console.error("Error reading logo from template:", err);
171
+ return pdf_size;
172
+ }
173
+ }
174
+
175
+ // Cache header
176
+ if (header === "") {
177
+ header = get_header(pdf_template_path)
178
+ .replace("{{book_title}}", book_config.title)
179
+ .replace("{{hb_logo}}", hb_logo);
180
+ }
181
+
182
+ const processed_html_source = pdf_template_content
183
+ .replace("{{book_title}}", book_config.title)
184
+ .replace("{{document_content}}", html_source);
185
+
186
+ const page = await browser.newPage();
187
+
188
+ // To reflect CSS used for screens instead of print
189
+ await page.emulateMediaType("screen");
190
+
191
+ // Set HTML content from HTML source
192
+ await page.setContent(processed_html_source, {
193
+ waitUntil: "domcontentloaded",
194
+ });
195
+ for (let i = 0; i < css_templates.length; i++) {
196
+ try {
197
+ await page.addStyleTag({
198
+ content: css_templates[i],
199
+ });
200
+ } catch (e) {
201
+ console.error(`Error applying template for [${target_file}]: ${e}`);
202
+ }
203
+ }
204
+
205
+ try {
206
+ const pdf_gen = await page.pdf({
207
+ path: target_file,
208
+ printBackground: true,
209
+ format: "A4",
210
+ displayHeaderFooter: true,
211
+ headerTemplate: header,
212
+ footerTemplate: footer,
213
+ margin: {
214
+ top: "90px",
215
+ right: "30px",
216
+ bottom: "60px",
217
+ left: "30px",
218
+ },
219
+ timeout: 0,
220
+ });
221
+ const currdate = new Date();
222
+ const datetime = currdate.toISOString();
223
+ if (verbose)
224
+ console.log(`[${datetime}] PDF generation success: ${target_file}`);
225
+
226
+ pdf_size = pdf_gen.byteLength;
227
+ } catch (err) {
228
+ console.error(`Error generating PDF ${target_file} - ${err}`);
229
+ }
230
+ await page.close();
231
+ return pdf_size;
232
+ };
233
+ })();
package/hdoc-build.js CHANGED
@@ -60,6 +60,7 @@
60
60
  let conversion_failed = 0;
61
61
  let doc_header_template = "";
62
62
  let doc_header_template_non_git = "";
63
+ let github_repo_details = {};
63
64
  let global_source_path = "";
64
65
  let pdf_created = 0;
65
66
  let pdf_enable = false;
@@ -306,7 +307,8 @@
306
307
  hdocbook_config.publicSource,
307
308
  file_path.relativePath,
308
309
  );
309
- const contributors = hdoc.get_github_contributors(
310
+
311
+ const contributors = await hdoc.get_github_contributors(
310
312
  github_paths.api_path,
311
313
  git_token,
312
314
  );
@@ -1184,6 +1186,18 @@
1184
1186
  prods_supported = prods.prods_supported;
1185
1187
  }
1186
1188
 
1189
+ const clean_repo = hdocbook_config.publicSource.endsWith("/") ? hdocbook_config.publicSource.slice(0, -1) : hdocbook_config.publicSource;
1190
+ const api_path = clean_repo.replace(
1191
+ "https://github.com/",
1192
+ "https://api.github.com/repos/",
1193
+ );
1194
+
1195
+ // Get github repo details
1196
+ github_repo_details = await hdoc.get_github_repo_details( api_path, git_token );
1197
+ if (github_repo_details.success) {
1198
+ console.warn(`Unable to retrieve GitHub Repository details: ${github_repo_details.error}`);
1199
+ }
1200
+
1187
1201
  if (!validate) {
1188
1202
  console.log("Caching CSS for PDF generation...");
1189
1203
  const css_files = [
@@ -1394,6 +1408,7 @@
1394
1408
  gen_exclude,
1395
1409
  redirects,
1396
1410
  draft_links,
1411
+ github_repo_details.data.private,
1397
1412
  );
1398
1413
  if (!validation_success) {
1399
1414
  const end_time = Date.now();
package/hdoc-module.js CHANGED
@@ -434,6 +434,60 @@
434
434
  return github_paths;
435
435
  };
436
436
 
437
+ exports.get_github_repo_details = async (
438
+ github_url,
439
+ github_api_token,
440
+ ) => {
441
+ const response = {
442
+ success: false,
443
+ error: "",
444
+ data: {},
445
+ private: false
446
+ };
447
+ const request_options = {
448
+ headers: {
449
+ "User-Agent": "HornbillDocsBuild",
450
+ "Cache-Control": "no-cache",
451
+ Host: "api.github.com",
452
+ Accept: "application/json",
453
+ },
454
+ timeout: 5000,
455
+ };
456
+ if (github_api_token !== "") {
457
+ request_options.headers.authorization = `Bearer ${github_api_token}`;
458
+ }
459
+
460
+ let github_response;
461
+ try {
462
+ github_response = await axios.get(github_url, request_options);
463
+ if (retried) {
464
+ retried = false;
465
+ console.log("API call retry success!");
466
+ }
467
+ } catch (err) {
468
+ if (err.response) {
469
+ if (err.response.status !== 403 && err.response.status !== 401) {
470
+ response.error = err;
471
+ return response;
472
+ }
473
+ github_response = err.response;
474
+ } else {
475
+ response.error = `Unexpected response from GitHub for [${github_url}:\n${JSON.stringify(
476
+ err,
477
+ )}]`;
478
+ }
479
+ }
480
+ if (github_response.status === 200) {
481
+ response.success = true;
482
+ response.data = github_response.data;
483
+ response.private = github_response.data.private;
484
+ } else {
485
+ // Is it a 404 or 403?
486
+ response.error = `${github_response.status} : ${data.message}`;
487
+ }
488
+ return response;
489
+ };
490
+
437
491
  exports.get_github_contributors = async (
438
492
  github_url,
439
493
  github_api_token,
package/hdoc-validate.js CHANGED
@@ -28,6 +28,7 @@ const e = require("express");
28
28
  const md_to_validate = [];
29
29
  const exclude_links = {};
30
30
  const exclude_spellcheck = {};
31
+ let private_repo = false;
31
32
  let redirects = {};
32
33
  const exclude_h1_count = {};
33
34
  const exclude_spellcheck_output = [];
@@ -545,6 +546,17 @@ const e = require("express");
545
546
  continue;
546
547
  }
547
548
 
549
+ if (
550
+ links[i].toLowerCase().includes("docs-internal.hornbill.com") &&
551
+ markdown_paths.relativePath.includes('/_inline/') &&
552
+ !private_repo
553
+ ) {
554
+ // Is the parent book in a public repo? If so, flag this as an error.
555
+ const error_message = processErrorMessage(`Hornbill docs-internal links should not be used in public book inline content: ${links[i]}`, markdown_paths.relativePath, markdown_content, links[i]);
556
+ errors[htmlFile.relativePath].push( error_message );
557
+ continue;
558
+ }
559
+
548
560
  try {
549
561
  await axios({
550
562
  url: links[i],
@@ -810,9 +822,11 @@ const e = require("express");
810
822
  gen_exclude,
811
823
  gen_redirects,
812
824
  draft_links,
825
+ is_private,
813
826
  ) => {
814
827
  console.log("Performing Validation and Building SEO Link List...");
815
828
  redirects = gen_redirects;
829
+ private_repo = is_private;
816
830
 
817
831
  // Get a list of HTML files in source_path
818
832
  dree.scan(source_path, dreeOptions, fileContentCallback);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "hdoc-tools",
3
- "version": "0.31.0",
3
+ "version": "0.32.1",
4
4
  "description": "Hornbill HDocBook Development Support Tool",
5
5
  "main": "hdoc.js",
6
6
  "bin": {